{-
    Copyright 2012-2022 Vidar Holen

    This file is part of ShellCheck.
    https://www.shellcheck.net

    ShellCheck is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    ShellCheck is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE FlexibleContexts #-}
module ShellCheck.Analytics (checker, optionalChecks, ShellCheck.Analytics.runTests) where

import ShellCheck.AST
import ShellCheck.ASTLib
import ShellCheck.AnalyzerLib hiding (producesComments)
import ShellCheck.CFG
import qualified ShellCheck.CFGAnalysis as CF
import ShellCheck.Data
import ShellCheck.Parser
import ShellCheck.Prelude
import ShellCheck.Interface
import ShellCheck.Regex

import Control.Arrow (first)
import Control.Monad
import Control.Monad.Identity
import Control.Monad.State
import Control.Monad.Writer hiding ((<>))
import Control.Monad.Reader
import Data.Char
import Data.Functor
import Data.Function (on)
import Data.List
import Data.Maybe
import Data.Ord
import Data.Semigroup
import Debug.Trace -- STRIP
import qualified Data.Map.Strict as Map
import qualified Data.Set as S
import Test.QuickCheck.All (forAllProperties)
import Test.QuickCheck.Test (quickCheckWithResult, stdArgs, maxSuccess)

-- Checks that are run on the AST root
treeChecks :: [Parameters -> Token -> [TokenComment]]
treeChecks :: [Parameters -> Token -> [TokenComment]]
treeChecks = [
    forall {w} {t :: * -> *} {t} {b}.
(Monoid w, Foldable t) =>
t (t -> Token -> WriterT w Identity b) -> t -> Token -> w
nodeChecksToTreeCheck [Parameters -> Token -> Writer [TokenComment] ()]
nodeChecks
    ,forall {p}. Parameters -> p -> [TokenComment]
subshellAssignmentCheck
    ,forall {p}. Parameters -> p -> [TokenComment]
checkQuotesInLiterals
    ,forall {t}. t -> Token -> [TokenComment]
checkShebangParameters
    ,Parameters -> Token -> [TokenComment]
checkFunctionsUsedExternally
    ,forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments
    ,Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions
    ,forall {p}. Parameters -> p -> [TokenComment]
checkArrayWithoutIndex
    ,Parameters -> Token -> [TokenComment]
checkShebang
    ,forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences
    ,Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd
    ,Parameters -> Token -> [TokenComment]
checkArrayAssignmentIndices
    ,forall {t}. t -> Token -> [TokenComment]
checkUseBeforeDefinition
    ,Parameters -> Token -> [TokenComment]
checkAliasUsedInSameParsingUnit
    ,forall {p}. Parameters -> p -> [TokenComment]
checkArrayValueUsedAsIndex
    ]

checker :: AnalysisSpec -> Parameters -> Checker
checker AnalysisSpec
spec Parameters
params = AnalysisSpec
-> Parameters -> [Parameters -> Token -> [TokenComment]] -> Checker
mkChecker AnalysisSpec
spec Parameters
params [Parameters -> Token -> [TokenComment]]
treeChecks

mkChecker :: AnalysisSpec
-> Parameters -> [Parameters -> Token -> [TokenComment]] -> Checker
mkChecker AnalysisSpec
spec Parameters
params [Parameters -> Token -> [TokenComment]]
checks =
    Checker {
        perScript :: Root -> Analysis
perScript = \(Root Token
root) -> do
            forall w (m :: * -> *). MonadWriter w m => w -> m ()
tell forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (\Parameters -> Token -> [TokenComment]
f -> Parameters -> Token -> [TokenComment]
f Parameters
params Token
root) [Parameters -> Token -> [TokenComment]]
all,
        perToken :: Token -> Analysis
perToken = forall a b. a -> b -> a
const forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) a. Monad m => a -> m a
return ()
    }
  where
    all :: [Parameters -> Token -> [TokenComment]]
all = [Parameters -> Token -> [TokenComment]]
checks forall a. [a] -> [a] -> [a]
++ [Parameters -> Token -> [TokenComment]]
optionals
    optionalKeys :: [String]
optionalKeys = AnalysisSpec -> [String]
asOptionalChecks AnalysisSpec
spec
    optionals :: [Parameters -> Token -> [TokenComment]]
optionals =
        if String
"all" forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
optionalKeys
        then forall a b. (a -> b) -> [a] -> [b]
map forall a b. (a, b) -> b
snd [(CheckDescription, Parameters -> Token -> [TokenComment])]
optionalTreeChecks
        else forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (\String
c -> forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup String
c Map String (Parameters -> Token -> [TokenComment])
optionalCheckMap) [String]
optionalKeys


checkList :: t (t -> [b]) -> t -> [b]
checkList t (t -> [b])
l t
t = forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (\t -> [b]
f -> t -> [b]
f t
t) t (t -> [b])
l

-- Checks that are run on each node in the AST
runNodeAnalysis :: (t -> Token -> WriterT w Identity ()) -> t -> Token -> w
runNodeAnalysis t -> Token -> WriterT w Identity ()
f t
p Token
t = forall w a. Writer w a -> w
execWriter (forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> Token -> m Token
doAnalysis (t -> Token -> WriterT w Identity ()
f t
p) Token
t)

-- Perform multiple node checks in a single iteration over the tree
nodeChecksToTreeCheck :: t (t -> Token -> WriterT w Identity b) -> t -> Token -> w
nodeChecksToTreeCheck t (t -> Token -> WriterT w Identity b)
checkList =
    forall {w} {t}.
Monoid w =>
(t -> Token -> WriterT w Identity ()) -> t -> Token -> w
runNodeAnalysis
        (\t
p Token
t -> (forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ ((\ Token -> WriterT w Identity b
f -> Token -> WriterT w Identity b
f Token
t) forall b c a. (b -> c) -> (a -> b) -> a -> c
. (\ t -> Token -> WriterT w Identity b
f -> t -> Token -> WriterT w Identity b
f t
p))
            t (t -> Token -> WriterT w Identity b)
checkList))

nodeChecks :: [Parameters -> Token -> Writer [TokenComment] ()]
nodeChecks :: [Parameters -> Token -> Writer [TokenComment] ()]
nodeChecks = [
    forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoc
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForInQuoted
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkForInLs
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkShorthandIf
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkDollarStar
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedDollarAt
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkStderrRedirect
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUnquotedN
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleBracketOperators
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkDoubleBracketOperators
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkLiteralBreakingTest
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantNullary
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkDivBeforeMult
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkArithmeticDeref
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkArithmeticBadOctal
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkComparisonAgainstGlob
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCaseAgainstGlob
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCommarrays
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOrNeq
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkEchoWc
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantIfs
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipedAssignment
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkAssignAteCommand
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoeVar
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkQuotedCondRegex
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkForInCat
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkFindExec
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkValidCondOps
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobbedRegex
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTestRedirects
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkBadParameterSubstitution
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPS1Assignments
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkBackticks
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkInexplicablyUnquoted
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTildeInQuotes
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkLonelyDotDash
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExec
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExpansion
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkDollarBrackets
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSshHereDoc
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobsAsOptions
    ,Parameters -> Token -> Writer [TokenComment] ()
checkWhileReadPitfalls
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkArithmeticOpCommand
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCharRangeGlob
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedExpansions
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectToSame
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkPrefixAssignmentReference
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkLoopKeywordScope
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCdAndBack
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkWrongArithmeticAssignment
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConditionalAndOrs
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkFunctionDeclarations
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkStderrPipe
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOverridingPath
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkArrayAsString
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnsupported
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkMultipleAppends
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSuspiciousIFS
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkShouldUseGrepQ
    ,Parameters -> Token -> Writer [TokenComment] ()
checkTestArgumentSplitting
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkConcatenatedDollarAt
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTildeInPath
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkReadWithoutR
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkLoopVariableReassignment
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTrailingBracket
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkReturnAgainstZero
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectedNowhere
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnmatchableCases
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSubshellAsTest
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSplittingInArrays
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkRedirectionToNumber
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobAsCommand
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkFlagAsCommand
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkEmptyCondition
    ,Parameters -> Token -> Writer [TokenComment] ()
checkPipeToNowhere
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkForLoopGlobVariables
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSubshelledTests
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkInvertedStringTest
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkRedirectionToCommand
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkDollarQuoteParen
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUselessBang
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkTranslatedStringVariable
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkModifiedArithmeticInRedirection
    ,Parameters -> Token -> Writer [TokenComment] ()
checkBlatantRecursion
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkBadTestAndOr
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkAssignToSelf
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkEqualsInCommand
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSecondArgIsComparison
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkComparisonWithLeadingX
    ,forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCommandWithTrailingSymbol
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedParameterExpansionPattern
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkBatsTestDoesNotUseNegation
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCommandIsUnreachable
    ,Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkOverwrittenExitCode
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnnecessaryArithmeticExpansionIndex
    ,forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnnecessaryParens
    ]

optionalChecks :: [CheckDescription]
optionalChecks = forall a b. (a -> b) -> [a] -> [b]
map forall a b. (a, b) -> a
fst [(CheckDescription, Parameters -> Token -> [TokenComment])]
optionalTreeChecks


prop_verifyOptionalExamples :: Bool
prop_verifyOptionalExamples = forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (CheckDescription, Parameters -> Token -> [TokenComment]) -> Bool
check [(CheckDescription, Parameters -> Token -> [TokenComment])]
optionalTreeChecks
  where
    check :: (CheckDescription, Parameters -> Token -> [TokenComment]) -> Bool
check (CheckDescription
desc, Parameters -> Token -> [TokenComment]
check) =
        (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
check (CheckDescription -> String
cdPositive CheckDescription
desc)
        Bool -> Bool -> Bool
&& (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
check (CheckDescription -> String
cdNegative CheckDescription
desc)

optionalTreeChecks :: [(CheckDescription, (Parameters -> Token -> [TokenComment]))]
optionalTreeChecks :: [(CheckDescription, Parameters -> Token -> [TokenComment])]
optionalTreeChecks = [
    (CheckDescription
newCheckDescription {
        cdName :: String
cdName = String
"quote-safe-variables",
        cdDescription :: String
cdDescription = String
"Suggest quoting variables without metacharacters",
        cdPositive :: String
cdPositive = String
"var=hello; echo $var",
        cdNegative :: String
cdNegative = String
"var=hello; echo \"$var\""
    }, forall {w} {t :: * -> *} {t} {b}.
(Monoid w, Foldable t) =>
t (t -> Token -> WriterT w Identity b) -> t -> Token -> w
nodeChecksToTreeCheck [Parameters -> Token -> Writer [TokenComment] ()
checkVerboseSpacefulnessCfg])

    ,(CheckDescription
newCheckDescription {
        cdName :: String
cdName = String
"avoid-nullary-conditions",
        cdDescription :: String
cdDescription = String
"Suggest explicitly using -n in `[ $var ]`",
        cdPositive :: String
cdPositive = String
"[ \"$var\" ]",
        cdNegative :: String
cdNegative = String
"[ -n \"$var\" ]"
    }, forall {w} {t :: * -> *} {t} {b}.
(Monoid w, Foldable t) =>
t (t -> Token -> WriterT w Identity b) -> t -> Token -> w
nodeChecksToTreeCheck [forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNullaryExpansionTest])

    ,(CheckDescription
newCheckDescription {
        cdName :: String
cdName = String
"add-default-case",
        cdDescription :: String
cdDescription = String
"Suggest adding a default case in `case` statements",
        cdPositive :: String
cdPositive = String
"case $? in 0) echo 'Success';; esac",
        cdNegative :: String
cdNegative = String
"case $? in 0) echo 'Success';; *) echo 'Fail' ;; esac"
    }, forall {w} {t :: * -> *} {t} {b}.
(Monoid w, Foldable t) =>
t (t -> Token -> WriterT w Identity b) -> t -> Token -> w
nodeChecksToTreeCheck [forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkDefaultCase])

    ,(CheckDescription
newCheckDescription {
        cdName :: String
cdName = String
"require-variable-braces",
        cdDescription :: String
cdDescription = String
"Suggest putting braces around all variable references",
        cdPositive :: String
cdPositive = String
"var=hello; echo $var",
        cdNegative :: String
cdNegative = String
"var=hello; echo ${var}"
    }, forall {w} {t :: * -> *} {t} {b}.
(Monoid w, Foldable t) =>
t (t -> Token -> WriterT w Identity b) -> t -> Token -> w
nodeChecksToTreeCheck [forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkVariableBraces])

    ,(CheckDescription
newCheckDescription {
        cdName :: String
cdName = String
"check-unassigned-uppercase",
        cdDescription :: String
cdDescription = String
"Warn when uppercase variables are unassigned",
        cdPositive :: String
cdPositive = String
"echo $VAR",
        cdNegative :: String
cdNegative = String
"VAR=hello; echo $VAR"
    }, forall {p}. Bool -> Parameters -> p -> [TokenComment]
checkUnassignedReferences' Bool
True)

    ,(CheckDescription
newCheckDescription {
        cdName :: String
cdName = String
"require-double-brackets",
        cdDescription :: String
cdDescription = String
"Require [[ and warn about [ in Bash/Ksh",
        cdPositive :: String
cdPositive = String
"[ -e /etc/issue ]",
        cdNegative :: String
cdNegative = String
"[[ -e /etc/issue ]]"
    }, Parameters -> Token -> [TokenComment]
checkRequireDoubleBracket)

    ,(CheckDescription
newCheckDescription {
        cdName :: String
cdName = String
"check-set-e-suppressed",
        cdDescription :: String
cdDescription = String
"Notify when set -e is suppressed during function invocation",
        cdPositive :: String
cdPositive = String
"set -e; func() { cp *.txt ~/backup; rm *.txt; }; func && echo ok",
        cdNegative :: String
cdNegative = String
"set -e; func() { cp *.txt ~/backup; rm *.txt; }; func; echo ok"
    }, Parameters -> Token -> [TokenComment]
checkSetESuppressed)

    ,(CheckDescription
newCheckDescription {
        cdName :: String
cdName = String
"check-extra-masked-returns",
        cdDescription :: String
cdDescription = String
"Check for additional cases where exit codes are masked",
        cdPositive :: String
cdPositive = String
"rm -r \"$(get_chroot_dir)/home\"",
        cdNegative :: String
cdNegative = String
"set -e; dir=\"$(get_chroot_dir)\"; rm -r \"$dir/home\""
    }, Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns)
    ]

optionalCheckMap :: Map.Map String (Parameters -> Token -> [TokenComment])
optionalCheckMap :: Map String (Parameters -> Token -> [TokenComment])
optionalCheckMap = forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map forall {b}. (CheckDescription, b) -> (String, b)
item [(CheckDescription, Parameters -> Token -> [TokenComment])]
optionalTreeChecks
  where
    item :: (CheckDescription, b) -> (String, b)
item (CheckDescription
desc, b
check) = (CheckDescription -> String
cdName CheckDescription
desc, b
check)

wouldHaveBeenGlob :: t Char -> Bool
wouldHaveBeenGlob t Char
s = Char
'*' forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` t Char
s

verify :: (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify :: (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
f String
s = (Parameters -> Token -> Writer [TokenComment] ())
-> String -> Maybe Bool
checkNode Parameters -> Token -> Writer [TokenComment] ()
f String
s forall a. Eq a => a -> a -> Bool
== forall a. a -> Maybe a
Just Bool
True

verifyNot :: (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot :: (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
f String
s = (Parameters -> Token -> Writer [TokenComment] ())
-> String -> Maybe Bool
checkNode Parameters -> Token -> Writer [TokenComment] ()
f String
s forall a. Eq a => a -> a -> Bool
== forall a. a -> Maybe a
Just Bool
False

verifyTree :: (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree :: (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
f String
s = (Parameters -> Token -> [TokenComment]) -> String -> Maybe Bool
producesComments Parameters -> Token -> [TokenComment]
f String
s forall a. Eq a => a -> a -> Bool
== forall a. a -> Maybe a
Just Bool
True

verifyNotTree :: (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree :: (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
f String
s = (Parameters -> Token -> [TokenComment]) -> String -> Maybe Bool
producesComments Parameters -> Token -> [TokenComment]
f String
s forall a. Eq a => a -> a -> Bool
== forall a. a -> Maybe a
Just Bool
False

checkCommand :: String -> (Token -> [Token] -> m ()) -> Token -> m ()
checkCommand String
str Token -> [Token] -> m ()
f t :: Token
t@(T_SimpleCommand Id
id [Token]
_ (Token
cmd:[Token]
rest))
    | Token
t Token -> String -> Bool
`isCommand` String
str = Token -> [Token] -> m ()
f Token
cmd [Token]
rest
checkCommand String
_ Token -> [Token] -> m ()
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

checkUnqualifiedCommand :: String -> (Token -> [Token] -> m ()) -> Token -> m ()
checkUnqualifiedCommand String
str Token -> [Token] -> m ()
f t :: Token
t@(T_SimpleCommand Id
id [Token]
_ (Token
cmd:[Token]
rest))
    | Token
t Token -> String -> Bool
`isUnqualifiedCommand` String
str = Token -> [Token] -> m ()
f Token
cmd [Token]
rest
checkUnqualifiedCommand String
_ Token -> [Token] -> m ()
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

verifyCodes :: (Parameters -> Token -> Writer [TokenComment] ()) -> [Code] -> String -> Bool
verifyCodes :: (Parameters -> Token -> Writer [TokenComment] ())
-> [Code] -> String -> Bool
verifyCodes Parameters -> Token -> Writer [TokenComment] ()
f [Code]
l String
s = Maybe [Code]
codes forall a. Eq a => a -> a -> Bool
== forall a. a -> Maybe a
Just [Code]
l
  where
    treeCheck :: Parameters -> Token -> [TokenComment]
treeCheck = forall {w} {t}.
Monoid w =>
(t -> Token -> WriterT w Identity ()) -> t -> Token -> w
runNodeAnalysis Parameters -> Token -> Writer [TokenComment] ()
f
    comments :: Maybe [TokenComment]
comments = (Parameters -> Token -> [TokenComment])
-> String -> Maybe [TokenComment]
runAndGetComments Parameters -> Token -> [TokenComment]
treeCheck String
s
    codes :: Maybe [Code]
codes = forall a b. (a -> b) -> [a] -> [b]
map (Comment -> Code
cCode forall b c a. (b -> c) -> (a -> b) -> a -> c
. TokenComment -> Comment
tcComment) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe [TokenComment]
comments

checkNode :: (Parameters -> Token -> Writer [TokenComment] ())
-> String -> Maybe Bool
checkNode Parameters -> Token -> Writer [TokenComment] ()
f = (Parameters -> Token -> [TokenComment]) -> String -> Maybe Bool
producesComments (forall {w} {t}.
Monoid w =>
(t -> Token -> WriterT w Identity ()) -> t -> Token -> w
runNodeAnalysis Parameters -> Token -> Writer [TokenComment] ()
f)
producesComments :: (Parameters -> Token -> [TokenComment]) -> String -> Maybe Bool
producesComments :: (Parameters -> Token -> [TokenComment]) -> String -> Maybe Bool
producesComments Parameters -> Token -> [TokenComment]
f String
s = Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: * -> *) a. Foldable t => t a -> Bool
null forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (Parameters -> Token -> [TokenComment])
-> String -> Maybe [TokenComment]
runAndGetComments Parameters -> Token -> [TokenComment]
f String
s

runAndGetComments :: (Parameters -> Token -> [TokenComment])
-> String -> Maybe [TokenComment]
runAndGetComments Parameters -> Token -> [TokenComment]
f String
s = do
        let pr :: ParseResult
pr = String -> ParseResult
pScript String
s
        Token
root <- ParseResult -> Maybe Token
prRoot ParseResult
pr
        let spec :: AnalysisSpec
spec = ParseResult -> AnalysisSpec
defaultSpec ParseResult
pr
        let params :: Parameters
params = AnalysisSpec -> Parameters
makeParameters AnalysisSpec
spec
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$
            AnalysisSpec -> Parameters -> [TokenComment] -> [TokenComment]
filterByAnnotation AnalysisSpec
spec Parameters
params forall a b. (a -> b) -> a -> b
$
                Parameters -> Token -> [TokenComment]
f Parameters
params Token
root

-- Copied from https://wiki.haskell.org/Edit_distance
dist :: Eq a => [a] -> [a] -> Int
dist :: forall a. Eq a => [a] -> [a] -> Int
dist [a]
a [a]
b
    = forall a. [a] -> a
last (if Int
lab forall a. Eq a => a -> a -> Bool
== Int
0 then [Int]
mainDiag
            else if Int
lab forall a. Ord a => a -> a -> Bool
> Int
0 then [[Int]]
lowers forall a. [a] -> Int -> a
!! (Int
lab forall a. Num a => a -> a -> a
- Int
1)
                 else{- < 0 -}   [[Int]]
uppers forall a. [a] -> Int -> a
!! (-Int
1 forall a. Num a => a -> a -> a
- Int
lab))
    where mainDiag :: [Int]
mainDiag = forall {a} {a}.
(Num a, Ord a, Eq a) =>
[a] -> [a] -> [a] -> [a] -> [a]
oneDiag [a]
a [a]
b (forall a. [a] -> a
head [[Int]]
uppers) (-Int
1 forall a. a -> [a] -> [a]
: forall a. [a] -> a
head [[Int]]
lowers)
          uppers :: [[Int]]
uppers = forall {a} {a}.
(Num a, Ord a, Eq a) =>
[a] -> [a] -> [[a]] -> [[a]]
eachDiag [a]
a [a]
b ([Int]
mainDiag forall a. a -> [a] -> [a]
: [[Int]]
uppers) -- upper diagonals
          lowers :: [[Int]]
lowers = forall {a} {a}.
(Num a, Ord a, Eq a) =>
[a] -> [a] -> [[a]] -> [[a]]
eachDiag [a]
b [a]
a ([Int]
mainDiag forall a. a -> [a] -> [a]
: [[Int]]
lowers) -- lower diagonals
          eachDiag :: [a] -> [a] -> [[a]] -> [[a]]
eachDiag [a]
a [] [[a]]
diags = []
          eachDiag [a]
a (a
bch:[a]
bs) ([a]
lastDiag:[[a]]
diags) = forall {a} {a}.
(Num a, Ord a, Eq a) =>
[a] -> [a] -> [a] -> [a] -> [a]
oneDiag [a]
a [a]
bs [a]
nextDiag [a]
lastDiag forall a. a -> [a] -> [a]
: [a] -> [a] -> [[a]] -> [[a]]
eachDiag [a]
a [a]
bs [[a]]
diags
              where nextDiag :: [a]
nextDiag = forall a. [a] -> a
head (forall a. [a] -> [a]
tail [[a]]
diags)
          oneDiag :: [a] -> [a] -> [a] -> [a] -> [a]
oneDiag [a]
a [a]
b [a]
diagAbove [a]
diagBelow = [a]
thisdiag
              where doDiag :: [a] -> [a] -> a -> [a] -> [a] -> [a]
doDiag [] [a]
b a
nw [a]
n [a]
w = []
                    doDiag [a]
a [] a
nw [a]
n [a]
w = []
                    doDiag (a
ach:[a]
as) (a
bch:[a]
bs) a
nw [a]
n [a]
w = a
me forall a. a -> [a] -> [a]
: [a] -> [a] -> a -> [a] -> [a] -> [a]
doDiag [a]
as [a]
bs a
me (forall a. [a] -> [a]
tail [a]
n) (forall a. [a] -> [a]
tail [a]
w)
                        where me :: a
me = if a
ach forall a. Eq a => a -> a -> Bool
== a
bch then a
nw else a
1 forall a. Num a => a -> a -> a
+ forall {a}. Ord a => a -> a -> a -> a
min3 (forall a. [a] -> a
head [a]
w) a
nw (forall a. [a] -> a
head [a]
n)
                    firstelt :: a
firstelt = a
1 forall a. Num a => a -> a -> a
+ forall a. [a] -> a
head [a]
diagBelow
                    thisdiag :: [a]
thisdiag = a
firstelt forall a. a -> [a] -> [a]
: forall {a} {a}.
(Num a, Ord a, Eq a) =>
[a] -> [a] -> a -> [a] -> [a] -> [a]
doDiag [a]
a [a]
b a
firstelt [a]
diagAbove (forall a. [a] -> [a]
tail [a]
diagBelow)
          lab :: Int
lab = forall (t :: * -> *) a. Foldable t => t a -> Int
length [a]
a forall a. Num a => a -> a -> a
- forall (t :: * -> *) a. Foldable t => t a -> Int
length [a]
b
          min3 :: a -> a -> a -> a
min3 a
x a
y a
z = if a
x forall a. Ord a => a -> a -> Bool
< a
y then a
x else forall a. Ord a => a -> a -> a
min a
y a
z

hasFloatingPoint :: Parameters -> Bool
hasFloatingPoint Parameters
params = Parameters -> Shell
shellType Parameters
params forall a. Eq a => a -> a -> Bool
== Shell
Ksh

-- Checks whether the current parent path is part of a condition
isCondition :: [Token] -> Bool
isCondition [] = Bool
False
isCondition [Token
_] = Bool
False
isCondition (Token
child:Token
parent:[Token]
rest) =
    case Token
child of
        T_BatsTest {} -> Bool
True -- count anything in a @test as conditional
        Token
_ -> Token -> Id
getId Token
child forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` forall a b. (a -> b) -> [a] -> [b]
map Token -> Id
getId (Token -> [Token]
getConditionChildren Token
parent) Bool -> Bool -> Bool
|| [Token] -> Bool
isCondition (Token
parentforall a. a -> [a] -> [a]
:[Token]
rest)
  where
    getConditionChildren :: Token -> [Token]
getConditionChildren Token
t =
        case Token
t of
            T_AndIf Id
_ Token
left Token
right -> [Token
left]
            T_OrIf Id
id Token
left Token
right -> [Token
left]
            T_IfExpression Id
id [([Token], [Token])]
conditions [Token]
elses -> forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (forall a. Int -> [a] -> [a]
take Int
1 forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. [a] -> [a]
reverse forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a, b) -> a
fst) [([Token], [Token])]
conditions
            T_WhileExpression Id
id [Token]
c [Token]
l -> forall a. Int -> [a] -> [a]
take Int
1 forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. [a] -> [a]
reverse forall a b. (a -> b) -> a -> b
$ [Token]
c
            T_UntilExpression Id
id [Token]
c [Token]
l -> forall a. Int -> [a] -> [a]
take Int
1 forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. [a] -> [a]
reverse forall a b. (a -> b) -> a -> b
$ [Token]
c
            Token
_ -> []

-- helpers to build replacements
replaceStart :: Id -> Parameters -> Code -> String -> Replacement
replaceStart Id
id Parameters
params Code
n String
r =
    let tp :: Map Id (Position, Position)
tp = Parameters -> Map Id (Position, Position)
tokenPositions Parameters
params
        (Position
start, Position
_) = Map Id (Position, Position)
tp forall k a. Ord k => Map k a -> k -> a
Map.! Id
id
        new_end :: Position
new_end = Position
start {
            posColumn :: Code
posColumn = Position -> Code
posColumn Position
start forall a. Num a => a -> a -> a
+ Code
n
        }
        depth :: Int
depth = forall (t :: * -> *) a. Foldable t => t a -> Int
length forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) (Id -> Token
T_EOF Id
id)
    in
    Replacement
newReplacement {
        repStartPos :: Position
repStartPos = Position
start,
        repEndPos :: Position
repEndPos = Position
new_end,
        repString :: String
repString = String
r,
        repPrecedence :: Int
repPrecedence = Int
depth,
        repInsertionPoint :: InsertionPoint
repInsertionPoint = InsertionPoint
InsertAfter
    }
replaceEnd :: Id -> Parameters -> Code -> String -> Replacement
replaceEnd Id
id Parameters
params Code
n String
r =
    let tp :: Map Id (Position, Position)
tp = Parameters -> Map Id (Position, Position)
tokenPositions Parameters
params
        (Position
_, Position
end) = Map Id (Position, Position)
tp forall k a. Ord k => Map k a -> k -> a
Map.! Id
id
        new_start :: Position
new_start = Position
end {
            posColumn :: Code
posColumn = Position -> Code
posColumn Position
end forall a. Num a => a -> a -> a
- Code
n
        }
        new_end :: Position
new_end = Position
end {
            posColumn :: Code
posColumn = Position -> Code
posColumn Position
end
        }
        depth :: Int
depth = forall (t :: * -> *) a. Foldable t => t a -> Int
length forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) (Id -> Token
T_EOF Id
id)
    in
    Replacement
newReplacement {
        repStartPos :: Position
repStartPos = Position
new_start,
        repEndPos :: Position
repEndPos = Position
new_end,
        repString :: String
repString = String
r,
        repPrecedence :: Int
repPrecedence = Int
depth,
        repInsertionPoint :: InsertionPoint
repInsertionPoint = InsertionPoint
InsertBefore
    }
replaceToken :: Id -> Parameters -> String -> Replacement
replaceToken Id
id Parameters
params String
r =
    let tp :: Map Id (Position, Position)
tp = Parameters -> Map Id (Position, Position)
tokenPositions Parameters
params
        (Position
start, Position
end) = Map Id (Position, Position)
tp forall k a. Ord k => Map k a -> k -> a
Map.! Id
id
        depth :: Int
depth = forall (t :: * -> *) a. Foldable t => t a -> Int
length forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) (Id -> Token
T_EOF Id
id)
    in
    Replacement
newReplacement {
        repStartPos :: Position
repStartPos = Position
start,
        repEndPos :: Position
repEndPos = Position
end,
        repString :: String
repString = String
r,
        repPrecedence :: Int
repPrecedence = Int
depth,
        repInsertionPoint :: InsertionPoint
repInsertionPoint = InsertionPoint
InsertBefore
    }

surroundWith :: Id -> Parameters -> String -> Fix
surroundWith Id
id Parameters
params String
s = [Replacement] -> Fix
fixWith [Id -> Parameters -> Code -> String -> Replacement
replaceStart Id
id Parameters
params Code
0 String
s, Id -> Parameters -> Code -> String -> Replacement
replaceEnd Id
id Parameters
params Code
0 String
s]
fixWith :: [Replacement] -> Fix
fixWith [Replacement]
fixes = Fix
newFix { fixReplacements :: [Replacement]
fixReplacements = [Replacement]
fixes }

analyse :: (Token -> StateT [a] Identity ()) -> Token -> [a]
analyse Token -> StateT [a] Identity ()
f Token
t = forall s a. State s a -> s -> s
execState (forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> Token -> m Token
doAnalysis Token -> StateT [a] Identity ()
f Token
t) []

-- Make a map from functions to definition IDs
functions :: Token -> Map String Id
functions Token
t = forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList forall a b. (a -> b) -> a -> b
$ forall {a}. (Token -> StateT [a] Identity ()) -> Token -> [a]
analyse forall {m :: * -> *}. MonadState [(String, Id)] m => Token -> m ()
findFunctions Token
t
findFunctions :: Token -> m ()
findFunctions (T_Function Id
id FunctionKeyword
_ FunctionParentheses
_ String
name Token
_)
    = forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify ((String
name, Id
id)forall a. a -> [a] -> [a]
:)
findFunctions Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

-- Make a map from aliases to definition IDs
aliases :: Token -> Map String Id
aliases Token
t = forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList forall a b. (a -> b) -> a -> b
$ forall {a}. (Token -> StateT [a] Identity ()) -> Token -> [a]
analyse forall {m :: * -> *}. MonadState [(String, Id)] m => Token -> m ()
findAliases Token
t
findAliases :: Token -> m ()
findAliases t :: Token
t@(T_SimpleCommand Id
_ [Token]
_ (Token
_:[Token]
args))
    | Token
t Token -> String -> Bool
`isUnqualifiedCommand` String
"alias" = forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadState [(String, Id)] m => Token -> m ()
getAlias [Token]
args
findAliases Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()
getAlias :: Token -> f ()
getAlias Token
arg =
    let string :: String
string = Token -> String
onlyLiteralString Token
arg
    in forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Char
'=' forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
string) forall a b. (a -> b) -> a -> b
$
        forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify ((forall a. (a -> Bool) -> [a] -> [a]
takeWhile (forall a. Eq a => a -> a -> Bool
/= Char
'=') String
string, Token -> Id
getId Token
arg)forall a. a -> [a] -> [a]
:)

prop_checkEchoWc3 :: Bool
prop_checkEchoWc3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkEchoWc String
"n=$(echo $foo | wc -c)"
checkEchoWc :: p -> Token -> f ()
checkEchoWc p
_ (T_Pipeline Id
id [Token]
_ [Token
a, Token
b]) =
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ([String]
acmd forall a. Eq a => a -> a -> Bool
== [String
"echo", String
"${VAR}"]) forall a b. (a -> b) -> a -> b
$
        case [String]
bcmd of
            [String
"wc", String
"-c"] -> f ()
countMsg
            [String
"wc", String
"-m"] -> f ()
countMsg
            [String]
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    acmd :: [String]
acmd = Token -> [String]
oversimplify Token
a
    bcmd :: [String]
bcmd = Token -> [String]
oversimplify Token
b
    countMsg :: f ()
countMsg = forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style Id
id Code
2000 String
"See if you can use ${#variable} instead."
checkEchoWc p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkPipedAssignment1 :: Bool
prop_checkPipedAssignment1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipedAssignment String
"A=ls | grep foo"
prop_checkPipedAssignment2 :: Bool
prop_checkPipedAssignment2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipedAssignment String
"A=foo cmd | grep foo"
prop_checkPipedAssignment3 :: Bool
prop_checkPipedAssignment3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipedAssignment String
"A=foo"
checkPipedAssignment :: p -> Token -> m ()
checkPipedAssignment p
_ (T_Pipeline Id
_ [Token]
_ (T_Redirecting Id
_ [Token]
_ (T_SimpleCommand Id
id (Token
_:[Token]
_) []):Token
_:[Token]
_)) =
    forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2036 String
"If you wanted to assign the output of the pipeline, use a=$(b | c) ."
checkPipedAssignment p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkAssignAteCommand1 :: Bool
prop_checkAssignAteCommand1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkAssignAteCommand String
"A=ls -l"
prop_checkAssignAteCommand2 :: Bool
prop_checkAssignAteCommand2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkAssignAteCommand String
"A=ls --sort=$foo"
prop_checkAssignAteCommand3 :: Bool
prop_checkAssignAteCommand3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkAssignAteCommand String
"A=cat foo | grep bar"
prop_checkAssignAteCommand4 :: Bool
prop_checkAssignAteCommand4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkAssignAteCommand String
"A=foo ls -l"
prop_checkAssignAteCommand5 :: Bool
prop_checkAssignAteCommand5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkAssignAteCommand String
"PAGER=cat grep bar"
prop_checkAssignAteCommand6 :: Bool
prop_checkAssignAteCommand6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkAssignAteCommand String
"PAGER=\"cat\" grep bar"
prop_checkAssignAteCommand7 :: Bool
prop_checkAssignAteCommand7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkAssignAteCommand String
"here=pwd"
checkAssignAteCommand :: p -> Token -> m ()
checkAssignAteCommand p
_ (T_SimpleCommand Id
id [T_Assignment Id
_ AssignmentMode
_ String
_ [Token]
_ Token
assignmentTerm] [Token]
list) =
    -- Check if first word is intended as an argument (flag or glob).
    if [Token] -> Bool
firstWordIsArg [Token]
list
    then
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2037 String
"To assign the output of a command, use var=$(cmd) ."
    else
        -- Check if it's a known, unquoted command name.
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Maybe String -> Bool
isCommonCommand forall a b. (a -> b) -> a -> b
$ Token -> Maybe String
getUnquotedLiteral Token
assignmentTerm) forall a b. (a -> b) -> a -> b
$
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2209 String
"Use var=$(command) to assign output (or quote to assign string)."
  where
    isCommonCommand :: Maybe String -> Bool
isCommonCommand (Just String
s) = String
s forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
commonCommands
    isCommonCommand Maybe String
_ = Bool
False
    firstWordIsArg :: [Token] -> Bool
firstWordIsArg [Token]
list = forall a. a -> Maybe a -> a
fromMaybe Bool
False forall a b. (a -> b) -> a -> b
$ do
        Token
head <- [Token]
list forall {a}. [a] -> Int -> Maybe a
!!! Int
0
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Token -> Bool
isGlob Token
head Bool -> Bool -> Bool
|| Token -> Bool
isUnquotedFlag Token
head

checkAssignAteCommand p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkArithmeticOpCommand1 :: Bool
prop_checkArithmeticOpCommand1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkArithmeticOpCommand String
"i=i + 1"
prop_checkArithmeticOpCommand2 :: Bool
prop_checkArithmeticOpCommand2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkArithmeticOpCommand String
"foo=bar * 2"
prop_checkArithmeticOpCommand3 :: Bool
prop_checkArithmeticOpCommand3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkArithmeticOpCommand String
"foo + opts"
checkArithmeticOpCommand :: p -> Token -> m ()
checkArithmeticOpCommand p
_ (T_SimpleCommand Id
id [T_Assignment {}] (Token
firstWord:[Token]
_)) =
    forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
String -> f ()
check forall a b. (a -> b) -> a -> b
$ Token -> Maybe String
getGlobOrLiteralString Token
firstWord
  where
    check :: String -> f ()
check String
op =
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
op forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"+", String
"-", String
"*", String
"/"]) forall a b. (a -> b) -> a -> b
$
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
firstWord) Code
2099 forall a b. (a -> b) -> a -> b
$
                String
"Use $((..)) for arithmetics, e.g. i=$((i " forall a. [a] -> [a] -> [a]
++ String
op forall a. [a] -> [a] -> [a]
++ String
" 2))"
checkArithmeticOpCommand p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkWrongArit :: Bool
prop_checkWrongArit = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkWrongArithmeticAssignment String
"i=i+1"
prop_checkWrongArit2 :: Bool
prop_checkWrongArit2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkWrongArithmeticAssignment String
"n=2; i=n*2"
checkWrongArithmeticAssignment :: Parameters -> Token -> m ()
checkWrongArithmeticAssignment Parameters
params (T_SimpleCommand Id
id [T_Assignment Id
_ AssignmentMode
_ String
_ [Token]
_ Token
val] []) =
  forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
    String
str <- Token -> Maybe String
getNormalString Token
val
    [String]
match <- Regex -> String -> Maybe [String]
matchRegex Regex
regex String
str
    String
var <- [String]
match forall {a}. [a] -> Int -> Maybe a
!!! Int
0
    String
op <- [String]
match forall {a}. [a] -> Int -> Maybe a
!!! Int
1
    forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup String
var Map String ()
references
    forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
val) Code
2100 forall a b. (a -> b) -> a -> b
$
        String
"Use $((..)) for arithmetics, e.g. i=$((i " forall a. [a] -> [a] -> [a]
++ String
op forall a. [a] -> [a] -> [a]
++ String
" 2))"
  where
    regex :: Regex
regex = String -> Regex
mkRegex String
"^([_a-zA-Z][_a-zA-Z0-9]*)([+*-]).+$"
    references :: Map String ()
references = forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl (forall a b c. (a -> b -> c) -> b -> a -> c
flip forall a b. (a -> b) -> a -> b
($)) forall k a. Map k a
Map.empty (forall a b. (a -> b) -> [a] -> [b]
map StackData -> Map String () -> Map String ()
insertRef forall a b. (a -> b) -> a -> b
$ Parameters -> [StackData]
variableFlow Parameters
params)
    insertRef :: StackData -> Map String () -> Map String ()
insertRef (Assignment (Token
_, Token
_, String
name, DataType
_)) =
        forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert String
name ()
    insertRef StackData
_ = forall a. a -> a
Prelude.id

    getNormalString :: Token -> Maybe String
getNormalString (T_NormalWord Id
_ [Token]
words) = do
        [String]
parts <- forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
mapM Token -> Maybe String
getLiterals [Token]
words
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [String]
parts
    getNormalString Token
_ = forall a. Maybe a
Nothing

    getLiterals :: Token -> Maybe String
getLiterals (T_Literal Id
_ String
s) = forall (m :: * -> *) a. Monad m => a -> m a
return String
s
    getLiterals (T_Glob Id
_ String
s) = forall (m :: * -> *) a. Monad m => a -> m a
return String
s
    getLiterals Token
_ = forall a. Maybe a
Nothing
checkWrongArithmeticAssignment Parameters
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkUuoc1 :: Bool
prop_checkUuoc1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoc String
"cat foo | grep bar"
prop_checkUuoc2 :: Bool
prop_checkUuoc2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoc String
"cat * | grep bar"
prop_checkUuoc3 :: Bool
prop_checkUuoc3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoc String
"cat \"$var\" | grep bar"
prop_checkUuoc3b :: Bool
prop_checkUuoc3b = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoc String
"cat $var | grep bar"
prop_checkUuoc3c :: Bool
prop_checkUuoc3c = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoc String
"cat \"${!var}\" | grep bar"
prop_checkUuoc4 :: Bool
prop_checkUuoc4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoc String
"cat $var"
prop_checkUuoc5 :: Bool
prop_checkUuoc5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoc String
"cat \"$@\""
prop_checkUuoc6 :: Bool
prop_checkUuoc6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoc String
"cat -n | grep bar"
checkUuoc :: p -> Token -> m ()
checkUuoc p
_ (T_Pipeline Id
_ [Token]
_ (T_Redirecting Id
_ [Token]
_ Token
cmd:Token
_:[Token]
_)) =
    forall {m :: * -> *}.
Monad m =>
String -> (Token -> [Token] -> m ()) -> Token -> m ()
checkCommand String
"cat" (forall a b. a -> b -> a
const forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
[Token] -> m ()
f) Token
cmd
  where
    f :: [Token] -> m ()
f [Token
word] | Bool -> Bool
not (Token -> Bool
mayBecomeMultipleArgs Token
word Bool -> Bool -> Bool
|| Token -> Bool
isOption Token
word) =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style (Token -> Id
getId Token
word) Code
2002 String
"Useless cat. Consider 'cmd < file | ..' or 'cmd file | ..' instead."
    f [Token]
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()
    isOption :: Token -> Bool
isOption Token
word = String
"-" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` Token -> String
onlyLiteralString Token
word
checkUuoc p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkPipePitfalls3 :: Bool
prop_checkPipePitfalls3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"ls | grep -v mp3"
prop_checkPipePitfalls4 :: Bool
prop_checkPipePitfalls4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"find . -print0 | xargs -0 foo"
prop_checkPipePitfalls5 :: Bool
prop_checkPipePitfalls5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"ls -N | foo"
prop_checkPipePitfalls6 :: Bool
prop_checkPipePitfalls6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"find . | xargs foo"
prop_checkPipePitfalls7 :: Bool
prop_checkPipePitfalls7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"find . -printf '%s\\n' | xargs foo"
prop_checkPipePitfalls8 :: Bool
prop_checkPipePitfalls8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"foo | grep bar | wc -l"
prop_checkPipePitfalls9 :: Bool
prop_checkPipePitfalls9 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"foo | grep -o bar | wc -l"
prop_checkPipePitfalls10 :: Bool
prop_checkPipePitfalls10 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"foo | grep -o bar | wc"
prop_checkPipePitfalls11 :: Bool
prop_checkPipePitfalls11 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"foo | grep bar | wc"
prop_checkPipePitfalls12 :: Bool
prop_checkPipePitfalls12 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"foo | grep -o bar | wc -c"
prop_checkPipePitfalls13 :: Bool
prop_checkPipePitfalls13 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"foo | grep bar | wc -c"
prop_checkPipePitfalls14 :: Bool
prop_checkPipePitfalls14 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"foo | grep -o bar | wc -cmwL"
prop_checkPipePitfalls15 :: Bool
prop_checkPipePitfalls15 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"foo | grep bar | wc -cmwL"
prop_checkPipePitfalls16 :: Bool
prop_checkPipePitfalls16 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"foo | grep -r bar | wc -l"
prop_checkPipePitfalls17 :: Bool
prop_checkPipePitfalls17 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"foo | grep -l bar | wc -l"
prop_checkPipePitfalls18 :: Bool
prop_checkPipePitfalls18 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"foo | grep -L bar | wc -l"
prop_checkPipePitfalls19 :: Bool
prop_checkPipePitfalls19 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"foo | grep -A2 bar | wc -l"
prop_checkPipePitfalls20 :: Bool
prop_checkPipePitfalls20 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"foo | grep -B999 bar | wc -l"
prop_checkPipePitfalls21 :: Bool
prop_checkPipePitfalls21 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"foo | grep --after-context 999 bar | wc -l"
prop_checkPipePitfalls22 :: Bool
prop_checkPipePitfalls22 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"foo | grep -B 1 --after-context 999 bar | wc -l"
prop_checkPipePitfalls23 :: Bool
prop_checkPipePitfalls23 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"ps -o pid,args -p $(pgrep java) | grep -F net.shellcheck.Test"
checkPipePitfalls :: p -> Token -> m ()
checkPipePitfalls p
_ (T_Pipeline Id
id [Token]
_ [Token]
commands) = do
    forall {m :: * -> *} {b}.
Monad m =>
[String] -> ([Token] -> m b) -> m Bool
for [String
"find", String
"xargs"] forall a b. (a -> b) -> a -> b
$
        \(Token
find:Token
xargs:[Token]
_) ->
          let args :: [String]
args = Token -> [String]
oversimplify Token
xargs forall a. [a] -> [a] -> [a]
++ Token -> [String]
oversimplify Token
find
          in
            forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (forall a b. (a -> b) -> a -> b
$ [String]
args) [
                forall {t :: * -> *}. Foldable t => Char -> t String -> Bool
hasShortParameter Char
'0',
                forall {t :: * -> *}. Foldable t => String -> t String -> Bool
hasParameter String
"null",
                forall {t :: * -> *}. Foldable t => String -> t String -> Bool
hasParameter String
"print0",
                forall {t :: * -> *}. Foldable t => String -> t String -> Bool
hasParameter String
"printf"
              ]) forall a b. (a -> b) -> a -> b
$ forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
find) Code
2038
                      String
"Use -print0/-0 or -exec + to allow for non-alphanumeric filenames."

    forall {m :: * -> *} {b}.
Monad m =>
[String] -> ([Token] -> m b) -> m Bool
for [String
"ps", String
"grep"] forall a b. (a -> b) -> a -> b
$
        \(Token
ps:Token
grep:[Token]
_) ->
            let
                psFlags :: [String]
psFlags = forall b a. b -> (a -> b) -> Maybe a -> b
maybe [] (forall a b. (a -> b) -> [a] -> [b]
map forall a b. (a, b) -> b
snd forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [(Token, String)]
getAllFlags) forall a b. (a -> b) -> a -> b
$ Token -> Maybe Token
getCommand Token
ps
            in
                -- There are many ways to specify a pid: 1, -1, p 1, wup 1, -q 1, -p 1, --pid 1.
                -- For simplicity we only deal with the most canonical looking flags:
                forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"p", String
"pid", String
"q", String
"quick-pid"]) [String]
psFlags) forall a b. (a -> b) -> a -> b
$
                    forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
ps) Code
2009 String
"Consider using pgrep instead of grepping ps output."

    forall {m :: * -> *} {b}.
Monad m =>
[String] -> ([Token] -> m b) -> m Bool
for [String
"grep", String
"wc"] forall a b. (a -> b) -> a -> b
$
        \(Token
grep:Token
wc:[Token]
_) ->
            let flagsGrep :: [String]
flagsGrep = forall b a. b -> (a -> b) -> Maybe a -> b
maybe [] (forall a b. (a -> b) -> [a] -> [b]
map forall a b. (a, b) -> b
snd forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [(Token, String)]
getAllFlags) forall a b. (a -> b) -> a -> b
$ Token -> Maybe Token
getCommand Token
grep
                flagsWc :: [String]
flagsWc = forall b a. b -> (a -> b) -> Maybe a -> b
maybe [] (forall a b. (a -> b) -> [a] -> [b]
map forall a b. (a, b) -> b
snd forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [(Token, String)]
getAllFlags) forall a b. (a -> b) -> a -> b
$ Token -> Maybe Token
getCommand Token
wc
            in
                forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"l", String
"files-with-matches", String
"L", String
"files-without-matches", String
"o", String
"only-matching", String
"r", String
"R", String
"recursive", String
"A", String
"after-context", String
"B", String
"before-context"]) [String]
flagsGrep
                        Bool -> Bool -> Bool
|| forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"m", String
"chars", String
"w", String
"words", String
"c", String
"bytes", String
"L", String
"max-line-length"]) [String]
flagsWc
                        Bool -> Bool -> Bool
|| forall (t :: * -> *) a. Foldable t => t a -> Bool
null [String]
flagsWc) forall a b. (a -> b) -> a -> b
$
                    forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style (Token -> Id
getId Token
grep) Code
2126 String
"Consider using 'grep -c' instead of 'grep|wc -l'."

    Bool
didLs <- forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap forall (t :: * -> *). Foldable t => t Bool -> Bool
or forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: * -> *) (m :: * -> *) a.
(Traversable t, Monad m) =>
t (m a) -> m (t a)
sequence forall a b. (a -> b) -> a -> b
$ [
        forall {m :: * -> *}. Monad m => [String] -> (Id -> m ()) -> m Bool
for' [String
"ls", String
"grep"] forall a b. (a -> b) -> a -> b
$
            \Id
x -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
x Code
2010 String
"Don't use ls | grep. Use a glob or a for loop with a condition to allow non-alphanumeric filenames.",
        forall {m :: * -> *}. Monad m => [String] -> (Id -> m ()) -> m Bool
for' [String
"ls", String
"xargs"] forall a b. (a -> b) -> a -> b
$
            \Id
x -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
x Code
2011 String
"Use 'find .. -print0 | xargs -0 ..' or 'find .. -exec .. +' to allow non-alphanumeric filenames."
        ]
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless Bool
didLs forall a b. (a -> b) -> a -> b
$ forall (f :: * -> *) a. Functor f => f a -> f ()
void forall a b. (a -> b) -> a -> b
$
        forall {m :: * -> *} {b}.
Monad m =>
[String] -> ([Token] -> m b) -> m Bool
for [String
"ls", String
"?"] forall a b. (a -> b) -> a -> b
$
            \(Token
ls:[Token]
_) -> forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (forall {t :: * -> *}. Foldable t => Char -> t String -> Bool
hasShortParameter Char
'N' (Token -> [String]
oversimplify Token
ls)) forall a b. (a -> b) -> a -> b
$
                forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
ls) Code
2012 String
"Use find instead of ls to better handle non-alphanumeric filenames."
  where
    for :: [String] -> ([Token] -> m b) -> m Bool
for [String]
l [Token] -> m b
f =
        let indices :: [Int]
indices = forall {a}. Num a => [String] -> [String] -> [a]
indexOfSublists [String]
l (forall a b. (a -> b) -> [a] -> [b]
map (forall {a}. a -> [a] -> a
headOrDefault String
"" forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [String]
oversimplify) [Token]
commands)
        in do
            forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ ([Token] -> m b
f forall b c a. (b -> c) -> (a -> b) -> a -> c
. (\ Int
n -> forall a. Int -> [a] -> [a]
take (forall (t :: * -> *) a. Foldable t => t a -> Int
length [String]
l) forall a b. (a -> b) -> a -> b
$ forall a. Int -> [a] -> [a]
drop Int
n [Token]
commands)) [Int]
indices
            forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: * -> *) a. Foldable t => t a -> Bool
null forall a b. (a -> b) -> a -> b
$ [Int]
indices
    for' :: [String] -> (Id -> m ()) -> m Bool
for' [String]
l Id -> m ()
f = forall {m :: * -> *} {b}.
Monad m =>
[String] -> ([Token] -> m b) -> m Bool
for [String]
l (forall {m :: * -> *}. Monad m => (Id -> m ()) -> [Token] -> m ()
first Id -> m ()
f)
    first :: (Id -> m ()) -> [Token] -> m ()
first Id -> m ()
func (Token
x:[Token]
_) = Id -> m ()
func (Token -> Id
getId forall a b. (a -> b) -> a -> b
$ Token -> Token
getCommandTokenOrThis Token
x)
    first Id -> m ()
_ [Token]
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()
    hasShortParameter :: Char -> t String -> Bool
hasShortParameter Char
char = forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (\String
x -> String
"-" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
x Bool -> Bool -> Bool
&& Char
char forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
x)
    hasParameter :: String -> t String -> Bool
hasParameter String
string =
        forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (forall a. Eq a => [a] -> [a] -> Bool
isPrefixOf String
string forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. (a -> Bool) -> [a] -> [a]
dropWhile (forall a. Eq a => a -> a -> Bool
== Char
'-'))
checkPipePitfalls p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

indexOfSublists :: [String] -> [String] -> [a]
indexOfSublists [String]
sub = forall {a}. Num a => a -> [String] -> [a]
f a
0
  where
    f :: a -> [String] -> [a]
f a
_ [] = []
    f a
n a :: [String]
a@(String
r:[String]
rest) =
        let others :: [a]
others = a -> [String] -> [a]
f (a
nforall a. Num a => a -> a -> a
+a
1) [String]
rest in
            if [String] -> [String] -> Bool
match [String]
sub [String]
a
              then a
nforall a. a -> [a] -> [a]
:[a]
others
              else [a]
others
    match :: [String] -> [String] -> Bool
match (String
"?":[String]
r1) (String
_:[String]
r2) = [String] -> [String] -> Bool
match [String]
r1 [String]
r2
    match (String
x1:[String]
r1) (String
x2:[String]
r2) | String
x1 forall a. Eq a => a -> a -> Bool
== String
x2 = [String] -> [String] -> Bool
match [String]
r1 [String]
r2
    match [] [String]
_ = Bool
True
    match [String]
_ [String]
_ = Bool
False


prop_checkShebangParameters1 :: Bool
prop_checkShebangParameters1 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {t}. t -> Token -> [TokenComment]
checkShebangParameters String
"#!/usr/bin/env bash -x\necho cow"
prop_checkShebangParameters2 :: Bool
prop_checkShebangParameters2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {t}. t -> Token -> [TokenComment]
checkShebangParameters String
"#! /bin/sh  -l "
prop_checkShebangParameters3 :: Bool
prop_checkShebangParameters3 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {t}. t -> Token -> [TokenComment]
checkShebangParameters String
"#!/usr/bin/env -S bash -x\necho cow"
prop_checkShebangParameters4 :: Bool
prop_checkShebangParameters4 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {t}. t -> Token -> [TokenComment]
checkShebangParameters String
"#!/usr/bin/env --split-string bash -x\necho cow"
checkShebangParameters :: t -> Token -> [TokenComment]
checkShebangParameters t
p (T_Annotation Id
_ [Annotation]
_ Token
t) = t -> Token -> [TokenComment]
checkShebangParameters t
p Token
t
checkShebangParameters t
_ (T_Script Id
_ (T_Literal Id
id String
sb) [Token]
_) =
    [Severity -> Id -> Code -> String -> TokenComment
makeComment Severity
ErrorC Id
id Code
2096 String
"On most OS, shebangs can only specify a single parameter." | Bool
isMultiWord]
  where
    isMultiWord :: Bool
isMultiWord = forall (t :: * -> *) a. Foldable t => t a -> Int
length (String -> [String]
words String
sb) forall a. Ord a => a -> a -> Bool
> Int
2 Bool -> Bool -> Bool
&& Bool -> Bool
not (String
sb String -> Regex -> Bool
`matches` Regex
re)
    re :: Regex
re = String -> Regex
mkRegex String
"env +(-S|--split-string)"

prop_checkShebang1 :: Bool
prop_checkShebang1 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkShebang String
"#!/usr/bin/env bash -x\necho cow"
prop_checkShebang2 :: Bool
prop_checkShebang2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkShebang String
"#! /bin/sh  -l "
prop_checkShebang3 :: Bool
prop_checkShebang3 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkShebang String
"ls -l"
prop_checkShebang4 :: Bool
prop_checkShebang4 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkShebang String
"#shellcheck shell=sh\nfoo"
prop_checkShebang5 :: Bool
prop_checkShebang5 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkShebang String
"#!/usr/bin/env ash"
prop_checkShebang6 :: Bool
prop_checkShebang6 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkShebang String
"#!/usr/bin/env ash\n# shellcheck shell=dash\n"
prop_checkShebang7 :: Bool
prop_checkShebang7 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkShebang String
"#!/usr/bin/env ash\n# shellcheck shell=sh\n"
prop_checkShebang8 :: Bool
prop_checkShebang8 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkShebang String
"#!bin/sh\ntrue"
prop_checkShebang9 :: Bool
prop_checkShebang9 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkShebang String
"# shellcheck shell=sh\ntrue"
prop_checkShebang10 :: Bool
prop_checkShebang10 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkShebang String
"#!foo\n# shellcheck shell=sh ignore=SC2239\ntrue"
prop_checkShebang11 :: Bool
prop_checkShebang11 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkShebang String
"#!/bin/sh/\ntrue"
prop_checkShebang12 :: Bool
prop_checkShebang12 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkShebang String
"#!/bin/sh/ -xe\ntrue"
prop_checkShebang13 :: Bool
prop_checkShebang13 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkShebang String
"#!/bin/busybox sh"
prop_checkShebang14 :: Bool
prop_checkShebang14 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkShebang String
"#!/bin/busybox sh\n# shellcheck shell=sh\n"
prop_checkShebang15 :: Bool
prop_checkShebang15 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkShebang String
"#!/bin/busybox sh\n# shellcheck shell=dash\n"
prop_checkShebang16 :: Bool
prop_checkShebang16 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkShebang String
"#!/bin/busybox ash"
prop_checkShebang17 :: Bool
prop_checkShebang17 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkShebang String
"#!/bin/busybox ash\n# shellcheck shell=dash\n"
prop_checkShebang18 :: Bool
prop_checkShebang18 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkShebang String
"#!/bin/busybox ash\n# shellcheck shell=sh\n"
checkShebang :: Parameters -> Token -> [TokenComment]
checkShebang Parameters
params (T_Annotation Id
_ [Annotation]
list Token
t) =
    if forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Annotation -> Bool
isOverride [Annotation]
list then [] else Parameters -> Token -> [TokenComment]
checkShebang Parameters
params Token
t
  where
    isOverride :: Annotation -> Bool
isOverride (ShellOverride String
_) = Bool
True
    isOverride Annotation
_ = Bool
False
checkShebang Parameters
params (T_Script Id
_ (T_Literal Id
id String
sb) [Token]
_) = forall w a. Writer w a -> w
execWriter forall a b. (a -> b) -> a -> b
$ do
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Parameters -> Bool
shellTypeSpecified Parameters
params) forall a b. (a -> b) -> a -> b
$ do
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
sb) forall a b. (a -> b) -> a -> b
$
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2148 String
"Tips depend on target shell and yours is unknown. Add a shebang or a 'shell' directive."
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String -> String
executableFromShebang String
sb forall a. Eq a => a -> a -> Bool
== String
"ash") forall a b. (a -> b) -> a -> b
$
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2187 String
"Ash scripts will be checked as Dash. Add '# shellcheck shell=dash' to silence."
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
sb) forall a b. (a -> b) -> a -> b
$ do
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (String
"/" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
sb) forall a b. (a -> b) -> a -> b
$
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2239 String
"Ensure the shebang uses an absolute path to the interpreter."
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
"/" forall a. Eq a => [a] -> [a] -> Bool
`isSuffixOf` forall a. [a] -> a
head (String -> [String]
words String
sb)) forall a b. (a -> b) -> a -> b
$
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2246 String
"This shebang specifies a directory. Ensure the interpreter is a file."


prop_checkForInQuoted :: Bool
prop_checkForInQuoted = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForInQuoted String
"for f in \"$(ls)\"; do echo foo; done"
prop_checkForInQuoted2 :: Bool
prop_checkForInQuoted2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForInQuoted String
"for f in \"$@\"; do echo foo; done"
prop_checkForInQuoted2a :: Bool
prop_checkForInQuoted2a = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForInQuoted String
"for f in *.mp3; do echo foo; done"
prop_checkForInQuoted2b :: Bool
prop_checkForInQuoted2b = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForInQuoted String
"for f in \"*.mp3\"; do echo foo; done"
prop_checkForInQuoted3 :: Bool
prop_checkForInQuoted3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForInQuoted String
"for f in 'find /'; do true; done"
prop_checkForInQuoted4 :: Bool
prop_checkForInQuoted4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForInQuoted String
"for f in 1,2,3; do true; done"
prop_checkForInQuoted4a :: Bool
prop_checkForInQuoted4a = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForInQuoted String
"for f in foo{1,2,3}; do true; done"
prop_checkForInQuoted5 :: Bool
prop_checkForInQuoted5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForInQuoted String
"for f in ls; do true; done"
prop_checkForInQuoted6 :: Bool
prop_checkForInQuoted6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForInQuoted String
"for f in \"${!arr}\"; do true; done"
prop_checkForInQuoted7 :: Bool
prop_checkForInQuoted7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForInQuoted String
"for f in ls, grep, mv; do true; done"
prop_checkForInQuoted8 :: Bool
prop_checkForInQuoted8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForInQuoted String
"for f in 'ls', 'grep', 'mv'; do true; done"
prop_checkForInQuoted9 :: Bool
prop_checkForInQuoted9 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForInQuoted String
"for f in 'ls,' 'grep,' 'mv'; do true; done"
checkForInQuoted :: Parameters -> Token -> m ()
checkForInQuoted Parameters
_ (T_ForIn Id
_ String
f [T_NormalWord Id
_ [word :: Token
word@(T_DoubleQuoted Id
id [Token]
list)]] [Token]
_)
    | forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
willSplit [Token]
list Bool -> Bool -> Bool
&& Bool -> Bool
not (Token -> Bool
mayBecomeMultipleArgs Token
word)
            Bool -> Bool -> Bool
|| forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
False forall {t :: * -> *}. Foldable t => t Char -> Bool
wouldHaveBeenGlob (Token -> Maybe String
getLiteralString Token
word) =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2066 String
"Since you double quoted this, it will not word split, and the loop will only run once."
checkForInQuoted Parameters
_ (T_ForIn Id
_ String
f [T_NormalWord Id
_ [T_SingleQuoted Id
id String
_]] [Token]
_) =
    forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2041 String
"This is a literal string. To run as a command, use $(..) instead of '..' . "
checkForInQuoted Parameters
_ (T_ForIn Id
_ String
_ [Token
single] [Token]
_)
    | forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
False (Char
',' forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem`) forall a b. (a -> b) -> a -> b
$ Token -> Maybe String
getUnquotedLiteral Token
single =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
single) Code
2042 String
"Use spaces, not commas, to separate loop elements."
    | Bool -> Bool
not (Token -> Bool
willSplit Token
single Bool -> Bool -> Bool
|| Token -> Bool
mayBecomeMultipleArgs Token
single) =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
single) Code
2043 String
"This loop will only ever run once. Bad quoting or missing glob/expansion?"
checkForInQuoted Parameters
params (T_ForIn Id
_ String
_ [Token]
multiple [Token]
_) =
    forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ [Token]
multiple forall a b. (a -> b) -> a -> b
$ \Token
arg -> forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
        Token
suffix <- Token -> Maybe Token
getTrailingUnquotedLiteral Token
arg
        String
string <- Token -> Maybe String
getLiteralString Token
suffix
        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ String
"," forall a. Eq a => [a] -> [a] -> Bool
`isSuffixOf` String
string
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$
            forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> Fix -> m ()
warnWithFix (Token -> Id
getId Token
arg) Code
2258
                String
"The trailing comma is part of the value, not a separator. Delete or quote it."
                ([Replacement] -> Fix
fixWith [Id -> Parameters -> Code -> String -> Replacement
replaceEnd (Token -> Id
getId Token
suffix) Parameters
params Code
1 String
""])
checkForInQuoted Parameters
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkForInCat1 :: Bool
prop_checkForInCat1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkForInCat String
"for f in $(cat foo); do stuff; done"
prop_checkForInCat1a :: Bool
prop_checkForInCat1a = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkForInCat String
"for f in `cat foo`; do stuff; done"
prop_checkForInCat2 :: Bool
prop_checkForInCat2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkForInCat String
"for f in $(cat foo | grep lol); do stuff; done"
prop_checkForInCat2a :: Bool
prop_checkForInCat2a = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkForInCat String
"for f in `cat foo | grep lol`; do stuff; done"
prop_checkForInCat3 :: Bool
prop_checkForInCat3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkForInCat String
"for f in $(cat foo | grep bar | wc -l); do stuff; done"
checkForInCat :: p -> Token -> m ()
checkForInCat p
_ (T_ForIn Id
_ String
f [T_NormalWord Id
_ [Token]
w] [Token]
_) = forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkF [Token]
w
  where
    checkF :: Token -> m ()
checkF (T_DollarExpansion Id
id [T_Pipeline Id
_ [Token]
_ [Token]
r])
        | forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Token -> Bool
isLineBased [Token]
r =
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info Id
id Code
2013 String
"To read lines rather than words, pipe/redirect to a 'while read' loop."
    checkF (T_Backticked Id
id [Token]
cmds) = Token -> m ()
checkF (Id -> [Token] -> Token
T_DollarExpansion Id
id [Token]
cmds)
    checkF Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()
    isLineBased :: Token -> Bool
isLineBased Token
cmd = forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Token
cmd Token -> String -> Bool
`isCommand`)
                        [String
"grep", String
"fgrep", String
"egrep", String
"sed", String
"cat", String
"awk", String
"cut", String
"sort"]
checkForInCat p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkForInLs :: Bool
prop_checkForInLs = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkForInLs String
"for f in $(ls *.mp3); do mplayer \"$f\"; done"
prop_checkForInLs2 :: Bool
prop_checkForInLs2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkForInLs String
"for f in `ls *.mp3`; do mplayer \"$f\"; done"
prop_checkForInLs3 :: Bool
prop_checkForInLs3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkForInLs String
"for f in `find / -name '*.mp3'`; do mplayer \"$f\"; done"
checkForInLs :: p -> Token -> m ()
checkForInLs p
_ = forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
try
  where
   try :: Token -> m ()
try (T_ForIn Id
_ String
f [T_NormalWord Id
_ [T_DollarExpansion Id
id [Token
x]]] [Token]
_) =
        forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
Id -> p -> Token -> m ()
check Id
id String
f Token
x
   try (T_ForIn Id
_ String
f [T_NormalWord Id
_ [T_Backticked Id
id [Token
x]]] [Token]
_) =
        forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
Id -> p -> Token -> m ()
check Id
id String
f Token
x
   try Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()
   check :: Id -> p -> Token -> m ()
check Id
id p
f Token
x =
    case Token -> [String]
oversimplify Token
x of
      (String
"ls":[String]
n) ->
        let warntype :: Id -> Code -> String -> m ()
warntype = if forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (String
"-" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf`) [String]
n then forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn else forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err in
          Id -> Code -> String -> m ()
warntype Id
id Code
2045 String
"Iterating over ls output is fragile. Use globs."
      (String
"find":[String]
_) -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2044 String
"For loops over find output are fragile. Use find -exec or a while read loop."
      [String]
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkFindExec1 :: Bool
prop_checkFindExec1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkFindExec String
"find / -name '*.php' -exec rm {};"
prop_checkFindExec2 :: Bool
prop_checkFindExec2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkFindExec String
"find / -exec touch {} && ls {} \\;"
prop_checkFindExec3 :: Bool
prop_checkFindExec3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkFindExec String
"find / -execdir cat {} | grep lol +"
prop_checkFindExec4 :: Bool
prop_checkFindExec4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkFindExec String
"find / -name '*.php' -exec foo {} +"
prop_checkFindExec5 :: Bool
prop_checkFindExec5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkFindExec String
"find / -execdir bash -c 'a && b' \\;"
prop_checkFindExec6 :: Bool
prop_checkFindExec6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkFindExec String
"find / -type d -execdir rm *.jpg \\;"
checkFindExec :: p -> Token -> m ()
checkFindExec p
_ cmd :: Token
cmd@(T_SimpleCommand Id
_ [Token]
_ t :: [Token]
t@(Token
h:[Token]
r)) | Token
cmd Token -> String -> Bool
`isCommand` String
"find" = do
    Bool
c <- forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
[Token] -> Bool -> m Bool
broken [Token]
r Bool
False
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when Bool
c forall a b. (a -> b) -> a -> b
$
        let wordId :: Id
wordId = Token -> Id
getId forall a b. (a -> b) -> a -> b
$ forall a. [a] -> a
last [Token]
t in
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
wordId Code
2067 String
"Missing ';' or + terminating -exec. You can't use |/||/&&, and ';' has to be a separate, quoted argument."

  where
    broken :: [Token] -> Bool -> m Bool
broken [] Bool
v = forall (m :: * -> *) a. Monad m => a -> m a
return Bool
v
    broken (Token
w:[Token]
r) Bool
v = do
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when Bool
v (forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
warnFor forall a b. (a -> b) -> a -> b
$ Token -> [Token]
fromWord Token
w)
        case Token -> Maybe String
getLiteralString Token
w of
            Just String
"-exec" -> [Token] -> Bool -> m Bool
broken [Token]
r Bool
True
            Just String
"-execdir" -> [Token] -> Bool -> m Bool
broken [Token]
r Bool
True
            Just String
"+" -> [Token] -> Bool -> m Bool
broken [Token]
r Bool
False
            Just String
";" -> [Token] -> Bool -> m Bool
broken [Token]
r Bool
False
            Maybe String
_ -> [Token] -> Bool -> m Bool
broken [Token]
r Bool
v

    shouldWarn :: Token -> Bool
shouldWarn Token
x =
      case Token
x of
        T_DollarExpansion Id
_ [Token]
_ -> Bool
True
        T_Backticked Id
_ [Token]
_ -> Bool
True
        T_Glob Id
_ String
_ -> Bool
True
        T_Extglob {} -> Bool
True
        Token
_ -> Bool
False

    warnFor :: Token -> f ()
warnFor Token
x =
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when(Token -> Bool
shouldWarn Token
x) forall a b. (a -> b) -> a -> b
$
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
x) Code
2014 String
"This will expand once before find runs, not per file found."

    fromWord :: Token -> [Token]
fromWord (T_NormalWord Id
_ [Token]
l) = [Token]
l
    fromWord Token
_ = []
checkFindExec p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkUnquotedExpansions1 :: Bool
prop_checkUnquotedExpansions1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedExpansions String
"rm $(ls)"
prop_checkUnquotedExpansions1a :: Bool
prop_checkUnquotedExpansions1a = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedExpansions String
"rm `ls`"
prop_checkUnquotedExpansions2 :: Bool
prop_checkUnquotedExpansions2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedExpansions String
"rm foo$(date)"
prop_checkUnquotedExpansions3 :: Bool
prop_checkUnquotedExpansions3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedExpansions String
"[ $(foo) == cow ]"
prop_checkUnquotedExpansions3a :: Bool
prop_checkUnquotedExpansions3a = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedExpansions String
"[ ! $(foo) ]"
prop_checkUnquotedExpansions4 :: Bool
prop_checkUnquotedExpansions4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedExpansions String
"[[ $(foo) == cow ]]"
prop_checkUnquotedExpansions5 :: Bool
prop_checkUnquotedExpansions5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedExpansions String
"for f in $(cmd); do echo $f; done"
prop_checkUnquotedExpansions6 :: Bool
prop_checkUnquotedExpansions6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedExpansions String
"$(cmd)"
prop_checkUnquotedExpansions7 :: Bool
prop_checkUnquotedExpansions7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedExpansions String
"cat << foo\n$(ls)\nfoo"
prop_checkUnquotedExpansions8 :: Bool
prop_checkUnquotedExpansions8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedExpansions String
"set -- $(seq 1 4)"
prop_checkUnquotedExpansions9 :: Bool
prop_checkUnquotedExpansions9 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedExpansions String
"echo foo `# inline comment`"
prop_checkUnquotedExpansions10 :: Bool
prop_checkUnquotedExpansions10 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedExpansions String
"#!/bin/sh\nexport var=$(val)"
prop_checkUnquotedExpansions11 :: Bool
prop_checkUnquotedExpansions11 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedExpansions String
"ps -p $(pgrep foo)"
checkUnquotedExpansions :: Parameters -> Token -> f ()
checkUnquotedExpansions Parameters
params =
    forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
check
  where
    check :: Token -> f ()
check t :: Token
t@(T_DollarExpansion Id
_ [Token]
c) = forall {f :: * -> *} {t :: * -> *} {a}.
(Foldable t, MonadWriter [TokenComment] f) =>
Token -> t a -> f ()
examine Token
t [Token]
c
    check t :: Token
t@(T_Backticked Id
_ [Token]
c) = forall {f :: * -> *} {t :: * -> *} {a}.
(Foldable t, MonadWriter [TokenComment] f) =>
Token -> t a -> f ()
examine Token
t [Token]
c
    check t :: Token
t@(T_DollarBraceCommandExpansion Id
_ [Token]
c) = forall {f :: * -> *} {t :: * -> *} {a}.
(Foldable t, MonadWriter [TokenComment] f) =>
Token -> t a -> f ()
examine Token
t [Token]
c
    check Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()
    tree :: Map Id Token
tree = Parameters -> Map Id Token
parentMap Parameters
params
    examine :: Token -> t a -> f ()
examine Token
t t a
contents =
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (forall (t :: * -> *) a. Foldable t => t a -> Bool
null t a
contents Bool -> Bool -> Bool
|| Token -> Bool
shouldBeSplit Token
t Bool -> Bool -> Bool
|| Shell -> Map Id Token -> Token -> Bool
isQuoteFree (Parameters -> Shell
shellType Parameters
params) Map Id Token
tree Token
t Bool -> Bool -> Bool
|| Map Id Token -> Token -> Bool
usedAsCommandName Map Id Token
tree Token
t) forall a b. (a -> b) -> a -> b
$
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
t) Code
2046 String
"Quote this to prevent word splitting."

    shouldBeSplit :: Token -> Bool
shouldBeSplit Token
t =
        Token -> Maybe String
getCommandNameFromExpansion Token
t forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [forall a. a -> Maybe a
Just String
"seq", forall a. a -> Maybe a
Just String
"pgrep"]


prop_checkRedirectToSame :: Bool
prop_checkRedirectToSame = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectToSame String
"cat foo > foo"
prop_checkRedirectToSame2 :: Bool
prop_checkRedirectToSame2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectToSame String
"cat lol | sed -e 's/a/b/g' > lol"
prop_checkRedirectToSame3 :: Bool
prop_checkRedirectToSame3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectToSame String
"cat lol | sed -e 's/a/b/g' > foo.bar && mv foo.bar lol"
prop_checkRedirectToSame4 :: Bool
prop_checkRedirectToSame4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectToSame String
"foo /dev/null > /dev/null"
prop_checkRedirectToSame5 :: Bool
prop_checkRedirectToSame5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectToSame String
"foo > bar 2> bar"
prop_checkRedirectToSame6 :: Bool
prop_checkRedirectToSame6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectToSame String
"echo foo > foo"
prop_checkRedirectToSame7 :: Bool
prop_checkRedirectToSame7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectToSame String
"sed 's/foo/bar/g' file | sponge file"
prop_checkRedirectToSame8 :: Bool
prop_checkRedirectToSame8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectToSame String
"while read -r line; do _=\"$fname\"; done <\"$fname\""
prop_checkRedirectToSame9 :: Bool
prop_checkRedirectToSame9 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectToSame String
"while read -r line; do cat < \"$fname\"; done <\"$fname\""
prop_checkRedirectToSame10 :: Bool
prop_checkRedirectToSame10 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectToSame String
"mapfile -t foo <foo"
checkRedirectToSame :: Parameters -> Token -> m ()
checkRedirectToSame Parameters
params s :: Token
s@(T_Pipeline Id
_ [Token]
_ [Token]
list) =
    forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (\Token
l -> (forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (\Token
x -> forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> Token -> m Token
doAnalysis (forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Token -> Token -> m ()
checkOccurrences Token
x) Token
l) ([Token] -> [Token]
getAllRedirs [Token]
list))) [Token]
list
  where
    note :: Id -> TokenComment
note Id
x = Severity -> Id -> Code -> String -> TokenComment
makeComment Severity
InfoC Id
x Code
2094
                String
"Make sure not to read and write the same file in the same pipeline."
    checkOccurrences :: Token -> Token -> m ()
checkOccurrences t :: Token
t@(T_NormalWord Id
exceptId [Token]
x) u :: Token
u@(T_NormalWord Id
newId [Token]
y) |
        Id
exceptId forall a. Eq a => a -> a -> Bool
/= Id
newId
                Bool -> Bool -> Bool
&& [Token]
x forall a. Eq a => a -> a -> Bool
== [Token]
y
                Bool -> Bool -> Bool
&& Bool -> Bool
not (Token -> Bool
isInput Token
t Bool -> Bool -> Bool
&& Token -> Bool
isInput Token
u)
                Bool -> Bool -> Bool
&& Bool -> Bool
not (Token -> Bool
isOutput Token
t Bool -> Bool -> Bool
&& Token -> Bool
isOutput Token
u)
                Bool -> Bool -> Bool
&& Bool -> Bool
not (Token -> Bool
special Token
t)
                Bool -> Bool -> Bool
&& Bool -> Bool
not (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
isHarmlessCommand [Token
t,Token
u])
                Bool -> Bool -> Bool
&& Bool -> Bool
not (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
containsAssignment [Token
u]) = do
            forall {a} {m :: * -> *}.
(NFData a, MonadWriter [a] m) =>
a -> m ()
addComment forall a b. (a -> b) -> a -> b
$ Id -> TokenComment
note Id
newId
            forall {a} {m :: * -> *}.
(NFData a, MonadWriter [a] m) =>
a -> m ()
addComment forall a b. (a -> b) -> a -> b
$ Id -> TokenComment
note Id
exceptId
    checkOccurrences Token
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()
    getAllRedirs :: [Token] -> [Token]
getAllRedirs = forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (\Token
t ->
        case Token
t of
            T_Redirecting Id
_ [Token]
ls Token
_ -> forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Token -> [Token]
getRedirs [Token]
ls
            Token
_ -> [])
    getRedirs :: Token -> [Token]
getRedirs (T_FdRedirect Id
_ String
_ (T_IoFile Id
_ Token
op Token
file)) =
            case Token
op of T_Greater Id
_ -> [Token
file]
                       T_Less Id
_    -> [Token
file]
                       T_DGREAT Id
_  -> [Token
file]
                       Token
_ -> []
    getRedirs Token
_ = []
    special :: Token -> Bool
special Token
x = String
"/dev/" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat (Token -> [String]
oversimplify Token
x)
    isInput :: Token -> Bool
isInput Token
t =
        case forall a. Int -> [a] -> [a]
drop Int
1 forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) Token
t of
            T_IoFile Id
_ Token
op Token
_:[Token]
_ ->
                case Token
op of
                    T_Less Id
_  -> Bool
True
                    Token
_ -> Bool
False
            [Token]
_ -> Bool
False
    isOutput :: Token -> Bool
isOutput Token
t =
        case forall a. Int -> [a] -> [a]
drop Int
1 forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) Token
t of
            T_IoFile Id
_ Token
op Token
_:[Token]
_ ->
                case Token
op of
                    T_Greater Id
_  -> Bool
True
                    T_DGREAT Id
_ -> Bool
True
                    Token
_ -> Bool
False
            [Token]
_ -> Bool
False
    isHarmlessCommand :: Token -> Bool
isHarmlessCommand Token
arg = forall a. a -> Maybe a -> a
fromMaybe Bool
False forall a b. (a -> b) -> a -> b
$ do
        Token
cmd <- Map Id Token -> Token -> Maybe Token
getClosestCommand (Parameters -> Map Id Token
parentMap Parameters
params) Token
arg
        String
name <- Token -> Maybe String
getCommandBasename Token
cmd
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ String
name forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"echo", String
"mapfile", String
"printf", String
"sponge"]
    containsAssignment :: Token -> Bool
containsAssignment Token
arg = forall a. a -> Maybe a -> a
fromMaybe Bool
False forall a b. (a -> b) -> a -> b
$ do
        Token
cmd <- Map Id Token -> Token -> Maybe Token
getClosestCommand (Parameters -> Map Id Token
parentMap Parameters
params) Token
arg
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Token -> Bool
isAssignment Token
cmd

checkRedirectToSame Parameters
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkShorthandIf :: Bool
prop_checkShorthandIf  = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkShorthandIf String
"[[ ! -z file ]] && scp file host || rm file"
prop_checkShorthandIf2 :: Bool
prop_checkShorthandIf2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkShorthandIf String
"[[ ! -z file ]] && { scp file host || echo 'Eek'; }"
prop_checkShorthandIf3 :: Bool
prop_checkShorthandIf3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkShorthandIf String
"foo && bar || echo baz"
prop_checkShorthandIf4 :: Bool
prop_checkShorthandIf4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkShorthandIf String
"foo && a=b || a=c"
prop_checkShorthandIf5 :: Bool
prop_checkShorthandIf5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkShorthandIf String
"foo && rm || printf b"
prop_checkShorthandIf6 :: Bool
prop_checkShorthandIf6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkShorthandIf String
"if foo && bar || baz; then true; fi"
prop_checkShorthandIf7 :: Bool
prop_checkShorthandIf7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkShorthandIf String
"while foo && bar || baz; do true; done"
prop_checkShorthandIf8 :: Bool
prop_checkShorthandIf8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkShorthandIf String
"if true; then foo && bar || baz; fi"
checkShorthandIf :: Parameters -> Token -> m ()
checkShorthandIf Parameters
params x :: Token
x@(T_OrIf Id
_ (T_AndIf Id
id Token
_ Token
_) (T_Pipeline Id
_ [Token]
_ [Token]
t))
        | Bool -> Bool
not ([Token] -> Bool
isOk [Token]
t Bool -> Bool -> Bool
|| Bool
inCondition) =
    forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info Id
id Code
2015 String
"Note that A && B || C is not if-then-else. C may run when A is true."
  where
    isOk :: [Token] -> Bool
isOk [Token
t] = Token -> Bool
isAssignment Token
t Bool -> Bool -> Bool
|| forall a. a -> Maybe a -> a
fromMaybe Bool
False (do
        String
name <- Token -> Maybe String
getCommandBasename Token
t
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ String
name forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"echo", String
"exit", String
"return", String
"printf"])
    isOk [Token]
_ = Bool
False
    inCondition :: Bool
inCondition = [Token] -> Bool
isCondition forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) Token
x
checkShorthandIf Parameters
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkDollarStar :: Bool
prop_checkDollarStar = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkDollarStar String
"for f in $*; do ..; done"
prop_checkDollarStar2 :: Bool
prop_checkDollarStar2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkDollarStar String
"a=$*"
prop_checkDollarStar3 :: Bool
prop_checkDollarStar3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkDollarStar String
"[[ $* = 'a b' ]]"
prop_checkDollarStar4 :: Bool
prop_checkDollarStar4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkDollarStar String
"for f in ${var[*]}; do ..; done"
prop_checkDollarStar5 :: Bool
prop_checkDollarStar5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkDollarStar String
"ls ${*//foo/bar}"
prop_checkDollarStar6 :: Bool
prop_checkDollarStar6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkDollarStar String
"ls ${var[*]%%.*}"
prop_checkDollarStar7 :: Bool
prop_checkDollarStar7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkDollarStar String
"ls ${*}"
prop_checkDollarStar8 :: Bool
prop_checkDollarStar8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkDollarStar String
"ls ${#*}"
prop_checkDollarStar9 :: Bool
prop_checkDollarStar9 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkDollarStar String
"ls ${arr[*]}"
prop_checkDollarStar10 :: Bool
prop_checkDollarStar10 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkDollarStar String
"ls ${#arr[*]}"
checkDollarStar :: Parameters -> Token -> m ()
checkDollarStar Parameters
p t :: Token
t@(T_NormalWord Id
_ [T_DollarBraced Id
id Bool
_ Token
l])
        | Bool -> Bool
not (Shell -> Map Id Token -> Token -> Bool
isStrictlyQuoteFree (Parameters -> Shell
shellType Parameters
p) (Parameters -> Map Id Token
parentMap Parameters
p) Token
t) = do
      let str :: String
str = forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat (Token -> [String]
oversimplify Token
l)
      forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
"*" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
str) forall a b. (a -> b) -> a -> b
$
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2048 String
"Use \"$@\" (with quotes) to prevent whitespace problems."
      forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
"[*]" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` (String -> String
getBracedModifier String
str) Bool -> Bool -> Bool
&& Char -> Bool
isVariableChar (forall {a}. a -> [a] -> a
headOrDefault Char
'!' String
str)) forall a b. (a -> b) -> a -> b
$
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2048 String
"Use \"${array[@]}\" (with quotes) to prevent whitespace problems."

checkDollarStar Parameters
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkUnquotedDollarAt :: Bool
prop_checkUnquotedDollarAt = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedDollarAt String
"ls $@"
prop_checkUnquotedDollarAt1 :: Bool
prop_checkUnquotedDollarAt1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedDollarAt String
"ls ${#@}"
prop_checkUnquotedDollarAt2 :: Bool
prop_checkUnquotedDollarAt2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedDollarAt String
"ls ${foo[@]}"
prop_checkUnquotedDollarAt3 :: Bool
prop_checkUnquotedDollarAt3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedDollarAt String
"ls ${#foo[@]}"
prop_checkUnquotedDollarAt4 :: Bool
prop_checkUnquotedDollarAt4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedDollarAt String
"ls \"$@\""
prop_checkUnquotedDollarAt5 :: Bool
prop_checkUnquotedDollarAt5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedDollarAt String
"ls ${foo/@/ at }"
prop_checkUnquotedDollarAt6 :: Bool
prop_checkUnquotedDollarAt6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedDollarAt String
"a=$@"
prop_checkUnquotedDollarAt7 :: Bool
prop_checkUnquotedDollarAt7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedDollarAt String
"for f in ${var[@]}; do true; done"
prop_checkUnquotedDollarAt8 :: Bool
prop_checkUnquotedDollarAt8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedDollarAt String
"echo \"${args[@]:+${args[@]}}\""
prop_checkUnquotedDollarAt9 :: Bool
prop_checkUnquotedDollarAt9 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedDollarAt String
"echo ${args[@]:+\"${args[@]}\"}"
prop_checkUnquotedDollarAt10 :: Bool
prop_checkUnquotedDollarAt10 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedDollarAt String
"echo ${@+\"$@\"}"
checkUnquotedDollarAt :: Parameters -> Token -> m ()
checkUnquotedDollarAt Parameters
p word :: Token
word@(T_NormalWord Id
_ [Token]
parts) | Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ Shell -> Map Id Token -> Token -> Bool
isStrictlyQuoteFree (Parameters -> Shell
shellType Parameters
p) (Parameters -> Map Id Token
parentMap Parameters
p) Token
word =
    forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find Token -> Bool
isArrayExpansion [Token]
parts) forall a b. (a -> b) -> a -> b
$ \Token
x ->
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Token -> Bool
isQuotedAlternativeReference Token
x) forall a b. (a -> b) -> a -> b
$
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
x) Code
2068
                String
"Double quote array expansions to avoid re-splitting elements."
checkUnquotedDollarAt Parameters
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkConcatenatedDollarAt1 :: Bool
prop_checkConcatenatedDollarAt1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkConcatenatedDollarAt String
"echo \"foo$@\""
prop_checkConcatenatedDollarAt2 :: Bool
prop_checkConcatenatedDollarAt2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkConcatenatedDollarAt String
"echo ${arr[@]}lol"
prop_checkConcatenatedDollarAt3 :: Bool
prop_checkConcatenatedDollarAt3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkConcatenatedDollarAt String
"echo $a$@"
prop_checkConcatenatedDollarAt4 :: Bool
prop_checkConcatenatedDollarAt4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkConcatenatedDollarAt String
"echo $@"
prop_checkConcatenatedDollarAt5 :: Bool
prop_checkConcatenatedDollarAt5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkConcatenatedDollarAt String
"echo \"${arr[@]}\""
checkConcatenatedDollarAt :: Parameters -> Token -> m ()
checkConcatenatedDollarAt Parameters
p word :: Token
word@T_NormalWord {}
    | Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ Shell -> Map Id Token -> Token -> Bool
isQuoteFree (Parameters -> Shell
shellType Parameters
p) (Parameters -> Map Id Token
parentMap Parameters
p) Token
word
    Bool -> Bool -> Bool
|| forall (t :: * -> *) a. Foldable t => t a -> Bool
null (forall a. Int -> [a] -> [a]
drop Int
1 [Token]
parts) =
        forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
for Maybe Token
array
  where
    parts :: [Token]
parts = Token -> [Token]
getWordParts Token
word
    array :: Maybe Token
array = forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find Token -> Bool
isArrayExpansion [Token]
parts
    for :: Token -> m ()
for Token
t = forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
t) Code
2145 String
"Argument mixes string and array. Use * or separate argument."
checkConcatenatedDollarAt Parameters
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkArrayAsString1 :: Bool
prop_checkArrayAsString1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkArrayAsString String
"a=$@"
prop_checkArrayAsString2 :: Bool
prop_checkArrayAsString2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkArrayAsString String
"a=\"${arr[@]}\""
prop_checkArrayAsString3 :: Bool
prop_checkArrayAsString3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkArrayAsString String
"a=*.png"
prop_checkArrayAsString4 :: Bool
prop_checkArrayAsString4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkArrayAsString String
"a={1..10}"
prop_checkArrayAsString5 :: Bool
prop_checkArrayAsString5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkArrayAsString String
"a='*.gif'"
prop_checkArrayAsString6 :: Bool
prop_checkArrayAsString6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkArrayAsString String
"a=$*"
prop_checkArrayAsString7 :: Bool
prop_checkArrayAsString7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkArrayAsString String
"a=( $@ )"
checkArrayAsString :: p -> Token -> m ()
checkArrayAsString p
_ (T_Assignment Id
id AssignmentMode
_ String
_ [Token]
_ Token
word) =
    if Token -> Bool
willConcatInAssignment Token
word
    then
      forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
word) Code
2124
        String
"Assigning an array to a string! Assign as array, or use * instead of @ to concatenate."
    else
      forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
willBecomeMultipleArgs Token
word) forall a b. (a -> b) -> a -> b
$
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
word) Code
2125
          String
"Brace expansions and globs are literal in assignments. Quote it or use an array."
checkArrayAsString p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkArrayWithoutIndex1 :: Bool
prop_checkArrayWithoutIndex1 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkArrayWithoutIndex String
"foo=(a b); echo $foo"
prop_checkArrayWithoutIndex2 :: Bool
prop_checkArrayWithoutIndex2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkArrayWithoutIndex String
"foo='bar baz'; foo=($foo); echo ${foo[0]}"
prop_checkArrayWithoutIndex3 :: Bool
prop_checkArrayWithoutIndex3 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkArrayWithoutIndex String
"coproc foo while true; do echo cow; done; echo $foo"
prop_checkArrayWithoutIndex4 :: Bool
prop_checkArrayWithoutIndex4 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkArrayWithoutIndex String
"coproc tail -f log; echo $COPROC"
prop_checkArrayWithoutIndex5 :: Bool
prop_checkArrayWithoutIndex5 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkArrayWithoutIndex String
"a[0]=foo; echo $a"
prop_checkArrayWithoutIndex6 :: Bool
prop_checkArrayWithoutIndex6 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkArrayWithoutIndex String
"echo $PIPESTATUS"
prop_checkArrayWithoutIndex7 :: Bool
prop_checkArrayWithoutIndex7 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkArrayWithoutIndex String
"a=(a b); a+=c"
prop_checkArrayWithoutIndex8 :: Bool
prop_checkArrayWithoutIndex8 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkArrayWithoutIndex String
"declare -a foo; foo=bar;"
prop_checkArrayWithoutIndex9 :: Bool
prop_checkArrayWithoutIndex9 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkArrayWithoutIndex String
"read -r -a arr <<< 'foo bar'; echo \"$arr\""
prop_checkArrayWithoutIndex10 :: Bool
prop_checkArrayWithoutIndex10 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkArrayWithoutIndex String
"read -ra arr <<< 'foo bar'; echo \"$arr\""
prop_checkArrayWithoutIndex11 :: Bool
prop_checkArrayWithoutIndex11 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkArrayWithoutIndex String
"read -rpfoobar r; r=42"
checkArrayWithoutIndex :: Parameters -> p -> [TokenComment]
checkArrayWithoutIndex Parameters
params p
_ =
    forall t v.
(Token -> Token -> String -> State t [v])
-> (Token -> Token -> String -> DataType -> State t [v])
-> t
-> [StackData]
-> [v]
doVariableFlowAnalysis forall {m :: * -> *} {a} {p} {p}.
MonadState (Map String a) m =>
p -> Token -> p -> m [TokenComment]
readF forall {m :: * -> *} {p}.
MonadState (Map String ()) m =>
p -> Token -> String -> DataType -> m [TokenComment]
writeF Map String ()
defaultMap (Parameters -> [StackData]
variableFlow Parameters
params)
  where
    defaultMap :: Map String ()
defaultMap = forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map (\String
x -> (String
x,())) [String]
arrayVariables
    readF :: p -> Token -> p -> m [TokenComment]
readF p
_ (T_DollarBraced Id
id Bool
_ Token
token) p
_ = do
        Map String a
map <- forall s (m :: * -> *). MonadState s m => m s
get
        forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Maybe a -> [a]
maybeToList forall a b. (a -> b) -> a -> b
$ do
            String
name <- Token -> Maybe String
getLiteralString Token
token
            a
assigned <- forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup String
name Map String a
map
            forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Severity -> Id -> Code -> String -> TokenComment
makeComment Severity
WarningC Id
id Code
2128
                    String
"Expanding an array without an index only gives the first element."
    readF p
_ Token
_ p
_ = forall (m :: * -> *) a. Monad m => a -> m a
return []

    writeF :: p -> Token -> String -> DataType -> m [TokenComment]
writeF p
_ (T_Assignment Id
id AssignmentMode
mode String
name [] Token
_) String
_ (DataString DataSource
_) = do
        Bool
isArray <- forall s (m :: * -> *) a. MonadState s m => (s -> a) -> m a
gets (forall k a. Ord k => k -> Map k a -> Bool
Map.member String
name)
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ if Bool -> Bool
not Bool
isArray then [] else
            case AssignmentMode
mode of
                AssignmentMode
Assign -> [Severity -> Id -> Code -> String -> TokenComment
makeComment Severity
WarningC Id
id Code
2178 String
"Variable was used as an array but is now assigned a string."]
                AssignmentMode
Append -> [Severity -> Id -> Code -> String -> TokenComment
makeComment Severity
WarningC Id
id Code
2179 String
"Use array+=(\"item\") to append items to an array."]

    writeF p
_ Token
t String
name (DataArray DataSource
_) = do
        forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify (forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert String
name ())
        forall (m :: * -> *) a. Monad m => a -> m a
return []
    writeF p
_ Token
expr String
name DataType
_ = do
        if Token -> Bool
isIndexed Token
expr
          then forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify (forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert String
name ())
          else forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify (forall k a. Ord k => k -> Map k a -> Map k a
Map.delete String
name)
        forall (m :: * -> *) a. Monad m => a -> m a
return []

    isIndexed :: Token -> Bool
isIndexed Token
expr =
        case Token
expr of
            T_Assignment Id
_ AssignmentMode
_ String
_ (Token
_:[Token]
_) Token
_ -> Bool
True
            Token
_ -> Bool
False

prop_checkStderrRedirect :: Bool
prop_checkStderrRedirect = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkStderrRedirect String
"test 2>&1 > cow"
prop_checkStderrRedirect2 :: Bool
prop_checkStderrRedirect2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkStderrRedirect String
"test > cow 2>&1"
prop_checkStderrRedirect3 :: Bool
prop_checkStderrRedirect3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkStderrRedirect String
"test 2>&1 > file | grep stderr"
prop_checkStderrRedirect4 :: Bool
prop_checkStderrRedirect4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkStderrRedirect String
"errors=$(test 2>&1 > file)"
prop_checkStderrRedirect5 :: Bool
prop_checkStderrRedirect5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkStderrRedirect String
"read < <(test 2>&1 > file)"
prop_checkStderrRedirect6 :: Bool
prop_checkStderrRedirect6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkStderrRedirect String
"foo | bar 2>&1 > /dev/null"
prop_checkStderrRedirect7 :: Bool
prop_checkStderrRedirect7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkStderrRedirect String
"{ cmd > file; } 2>&1"
checkStderrRedirect :: Parameters -> Token -> f ()
checkStderrRedirect Parameters
params redir :: Token
redir@(T_Redirecting Id
_ [
    T_FdRedirect Id
id String
"2" (T_IoDuplicate Id
_ (T_GREATAND Id
_) String
"1"),
    T_FdRedirect Id
_ String
_ (T_IoFile Id
_ Token
op Token
_)
    ] Token
_) = case Token
op of
            T_Greater Id
_ -> f ()
error
            T_DGREAT Id
_ -> f ()
error
            Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    usesOutput :: Token -> Bool
usesOutput Token
t =
        case Token
t of
            (T_Pipeline Id
_ [Token]
_ [Token]
list) -> forall (t :: * -> *) a. Foldable t => t a -> Int
length [Token]
list forall a. Ord a => a -> a -> Bool
> Int
1 Bool -> Bool -> Bool
&& Bool -> Bool
not (Map Id Token -> Token -> Token -> Bool
isParentOf (Parameters -> Map Id Token
parentMap Parameters
params) (forall a. [a] -> a
last [Token]
list) Token
redir)
            T_ProcSub {} -> Bool
True
            T_DollarExpansion {} -> Bool
True
            T_Backticked {} -> Bool
True
            Token
_ -> Bool
False
    isCaptured :: Bool
isCaptured = forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
usesOutput forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) Token
redir

    error :: f ()
error = forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless Bool
isCaptured forall a b. (a -> b) -> a -> b
$
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2069 String
"To redirect stdout+stderr, 2>&1 must be last (or use '{ cmd > file; } 2>&1' to clarify)."

checkStderrRedirect Parameters
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

lt :: a -> a
lt a
x = forall a. String -> a -> a
trace (String
"Tracing " forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> String
show a
x) a
x -- STRIP
ltt :: a -> a -> a
ltt a
t = forall a. String -> a -> a
trace (String
"Tracing " forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> String
show a
t)  -- STRIP


prop_checkSingleQuotedVariables :: Bool
prop_checkSingleQuotedVariables  = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"echo '$foo'"
prop_checkSingleQuotedVariables2 :: Bool
prop_checkSingleQuotedVariables2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"echo 'lol$1.jpg'"
prop_checkSingleQuotedVariables3 :: Bool
prop_checkSingleQuotedVariables3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"sed 's/foo$/bar/'"
prop_checkSingleQuotedVariables3a :: Bool
prop_checkSingleQuotedVariables3a = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"sed 's/${foo}/bar/'"
prop_checkSingleQuotedVariables3b :: Bool
prop_checkSingleQuotedVariables3b = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"sed 's/$(echo cow)/bar/'"
prop_checkSingleQuotedVariables3c :: Bool
prop_checkSingleQuotedVariables3c = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"sed 's/$((1+foo))/bar/'"
prop_checkSingleQuotedVariables4 :: Bool
prop_checkSingleQuotedVariables4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"awk '{print $1}'"
prop_checkSingleQuotedVariables5 :: Bool
prop_checkSingleQuotedVariables5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"trap 'echo $SECONDS' EXIT"
prop_checkSingleQuotedVariables6 :: Bool
prop_checkSingleQuotedVariables6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"sed -n '$p'"
prop_checkSingleQuotedVariables6a :: Bool
prop_checkSingleQuotedVariables6a = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"sed -n '$pattern'"
prop_checkSingleQuotedVariables7 :: Bool
prop_checkSingleQuotedVariables7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"PS1='$PWD \\$ '"
prop_checkSingleQuotedVariables8 :: Bool
prop_checkSingleQuotedVariables8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"find . -exec echo '$1' {} +"
prop_checkSingleQuotedVariables9 :: Bool
prop_checkSingleQuotedVariables9 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"find . -exec awk '{print $1}' {} \\;"
prop_checkSingleQuotedVariables10 :: Bool
prop_checkSingleQuotedVariables10 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"echo '`pwd`'"
prop_checkSingleQuotedVariables11 :: Bool
prop_checkSingleQuotedVariables11 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"sed '${/lol/d}'"
prop_checkSingleQuotedVariables12 :: Bool
prop_checkSingleQuotedVariables12 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"eval 'echo $1'"
prop_checkSingleQuotedVariables13 :: Bool
prop_checkSingleQuotedVariables13 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"busybox awk '{print $1}'"
prop_checkSingleQuotedVariables14 :: Bool
prop_checkSingleQuotedVariables14 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"[ -v 'bar[$foo]' ]"
prop_checkSingleQuotedVariables15 :: Bool
prop_checkSingleQuotedVariables15 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"git filter-branch 'test $GIT_COMMIT'"
prop_checkSingleQuotedVariables16 :: Bool
prop_checkSingleQuotedVariables16 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"git '$a'"
prop_checkSingleQuotedVariables17 :: Bool
prop_checkSingleQuotedVariables17 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"rename 's/(.)a/$1/g' *"
prop_checkSingleQuotedVariables18 :: Bool
prop_checkSingleQuotedVariables18 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"echo '``'"
prop_checkSingleQuotedVariables19 :: Bool
prop_checkSingleQuotedVariables19 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"echo '```'"
prop_checkSingleQuotedVariables20 :: Bool
prop_checkSingleQuotedVariables20 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"mumps -run %XCMD 'W $O(^GLOBAL(5))'"
prop_checkSingleQuotedVariables21 :: Bool
prop_checkSingleQuotedVariables21 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"mumps -run LOOP%XCMD --xec 'W $O(^GLOBAL(6))'"
prop_checkSingleQuotedVariables22 :: Bool
prop_checkSingleQuotedVariables22 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"jq '$__loc__'"
prop_checkSingleQuotedVariables23 :: Bool
prop_checkSingleQuotedVariables23 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"command jq '$__loc__'"
prop_checkSingleQuotedVariables24 :: Bool
prop_checkSingleQuotedVariables24 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"exec jq '$__loc__'"
prop_checkSingleQuotedVariables25 :: Bool
prop_checkSingleQuotedVariables25 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"exec -c -a foo jq '$__loc__'"


checkSingleQuotedVariables :: Parameters -> Token -> f ()
checkSingleQuotedVariables Parameters
params t :: Token
t@(T_SingleQuoted Id
id String
s) =
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
s String -> Regex -> Bool
`matches` Regex
re) forall a b. (a -> b) -> a -> b
$
        if String
"sed" forall a. Eq a => a -> a -> Bool
== String
commandName
        then forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (String
s String -> Regex -> Bool
`matches` Regex
sedContra) f ()
showMessage
        else forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless Bool
isProbablyOk f ()
showMessage
  where
    parents :: Map Id Token
parents = Parameters -> Map Id Token
parentMap Parameters
params
    showMessage :: f ()
showMessage = forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info Id
id Code
2016
        String
"Expressions don't expand in single quotes, use double quotes for that."
    commandName :: String
commandName = forall a. a -> Maybe a -> a
fromMaybe String
"" forall a b. (a -> b) -> a -> b
$ do
        Token
cmd <- Map Id Token -> Token -> Maybe Token
getClosestCommand Map Id Token
parents Token
t
        String
name <- Token -> Maybe String
getCommandBasename Token
cmd
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ if String
name forall a. Eq a => a -> a -> Bool
== String
"find" then Token -> String
getFindCommand Token
cmd else if String
name forall a. Eq a => a -> a -> Bool
== String
"git" then Token -> String
getGitCommand Token
cmd else if String
name forall a. Eq a => a -> a -> Bool
== String
"mumps" then Token -> String
getMumpsCommand Token
cmd else String
name

    isProbablyOk :: Bool
isProbablyOk =
            forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
isOkAssignment (forall a. Int -> [a] -> [a]
take Int
3 forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> [Token]
getPath Map Id Token
parents Token
t)
            Bool -> Bool -> Bool
|| String
commandName forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [
                String
"trap"
                ,String
"sh"
                ,String
"bash"
                ,String
"ksh"
                ,String
"zsh"
                ,String
"ssh"
                ,String
"eval"
                ,String
"xprop"
                ,String
"alias"
                ,String
"sudo" -- covering "sudo sh" and such
                ,String
"docker" -- like above
                ,String
"podman"
                ,String
"dpkg-query"
                ,String
"jq"  -- could also check that user provides --arg
                ,String
"rename"
                ,String
"rg"
                ,String
"unset"
                ,String
"git filter-branch"
                ,String
"mumps -run %XCMD"
                ,String
"mumps -run LOOP%XCMD"
                ]
            Bool -> Bool -> Bool
|| String
"awk" forall a. Eq a => [a] -> [a] -> Bool
`isSuffixOf` String
commandName
            Bool -> Bool -> Bool
|| String
"perl" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
commandName

    commonlyQuoted :: [String]
commonlyQuoted = [String
"PS1", String
"PS2", String
"PS3", String
"PS4", String
"PROMPT_COMMAND"]
    isOkAssignment :: Token -> Bool
isOkAssignment Token
t =
        case Token
t of
            T_Assignment Id
_ AssignmentMode
_ String
name [Token]
_ Token
_ -> String
name forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
commonlyQuoted
            TC_Unary Id
_ ConditionType
_ String
"-v" Token
_ -> Bool
True
            Token
_ -> Bool
False

    re :: Regex
re = String -> Regex
mkRegex String
"\\$[{(0-9a-zA-Z_]|`[^`]+`"
    sedContra :: Regex
sedContra = String -> Regex
mkRegex String
"\\$[{dpsaic]($|[^a-zA-Z])"

    getFindCommand :: Token -> String
getFindCommand (T_SimpleCommand Id
_ [Token]
_ [Token]
words) =
        let list :: [Maybe String]
list = forall a b. (a -> b) -> [a] -> [b]
map Token -> Maybe String
getLiteralString [Token]
words
            cmd :: [Maybe String]
cmd  = forall a. (a -> Bool) -> [a] -> [a]
dropWhile (\Maybe String
x -> Maybe String
x forall a. Eq a => a -> a -> Bool
/= forall a. a -> Maybe a
Just String
"-exec" Bool -> Bool -> Bool
&& Maybe String
x forall a. Eq a => a -> a -> Bool
/= forall a. a -> Maybe a
Just String
"-execdir") [Maybe String]
list
        in
          case [Maybe String]
cmd of
            (Maybe String
flag:Maybe String
cmd:[Maybe String]
rest) -> forall a. a -> Maybe a -> a
fromMaybe String
"find" Maybe String
cmd
            [Maybe String]
_ -> String
"find"
    getFindCommand (T_Redirecting Id
_ [Token]
_ Token
cmd) = Token -> String
getFindCommand Token
cmd
    getFindCommand Token
_ = String
"find"
    getGitCommand :: Token -> String
getGitCommand (T_SimpleCommand Id
_ [Token]
_ [Token]
words) =
        case forall a b. (a -> b) -> [a] -> [b]
map Token -> Maybe String
getLiteralString [Token]
words of
            Just String
"git":Just String
"filter-branch":[Maybe String]
_ -> String
"git filter-branch"
            [Maybe String]
_ -> String
"git"
    getGitCommand (T_Redirecting Id
_ [Token]
_ Token
cmd) = Token -> String
getGitCommand Token
cmd
    getGitCommand Token
_ = String
"git"
    getMumpsCommand :: Token -> String
getMumpsCommand (T_SimpleCommand Id
_ [Token]
_ [Token]
words) =
        case forall a b. (a -> b) -> [a] -> [b]
map Token -> Maybe String
getLiteralString [Token]
words of
            Just String
"mumps":Just String
"-run":Just String
"%XCMD":[Maybe String]
_ -> String
"mumps -run %XCMD"
            Just String
"mumps":Just String
"-run":Just String
"LOOP%XCMD":[Maybe String]
_ -> String
"mumps -run LOOP%XCMD"
            [Maybe String]
_ -> String
"mumps"
    getMumpsCommand (T_Redirecting Id
_ [Token]
_ Token
cmd) = Token -> String
getMumpsCommand Token
cmd
    getMumpsCommand Token
_ = String
"mumps"
checkSingleQuotedVariables Parameters
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkUnquotedN :: Bool
prop_checkUnquotedN = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUnquotedN String
"if [ -n $foo ]; then echo cow; fi"
prop_checkUnquotedN2 :: Bool
prop_checkUnquotedN2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUnquotedN String
"[ -n $cow ]"
prop_checkUnquotedN3 :: Bool
prop_checkUnquotedN3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUnquotedN String
"[[ -n $foo ]] && echo cow"
prop_checkUnquotedN4 :: Bool
prop_checkUnquotedN4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUnquotedN String
"[ -n $cow -o -t 1 ]"
prop_checkUnquotedN5 :: Bool
prop_checkUnquotedN5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUnquotedN String
"[ -n \"$@\" ]"
checkUnquotedN :: p -> Token -> f ()
checkUnquotedN p
_ (TC_Unary Id
_ ConditionType
SingleBracket String
"-n" Token
t) | Token -> Bool
willSplit Token
t =
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
isArrayExpansion forall a b. (a -> b) -> a -> b
$ Token -> [Token]
getWordParts Token
t) forall a b. (a -> b) -> a -> b
$ -- There's SC2198 for these
       forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
t) Code
2070 String
"-n doesn't work with unquoted arguments. Quote or use [[ ]]."
checkUnquotedN p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkNumberComparisons1 :: Bool
prop_checkNumberComparisons1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[[ $foo < 3 ]]"
prop_checkNumberComparisons2 :: Bool
prop_checkNumberComparisons2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[[ 0 >= $(cmd) ]]"
prop_checkNumberComparisons3 :: Bool
prop_checkNumberComparisons3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[[ $foo ]] > 3"
prop_checkNumberComparisons4 :: Bool
prop_checkNumberComparisons4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[[ $foo > 2.72 ]]"
prop_checkNumberComparisons5 :: Bool
prop_checkNumberComparisons5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[[ $foo -le 2.72 ]]"
prop_checkNumberComparisons6 :: Bool
prop_checkNumberComparisons6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[[ 3.14 -eq $foo ]]"
prop_checkNumberComparisons7 :: Bool
prop_checkNumberComparisons7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[[ 3.14 == $foo ]]"
prop_checkNumberComparisons8 :: Bool
prop_checkNumberComparisons8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[ foo <= bar ]"
prop_checkNumberComparisons9 :: Bool
prop_checkNumberComparisons9 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[ foo \\>= bar ]"
prop_checkNumberComparisons11 :: Bool
prop_checkNumberComparisons11 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[ $foo -eq 'N' ]"
prop_checkNumberComparisons12 :: Bool
prop_checkNumberComparisons12 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[ x$foo -gt x${N} ]"
prop_checkNumberComparisons13 :: Bool
prop_checkNumberComparisons13 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[ $foo > $bar ]"
prop_checkNumberComparisons14 :: Bool
prop_checkNumberComparisons14 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[[ foo < bar ]]"
prop_checkNumberComparisons15 :: Bool
prop_checkNumberComparisons15 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[ $foo '>' $bar ]"
prop_checkNumberComparisons16 :: Bool
prop_checkNumberComparisons16 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[ foo -eq 'y' ]"
prop_checkNumberComparisons17 :: Bool
prop_checkNumberComparisons17 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[[ 'foo' -eq 2 ]]"
prop_checkNumberComparisons18 :: Bool
prop_checkNumberComparisons18 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[[ foo -eq 2 ]]"
prop_checkNumberComparisons19 :: Bool
prop_checkNumberComparisons19 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"foo=1; [[ foo -eq 2 ]]"
prop_checkNumberComparisons20 :: Bool
prop_checkNumberComparisons20 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[[ 2 -eq / ]]"
prop_checkNumberComparisons21 :: Bool
prop_checkNumberComparisons21 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[[ foo -eq foo ]]"
prop_checkNumberComparisons22 :: Bool
prop_checkNumberComparisons22 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"x=10; [[ $x > $z ]]"
prop_checkNumberComparisons23 :: Bool
prop_checkNumberComparisons23 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"x=0; if [[ -n $def ]]; then x=$def; fi; while [ $x > $z ]; do lol; done"
prop_checkNumberComparisons24 :: Bool
prop_checkNumberComparisons24 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"x=$RANDOM; [ $x > $z ]"
prop_checkNumberComparisons25 :: Bool
prop_checkNumberComparisons25 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[[ $((n++)) > $x ]]"

checkNumberComparisons :: Parameters -> Token -> m ()
checkNumberComparisons Parameters
params (TC_Binary Id
id ConditionType
typ String
op Token
lhs Token
rhs) = do
    if Token -> Bool
isNum Token
lhs Bool -> Bool -> Bool
|| Token -> Bool
isNum Token
rhs
      then do
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String -> Bool
isLtGt String
op) forall a b. (a -> b) -> a -> b
$
          forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2071 forall a b. (a -> b) -> a -> b
$
            String
op forall a. [a] -> [a] -> [a]
++ String
" is for string comparisons. Use " forall a. [a] -> [a] -> [a]
++ String -> String
eqv String
op forall a. [a] -> [a] -> [a]
++ String
" instead."
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String -> Bool
isLeGe String
op Bool -> Bool -> Bool
&& Bool
hasStringComparison) forall a b. (a -> b) -> a -> b
$
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2071 forall a b. (a -> b) -> a -> b
$ String
op forall a. [a] -> [a] -> [a]
++ String
" is not a valid operator. " forall a. [a] -> [a] -> [a]
++
              String
"Use " forall a. [a] -> [a] -> [a]
++ String -> String
eqv String
op forall a. [a] -> [a] -> [a]
++ String
" ."
      else do
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String -> Bool
isLeGe String
op Bool -> Bool -> Bool
|| String -> Bool
isLtGt String
op) forall a b. (a -> b) -> a -> b
$
            forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkDecimals [Token
lhs, Token
rhs]

        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String -> Bool
isLeGe String
op Bool -> Bool -> Bool
&& Bool
hasStringComparison) forall a b. (a -> b) -> a -> b
$
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2122 forall a b. (a -> b) -> a -> b
$ String
op forall a. [a] -> [a] -> [a]
++ String
" is not a valid operator. " forall a. [a] -> [a] -> [a]
++
                String
"Use '! a " forall a. [a] -> [a] -> [a]
++ String
esc forall a. [a] -> [a] -> [a]
++ String -> String
invert String
op forall a. [a] -> [a] -> [a]
++ String
" b' instead."

        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (ConditionType
typ forall a. Eq a => a -> a -> Bool
== ConditionType
SingleBracket Bool -> Bool -> Bool
&& String
op forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"<", String
">"]) forall a b. (a -> b) -> a -> b
$
            case Parameters -> Shell
shellType Parameters
params of
                Shell
Sh -> forall (m :: * -> *) a. Monad m => a -> m a
return ()  -- These are unsupported and will be caught by bashism checks.
                Shell
Dash -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2073 forall a b. (a -> b) -> a -> b
$ String
"Escape \\" forall a. [a] -> [a] -> [a]
++ String
op forall a. [a] -> [a] -> [a]
++ String
" to prevent it redirecting."
                Shell
_ -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2073 forall a b. (a -> b) -> a -> b
$ String
"Escape \\" forall a. [a] -> [a] -> [a]
++ String
op forall a. [a] -> [a] -> [a]
++ String
" to prevent it redirecting (or switch to [[ .. ]])."

    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
op forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
arithmeticBinaryTestOps) forall a b. (a -> b) -> a -> b
$ do
        forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkDecimals [Token
lhs, Token
rhs]
        forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkString [Token
lhs, Token
rhs]


  where
      hasStringComparison :: Bool
hasStringComparison = Parameters -> Shell
shellType Parameters
params forall a. Eq a => a -> a -> Bool
/= Shell
Sh
      isLtGt :: String -> Bool
isLtGt = forall a b c. (a -> b -> c) -> b -> a -> c
flip forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
elem [String
"<", String
"\\<", String
">", String
"\\>"]
      isLeGe :: String -> Bool
isLeGe = forall a b c. (a -> b -> c) -> b -> a -> c
flip forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
elem [String
"<=", String
"\\<=", String
">=", String
"\\>="]

      checkDecimals :: Token -> f ()
checkDecimals Token
hs =
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isFraction Token
hs Bool -> Bool -> Bool
&& Bool -> Bool
not (Parameters -> Bool
hasFloatingPoint Parameters
params)) forall a b. (a -> b) -> a -> b
$
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
hs) Code
2072 String
decimalError
      decimalError :: String
decimalError = String
"Decimals are not supported. " forall a. [a] -> [a] -> [a]
++
        String
"Either use integers only, or use bc or awk to compare."

      checkString :: Token -> f ()
checkString Token
t =
        let
            asString :: String
asString = String -> Token -> String
getLiteralStringDef String
"\0" Token
t
            isVar :: Bool
isVar = String -> Bool
isVariableName String
asString
            kind :: String
kind = if Bool
isVar then String
"a variable" else String
"an arithmetic expression"
            fix :: String
fix = if Bool
isVar then String
"$var" else String
"$((expr))"
        in
            forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isNonNum Token
t) forall a b. (a -> b) -> a -> b
$
                if ConditionType
typ forall a. Eq a => a -> a -> Bool
== ConditionType
SingleBracket
                then
                    forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
t) Code
2170 forall a b. (a -> b) -> a -> b
$
                        String
"Invalid number for " forall a. [a] -> [a] -> [a]
++ String
op forall a. [a] -> [a] -> [a]
++ String
". Use " forall a. [a] -> [a] -> [a]
++ String -> String
seqv String
op forall a. [a] -> [a] -> [a]
++
                        String
" to compare as string (or use " forall a. [a] -> [a] -> [a]
++ String
fix forall a. [a] -> [a] -> [a]
++
                        String
" to expand as " forall a. [a] -> [a] -> [a]
++ String
kind forall a. [a] -> [a] -> [a]
++ String
")."
                else
                    -- We should warn if any of the following holds:
                    --   The string is not a variable name
                    --   Any part of it is quoted
                    --   It's not a recognized variable name
                    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Bool -> Bool
not Bool
isVar Bool -> Bool -> Bool
|| forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
isQuotes (Token -> [Token]
getWordParts Token
t) Bool -> Bool -> Bool
|| String
asString forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [String]
assignedVariables) forall a b. (a -> b) -> a -> b
$
                        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
t) Code
2309 forall a b. (a -> b) -> a -> b
$
                            String
op forall a. [a] -> [a] -> [a]
++ String
" treats this as " forall a. [a] -> [a] -> [a]
++ String
kind forall a. [a] -> [a] -> [a]
++ String
". " forall a. [a] -> [a] -> [a]
++
                            String
"Use " forall a. [a] -> [a] -> [a]
++ String -> String
seqv String
op forall a. [a] -> [a] -> [a]
++ String
" to compare as string (or expand explicitly with " forall a. [a] -> [a] -> [a]
++ String
fix forall a. [a] -> [a] -> [a]
++ String
")."

      assignedVariables :: [String]
      assignedVariables :: [String]
assignedVariables = forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe forall {m :: * -> *}. MonadFail m => StackData -> m String
f (Parameters -> [StackData]
variableFlow Parameters
params)
        where
            f :: StackData -> m String
f StackData
t = do
                Assignment (Token
_, Token
_, String
name, DataType
_) <- forall (m :: * -> *) a. Monad m => a -> m a
return StackData
t
                forall (m :: * -> *) a. Monad m => a -> m a
return String
name

      isNonNum :: Token -> Bool
isNonNum Token
t = Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
numChar forall a b. (a -> b) -> a -> b
$ Token -> String
onlyLiteralString Token
t
      numChar :: Char -> Bool
numChar Char
x = Char -> Bool
isDigit Char
x Bool -> Bool -> Bool
|| Char
x forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
"+-. "

      isNum :: Token -> Bool
isNum Token
t =
        case Token -> [Token]
getWordParts Token
t of
            [T_DollarArithmetic {}] -> Bool
True
            [b :: Token
b@(T_DollarBraced Id
id Bool
_ Token
c)] ->
                let
                    str :: String
str = forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
c
                    var :: String
var = String -> String
getBracedReference String
str
                in forall a. a -> Maybe a -> a
fromMaybe Bool
False forall a b. (a -> b) -> a -> b
$ do
                    ProgramState
state <- CFGAnalysis -> Id -> Maybe ProgramState
CF.getIncomingState (Parameters -> CFGAnalysis
cfgAnalysis Parameters
params) Id
id
                    VariableState
value <- forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup String
var forall a b. (a -> b) -> a -> b
$ ProgramState -> Map String VariableState
CF.variablesInScope ProgramState
state
                    forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ VariableValue -> NumericalStatus
CF.numericalStatus (VariableState -> VariableValue
CF.variableValue VariableState
value) forall a. Ord a => a -> a -> Bool
>= NumericalStatus
CF.NumericalStatusMaybe
            [Token]
_ ->
                case Token -> [String]
oversimplify Token
t of
                    [String
v] -> forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isDigit String
v
                    [String]
_ -> Bool
False

      isFraction :: Token -> Bool
isFraction Token
t =
        case Token -> [String]
oversimplify Token
t of
            [String
v] -> forall a. Maybe a -> Bool
isJust forall a b. (a -> b) -> a -> b
$ Regex -> String -> Maybe [String]
matchRegex Regex
floatRegex String
v
            [String]
_ -> Bool
False

      eqv :: String -> String
eqv (Char
'\\':String
s) = String -> String
eqv String
s
      eqv String
"<" = String
"-lt"
      eqv String
">" = String
"-gt"
      eqv String
"<=" = String
"-le"
      eqv String
">=" = String
"-ge"
      eqv String
_ = String
"the numerical equivalent"

      esc :: String
esc = if ConditionType
typ forall a. Eq a => a -> a -> Bool
== ConditionType
SingleBracket then String
"\\" else String
""
      seqv :: String -> String
seqv String
"-ge" = String
"! a " forall a. [a] -> [a] -> [a]
++ String
esc forall a. [a] -> [a] -> [a]
++ String
"< b"
      seqv String
"-gt" = String
esc forall a. [a] -> [a] -> [a]
++ String
">"
      seqv String
"-le" = String
"! a " forall a. [a] -> [a] -> [a]
++ String
esc forall a. [a] -> [a] -> [a]
++ String
"> b"
      seqv String
"-lt" = String
esc forall a. [a] -> [a] -> [a]
++ String
"<"
      seqv String
"-eq" = String
"="
      seqv String
"-ne" = String
"!="
      seqv String
_ = String
"the string equivalent"

      invert :: String -> String
invert (Char
'\\':String
s) = String -> String
invert String
s
      invert String
"<=" = String
">"
      invert String
">=" = String
"<"

      floatRegex :: Regex
floatRegex = String -> Regex
mkRegex String
"^[-+]?[0-9]+\\.[0-9]+$"
checkNumberComparisons Parameters
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkSingleBracketOperators1 :: Bool
prop_checkSingleBracketOperators1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleBracketOperators String
"[ test =~ foo ]"
checkSingleBracketOperators :: Parameters -> Token -> m ()
checkSingleBracketOperators Parameters
params (TC_Binary Id
id ConditionType
SingleBracket String
"=~" Token
lhs Token
rhs)
    | Parameters -> Shell
shellType Parameters
params forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Shell
Bash, Shell
Ksh] =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2074 forall a b. (a -> b) -> a -> b
$ String
"Can't use =~ in [ ]. Use [[..]] instead."
checkSingleBracketOperators Parameters
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkDoubleBracketOperators1 :: Bool
prop_checkDoubleBracketOperators1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkDoubleBracketOperators String
"[[ 3 \\< 4 ]]"
prop_checkDoubleBracketOperators3 :: Bool
prop_checkDoubleBracketOperators3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkDoubleBracketOperators String
"[[ foo < bar ]]"
checkDoubleBracketOperators :: p -> Token -> m ()
checkDoubleBracketOperators p
_ x :: Token
x@(TC_Binary Id
id ConditionType
typ String
op Token
lhs Token
rhs)
    | ConditionType
typ forall a. Eq a => a -> a -> Bool
== ConditionType
DoubleBracket Bool -> Bool -> Bool
&& String
op forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"\\<", String
"\\>"] =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2075 forall a b. (a -> b) -> a -> b
$ String
"Escaping " forall a. [a] -> [a] -> [a]
++ String
op forall a. [a] -> [a] -> [a]
++String
" is required in [..], but invalid in [[..]]"
checkDoubleBracketOperators p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkConditionalAndOrs1 :: Bool
prop_checkConditionalAndOrs1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConditionalAndOrs String
"[ foo && bar ]"
prop_checkConditionalAndOrs2 :: Bool
prop_checkConditionalAndOrs2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConditionalAndOrs String
"[[ foo -o bar ]]"
prop_checkConditionalAndOrs3 :: Bool
prop_checkConditionalAndOrs3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConditionalAndOrs String
"[[ foo || bar ]]"
prop_checkConditionalAndOrs4 :: Bool
prop_checkConditionalAndOrs4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConditionalAndOrs String
"[ foo -a bar ]"
prop_checkConditionalAndOrs5 :: Bool
prop_checkConditionalAndOrs5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConditionalAndOrs String
"[ -z 3 -o a = b ]"
checkConditionalAndOrs :: p -> Token -> m ()
checkConditionalAndOrs p
_ Token
t =
    case Token
t of
        (TC_And Id
id ConditionType
SingleBracket String
"&&" Token
_ Token
_) ->
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2107 String
"Instead of [ a && b ], use [ a ] && [ b ]."
        (TC_And Id
id ConditionType
DoubleBracket String
"-a" Token
_ Token
_) ->
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2108 String
"In [[..]], use && instead of -a."
        (TC_Or Id
id ConditionType
SingleBracket String
"||" Token
_ Token
_) ->
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2109 String
"Instead of [ a || b ], use [ a ] || [ b ]."
        (TC_Or Id
id ConditionType
DoubleBracket String
"-o" Token
_ Token
_) ->
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2110 String
"In [[..]], use || instead of -o."

        (TC_And Id
id ConditionType
SingleBracket String
"-a" Token
_ Token
_) ->
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2166 String
"Prefer [ p ] && [ q ] as [ p -a q ] is not well defined."
        (TC_Or Id
id ConditionType
SingleBracket String
"-o" Token
_ Token
_) ->
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2166 String
"Prefer [ p ] || [ q ] as [ p -o q ] is not well defined."

        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkQuotedCondRegex1 :: Bool
prop_checkQuotedCondRegex1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkQuotedCondRegex String
"[[ $foo =~ \"bar.*\" ]]"
prop_checkQuotedCondRegex2 :: Bool
prop_checkQuotedCondRegex2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkQuotedCondRegex String
"[[ $foo =~ '(cow|bar)' ]]"
prop_checkQuotedCondRegex3 :: Bool
prop_checkQuotedCondRegex3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkQuotedCondRegex String
"[[ $foo =~ $foo ]]"
prop_checkQuotedCondRegex4 :: Bool
prop_checkQuotedCondRegex4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkQuotedCondRegex String
"[[ $foo =~ \"bar\" ]]"
prop_checkQuotedCondRegex5 :: Bool
prop_checkQuotedCondRegex5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkQuotedCondRegex String
"[[ $foo =~ 'cow bar' ]]"
prop_checkQuotedCondRegex6 :: Bool
prop_checkQuotedCondRegex6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkQuotedCondRegex String
"[[ $foo =~ 'cow|bar' ]]"
checkQuotedCondRegex :: p -> Token -> f ()
checkQuotedCondRegex p
_ (TC_Binary Id
_ ConditionType
_ String
"=~" Token
_ Token
rhs) =
    case Token
rhs of
        T_NormalWord Id
id [T_DoubleQuoted Id
_ [Token]
_] -> forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
error Token
rhs
        T_NormalWord Id
id [T_SingleQuoted Id
_ String
_] -> forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
error Token
rhs
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    error :: Token -> f ()
error Token
t =
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Token -> Bool
isConstantNonRe Token
t) forall a b. (a -> b) -> a -> b
$
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
t) Code
2076
                String
"Remove quotes from right-hand side of =~ to match as a regex rather than literally."
    re :: Regex
re = String -> Regex
mkRegex String
"[][*.+()|]"
    hasMetachars :: String -> Bool
hasMetachars String
s = String
s String -> Regex -> Bool
`matches` Regex
re
    isConstantNonRe :: Token -> Bool
isConstantNonRe Token
t = forall a. a -> Maybe a -> a
fromMaybe Bool
False forall a b. (a -> b) -> a -> b
$ do
        String
s <- Token -> Maybe String
getLiteralString Token
t
        forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ String -> Bool
hasMetachars String
s
checkQuotedCondRegex p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkGlobbedRegex1 :: Bool
prop_checkGlobbedRegex1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobbedRegex String
"[[ $foo =~ *foo* ]]"
prop_checkGlobbedRegex2 :: Bool
prop_checkGlobbedRegex2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobbedRegex String
"[[ $foo =~ f* ]]"
prop_checkGlobbedRegex3 :: Bool
prop_checkGlobbedRegex3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobbedRegex String
"[[ $foo =~ $foo ]]"
prop_checkGlobbedRegex4 :: Bool
prop_checkGlobbedRegex4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobbedRegex String
"[[ $foo =~ ^c.* ]]"
prop_checkGlobbedRegex5 :: Bool
prop_checkGlobbedRegex5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobbedRegex String
"[[ $foo =~ \\* ]]"
prop_checkGlobbedRegex6 :: Bool
prop_checkGlobbedRegex6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobbedRegex String
"[[ $foo =~ (o*) ]]"
prop_checkGlobbedRegex7 :: Bool
prop_checkGlobbedRegex7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobbedRegex String
"[[ $foo =~ \\*foo ]]"
prop_checkGlobbedRegex8 :: Bool
prop_checkGlobbedRegex8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobbedRegex String
"[[ $foo =~ x\\* ]]"
checkGlobbedRegex :: p -> Token -> m ()
checkGlobbedRegex p
_ (TC_Binary Id
_ ConditionType
DoubleBracket String
"=~" Token
_ Token
rhs)
    | String -> Bool
isConfusedGlobRegex String
s =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
rhs) Code
2049 String
"=~ is for regex, but this looks like a glob. Use = instead."
    where s :: String
s = forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
rhs
checkGlobbedRegex p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkConstantIfs1 :: Bool
prop_checkConstantIfs1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantIfs String
"[[ foo != bar ]]"
prop_checkConstantIfs2a :: Bool
prop_checkConstantIfs2a = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantIfs String
"[ n -le 4 ]"
prop_checkConstantIfs2b :: Bool
prop_checkConstantIfs2b = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantIfs String
"[[ n -le 4 ]]"
prop_checkConstantIfs3 :: Bool
prop_checkConstantIfs3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantIfs String
"[[ $n -le 4 && n != 2 ]]"
prop_checkConstantIfs4 :: Bool
prop_checkConstantIfs4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantIfs String
"[[ $n -le 3 ]]"
prop_checkConstantIfs5 :: Bool
prop_checkConstantIfs5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantIfs String
"[[ $n -le $n ]]"
prop_checkConstantIfs6 :: Bool
prop_checkConstantIfs6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantIfs String
"[[ a -ot b ]]"
prop_checkConstantIfs7 :: Bool
prop_checkConstantIfs7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantIfs String
"[ a -nt b ]"
prop_checkConstantIfs8 :: Bool
prop_checkConstantIfs8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantIfs String
"[[ ~foo == '~foo' ]]"
prop_checkConstantIfs9 :: Bool
prop_checkConstantIfs9 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantIfs String
"[[ *.png == [a-z] ]]"
prop_checkConstantIfs10 :: Bool
prop_checkConstantIfs10 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantIfs String
"[[ ~me == ~+ ]]"
prop_checkConstantIfs11 :: Bool
prop_checkConstantIfs11 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantIfs String
"[[ ~ == ~+ ]]"
prop_checkConstantIfs12 :: Bool
prop_checkConstantIfs12 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantIfs String
"[[ '~' == x ]]"
checkConstantIfs :: p -> Token -> m ()
checkConstantIfs p
_ (TC_Binary Id
id ConditionType
typ String
op Token
lhs Token
rhs) | Bool -> Bool
not Bool
isDynamic =
    if Token -> Bool
isConstant Token
lhs Bool -> Bool -> Bool
&& Token -> Bool
isConstant Token
rhs
        then  forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2050 String
"This expression is constant. Did you forget the $ on a variable?"
        else forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
Id -> String -> Token -> Token -> f ()
checkUnmatchable Id
id String
op Token
lhs Token
rhs
  where
    isDynamic :: Bool
isDynamic =
        String
op forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
arithmeticBinaryTestOps
            Bool -> Bool -> Bool
&& ConditionType
typ forall a. Eq a => a -> a -> Bool
== ConditionType
DoubleBracket
        Bool -> Bool -> Bool
|| String
op forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [ String
"-nt", String
"-ot", String
"-ef"]

    checkUnmatchable :: Id -> String -> Token -> Token -> f ()
checkUnmatchable Id
id String
op Token
lhs Token
rhs =
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
op forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"=", String
"==", String
"!="] Bool -> Bool -> Bool
&& Bool -> Bool
not (Token -> Token -> Bool
wordsCanBeEqual Token
lhs Token
rhs)) forall a b. (a -> b) -> a -> b
$
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2193 String
"The arguments to this comparison can never be equal. Make sure your syntax is correct."
checkConstantIfs p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkLiteralBreakingTest :: Bool
prop_checkLiteralBreakingTest = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkLiteralBreakingTest String
"[[ a==$foo ]]"
prop_checkLiteralBreakingTest2 :: Bool
prop_checkLiteralBreakingTest2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkLiteralBreakingTest String
"[ $foo=3 ]"
prop_checkLiteralBreakingTest3 :: Bool
prop_checkLiteralBreakingTest3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkLiteralBreakingTest String
"[ $foo!=3 ]"
prop_checkLiteralBreakingTest4 :: Bool
prop_checkLiteralBreakingTest4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkLiteralBreakingTest String
"[ \"$(ls) \" ]"
prop_checkLiteralBreakingTest5 :: Bool
prop_checkLiteralBreakingTest5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkLiteralBreakingTest String
"[ -n \"$(true) \" ]"
prop_checkLiteralBreakingTest6 :: Bool
prop_checkLiteralBreakingTest6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkLiteralBreakingTest String
"[ -z $(true)z ]"
prop_checkLiteralBreakingTest7 :: Bool
prop_checkLiteralBreakingTest7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkLiteralBreakingTest String
"[ -z $(true) ]"
prop_checkLiteralBreakingTest8 :: Bool
prop_checkLiteralBreakingTest8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkLiteralBreakingTest String
"[ $(true)$(true) ]"
prop_checkLiteralBreakingTest10 :: Bool
prop_checkLiteralBreakingTest10 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkLiteralBreakingTest String
"[ -z foo ]"
checkLiteralBreakingTest :: p -> Token -> m ()
checkLiteralBreakingTest p
_ Token
t = forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$
        case Token
t of
            (TC_Nullary Id
_ ConditionType
_ w :: Token
w@(T_NormalWord Id
_ [Token]
l)) -> do
                forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ Token -> Bool
isConstant Token
w -- Covered by SC2078
                forall {t :: * -> *} {m :: * -> *}.
(Foldable t, MonadWriter [TokenComment] m) =>
t Token -> Maybe (m ())
comparisonWarning [Token]
l forall (m :: * -> *) a. MonadPlus m => m a -> m a -> m a
`mplus` forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Token -> String -> Maybe (m ())
tautologyWarning Token
w String
"Argument to implicit -n is always true due to literal strings."
            (TC_Unary Id
_ ConditionType
_ String
op w :: Token
w@(T_NormalWord Id
_ [Token]
l)) ->
                case String
op of
                    String
"-n" -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Token -> String -> Maybe (m ())
tautologyWarning Token
w String
"Argument to -n is always true due to literal strings."
                    String
"-z" -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Token -> String -> Maybe (m ())
tautologyWarning Token
w String
"Argument to -z is always false due to literal strings."
                    String
_ -> forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
"not relevant"
            Token
_ -> forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
"not my problem"
  where
    hasEquals :: Token -> Bool
hasEquals = (String -> Bool) -> Token -> Bool
matchToken (Char
'=' forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem`)
    isNonEmpty :: Token -> Bool
isNonEmpty = (String -> Bool) -> Token -> Bool
matchToken (Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: * -> *) a. Foldable t => t a -> Bool
null)
    matchToken :: (String -> Bool) -> Token -> Bool
matchToken String -> Bool
m Token
t = forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
False String -> Bool
m (Token -> Maybe String
getLiteralString Token
t)

    comparisonWarning :: t Token -> Maybe (m ())
comparisonWarning t Token
list = do
        Token
token <- forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find Token -> Bool
hasEquals t Token
list
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
token) Code
2077 String
"You need spaces around the comparison operator."
    tautologyWarning :: Token -> String -> Maybe (m ())
tautologyWarning Token
t String
s = do
        Token
token <- forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find Token -> Bool
isNonEmpty forall a b. (a -> b) -> a -> b
$ Token -> [Token]
getWordParts Token
t
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
token) Code
2157 String
s

prop_checkConstantNullary :: Bool
prop_checkConstantNullary = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantNullary String
"[[ '$(foo)' ]]"
prop_checkConstantNullary2 :: Bool
prop_checkConstantNullary2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantNullary String
"[ \"-f lol\" ]"
prop_checkConstantNullary3 :: Bool
prop_checkConstantNullary3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantNullary String
"[[ cmd ]]"
prop_checkConstantNullary4 :: Bool
prop_checkConstantNullary4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantNullary String
"[[ ! cmd ]]"
prop_checkConstantNullary5 :: Bool
prop_checkConstantNullary5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantNullary String
"[[ true ]]"
prop_checkConstantNullary6 :: Bool
prop_checkConstantNullary6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantNullary String
"[ 1 ]"
prop_checkConstantNullary7 :: Bool
prop_checkConstantNullary7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantNullary String
"[ false ]"
checkConstantNullary :: p -> Token -> m ()
checkConstantNullary p
_ (TC_Nullary Id
_ ConditionType
_ Token
t) | Token -> Bool
isConstant Token
t =
    case forall a. a -> Maybe a -> a
fromMaybe String
"" forall a b. (a -> b) -> a -> b
$ Token -> Maybe String
getLiteralString Token
t of
        String
"false" -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
t) Code
2158 String
"[ false ] is true. Remove the brackets."
        String
"0" -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
t) Code
2159 String
"[ 0 ] is true. Use 'false' instead."
        String
"true" -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style (Token -> Id
getId Token
t) Code
2160 String
"Instead of '[ true ]', just use 'true'."
        String
"1" -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style (Token -> Id
getId Token
t) Code
2161 String
"Instead of '[ 1 ]', use 'true'."
        String
_ -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
t) Code
2078 String
"This expression is constant. Did you forget a $ somewhere?"
  where
    string :: String
string = forall a. a -> Maybe a -> a
fromMaybe String
"" forall a b. (a -> b) -> a -> b
$ Token -> Maybe String
getLiteralString Token
t

checkConstantNullary p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkForDecimals1 :: Bool
prop_checkForDecimals1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForDecimals String
"((3.14*c))"
prop_checkForDecimals2 :: Bool
prop_checkForDecimals2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForDecimals String
"foo[1.2]=bar"
prop_checkForDecimals3 :: Bool
prop_checkForDecimals3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForDecimals String
"declare -A foo; foo[1.2]=bar"
checkForDecimals :: Parameters -> Token -> m ()
checkForDecimals Parameters
params t :: Token
t@(TA_Expansion Id
id [Token]
_) = forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
    forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ Bool -> Bool
not (Parameters -> Bool
hasFloatingPoint Parameters
params)
    String
str <- Token -> Maybe String
getLiteralString Token
t
    Char
first <- String
str forall {a}. [a] -> Int -> Maybe a
!!! Int
0
    forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ Char -> Bool
isDigit Char
first Bool -> Bool -> Bool
&& Char
'.' forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
str
    forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2079 String
"(( )) doesn't support decimals. Use bc or awk."
checkForDecimals Parameters
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkDivBeforeMult :: Bool
prop_checkDivBeforeMult = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkDivBeforeMult String
"echo $((c/n*100))"
prop_checkDivBeforeMult2 :: Bool
prop_checkDivBeforeMult2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkDivBeforeMult String
"echo $((c*100/n))"
prop_checkDivBeforeMult3 :: Bool
prop_checkDivBeforeMult3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkDivBeforeMult String
"echo $((c/10*10))"
checkDivBeforeMult :: Parameters -> Token -> m ()
checkDivBeforeMult Parameters
params (TA_Binary Id
_ String
"*" (TA_Binary Id
id String
"/" Token
_ Token
x) Token
y)
    | Bool -> Bool
not (Parameters -> Bool
hasFloatingPoint Parameters
params) Bool -> Bool -> Bool
&& Token
x forall a. Eq a => a -> a -> Bool
/= Token
y =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info Id
id Code
2017 String
"Increase precision by replacing a/b*c with a*c/b."
checkDivBeforeMult Parameters
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkArithmeticDeref :: Bool
prop_checkArithmeticDeref = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkArithmeticDeref String
"echo $((3+$foo))"
prop_checkArithmeticDeref2 :: Bool
prop_checkArithmeticDeref2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkArithmeticDeref String
"cow=14; (( s+= $cow ))"
prop_checkArithmeticDeref3 :: Bool
prop_checkArithmeticDeref3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkArithmeticDeref String
"cow=1/40; (( s+= ${cow%%/*} ))"
prop_checkArithmeticDeref4 :: Bool
prop_checkArithmeticDeref4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkArithmeticDeref String
"(( ! $? ))"
prop_checkArithmeticDeref5 :: Bool
prop_checkArithmeticDeref5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkArithmeticDeref String
"(($1))"
prop_checkArithmeticDeref6 :: Bool
prop_checkArithmeticDeref6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkArithmeticDeref String
"(( a[$i] ))"
prop_checkArithmeticDeref7 :: Bool
prop_checkArithmeticDeref7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkArithmeticDeref String
"(( 10#$n ))"
prop_checkArithmeticDeref8 :: Bool
prop_checkArithmeticDeref8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkArithmeticDeref String
"let i=$i+1"
prop_checkArithmeticDeref9 :: Bool
prop_checkArithmeticDeref9 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkArithmeticDeref String
"(( a[foo] ))"
prop_checkArithmeticDeref10 :: Bool
prop_checkArithmeticDeref10 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkArithmeticDeref String
"(( a[\\$foo] ))"
prop_checkArithmeticDeref11 :: Bool
prop_checkArithmeticDeref11 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkArithmeticDeref String
"a[$foo]=wee"
prop_checkArithmeticDeref11b :: Bool
prop_checkArithmeticDeref11b = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkArithmeticDeref String
"declare -A a; a[$foo]=wee"
prop_checkArithmeticDeref12 :: Bool
prop_checkArithmeticDeref12 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkArithmeticDeref String
"for ((i=0; $i < 3; i)); do true; done"
prop_checkArithmeticDeref13 :: Bool
prop_checkArithmeticDeref13 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkArithmeticDeref String
"(( $$ ))"
prop_checkArithmeticDeref14 :: Bool
prop_checkArithmeticDeref14 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkArithmeticDeref String
"(( $! ))"
prop_checkArithmeticDeref15 :: Bool
prop_checkArithmeticDeref15 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkArithmeticDeref String
"(( ${!var} ))"
prop_checkArithmeticDeref16 :: Bool
prop_checkArithmeticDeref16 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkArithmeticDeref String
"(( ${x+1} + ${x=42} ))"
checkArithmeticDeref :: Parameters -> Token -> f ()
checkArithmeticDeref Parameters
params t :: Token
t@(TA_Expansion Id
_ [T_DollarBraced Id
id Bool
_ Token
l]) =
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (String -> Bool
isException forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
l) f ()
getWarning
  where
    isException :: String -> Bool
isException [] = Bool
True
    isException s :: String
s@(Char
h:String
_) = forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
"/.:#%?*@$-!+=^,") String
s Bool -> Bool -> Bool
|| Char -> Bool
isDigit Char
h
    getWarning :: f ()
getWarning = forall a. a -> Maybe a -> a
fromMaybe f ()
noWarning forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, MonadPlus m) =>
t (m a) -> m a
msum forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a -> b) -> [a] -> [b]
map Token -> Maybe (f ())
warningFor forall a b. (a -> b) -> a -> b
$ Parameters -> Token -> [Token]
parents Parameters
params Token
t
    warningFor :: Token -> Maybe (f ())
warningFor Token
t =
        case Token
t of
            T_Arithmetic {} -> forall (m :: * -> *) a. Monad m => a -> m a
return f ()
normalWarning
            T_DollarArithmetic {} -> forall (m :: * -> *) a. Monad m => a -> m a
return f ()
normalWarning
            T_ForArithmetic {} -> forall (m :: * -> *) a. Monad m => a -> m a
return f ()
normalWarning
            T_Assignment {} -> forall (m :: * -> *) a. Monad m => a -> m a
return f ()
normalWarning
            T_SimpleCommand {} -> forall (m :: * -> *) a. Monad m => a -> m a
return f ()
noWarning
            Token
_ -> forall a. Maybe a
Nothing

    normalWarning :: f ()
normalWarning = forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style Id
id Code
2004 String
"$/${} is unnecessary on arithmetic variables."
    noWarning :: f ()
noWarning = forall (m :: * -> *) a. Monad m => a -> m a
return ()
checkArithmeticDeref Parameters
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkArithmeticBadOctal1 :: Bool
prop_checkArithmeticBadOctal1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkArithmeticBadOctal String
"(( 0192 ))"
prop_checkArithmeticBadOctal2 :: Bool
prop_checkArithmeticBadOctal2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkArithmeticBadOctal String
"(( 0x192 ))"
prop_checkArithmeticBadOctal3 :: Bool
prop_checkArithmeticBadOctal3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkArithmeticBadOctal String
"(( 1 ^ 0777 ))"
checkArithmeticBadOctal :: p -> Token -> m ()
checkArithmeticBadOctal p
_ t :: Token
t@(TA_Expansion Id
id [Token]
_) = forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
    String
str <- Token -> Maybe String
getLiteralString Token
t
    forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ String
str String -> Regex -> Bool
`matches` Regex
octalRE
    forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2080 String
"Numbers with leading 0 are considered octal."
  where
    octalRE :: Regex
octalRE = String -> Regex
mkRegex String
"^0[0-7]*[8-9]"
checkArithmeticBadOctal p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkComparisonAgainstGlob :: Bool
prop_checkComparisonAgainstGlob = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkComparisonAgainstGlob String
"[[ $cow == $bar ]]"
prop_checkComparisonAgainstGlob2 :: Bool
prop_checkComparisonAgainstGlob2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkComparisonAgainstGlob String
"[[ $cow == \"$bar\" ]]"
prop_checkComparisonAgainstGlob3 :: Bool
prop_checkComparisonAgainstGlob3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkComparisonAgainstGlob String
"[ $cow = *foo* ]"
prop_checkComparisonAgainstGlob4 :: Bool
prop_checkComparisonAgainstGlob4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkComparisonAgainstGlob String
"[ $cow = foo ]"
prop_checkComparisonAgainstGlob5 :: Bool
prop_checkComparisonAgainstGlob5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkComparisonAgainstGlob String
"[[ $cow != $bar ]]"
prop_checkComparisonAgainstGlob6 :: Bool
prop_checkComparisonAgainstGlob6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkComparisonAgainstGlob String
"[ $f != /* ]"
checkComparisonAgainstGlob :: Parameters -> Token -> m ()
checkComparisonAgainstGlob Parameters
_ (TC_Binary Id
_ ConditionType
DoubleBracket String
op Token
_ (T_NormalWord Id
id [T_DollarBraced Id
_ Bool
_ Token
_]))
    | String
op forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"=", String
"==", String
"!="] =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2053 forall a b. (a -> b) -> a -> b
$ String
"Quote the right-hand side of " forall a. [a] -> [a] -> [a]
++ String
op forall a. [a] -> [a] -> [a]
++ String
" in [[ ]] to prevent glob matching."
checkComparisonAgainstGlob Parameters
params (TC_Binary Id
_ ConditionType
SingleBracket String
op Token
_ Token
word)
        | String
op forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"=", String
"==", String
"!="] Bool -> Bool -> Bool
&& Token -> Bool
isGlob Token
word =
    forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
word) Code
2081 String
msg
  where
    msg :: String
msg = if Parameters -> Bool
isBashLike Parameters
params
            then String
"[ .. ] can't match globs. Use [[ .. ]] or case statement."
            else String
"[ .. ] can't match globs. Use a case statement."

checkComparisonAgainstGlob Parameters
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkCaseAgainstGlob1 :: Bool
prop_checkCaseAgainstGlob1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCaseAgainstGlob String
"case foo in lol$n) foo;; esac"
prop_checkCaseAgainstGlob2 :: Bool
prop_checkCaseAgainstGlob2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCaseAgainstGlob String
"case foo in $(foo)) foo;; esac"
prop_checkCaseAgainstGlob3 :: Bool
prop_checkCaseAgainstGlob3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCaseAgainstGlob String
"case foo in *$bar*) foo;; esac"
checkCaseAgainstGlob :: p -> Token -> m ()
checkCaseAgainstGlob p
_ Token
t =
    case Token
t of
        (T_CaseExpression Id
_ Token
_ [(CaseType, [Token], [Token])]
cases) -> forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {t :: * -> *} {m :: * -> *} {a} {c}.
(Foldable t, MonadWriter [TokenComment] m) =>
(a, t Token, c) -> m ()
check [(CaseType, [Token], [Token])]
cases
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    check :: (a, t Token, c) -> m ()
check (a
_, t Token
list, c
_) = forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
check' t Token
list
    check' :: Token -> m ()
check' expr :: Token
expr@(T_NormalWord Id
_ [Token]
list)
        -- If it's already a glob, assume that's what the user wanted
        | Bool -> Bool
not (Token -> Bool
isGlob Token
expr) Bool -> Bool -> Bool
&& forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
isQuoteableExpansion [Token]
list =
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
expr) Code
2254 String
"Quote expansions in case patterns to match literally rather than as a glob."
    check' Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkCommarrays1 :: Bool
prop_checkCommarrays1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCommarrays String
"a=(1, 2)"
prop_checkCommarrays2 :: Bool
prop_checkCommarrays2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCommarrays String
"a+=(1,2,3)"
prop_checkCommarrays3 :: Bool
prop_checkCommarrays3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCommarrays String
"cow=(1 \"foo,bar\" 3)"
prop_checkCommarrays4 :: Bool
prop_checkCommarrays4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCommarrays String
"cow=('one,' 'two')"
prop_checkCommarrays5 :: Bool
prop_checkCommarrays5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCommarrays String
"a=([a]=b, [c]=d)"
prop_checkCommarrays6 :: Bool
prop_checkCommarrays6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCommarrays String
"a=([a]=b,[c]=d,[e]=f)"
prop_checkCommarrays7 :: Bool
prop_checkCommarrays7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCommarrays String
"a=(1,2)"
checkCommarrays :: p -> Token -> f ()
checkCommarrays p
_ (T_Array Id
id [Token]
l) =
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (String -> Bool
isCommaSeparated forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> String
literal) [Token]
l) forall a b. (a -> b) -> a -> b
$
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2054 String
"Use spaces, not commas, to separate array elements."
  where
    literal :: Token -> String
literal (T_IndexedElement Id
_ [Token]
_ Token
l) = Token -> String
literal Token
l
    literal (T_NormalWord Id
_ [Token]
l) = forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Token -> String
literal [Token]
l
    literal (T_Literal Id
_ String
str) = String
str
    literal Token
_ = String
""

    isCommaSeparated :: String -> Bool
isCommaSeparated = forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
elem Char
','
checkCommarrays p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkOrNeq1 :: Bool
prop_checkOrNeq1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOrNeq String
"if [[ $lol -ne cow || $lol -ne foo ]]; then echo foo; fi"
prop_checkOrNeq2 :: Bool
prop_checkOrNeq2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOrNeq String
"(( a!=lol || a!=foo ))"
prop_checkOrNeq3 :: Bool
prop_checkOrNeq3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOrNeq String
"[ \"$a\" != lol || \"$a\" != foo ]"
prop_checkOrNeq4 :: Bool
prop_checkOrNeq4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOrNeq String
"[ a != $cow || b != $foo ]"
prop_checkOrNeq5 :: Bool
prop_checkOrNeq5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOrNeq String
"[[ $a != /home || $a != */public_html/* ]]"
prop_checkOrNeq6 :: Bool
prop_checkOrNeq6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOrNeq String
"[ $a != a ] || [ $a != b ]"
prop_checkOrNeq7 :: Bool
prop_checkOrNeq7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOrNeq String
"[ $a != a ] || [ $a != b ] || true"
prop_checkOrNeq8 :: Bool
prop_checkOrNeq8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOrNeq String
"[[ $a != x || $a != x ]]"
prop_checkOrNeq9 :: Bool
prop_checkOrNeq9 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOrNeq String
"[ 0 -ne $FOO ] || [ 0 -ne $BAR ]"
-- This only catches the most idiomatic cases. Fixme?

-- For test-level "or": [ x != y -o x != z ]
checkOrNeq :: p -> Token -> m ()
checkOrNeq p
_ (TC_Or Id
id ConditionType
typ String
op (TC_Binary Id
_ ConditionType
_ String
op1 Token
lhs1 Token
rhs1 ) (TC_Binary Id
_ ConditionType
_ String
op2 Token
lhs2 Token
rhs2))
    | (String
op1 forall a. Eq a => a -> a -> Bool
== String
op2 Bool -> Bool -> Bool
&& (String
op1 forall a. Eq a => a -> a -> Bool
== String
"-ne" Bool -> Bool -> Bool
|| String
op1 forall a. Eq a => a -> a -> Bool
== String
"!=")) Bool -> Bool -> Bool
&& Token
lhs1 forall a. Eq a => a -> a -> Bool
== Token
lhs2 Bool -> Bool -> Bool
&& Token
rhs1 forall a. Eq a => a -> a -> Bool
/= Token
rhs2 Bool -> Bool -> Bool
&& Bool -> Bool
not (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
isGlob [Token
rhs1,Token
rhs2]) =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2055 forall a b. (a -> b) -> a -> b
$ String
"You probably wanted " forall a. [a] -> [a] -> [a]
++ (if ConditionType
typ forall a. Eq a => a -> a -> Bool
== ConditionType
SingleBracket then String
"-a" else String
"&&") forall a. [a] -> [a] -> [a]
++ String
" here, otherwise it's always true."

-- For arithmetic context "or"
checkOrNeq p
_ (TA_Binary Id
id String
"||" (TA_Binary Id
_ String
"!=" Token
word1 Token
_) (TA_Binary Id
_ String
"!=" Token
word2 Token
_))
    | Token
word1 forall a. Eq a => a -> a -> Bool
== Token
word2 =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2056 String
"You probably wanted && here, otherwise it's always true."

-- For command level "or": [ x != y ] || [ x != z ]
checkOrNeq p
_ (T_OrIf Id
id Token
lhs Token
rhs) = forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
    (Token
lhs1, String
op1, Token
rhs1) <- Token -> Maybe (Token, String, Token)
getExpr Token
lhs
    (Token
lhs2, String
op2, Token
rhs2) <- Token -> Maybe (Token, String, Token)
getExpr Token
rhs
    forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ String
op1 forall a. Eq a => a -> a -> Bool
== String
op2 Bool -> Bool -> Bool
&& String
op1 forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"-ne", String
"!="]
    forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ Token
lhs1 forall a. Eq a => a -> a -> Bool
== Token
lhs2 Bool -> Bool -> Bool
&& Token
rhs1 forall a. Eq a => a -> a -> Bool
/= Token
rhs2
    forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
isGlob [Token
rhs1, Token
rhs2]
    forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2252 String
"You probably wanted && here, otherwise it's always true."
  where
    getExpr :: Token -> Maybe (Token, String, Token)
getExpr Token
x =
        case Token
x of
            T_OrIf Id
_ Token
lhs Token
_ -> Token -> Maybe (Token, String, Token)
getExpr Token
lhs -- Fetches x and y in `T_OrIf x (T_OrIf y z)`
            T_Pipeline Id
_ [Token]
_ [Token
x] -> Token -> Maybe (Token, String, Token)
getExpr Token
x
            T_Redirecting Id
_ [Token]
_ Token
c -> Token -> Maybe (Token, String, Token)
getExpr Token
c
            T_Condition Id
_ ConditionType
_ Token
c -> Token -> Maybe (Token, String, Token)
getExpr Token
c
            TC_Binary Id
_ ConditionType
_ String
op Token
lhs Token
rhs -> forall {b}. (Token, b, Token) -> Maybe (Token, b, Token)
orient (Token
lhs, String
op, Token
rhs)
            Token
_ -> forall a. Maybe a
Nothing

    -- Swap items so that the constant side is rhs (or Nothing if both/neither is constant)
    orient :: (Token, b, Token) -> Maybe (Token, b, Token)
orient (Token
lhs, b
op, Token
rhs) =
        case (Token -> Bool
isConstant Token
lhs, Token -> Bool
isConstant Token
rhs) of
            (Bool
True, Bool
False) -> forall (m :: * -> *) a. Monad m => a -> m a
return (Token
rhs, b
op, Token
lhs)
            (Bool
False, Bool
True) -> forall (m :: * -> *) a. Monad m => a -> m a
return (Token
lhs, b
op, Token
rhs)
            (Bool, Bool)
_ -> forall a. Maybe a
Nothing


checkOrNeq p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkValidCondOps1 :: Bool
prop_checkValidCondOps1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkValidCondOps String
"[[ a -xz b ]]"
prop_checkValidCondOps2 :: Bool
prop_checkValidCondOps2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkValidCondOps String
"[ -M a ]"
prop_checkValidCondOps2a :: Bool
prop_checkValidCondOps2a = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkValidCondOps String
"[ 3 \\> 2 ]"
prop_checkValidCondOps3 :: Bool
prop_checkValidCondOps3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkValidCondOps String
"[ 1 = 2 -a 3 -ge 4 ]"
prop_checkValidCondOps4 :: Bool
prop_checkValidCondOps4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkValidCondOps String
"[[ ! -v foo ]]"
checkValidCondOps :: p -> Token -> m ()
checkValidCondOps p
_ (TC_Binary Id
id ConditionType
_ String
s Token
_ Token
_)
    | String
s forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [String]
binaryTestOps =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2057 String
"Unknown binary operator."
checkValidCondOps p
_ (TC_Unary Id
id ConditionType
_ String
s Token
_)
    | String
s forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem`  [String]
unaryTestOps =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2058 String
"Unknown unary operator."
checkValidCondOps p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkUuoeVar1 :: Bool
prop_checkUuoeVar1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoeVar String
"for f in $(echo $tmp); do echo lol; done"
prop_checkUuoeVar2 :: Bool
prop_checkUuoeVar2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoeVar String
"date +`echo \"$format\"`"
prop_checkUuoeVar3 :: Bool
prop_checkUuoeVar3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoeVar String
"foo \"$(echo -e '\r')\""
prop_checkUuoeVar4 :: Bool
prop_checkUuoeVar4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoeVar String
"echo $tmp"
prop_checkUuoeVar5 :: Bool
prop_checkUuoeVar5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoeVar String
"foo \"$(echo \"$(date) value:\" $value)\""
prop_checkUuoeVar6 :: Bool
prop_checkUuoeVar6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoeVar String
"foo \"$(echo files: *.png)\""
prop_checkUuoeVar7 :: Bool
prop_checkUuoeVar7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoeVar String
"foo $(echo $(bar))" -- covered by 2005
prop_checkUuoeVar8 :: Bool
prop_checkUuoeVar8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoeVar String
"#!/bin/sh\nz=$(echo)"
prop_checkUuoeVar9 :: Bool
prop_checkUuoeVar9 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoeVar String
"foo $(echo $(<file))"
checkUuoeVar :: p -> Token -> m ()
checkUuoeVar p
_ Token
p =
    case Token
p of
        T_Backticked Id
id [Token
cmd] -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Token -> m ()
check Id
id Token
cmd
        T_DollarExpansion Id
id [Token
cmd] -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Token -> m ()
check Id
id Token
cmd
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    couldBeOptimized :: Token -> Bool
couldBeOptimized Token
f = case Token
f of
        T_Glob {} -> Bool
False
        T_Extglob {} -> Bool
False
        T_BraceExpansion {} -> Bool
False
        T_NormalWord Id
_ [Token]
l -> forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Token -> Bool
couldBeOptimized [Token]
l
        T_DoubleQuoted Id
_ [Token]
l -> forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Token -> Bool
couldBeOptimized [Token]
l
        Token
_ -> Bool
True

    check :: Id -> Token -> m ()
check Id
id (T_Pipeline Id
_ [Token]
_ [T_Redirecting Id
_ [Token]
_ Token
c]) = forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Token -> m ()
warnForEcho Id
id Token
c
    check Id
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()
    isCovered :: Token -> t a -> Bool
isCovered Token
first t a
rest = forall (t :: * -> *) a. Foldable t => t a -> Bool
null t a
rest Bool -> Bool -> Bool
&& Token -> Bool
tokenIsJustCommandOutput Token
first
    warnForEcho :: Id -> Token -> m ()
warnForEcho Id
id = forall {m :: * -> *}.
Monad m =>
String -> (Token -> [Token] -> m ()) -> Token -> m ()
checkUnqualifiedCommand String
"echo" forall a b. (a -> b) -> a -> b
$ \Token
_ [Token]
vars ->
        case [Token]
vars of
          (Token
first:[Token]
rest) ->
            forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (forall {t :: * -> *} {a}. Foldable t => Token -> t a -> Bool
isCovered Token
first [Token]
rest Bool -> Bool -> Bool
|| String
"-" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` Token -> String
onlyLiteralString Token
first) forall a b. (a -> b) -> a -> b
$
                forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Token -> Bool
couldBeOptimized [Token]
vars) forall a b. (a -> b) -> a -> b
$ forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style Id
id Code
2116
                    String
"Useless echo? Instead of 'cmd $(echo foo)', just use 'cmd foo'."
          [Token]
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkTestRedirects1 :: Bool
prop_checkTestRedirects1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTestRedirects String
"test 3 > 1"
prop_checkTestRedirects2 :: Bool
prop_checkTestRedirects2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTestRedirects String
"test 3 \\> 1"
prop_checkTestRedirects3 :: Bool
prop_checkTestRedirects3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTestRedirects String
"/usr/bin/test $var > $foo"
prop_checkTestRedirects4 :: Bool
prop_checkTestRedirects4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTestRedirects String
"test 1 -eq 2 2> file"
checkTestRedirects :: p -> Token -> m ()
checkTestRedirects p
_ (T_Redirecting Id
id [Token]
redirs Token
cmd) | Token
cmd Token -> String -> Bool
`isCommand` String
"test" =
    forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
check [Token]
redirs
  where
    check :: Token -> f ()
check Token
t =
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
suspicious Token
t) forall a b. (a -> b) -> a -> b
$
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
t) Code
2065 String
"This is interpreted as a shell file redirection, not a comparison."
    suspicious :: Token -> Bool
suspicious Token
t = -- Ignore redirections of stderr because these are valid for squashing e.g. int errors,
        case Token
t of  -- and >> and similar redirections because these are probably not comparisons.
            T_FdRedirect Id
_ String
fd (T_IoFile Id
_ Token
op Token
_) -> String
fd forall a. Eq a => a -> a -> Bool
/= String
"2" Bool -> Bool -> Bool
&& Token -> Bool
isComparison Token
op
            Token
_ -> Bool
False
    isComparison :: Token -> Bool
isComparison Token
t =
        case Token
t of
            T_Greater Id
_ -> Bool
True
            T_Less Id
_ -> Bool
True
            Token
_ -> Bool
False
checkTestRedirects p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkPS11 :: Bool
prop_checkPS11 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPS1Assignments String
"PS1='\\033[1;35m\\$ '"
prop_checkPS11a :: Bool
prop_checkPS11a = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPS1Assignments String
"export PS1='\\033[1;35m\\$ '"
prop_checkPSf2 :: Bool
prop_checkPSf2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPS1Assignments String
"PS1='\\h \\e[0m\\$ '"
prop_checkPS13 :: Bool
prop_checkPS13 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPS1Assignments String
"PS1=$'\\x1b[c '"
prop_checkPS14 :: Bool
prop_checkPS14 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPS1Assignments String
"PS1=$'\\e[3m; '"
prop_checkPS14a :: Bool
prop_checkPS14a = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPS1Assignments String
"export PS1=$'\\e[3m; '"
prop_checkPS15 :: Bool
prop_checkPS15 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPS1Assignments String
"PS1='\\[\\033[1;35m\\]\\$ '"
prop_checkPS16 :: Bool
prop_checkPS16 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPS1Assignments String
"PS1='\\[\\e1m\\e[1m\\]\\$ '"
prop_checkPS17 :: Bool
prop_checkPS17 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPS1Assignments String
"PS1='e033x1B'"
prop_checkPS18 :: Bool
prop_checkPS18 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPS1Assignments String
"PS1='\\[\\e\\]'"
checkPS1Assignments :: p -> Token -> f ()
checkPS1Assignments p
_ (T_Assignment Id
_ AssignmentMode
_ String
"PS1" [Token]
_ Token
word) = forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
warnFor Token
word
  where
    warnFor :: Token -> f ()
warnFor Token
word =
        let contents :: String
contents = forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
word in
            forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String -> Bool
containsUnescaped String
contents) forall a b. (a -> b) -> a -> b
$
                forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
word) Code
2025 String
"Make sure all escape sequences are enclosed in \\[..\\] to prevent line wrapping issues"
    containsUnescaped :: String -> Bool
containsUnescaped String
s =
        let unenclosed :: String
unenclosed = Regex -> String -> String -> String
subRegex Regex
enclosedRegex String
s String
"" in
           forall a. Maybe a -> Bool
isJust forall a b. (a -> b) -> a -> b
$ Regex -> String -> Maybe [String]
matchRegex Regex
escapeRegex String
unenclosed
    enclosedRegex :: Regex
enclosedRegex = String -> Regex
mkRegex String
"\\\\\\[.*\\\\\\]" -- FIXME: shouldn't be eager
    escapeRegex :: Regex
escapeRegex = String -> Regex
mkRegex String
"\\\\x1[Bb]|\\\\e|\x1B|\\\\033"
checkPS1Assignments p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkBackticks1 :: Bool
prop_checkBackticks1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkBackticks String
"echo `foo`"
prop_checkBackticks2 :: Bool
prop_checkBackticks2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkBackticks String
"echo $(foo)"
prop_checkBackticks3 :: Bool
prop_checkBackticks3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkBackticks String
"echo `#inlined comment` foo"
checkBackticks :: Parameters -> Token -> m ()
checkBackticks Parameters
params (T_Backticked Id
id [Token]
list) | Bool -> Bool
not (forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Token]
list) =
    forall {a} {m :: * -> *}.
(NFData a, MonadWriter [a] m) =>
a -> m ()
addComment forall a b. (a -> b) -> a -> b
$
        Severity -> Id -> Code -> String -> Fix -> TokenComment
makeCommentWithFix Severity
StyleC Id
id Code
2006  String
"Use $(...) notation instead of legacy backticks `...`."
            ([Replacement] -> Fix
fixWith [Id -> Parameters -> Code -> String -> Replacement
replaceStart Id
id Parameters
params Code
1 String
"$(", Id -> Parameters -> Code -> String -> Replacement
replaceEnd Id
id Parameters
params Code
1 String
")"])
checkBackticks Parameters
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkBadParameterSubstitution1 :: Bool
prop_checkBadParameterSubstitution1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkBadParameterSubstitution String
"${foo$n}"
prop_checkBadParameterSubstitution2 :: Bool
prop_checkBadParameterSubstitution2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkBadParameterSubstitution String
"${foo//$n/lol}"
prop_checkBadParameterSubstitution3 :: Bool
prop_checkBadParameterSubstitution3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkBadParameterSubstitution String
"${$#}"
prop_checkBadParameterSubstitution4 :: Bool
prop_checkBadParameterSubstitution4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkBadParameterSubstitution String
"${var${n}_$((i%2))}"
prop_checkBadParameterSubstitution5 :: Bool
prop_checkBadParameterSubstitution5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkBadParameterSubstitution String
"${bar}"
prop_checkBadParameterSubstitution6 :: Bool
prop_checkBadParameterSubstitution6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkBadParameterSubstitution String
"${\"bar\"}"
prop_checkBadParameterSubstitution7 :: Bool
prop_checkBadParameterSubstitution7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkBadParameterSubstitution String
"${{var}"
prop_checkBadParameterSubstitution8 :: Bool
prop_checkBadParameterSubstitution8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkBadParameterSubstitution String
"${$(x)//x/y}"
prop_checkBadParameterSubstitution9 :: Bool
prop_checkBadParameterSubstitution9 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkBadParameterSubstitution String
"$# ${#} $! ${!} ${!#} ${#!}"
prop_checkBadParameterSubstitution10 :: Bool
prop_checkBadParameterSubstitution10 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkBadParameterSubstitution String
"${'foo'}"
prop_checkBadParameterSubstitution11 :: Bool
prop_checkBadParameterSubstitution11 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkBadParameterSubstitution String
"${${x%.*}##*/}"

checkBadParameterSubstitution :: p -> Token -> m ()
checkBadParameterSubstitution p
_ Token
t =
    case Token
t of
        (T_DollarBraced Id
i Bool
_ (T_NormalWord Id
_ contents :: [Token]
contents@(Token
first:[Token]
_))) ->
            if [Token] -> Bool
isIndirection [Token]
contents
            then forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
i Code
2082 String
"To expand via indirection, use arrays, ${!name} or (for sh only) eval."
            else forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkFirst Token
first
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()

  where

    isIndirection :: [Token] -> Bool
isIndirection [Token]
vars =
        let list :: [Bool]
list = forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe Token -> Maybe Bool
isIndirectionPart [Token]
vars in
            Bool -> Bool
not (forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Bool]
list) Bool -> Bool -> Bool
&& forall (t :: * -> *). Foldable t => t Bool -> Bool
and [Bool]
list

    isIndirectionPart :: Token -> Maybe Bool
isIndirectionPart Token
t =
        case Token
t of T_DollarExpansion {} ->  forall a. a -> Maybe a
Just Bool
True
                  T_Backticked {} ->       forall a. a -> Maybe a
Just Bool
True
                  T_DollarBraced {} ->     forall a. a -> Maybe a
Just Bool
True
                  T_DollarArithmetic {} -> forall a. a -> Maybe a
Just Bool
True
                  T_Literal Id
_ String
s -> if forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isVariableChar String
s
                                    then forall a. Maybe a
Nothing
                                    else forall a. a -> Maybe a
Just Bool
False
                  Token
_ -> forall a. a -> Maybe a
Just Bool
False

    checkFirst :: Token -> m ()
checkFirst Token
t =
        case Token
t of
            T_Literal Id
id (Char
c:String
_) ->
                if Char -> Bool
isVariableChar Char
c Bool -> Bool -> Bool
|| Char -> Bool
isSpecialVariableChar Char
c
                then forall (m :: * -> *) a. Monad m => a -> m a
return ()
                else forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2296 forall a b. (a -> b) -> a -> b
$ String
"Parameter expansions can't start with " forall a. [a] -> [a] -> [a]
++ String -> String
e4m [Char
c] forall a. [a] -> [a] -> [a]
++ String
". Double check syntax."

            T_ParamSubSpecialChar {} -> forall (m :: * -> *) a. Monad m => a -> m a
return ()

            T_DoubleQuoted Id
id [T_Literal Id
_ String
s] | String -> Bool
isVariable String
s ->
                forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2297 String
"Double quotes must be outside ${}: ${\"invalid\"} vs \"${valid}\"."

            T_DollarBraced Id
id Bool
braces Token
_ | Token -> Bool
isUnmodifiedParameterExpansion Token
t ->
                forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2298 forall a b. (a -> b) -> a -> b
$
                    (if Bool
braces then String
"${${x}}" else String
"${$x}")
                      forall a. [a] -> [a] -> [a]
++ String
" is invalid. For expansion, use ${x}. For indirection, use arrays, ${!x} or (for sh) eval."

            T_DollarBraced {} ->
                forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
t) Code
2299 String
"Parameter expansions can't be nested. Use temporary variables."

            Token
_ | Token -> Bool
isCommandSubstitution Token
t ->
                forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
t) Code
2300 String
"Parameter expansion can't be applied to command substitutions. Use temporary variables."

            Token
_ -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
t) Code
2301 forall a b. (a -> b) -> a -> b
$ String
"Parameter expansion starts with unexpected " forall a. [a] -> [a] -> [a]
++ Token -> String
name Token
t forall a. [a] -> [a] -> [a]
++ String
". Double check syntax."

    isVariable :: String -> Bool
isVariable String
str =
        case String
str of
            [Char
c] -> Char -> Bool
isVariableStartChar Char
c Bool -> Bool -> Bool
|| Char -> Bool
isSpecialVariableChar Char
c Bool -> Bool -> Bool
|| Char -> Bool
isDigit Char
c
            String
x -> String -> Bool
isVariableName String
x

    name :: Token -> String
name Token
t =
        case Token
t of
            T_SingleQuoted {} -> String
"quotes"
            T_DoubleQuoted {} -> String
"quotes"
            Token
_ -> String
"syntax"


prop_checkInexplicablyUnquoted1 :: Bool
prop_checkInexplicablyUnquoted1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkInexplicablyUnquoted String
"echo 'var='value';'"
prop_checkInexplicablyUnquoted2 :: Bool
prop_checkInexplicablyUnquoted2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkInexplicablyUnquoted String
"'foo'*"
prop_checkInexplicablyUnquoted3 :: Bool
prop_checkInexplicablyUnquoted3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkInexplicablyUnquoted String
"wget --user-agent='something'"
prop_checkInexplicablyUnquoted4 :: Bool
prop_checkInexplicablyUnquoted4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkInexplicablyUnquoted String
"echo \"VALUES (\"id\")\""
prop_checkInexplicablyUnquoted5 :: Bool
prop_checkInexplicablyUnquoted5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkInexplicablyUnquoted String
"\"$dir\"/\"$file\""
prop_checkInexplicablyUnquoted6 :: Bool
prop_checkInexplicablyUnquoted6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkInexplicablyUnquoted String
"\"$dir\"some_stuff\"$file\""
prop_checkInexplicablyUnquoted7 :: Bool
prop_checkInexplicablyUnquoted7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkInexplicablyUnquoted String
"${dir/\"foo\"/\"bar\"}"
prop_checkInexplicablyUnquoted8 :: Bool
prop_checkInexplicablyUnquoted8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkInexplicablyUnquoted String
"  'foo'\\\n  'bar'"
prop_checkInexplicablyUnquoted9 :: Bool
prop_checkInexplicablyUnquoted9 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkInexplicablyUnquoted String
"[[ $x =~ \"foo\"(\"bar\"|\"baz\") ]]"
prop_checkInexplicablyUnquoted10 :: Bool
prop_checkInexplicablyUnquoted10 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkInexplicablyUnquoted String
"cmd ${x+--name=\"$x\" --output=\"$x.out\"}"
prop_checkInexplicablyUnquoted11 :: Bool
prop_checkInexplicablyUnquoted11 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkInexplicablyUnquoted String
"echo \"foo\"/\"bar\""
prop_checkInexplicablyUnquoted12 :: Bool
prop_checkInexplicablyUnquoted12 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkInexplicablyUnquoted String
"declare \"foo\"=\"bar\""
checkInexplicablyUnquoted :: Parameters -> Token -> m ()
checkInexplicablyUnquoted Parameters
params (T_NormalWord Id
id [Token]
tokens) = forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
[Token] -> m ()
check (forall a. [a] -> [[a]]
tails [Token]
tokens)
  where
    check :: [Token] -> m ()
check (T_SingleQuoted Id
_ String
_:T_Literal Id
id String
str:[Token]
_)
        | Bool -> Bool
not (forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
str) Bool -> Bool -> Bool
&& forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isAlphaNum String
str =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info Id
id Code
2026 String
"This word is outside of quotes. Did you intend to 'nest '\"'single quotes'\"' instead'? "

    check (T_DoubleQuoted Id
_ [Token]
a:Token
trapped:T_DoubleQuoted Id
_ [Token]
b:[Token]
_) =
        case Token
trapped of
            T_DollarExpansion Id
id [Token]
_ -> forall {m :: * -> *}. MonadWriter [TokenComment] m => Id -> m ()
warnAboutExpansion Id
id
            T_DollarBraced Id
id Bool
_ Token
_ -> forall {m :: * -> *}. MonadWriter [TokenComment] m => Id -> m ()
warnAboutExpansion Id
id
            T_Literal Id
id String
s
                | Bool -> Bool
not ([Token] -> Bool
quotesSingleThing [Token]
a Bool -> Bool -> Bool
&& [Token] -> Bool
quotesSingleThing [Token]
b
                    Bool -> Bool -> Bool
|| String
s forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"=", String
":", String
"/"]
                    Bool -> Bool -> Bool
|| [Token] -> Bool
isSpecial (Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) Token
trapped)
                ) ->
                    forall {m :: * -> *}. MonadWriter [TokenComment] m => Id -> m ()
warnAboutLiteral Id
id
            Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()

    check [Token]
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

    -- Regexes for [[ .. =~ re ]] are parsed with metacharacters like ()| as unquoted
    -- literals. The same is true for ${x+"foo" "bar"}. Avoid overtriggering on these.
    isSpecial :: [Token] -> Bool
isSpecial [Token]
t =
        case [Token]
t of
            (T_Redirecting {} : [Token]
_) -> Bool
False
            T_DollarBraced {} : [Token]
_ -> Bool
True
            (Token
a:(TC_Binary Id
_ ConditionType
_ String
"=~" Token
lhs Token
rhs):[Token]
rest) -> Token -> Id
getId Token
a forall a. Eq a => a -> a -> Bool
== Token -> Id
getId Token
rhs
            Token
_:[Token]
rest -> [Token] -> Bool
isSpecial [Token]
rest
            [Token]
_ -> Bool
False

    -- If the surrounding quotes quote single things, like "$foo"_and_then_some_"$stuff",
    -- the quotes were probably intentional and harmless.
    quotesSingleThing :: [Token] -> Bool
quotesSingleThing [Token]
x = case [Token]
x of
        [T_DollarExpansion Id
_ [Token]
_] -> Bool
True
        [T_DollarBraced Id
_ Bool
_ Token
_] -> Bool
True
        [T_Backticked Id
_ [Token]
_] -> Bool
True
        [Token]
_ -> Bool
False

    warnAboutExpansion :: Id -> m ()
warnAboutExpansion Id
id =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2027 String
"The surrounding quotes actually unquote this. Remove or escape them."
    warnAboutLiteral :: Id -> m ()
warnAboutLiteral Id
id =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2140 String
"Word is of the form \"A\"B\"C\" (B indicated). Did you mean \"ABC\" or \"A\\\"B\\\"C\"?"
checkInexplicablyUnquoted Parameters
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkTildeInQuotes1 :: Bool
prop_checkTildeInQuotes1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTildeInQuotes String
"var=\"~/out.txt\""
prop_checkTildeInQuotes2 :: Bool
prop_checkTildeInQuotes2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTildeInQuotes String
"foo > '~/dir'"
prop_checkTildeInQuotes4 :: Bool
prop_checkTildeInQuotes4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTildeInQuotes String
"~/file"
prop_checkTildeInQuotes5 :: Bool
prop_checkTildeInQuotes5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTildeInQuotes String
"echo '/~foo/cow'"
prop_checkTildeInQuotes6 :: Bool
prop_checkTildeInQuotes6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTildeInQuotes String
"awk '$0 ~ /foo/'"
checkTildeInQuotes :: p -> Token -> m ()
checkTildeInQuotes p
_ = forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
check
  where
    verify :: Id -> String -> m ()
verify Id
id (Char
'~':Char
'/':String
_) = forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2088 String
"Tilde does not expand in quotes. Use $HOME."
    verify Id
_ String
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()
    check :: Token -> m ()
check (T_NormalWord Id
_ (T_SingleQuoted Id
id String
str:[Token]
_)) =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> String -> m ()
verify Id
id String
str
    check (T_NormalWord Id
_ (T_DoubleQuoted Id
_ (T_Literal Id
id String
str:[Token]
_):[Token]
_)) =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> String -> m ()
verify Id
id String
str
    check Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkLonelyDotDash1 :: Bool
prop_checkLonelyDotDash1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkLonelyDotDash String
"./ file"
prop_checkLonelyDotDash2 :: Bool
prop_checkLonelyDotDash2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkLonelyDotDash String
"./file"
checkLonelyDotDash :: p -> Token -> m ()
checkLonelyDotDash p
_ t :: Token
t@(T_Redirecting Id
id [Token]
_ Token
_)
    | Token -> String -> Bool
isUnqualifiedCommand Token
t String
"./" =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2083 String
"Don't add spaces after the slash in './file'."
checkLonelyDotDash p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkSpuriousExec1 :: Bool
prop_checkSpuriousExec1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExec String
"exec foo; true"
prop_checkSpuriousExec2 :: Bool
prop_checkSpuriousExec2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExec String
"if a; then exec b; exec c; fi"
prop_checkSpuriousExec3 :: Bool
prop_checkSpuriousExec3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExec String
"echo cow; exec foo"
prop_checkSpuriousExec4 :: Bool
prop_checkSpuriousExec4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExec String
"if a; then exec b; fi"
prop_checkSpuriousExec5 :: Bool
prop_checkSpuriousExec5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExec String
"exec > file; cmd"
prop_checkSpuriousExec6 :: Bool
prop_checkSpuriousExec6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExec String
"exec foo > file; cmd"
prop_checkSpuriousExec7 :: Bool
prop_checkSpuriousExec7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExec String
"exec file; echo failed; exit 3"
prop_checkSpuriousExec8 :: Bool
prop_checkSpuriousExec8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExec String
"exec {origout}>&1- >tmp.log 2>&1; bar"
prop_checkSpuriousExec9 :: Bool
prop_checkSpuriousExec9 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExec String
"for file in rc.d/*; do exec \"$file\"; done"
prop_checkSpuriousExec10 :: Bool
prop_checkSpuriousExec10 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExec String
"exec file; r=$?; printf >&2 'failed\n'; return $r"
prop_checkSpuriousExec11 :: Bool
prop_checkSpuriousExec11 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExec String
"exec file; :"
checkSpuriousExec :: p -> Token -> m ()
checkSpuriousExec p
_ = Token -> m ()
doLists
  where
    doLists :: Token -> m ()
doLists (T_Script Id
_ Token
_ [Token]
cmds) = [Token] -> Bool -> m ()
doList [Token]
cmds Bool
False
    doLists (T_BraceGroup Id
_ [Token]
cmds) = [Token] -> Bool -> m ()
doList [Token]
cmds Bool
False
    doLists (T_WhileExpression Id
_ [Token]
_ [Token]
cmds) = [Token] -> Bool -> m ()
doList [Token]
cmds Bool
True
    doLists (T_UntilExpression Id
_ [Token]
_ [Token]
cmds) = [Token] -> Bool -> m ()
doList [Token]
cmds Bool
True
    doLists (T_ForIn Id
_ String
_ [Token]
_ [Token]
cmds) = [Token] -> Bool -> m ()
doList [Token]
cmds Bool
True
    doLists (T_ForArithmetic Id
_ Token
_ Token
_ Token
_ [Token]
cmds) = [Token] -> Bool -> m ()
doList [Token]
cmds Bool
True
    doLists (T_IfExpression Id
_ [([Token], [Token])]
thens [Token]
elses) = do
        forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (\([Token]
_, [Token]
l) -> [Token] -> Bool -> m ()
doList [Token]
l Bool
False) [([Token], [Token])]
thens
        [Token] -> Bool -> m ()
doList [Token]
elses Bool
False
    doLists Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

    stripCleanup :: [Token] -> [Token]
stripCleanup = forall a. [a] -> [a]
reverse forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. (a -> Bool) -> [a] -> [a]
dropWhile Token -> Bool
cleanup forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. [a] -> [a]
reverse
    cleanup :: Token -> Bool
cleanup (T_Pipeline Id
_ [Token]
_ [Token
cmd]) =
        Token -> (String -> Bool) -> Bool
isCommandMatch Token
cmd (forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
":", String
"echo", String
"exit", String
"printf", String
"return"])
        Bool -> Bool -> Bool
|| Token -> Bool
isAssignment Token
cmd
    cleanup Token
_ = Bool
False

    doList :: [Token] -> Bool -> m ()
doList = [Token] -> Bool -> m ()
doList' forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Token] -> [Token]
stripCleanup
    -- The second parameter is True if we are in a loop
    -- In that case we should emit the warning also if `exec' is the last statement
    doList' :: [Token] -> Bool -> m ()
doList' (Token
current:t :: [Token]
t@(Token
following:[Token]
_)) Bool
False = do
        forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
commentIfExec Token
current
        [Token] -> Bool -> m ()
doList [Token]
t Bool
False
    doList' (Token
current:[Token]
tail) Bool
True = do
        forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
commentIfExec Token
current
        [Token] -> Bool -> m ()
doList [Token]
tail Bool
True
    doList' [Token]
_ Bool
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

    commentIfExec :: Token -> m ()
commentIfExec (T_Pipeline Id
id [Token]
_ [Token
c]) = Token -> m ()
commentIfExec Token
c
    commentIfExec (T_Redirecting Id
_ [Token]
_ (T_SimpleCommand Id
id [Token]
_ (Token
cmd:Token
additionalArg:[Token]
_))) |
        Token -> Maybe String
getLiteralString Token
cmd forall a. Eq a => a -> a -> Bool
== forall a. a -> Maybe a
Just String
"exec" =
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2093 String
"Remove \"exec \" if script should continue after this command."
    commentIfExec Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkSpuriousExpansion1 :: Bool
prop_checkSpuriousExpansion1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExpansion String
"if $(true); then true; fi"
prop_checkSpuriousExpansion3 :: Bool
prop_checkSpuriousExpansion3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExpansion String
"$(cmd) --flag1 --flag2"
prop_checkSpuriousExpansion4 :: Bool
prop_checkSpuriousExpansion4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExpansion String
"$((i++))"
checkSpuriousExpansion :: p -> Token -> m ()
checkSpuriousExpansion p
_ (T_SimpleCommand Id
_ [Token]
_ [T_NormalWord Id
_ [Token
word]]) = forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
check Token
word
  where
    check :: Token -> m ()
check Token
word = case Token
word of
        T_DollarExpansion Id
id [Token]
_ ->
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2091 String
"Remove surrounding $() to avoid executing output (or use eval if intentional)."
        T_Backticked Id
id [Token]
_ ->
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2092 String
"Remove backticks to avoid executing output (or use eval if intentional)."
        T_DollarArithmetic Id
id Token
_ ->
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2084 String
"Remove '$' or use '_=$((expr))' to avoid executing output."
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
checkSpuriousExpansion p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkDollarBrackets1 :: Bool
prop_checkDollarBrackets1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkDollarBrackets String
"echo $[1+2]"
prop_checkDollarBrackets2 :: Bool
prop_checkDollarBrackets2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkDollarBrackets String
"echo $((1+2))"
checkDollarBrackets :: p -> Token -> m ()
checkDollarBrackets p
_ (T_DollarBracket Id
id Token
_) =
    forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style Id
id Code
2007 String
"Use $((..)) instead of deprecated $[..]"
checkDollarBrackets p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkSshHereDoc1 :: Bool
prop_checkSshHereDoc1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSshHereDoc String
"ssh host << foo\necho $PATH\nfoo"
prop_checkSshHereDoc2 :: Bool
prop_checkSshHereDoc2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSshHereDoc String
"ssh host << 'foo'\necho $PATH\nfoo"
checkSshHereDoc :: p -> Token -> m ()
checkSshHereDoc p
_ (T_Redirecting Id
_ [Token]
redirs Token
cmd)
        | Token
cmd Token -> String -> Bool
`isCommand` String
"ssh" =
    forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkHereDoc [Token]
redirs
  where
    hasVariables :: Regex
hasVariables = String -> Regex
mkRegex String
"[`$]"
    checkHereDoc :: Token -> m ()
checkHereDoc (T_FdRedirect Id
_ String
_ (T_HereDoc Id
id Dashed
_ Quoted
Unquoted String
token [Token]
tokens))
        | Bool -> Bool
not (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Token -> Bool
isConstant [Token]
tokens) =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2087 forall a b. (a -> b) -> a -> b
$ String
"Quote '" forall a. [a] -> [a] -> [a]
++ (String -> String
e4m String
token) forall a. [a] -> [a] -> [a]
++ String
"' to make here document expansions happen on the server side rather than on the client."
    checkHereDoc Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()
checkSshHereDoc p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

--- Subshell detection
prop_subshellAssignmentCheck :: Bool
prop_subshellAssignmentCheck = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree     forall {p}. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"cat foo | while read bar; do a=$bar; done; echo \"$a\""
prop_subshellAssignmentCheck2 :: Bool
prop_subshellAssignmentCheck2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"while read bar; do a=$bar; done < file; echo \"$a\""
prop_subshellAssignmentCheck3 :: Bool
prop_subshellAssignmentCheck3 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    forall {p}. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"( A=foo; ); rm $A"
prop_subshellAssignmentCheck4 :: Bool
prop_subshellAssignmentCheck4 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"( A=foo; rm $A; )"
prop_subshellAssignmentCheck5 :: Bool
prop_subshellAssignmentCheck5 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    forall {p}. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"cat foo | while read cow; do true; done; echo $cow;"
prop_subshellAssignmentCheck6 :: Bool
prop_subshellAssignmentCheck6 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    forall {p}. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"( export lol=$(ls); ); echo $lol;"
prop_subshellAssignmentCheck6a :: Bool
prop_subshellAssignmentCheck6a = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    forall {p}. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"( typeset -a lol=a; ); echo $lol;"
prop_subshellAssignmentCheck7 :: Bool
prop_subshellAssignmentCheck7 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    forall {p}. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"cmd | while read foo; do (( n++ )); done; echo \"$n lines\""
prop_subshellAssignmentCheck8 :: Bool
prop_subshellAssignmentCheck8 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    forall {p}. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"n=3 & echo $((n++))"
prop_subshellAssignmentCheck9 :: Bool
prop_subshellAssignmentCheck9 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    forall {p}. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"read n & n=foo$n"
prop_subshellAssignmentCheck10 :: Bool
prop_subshellAssignmentCheck10 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    forall {p}. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"(( n <<= 3 )) & (( n |= 4 )) &"
prop_subshellAssignmentCheck11 :: Bool
prop_subshellAssignmentCheck11 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"cat /etc/passwd | while read line; do let n=n+1; done\necho $n"
prop_subshellAssignmentCheck12 :: Bool
prop_subshellAssignmentCheck12 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"cat /etc/passwd | while read line; do let ++n; done\necho $n"
prop_subshellAssignmentCheck13 :: Bool
prop_subshellAssignmentCheck13 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"#!/bin/bash\necho foo | read bar; echo $bar"
prop_subshellAssignmentCheck14 :: Bool
prop_subshellAssignmentCheck14 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"#!/bin/ksh93\necho foo | read bar; echo $bar"
prop_subshellAssignmentCheck15 :: Bool
prop_subshellAssignmentCheck15 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"#!/bin/ksh\ncat foo | while read bar; do a=$bar; done\necho \"$a\""
prop_subshellAssignmentCheck16 :: Bool
prop_subshellAssignmentCheck16 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"(set -e); echo $@"
prop_subshellAssignmentCheck17 :: Bool
prop_subshellAssignmentCheck17 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"foo=${ { bar=$(baz); } 2>&1; }; echo $foo $bar"
prop_subshellAssignmentCheck18 :: Bool
prop_subshellAssignmentCheck18 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"( exec {n}>&2; ); echo $n"
prop_subshellAssignmentCheck19 :: Bool
prop_subshellAssignmentCheck19 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"#!/bin/bash\nshopt -s lastpipe; echo a | read -r b; echo \"$b\""
prop_subshellAssignmentCheck20 :: Bool
prop_subshellAssignmentCheck20 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"@test 'foo' { a=1; }\n@test 'bar' { echo $a; }\n"
prop_subshellAssignmentCheck21 :: Bool
prop_subshellAssignmentCheck21 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"test1() { echo foo | if [[ $var ]]; then echo $var; fi; }; test2() { echo $var; }"
prop_subshellAssignmentCheck22 :: Bool
prop_subshellAssignmentCheck22 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"( [[ -n $foo || -z $bar ]] ); echo $foo $bar"
prop_subshellAssignmentCheck23 :: Bool
prop_subshellAssignmentCheck23 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"( export foo ); echo $foo"
subshellAssignmentCheck :: Parameters -> p -> [TokenComment]
subshellAssignmentCheck Parameters
params p
t =
    let flow :: [StackData]
flow = Parameters -> [StackData]
variableFlow Parameters
params
        check :: Writer [TokenComment] ()
check = forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
[StackData]
-> [(String, [(Token, Token, String, DataType)])]
-> Map String VariableState
-> m ()
findSubshelled [StackData]
flow [(String
"oops",[])] forall k a. Map k a
Map.empty
    in forall w a. Writer w a -> w
execWriter Writer [TokenComment] ()
check


findSubshelled :: [StackData]
-> [(String, [(Token, Token, String, DataType)])]
-> Map String VariableState
-> m ()
findSubshelled [] [(String, [(Token, Token, String, DataType)])]
_ Map String VariableState
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()
findSubshelled (Assignment x :: (Token, Token, String, DataType)
x@(Token
_, Token
_, String
str, DataType
data_):[StackData]
rest) scopes :: [(String, [(Token, Token, String, DataType)])]
scopes@((String
reason,[(Token, Token, String, DataType)]
scope):[(String, [(Token, Token, String, DataType)])]
restscope) Map String VariableState
deadVars =
    if DataType -> Bool
isTrueAssignmentSource DataType
data_
    then [StackData]
-> [(String, [(Token, Token, String, DataType)])]
-> Map String VariableState
-> m ()
findSubshelled [StackData]
rest ((String
reason, (Token, Token, String, DataType)
xforall a. a -> [a] -> [a]
:[(Token, Token, String, DataType)]
scope)forall a. a -> [a] -> [a]
:[(String, [(Token, Token, String, DataType)])]
restscope) forall a b. (a -> b) -> a -> b
$ forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert String
str VariableState
Alive Map String VariableState
deadVars
    else [StackData]
-> [(String, [(Token, Token, String, DataType)])]
-> Map String VariableState
-> m ()
findSubshelled [StackData]
rest [(String, [(Token, Token, String, DataType)])]
scopes Map String VariableState
deadVars

findSubshelled (Reference (Token
_, Token
readToken, String
str):[StackData]
rest) [(String, [(Token, Token, String, DataType)])]
scopes Map String VariableState
deadVars = do
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (String -> Bool
shouldIgnore String
str) forall a b. (a -> b) -> a -> b
$ case forall k a. Ord k => a -> k -> Map k a -> a
Map.findWithDefault VariableState
Alive String
str Map String VariableState
deadVars of
        VariableState
Alive -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
        Dead Token
writeToken String
reason -> do
                    forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
writeToken) Code
2030 forall a b. (a -> b) -> a -> b
$ String
"Modification of " forall a. [a] -> [a] -> [a]
++ String
str forall a. [a] -> [a] -> [a]
++ String
" is local (to subshell caused by "forall a. [a] -> [a] -> [a]
++ String
reason forall a. [a] -> [a] -> [a]
++String
")."
                    forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
readToken) Code
2031 forall a b. (a -> b) -> a -> b
$ String
str forall a. [a] -> [a] -> [a]
++ String
" was modified in a subshell. That change might be lost."
    [StackData]
-> [(String, [(Token, Token, String, DataType)])]
-> Map String VariableState
-> m ()
findSubshelled [StackData]
rest [(String, [(Token, Token, String, DataType)])]
scopes Map String VariableState
deadVars
  where
    shouldIgnore :: String -> Bool
shouldIgnore String
str =
        String
str forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"@", String
"*", String
"IFS"]

findSubshelled (StackScope (SubshellScope String
reason):[StackData]
rest) [(String, [(Token, Token, String, DataType)])]
scopes Map String VariableState
deadVars =
    [StackData]
-> [(String, [(Token, Token, String, DataType)])]
-> Map String VariableState
-> m ()
findSubshelled [StackData]
rest ((String
reason,[])forall a. a -> [a] -> [a]
:[(String, [(Token, Token, String, DataType)])]
scopes) Map String VariableState
deadVars

findSubshelled (StackData
StackScopeEnd:[StackData]
rest) ((String
reason, [(Token, Token, String, DataType)]
scope):[(String, [(Token, Token, String, DataType)])]
oldScopes) Map String VariableState
deadVars =
    [StackData]
-> [(String, [(Token, Token, String, DataType)])]
-> Map String VariableState
-> m ()
findSubshelled [StackData]
rest [(String, [(Token, Token, String, DataType)])]
oldScopes forall a b. (a -> b) -> a -> b
$
        forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl (\Map String VariableState
m (Token
_, Token
token, String
var, DataType
_) ->
            forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert String
var (Token -> String -> VariableState
Dead Token
token String
reason) Map String VariableState
m) Map String VariableState
deadVars [(Token, Token, String, DataType)]
scope


-- FIXME: This is a very strange way of doing it.
-- For each variable read/write, run a stateful function that emits
-- comments. The comments are collected and returned.
doVariableFlowAnalysis ::
    (Token -> Token -> String -> State t [v])
    -> (Token -> Token -> String -> DataType -> State t [v])
    -> t
    -> [StackData]
    -> [v]

doVariableFlowAnalysis :: forall t v.
(Token -> Token -> String -> State t [v])
-> (Token -> Token -> String -> DataType -> State t [v])
-> t
-> [StackData]
-> [v]
doVariableFlowAnalysis Token -> Token -> String -> State t [v]
readFunc Token -> Token -> String -> DataType -> State t [v]
writeFunc t
empty [StackData]
flow = forall s a. State s a -> s -> a
evalState (
    forall (t :: * -> *) (m :: * -> *) b a.
(Foldable t, Monad m) =>
(b -> a -> m b) -> b -> t a -> m b
foldM (\[v]
list StackData
x -> do { [v]
l <- StackData -> State t [v]
doFlow StackData
x;  forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ [v]
l forall a. [a] -> [a] -> [a]
++ [v]
list; }) [] [StackData]
flow
    ) t
empty
  where
    doFlow :: StackData -> State t [v]
doFlow (Reference (Token
base, Token
token, String
name)) =
        Token -> Token -> String -> State t [v]
readFunc Token
base Token
token String
name
    doFlow (Assignment (Token
base, Token
token, String
name, DataType
values)) =
        Token -> Token -> String -> DataType -> State t [v]
writeFunc Token
base Token
token String
name DataType
values
    doFlow StackData
_ = forall (m :: * -> *) a. Monad m => a -> m a
return []

-- Don't suggest quotes if this will instead be autocorrected
-- from $foo=bar to foo=bar. This is not pretty but ok.
quotesMayConflictWithSC2281 :: Parameters -> Token -> Bool
quotesMayConflictWithSC2281 Parameters
params Token
t =
    case Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) Token
t of
        Token
_ : T_NormalWord Id
parentId (Token
me:T_Literal Id
_ (Char
'=':String
_):[Token]
_) : T_SimpleCommand Id
_ [Token]
_ (Token
cmd:[Token]
_) : [Token]
_ ->
            (Token -> Id
getId Token
t) forall a. Eq a => a -> a -> Bool
== (Token -> Id
getId Token
me) Bool -> Bool -> Bool
&& (Id
parentId forall a. Eq a => a -> a -> Bool
== Token -> Id
getId Token
cmd)
        [Token]
_ -> Bool
False

addDoubleQuotesAround :: Parameters -> Token -> Fix
addDoubleQuotesAround Parameters
params Token
token = (Id -> Parameters -> String -> Fix
surroundWith (Token -> Id
getId Token
token) Parameters
params String
"\"")

prop_checkSpacefulnessCfg1 :: Bool
prop_checkSpacefulnessCfg1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"a='cow moo'; echo $a"
prop_checkSpacefulnessCfg2 :: Bool
prop_checkSpacefulnessCfg2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"a='cow moo'; [[ $a ]]"
prop_checkSpacefulnessCfg3 :: Bool
prop_checkSpacefulnessCfg3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"a='cow*.mp3'; echo \"$a\""
prop_checkSpacefulnessCfg4 :: Bool
prop_checkSpacefulnessCfg4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"for f in *.mp3; do echo $f; done"
prop_checkSpacefulnessCfg4a :: Bool
prop_checkSpacefulnessCfg4a = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"foo=3; foo=$(echo $foo)"
prop_checkSpacefulnessCfg5 :: Bool
prop_checkSpacefulnessCfg5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"a='*'; b=$a; c=lol${b//foo/bar}; echo $c"
prop_checkSpacefulnessCfg6 :: Bool
prop_checkSpacefulnessCfg6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"a=foo$(lol); echo $a"
prop_checkSpacefulnessCfg7 :: Bool
prop_checkSpacefulnessCfg7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"a=foo\\ bar; rm $a"
prop_checkSpacefulnessCfg8 :: Bool
prop_checkSpacefulnessCfg8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"a=foo\\ bar; a=foo; rm $a"
prop_checkSpacefulnessCfg10 :: Bool
prop_checkSpacefulnessCfg10 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"rm $1"
prop_checkSpacefulnessCfg11 :: Bool
prop_checkSpacefulnessCfg11 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"rm ${10//foo/bar}"
prop_checkSpacefulnessCfg12 :: Bool
prop_checkSpacefulnessCfg12 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"(( $1 + 3 ))"
prop_checkSpacefulnessCfg13 :: Bool
prop_checkSpacefulnessCfg13 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"if [[ $2 -gt 14 ]]; then true; fi"
prop_checkSpacefulnessCfg14 :: Bool
prop_checkSpacefulnessCfg14 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"foo=$3 env"
prop_checkSpacefulnessCfg15 :: Bool
prop_checkSpacefulnessCfg15 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"local foo=$1"
prop_checkSpacefulnessCfg16 :: Bool
prop_checkSpacefulnessCfg16 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"declare foo=$1"
prop_checkSpacefulnessCfg17 :: Bool
prop_checkSpacefulnessCfg17 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"echo foo=$1"
prop_checkSpacefulnessCfg18 :: Bool
prop_checkSpacefulnessCfg18 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"$1 --flags"
prop_checkSpacefulnessCfg19 :: Bool
prop_checkSpacefulnessCfg19 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"echo $PWD"
prop_checkSpacefulnessCfg20 :: Bool
prop_checkSpacefulnessCfg20 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"n+='foo bar'"
prop_checkSpacefulnessCfg21 :: Bool
prop_checkSpacefulnessCfg21 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"select foo in $bar; do true; done"
prop_checkSpacefulnessCfg22 :: Bool
prop_checkSpacefulnessCfg22 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"echo $\"$1\""
prop_checkSpacefulnessCfg23 :: Bool
prop_checkSpacefulnessCfg23 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"a=(1); echo ${a[@]}"
prop_checkSpacefulnessCfg24 :: Bool
prop_checkSpacefulnessCfg24 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"a='a    b'; cat <<< $a"
prop_checkSpacefulnessCfg25 :: Bool
prop_checkSpacefulnessCfg25 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"a='s/[0-9]//g'; sed $a"
prop_checkSpacefulnessCfg26 :: Bool
prop_checkSpacefulnessCfg26 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"a='foo bar'; echo {1,2,$a}"
prop_checkSpacefulnessCfg27 :: Bool
prop_checkSpacefulnessCfg27 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"echo ${a:+'foo'}"
prop_checkSpacefulnessCfg28 :: Bool
prop_checkSpacefulnessCfg28 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"exec {n}>&1; echo $n"
prop_checkSpacefulnessCfg29 :: Bool
prop_checkSpacefulnessCfg29 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"n=$(stuff); exec {n}>&-;"
prop_checkSpacefulnessCfg30 :: Bool
prop_checkSpacefulnessCfg30 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"file='foo bar'; echo foo > $file;"
prop_checkSpacefulnessCfg31 :: Bool
prop_checkSpacefulnessCfg31 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"echo \"`echo \\\"$1\\\"`\""
prop_checkSpacefulnessCfg32 :: Bool
prop_checkSpacefulnessCfg32 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"var=$1; [ -v var ]"
prop_checkSpacefulnessCfg33 :: Bool
prop_checkSpacefulnessCfg33 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"for file; do echo $file; done"
prop_checkSpacefulnessCfg34 :: Bool
prop_checkSpacefulnessCfg34 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"declare foo$n=$1"
prop_checkSpacefulnessCfg35 :: Bool
prop_checkSpacefulnessCfg35 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"echo ${1+\"$1\"}"
prop_checkSpacefulnessCfg36 :: Bool
prop_checkSpacefulnessCfg36 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"arg=$#; echo $arg"
prop_checkSpacefulnessCfg37 :: Bool
prop_checkSpacefulnessCfg37 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"@test 'status' {\n [ $status -eq 0 ]\n}"
prop_checkSpacefulnessCfg37v :: Bool
prop_checkSpacefulnessCfg37v = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkVerboseSpacefulnessCfg String
"@test 'status' {\n [ $status -eq 0 ]\n}"
prop_checkSpacefulnessCfg38 :: Bool
prop_checkSpacefulnessCfg38 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"a=; echo $a"
prop_checkSpacefulnessCfg39 :: Bool
prop_checkSpacefulnessCfg39 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"a=''\"\"''; b=x$a; echo $b"
prop_checkSpacefulnessCfg40 :: Bool
prop_checkSpacefulnessCfg40 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"a=$((x+1)); echo $a"
prop_checkSpacefulnessCfg41 :: Bool
prop_checkSpacefulnessCfg41 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"exec $1 --flags"
prop_checkSpacefulnessCfg42 :: Bool
prop_checkSpacefulnessCfg42 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"run $1 --flags"
prop_checkSpacefulnessCfg43 :: Bool
prop_checkSpacefulnessCfg43 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"$foo=42"
prop_checkSpacefulnessCfg44 :: Bool
prop_checkSpacefulnessCfg44 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"#!/bin/sh\nexport var=$value"
prop_checkSpacefulnessCfg45 :: Bool
prop_checkSpacefulnessCfg45 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"wait -zzx -p foo; echo $foo"
prop_checkSpacefulnessCfg46 :: Bool
prop_checkSpacefulnessCfg46 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"x=0; (( x += 1 )); echo $x"
prop_checkSpacefulnessCfg47 :: Bool
prop_checkSpacefulnessCfg47 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"x=0; (( x-- )); echo $x"
prop_checkSpacefulnessCfg48 :: Bool
prop_checkSpacefulnessCfg48 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"x=0; (( ++x )); echo $x"
prop_checkSpacefulnessCfg49 :: Bool
prop_checkSpacefulnessCfg49 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"for i in 1 2 3; do echo $i; done"
prop_checkSpacefulnessCfg50 :: Bool
prop_checkSpacefulnessCfg50 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"for i in 1 2 *; do echo $i; done"
prop_checkSpacefulnessCfg51 :: Bool
prop_checkSpacefulnessCfg51 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"x='foo bar'; x && x=1; echo $x"
prop_checkSpacefulnessCfg52 :: Bool
prop_checkSpacefulnessCfg52 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"x=1; if f; then x='foo bar'; exit; fi; echo $x"
prop_checkSpacefulnessCfg53 :: Bool
prop_checkSpacefulnessCfg53 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"s=1; f() { local s='a b'; }; f; echo $s"
prop_checkSpacefulnessCfg54 :: Bool
prop_checkSpacefulnessCfg54 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"s='a b'; f() { s=1; }; f; echo $s"
prop_checkSpacefulnessCfg55 :: Bool
prop_checkSpacefulnessCfg55 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"s='a b'; x && f() { s=1; }; f; echo $s"
prop_checkSpacefulnessCfg56 :: Bool
prop_checkSpacefulnessCfg56 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"s=1; cat <(s='a b'); echo $s"
prop_checkSpacefulnessCfg57 :: Bool
prop_checkSpacefulnessCfg57 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"declare -i s=0; s=$(f); echo $s"
prop_checkSpacefulnessCfg58 :: Bool
prop_checkSpacefulnessCfg58 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"f() { declare -i s; }; f; s=$(var); echo $s"
prop_checkSpacefulnessCfg59 :: Bool
prop_checkSpacefulnessCfg59 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"f() { declare -gi s; }; f; s=$(var); echo $s"
prop_checkSpacefulnessCfg60 :: Bool
prop_checkSpacefulnessCfg60 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"declare -i s; declare +i s; s=$(foo); echo $s"
prop_checkSpacefulnessCfg61 :: Bool
prop_checkSpacefulnessCfg61 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"declare -x X; y=foo$X; echo $y;"
prop_checkSpacefulnessCfg62 :: Bool
prop_checkSpacefulnessCfg62 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"f() { declare -x X; y=foo$X; echo $y; }"
prop_checkSpacefulnessCfg63 :: Bool
prop_checkSpacefulnessCfg63 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"f && declare -i s; s='x + y'; echo $s"
prop_checkSpacefulnessCfg64 :: Bool
prop_checkSpacefulnessCfg64 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"declare -i s; s='x + y'; x=$s; echo $x"
prop_checkSpacefulnessCfg65 :: Bool
prop_checkSpacefulnessCfg65 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"f() { s=$?; echo $s; }; f"
prop_checkSpacefulnessCfg66 :: Bool
prop_checkSpacefulnessCfg66 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg String
"f() { s=$?; echo $s; }"

checkSpacefulnessCfg :: Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg = Bool -> Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg' Bool
True
checkVerboseSpacefulnessCfg :: Parameters -> Token -> Writer [TokenComment] ()
checkVerboseSpacefulnessCfg = Bool -> Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg' Bool
False

checkSpacefulnessCfg' :: Bool -> (Parameters -> Token -> Writer [TokenComment] ())
checkSpacefulnessCfg' :: Bool -> Parameters -> Token -> Writer [TokenComment] ()
checkSpacefulnessCfg' Bool
dirtyPass Parameters
params token :: Token
token@(T_DollarBraced Id
id Bool
_ Token
list) =
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Bool
needsQuoting Bool -> Bool -> Bool
&& (Bool
dirtyPass forall a. Eq a => a -> a -> Bool
== Bool -> Bool
not Bool
isClean)) forall a b. (a -> b) -> a -> b
$
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (String
name forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
specialVariablesWithoutSpaces Bool -> Bool -> Bool
|| Parameters -> Token -> Bool
quotesMayConflictWithSC2281 Parameters
params Token
token) forall a b. (a -> b) -> a -> b
$
            if Bool
dirtyPass
            then
                if Map Id Token -> Token -> Bool
isDefaultAssignment (Parameters -> Map Id Token
parentMap Parameters
params) Token
token
                then
                    forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
token) Code
2223
                             String
"This default assignment may cause DoS due to globbing. Quote it."
                else
                    forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> Fix -> m ()
infoWithFix Id
id Code
2086 String
"Double quote to prevent globbing and word splitting." forall a b. (a -> b) -> a -> b
$
                        Parameters -> Token -> Fix
addDoubleQuotesAround Parameters
params Token
token
            else
                forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> Fix -> m ()
styleWithFix Id
id Code
2248 String
"Prefer double quoting even when variables don't contain special characters." forall a b. (a -> b) -> a -> b
$
                    Parameters -> Token -> Fix
addDoubleQuotesAround Parameters
params Token
token

  where
    name :: String
name = String -> String
getBracedReference forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
list
    parents :: Map Id Token
parents = Parameters -> Map Id Token
parentMap Parameters
params
    needsQuoting :: Bool
needsQuoting =
              Bool -> Bool
not (Token -> Bool
isArrayExpansion Token
token) -- There's another warning for this
              Bool -> Bool -> Bool
&& Bool -> Bool
not (Token -> Bool
isCountingReference Token
token)
              Bool -> Bool -> Bool
&& Bool -> Bool
not (Shell -> Map Id Token -> Token -> Bool
isQuoteFree (Parameters -> Shell
shellType Parameters
params) Map Id Token
parents Token
token)
              Bool -> Bool -> Bool
&& Bool -> Bool
not (Token -> Bool
isQuotedAlternativeReference Token
token)
              Bool -> Bool -> Bool
&& Bool -> Bool
not (Map Id Token -> Token -> Bool
usedAsCommandName Map Id Token
parents Token
token)

    isClean :: Bool
isClean = forall a. a -> Maybe a -> a
fromMaybe Bool
False forall a b. (a -> b) -> a -> b
$ do
        ProgramState
state <- CFGAnalysis -> Id -> Maybe ProgramState
CF.getIncomingState (Parameters -> CFGAnalysis
cfgAnalysis Parameters
params) Id
id
        VariableState
value <- forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup String
name forall a b. (a -> b) -> a -> b
$ ProgramState -> Map String VariableState
CF.variablesInScope ProgramState
state
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ VariableState -> Bool
isCleanState VariableState
value

    isCleanState :: VariableState -> Bool
isCleanState VariableState
state =
        (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (forall a. Ord a => a -> Set a -> Bool
S.member CFVariableProp
CFVPInteger) forall a b. (a -> b) -> a -> b
$ VariableState -> VariableProperties
CF.variableProperties VariableState
state)
        Bool -> Bool -> Bool
|| VariableValue -> SpaceStatus
CF.spaceStatus (VariableState -> VariableValue
CF.variableValue VariableState
state) forall a. Eq a => a -> a -> Bool
== SpaceStatus
CF.SpaceStatusClean

    isDefaultAssignment :: Map Id Token -> Token -> Bool
isDefaultAssignment Map Id Token
parents Token
token =
        let modifier :: String
modifier = String -> String
getBracedModifier forall a b. (a -> b) -> a -> b
$ Token -> String
bracedString Token
token in
            forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
modifier) [String
"=", String
":="]
            Bool -> Bool -> Bool
&& Map Id Token -> String -> Token -> Bool
isParamTo Map Id Token
parents String
":" Token
token

    -- Given a T_DollarBraced, return a simplified version of the string contents.
    bracedString :: Token -> String
bracedString (T_DollarBraced Id
_ Bool
_ Token
l) = forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
l
    bracedString Token
_ = forall a. HasCallStack => String -> a
error forall a b. (a -> b) -> a -> b
$ String -> String
pleaseReport String
"bracedString on non-variable"

checkSpacefulnessCfg' Bool
_ Parameters
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_CheckVariableBraces1 :: Bool
prop_CheckVariableBraces1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkVariableBraces String
"a='123'; echo $a"
prop_CheckVariableBraces2 :: Bool
prop_CheckVariableBraces2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkVariableBraces String
"a='123'; echo ${a}"
prop_CheckVariableBraces3 :: Bool
prop_CheckVariableBraces3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkVariableBraces String
"#shellcheck disable=SC2016\necho '$a'"
prop_CheckVariableBraces4 :: Bool
prop_CheckVariableBraces4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkVariableBraces String
"echo $* $1"
prop_CheckVariableBraces5 :: Bool
prop_CheckVariableBraces5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkVariableBraces String
"$foo=42"
checkVariableBraces :: Parameters -> Token -> m ()
checkVariableBraces Parameters
params t :: Token
t@(T_DollarBraced Id
id Bool
False Token
l)
    | String
name forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [String]
unbracedVariables Bool -> Bool -> Bool
&& Bool -> Bool
not (Parameters -> Token -> Bool
quotesMayConflictWithSC2281 Parameters
params Token
t) =
        forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> Fix -> m ()
styleWithFix Id
id Code
2250
            String
"Prefer putting braces around variable references even when not strictly required."
            (Token -> Fix
fixFor Token
t)
  where
    name :: String
name = String -> String
getBracedReference forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
l
    fixFor :: Token -> Fix
fixFor Token
token = [Replacement] -> Fix
fixWith [Id -> Parameters -> Code -> String -> Replacement
replaceStart (Token -> Id
getId Token
token) Parameters
params Code
1 String
"${"
                           ,Id -> Parameters -> Code -> String -> Replacement
replaceEnd (Token -> Id
getId Token
token) Parameters
params Code
0 String
"}"]
checkVariableBraces Parameters
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkQuotesInLiterals1 :: Bool
prop_checkQuotesInLiterals1 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkQuotesInLiterals String
"param='--foo=\"bar\"'; app $param"
prop_checkQuotesInLiterals1a :: Bool
prop_checkQuotesInLiterals1a = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkQuotesInLiterals String
"param=\"--foo='lolbar'\"; app $param"
prop_checkQuotesInLiterals2 :: Bool
prop_checkQuotesInLiterals2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkQuotesInLiterals String
"param='--foo=\"bar\"'; app \"$param\""
prop_checkQuotesInLiterals3 :: Bool
prop_checkQuotesInLiterals3 =(Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkQuotesInLiterals String
"param=('--foo='); app \"${param[@]}\""
prop_checkQuotesInLiterals4 :: Bool
prop_checkQuotesInLiterals4 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkQuotesInLiterals String
"param=\"don't bother with this one\"; app $param"
prop_checkQuotesInLiterals5 :: Bool
prop_checkQuotesInLiterals5 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkQuotesInLiterals String
"param=\"--foo='lolbar'\"; eval app $param"
prop_checkQuotesInLiterals6 :: Bool
prop_checkQuotesInLiterals6 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkQuotesInLiterals String
"param='my\\ file'; cmd=\"rm $param\"; $cmd"
prop_checkQuotesInLiterals6a :: Bool
prop_checkQuotesInLiterals6a = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkQuotesInLiterals String
"param='my\\ file'; cmd=\"rm ${#param}\"; $cmd"
prop_checkQuotesInLiterals7 :: Bool
prop_checkQuotesInLiterals7 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkQuotesInLiterals String
"param='my\\ file'; rm $param"
prop_checkQuotesInLiterals8 :: Bool
prop_checkQuotesInLiterals8 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkQuotesInLiterals String
"param=\"/foo/'bar baz'/etc\"; rm $param"
prop_checkQuotesInLiterals9 :: Bool
prop_checkQuotesInLiterals9 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkQuotesInLiterals String
"param=\"/foo/'bar baz'/etc\"; rm ${#param}"
checkQuotesInLiterals :: Parameters -> p -> [TokenComment]
checkQuotesInLiterals Parameters
params p
t =
    forall t v.
(Token -> Token -> String -> State t [v])
-> (Token -> Token -> String -> DataType -> State t [v])
-> t
-> [StackData]
-> [v]
doVariableFlowAnalysis forall {m :: * -> *} {k} {p}.
(MonadState (Map k Id) m, Ord k) =>
p -> Token -> k -> m [TokenComment]
readF forall {p} {p} {a}.
p -> p -> String -> DataType -> StateT (Map String Id) Identity [a]
writeF forall k a. Map k a
Map.empty (Parameters -> [StackData]
variableFlow Parameters
params)
  where
    getQuotes :: k -> m (Maybe a)
getQuotes k
name = forall s (m :: * -> *) a. MonadState s m => (s -> a) -> m a
gets (forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup k
name)
    setQuotes :: k -> a -> m ()
setQuotes k
name a
ref = forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify forall a b. (a -> b) -> a -> b
$ forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert k
name a
ref
    deleteQuotes :: String -> StateT (Map String Id) Identity ()
deleteQuotes = forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall k a. Ord k => k -> Map k a -> Map k a
Map.delete
    parents :: Map Id Token
parents = Parameters -> Map Id Token
parentMap Parameters
params
    quoteRegex :: Regex
quoteRegex = String -> Regex
mkRegex String
"\"|([/= ]|^)'|'( |$)|\\\\ "
    containsQuotes :: String -> Bool
containsQuotes String
s = String
s String -> Regex -> Bool
`matches` Regex
quoteRegex

    writeF :: p -> p -> String -> DataType -> StateT (Map String Id) Identity [a]
writeF p
_ p
_ String
name (DataString (SourceFrom [Token]
values)) = do
        Map String Id
quoteMap <- forall s (m :: * -> *). MonadState s m => m s
get
        let quotedVars :: Maybe Id
quotedVars = forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, MonadPlus m) =>
t (m a) -> m a
msum forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map (Map String Id -> Token -> Maybe Id
forToken Map String Id
quoteMap) [Token]
values
        case Maybe Id
quotedVars of
            Maybe Id
Nothing -> String -> StateT (Map String Id) Identity ()
deleteQuotes String
name
            Just Id
x -> forall {k} {a} {m :: * -> *}.
(MonadState (Map k a) m, Ord k) =>
k -> a -> m ()
setQuotes String
name Id
x
        forall (m :: * -> *) a. Monad m => a -> m a
return []
    writeF p
_ p
_ String
_ DataType
_ = forall (m :: * -> *) a. Monad m => a -> m a
return []

    forToken :: Map String Id -> Token -> Maybe Id
forToken Map String Id
map (T_DollarBraced Id
id Bool
_ Token
t) =
        -- skip getBracedReference here to avoid false positives on PE
        forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup (forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [String]
oversimplify forall a b. (a -> b) -> a -> b
$ Token
t) Map String Id
map
    forToken Map String Id
quoteMap (T_DoubleQuoted Id
id [Token]
tokens) =
        forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, MonadPlus m) =>
t (m a) -> m a
msum forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map (Map String Id -> Token -> Maybe Id
forToken Map String Id
quoteMap) [Token]
tokens
    forToken Map String Id
quoteMap (T_NormalWord Id
id [Token]
tokens) =
        forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, MonadPlus m) =>
t (m a) -> m a
msum forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map (Map String Id -> Token -> Maybe Id
forToken Map String Id
quoteMap) [Token]
tokens
    forToken Map String Id
_ Token
t =
        if String -> Bool
containsQuotes (forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
t)
        then forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Token -> Id
getId Token
t
        else forall a. Maybe a
Nothing

    squashesQuotes :: Token -> Bool
squashesQuotes Token
t =
        case Token
t of
            T_DollarBraced Id
id Bool
_ Token
l -> String
"#" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat (Token -> [String]
oversimplify Token
l)
            Token
_ -> Bool
False

    readF :: p -> Token -> k -> m [TokenComment]
readF p
_ Token
expr k
name = do
        Maybe Id
assignment <- forall {k} {a} {m :: * -> *}.
(MonadState (Map k a) m, Ord k) =>
k -> m (Maybe a)
getQuotes k
name
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ case Maybe Id
assignment of
          Just Id
j
              | Bool -> Bool
not (Map Id Token -> String -> Token -> Bool
isParamTo Map Id Token
parents String
"eval" Token
expr)
              Bool -> Bool -> Bool
&& Bool -> Bool
not (Shell -> Map Id Token -> Token -> Bool
isQuoteFree (Parameters -> Shell
shellType Parameters
params) Map Id Token
parents Token
expr)
              Bool -> Bool -> Bool
&& Bool -> Bool
not (Token -> Bool
squashesQuotes Token
expr)
              -> [
                  Severity -> Id -> Code -> String -> TokenComment
makeComment Severity
WarningC Id
j Code
2089 forall a b. (a -> b) -> a -> b
$
                      String
"Quotes/backslashes will be treated literally. " forall a. [a] -> [a] -> [a]
++ String
suggestion,
                  Severity -> Id -> Code -> String -> TokenComment
makeComment Severity
WarningC (Token -> Id
getId Token
expr) Code
2090
                      String
"Quotes/backslashes in this variable will not be respected."
                ]
          Maybe Id
_ -> []
    suggestion :: String
suggestion =
        if Shell -> Bool
supportsArrays (Parameters -> Shell
shellType Parameters
params)
        then String
"Use an array."
        else String
"Rewrite using set/\"$@\" or functions."


prop_checkFunctionsUsedExternally1 :: Bool
prop_checkFunctionsUsedExternally1 =
  (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkFunctionsUsedExternally String
"foo() { :; }; sudo foo"
prop_checkFunctionsUsedExternally2 :: Bool
prop_checkFunctionsUsedExternally2 =
  (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkFunctionsUsedExternally String
"alias f='a'; xargs -0 f"
prop_checkFunctionsUsedExternally2b :: Bool
prop_checkFunctionsUsedExternally2b =
  (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkFunctionsUsedExternally String
"alias f='a'; find . -type f"
prop_checkFunctionsUsedExternally2c :: Bool
prop_checkFunctionsUsedExternally2c =
  (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkFunctionsUsedExternally String
"alias f='a'; find . -type f -exec f +"
prop_checkFunctionsUsedExternally3 :: Bool
prop_checkFunctionsUsedExternally3 =
  (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkFunctionsUsedExternally String
"f() { :; }; echo f"
prop_checkFunctionsUsedExternally4 :: Bool
prop_checkFunctionsUsedExternally4 =
  (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkFunctionsUsedExternally String
"foo() { :; }; sudo \"foo\""
prop_checkFunctionsUsedExternally5 :: Bool
prop_checkFunctionsUsedExternally5 =
  (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkFunctionsUsedExternally String
"foo() { :; }; ssh host foo"
prop_checkFunctionsUsedExternally6 :: Bool
prop_checkFunctionsUsedExternally6 =
  (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkFunctionsUsedExternally String
"foo() { :; }; ssh host echo foo"
prop_checkFunctionsUsedExternally7 :: Bool
prop_checkFunctionsUsedExternally7 =
  (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkFunctionsUsedExternally String
"install() { :; }; sudo apt-get install foo"
prop_checkFunctionsUsedExternally8 :: Bool
prop_checkFunctionsUsedExternally8 =
  (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkFunctionsUsedExternally String
"foo() { :; }; command sudo foo"
prop_checkFunctionsUsedExternally9 :: Bool
prop_checkFunctionsUsedExternally9 =
  (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkFunctionsUsedExternally String
"foo() { :; }; exec -c sudo foo"
checkFunctionsUsedExternally :: Parameters -> Token -> [TokenComment]
checkFunctionsUsedExternally Parameters
params Token
t =
    forall {w} {t}.
Monoid w =>
(t -> Token -> WriterT w Identity ()) -> t -> Token -> w
runNodeAnalysis forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCommand Parameters
params Token
t
  where
    checkCommand :: p -> Token -> m ()
checkCommand p
_ t :: Token
t@(T_SimpleCommand Id
_ [Token]
_ [Token]
argv) =
        case Bool -> Token -> (Maybe String, Token)
getCommandNameAndToken Bool
False Token
t of
            (Just String
str, Token
t) -> do
                let name :: String
name = String -> String
basename String
str
                let args :: [Token]
args = Token -> [Token] -> [Token]
skipOver Token
t [Token]
argv
                let argStrings :: [(String, Token)]
argStrings = forall a b. (a -> b) -> [a] -> [b]
map (\Token
x -> (forall a. a -> Maybe a -> a
fromMaybe String
"" forall a b. (a -> b) -> a -> b
$ Token -> Maybe String
getLiteralString Token
x, Token
x)) [Token]
args
                let candidates :: [(String, Token)]
candidates = forall {b}. String -> [(String, b)] -> [(String, b)]
getPotentialCommands String
name [(String, Token)]
argStrings
                forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (forall {m :: * -> *} {a}.
MonadWriter [TokenComment] m =>
String -> Id -> (a, Token) -> m ()
checkArg String
name (Token -> Id
getId Token
t)) [(String, Token)]
candidates
            (Maybe String, Token)
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
    checkCommand p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

    skipOver :: Token -> [Token] -> [Token]
skipOver Token
t [Token]
list = forall a. Int -> [a] -> [a]
drop Int
1 forall a b. (a -> b) -> a -> b
$ forall a. (a -> Bool) -> [a] -> [a]
dropWhile (\Token
c -> Token -> Id
getId Token
c forall a. Eq a => a -> a -> Bool
/= Id
id) forall a b. (a -> b) -> a -> b
$ [Token]
list
      where id :: Id
id = Token -> Id
getId Token
t

    -- Try to pick out the argument[s] that may be commands
    getPotentialCommands :: String -> [(String, b)] -> [(String, b)]
getPotentialCommands String
name [(String, b)]
argAndString =
        case String
name of
            String
"chroot" -> [(String, b)]
firstNonFlag
            String
"screen" -> [(String, b)]
firstNonFlag
            String
"sudo" -> [(String, b)]
firstNonFlag
            String
"xargs" -> [(String, b)]
firstNonFlag
            String
"tmux" -> [(String, b)]
firstNonFlag
            String
"ssh" -> forall a. Int -> [a] -> [a]
take Int
1 forall a b. (a -> b) -> a -> b
$ forall a. Int -> [a] -> [a]
drop Int
1 forall a b. (a -> b) -> a -> b
$ forall {b}. [(String, b)] -> [(String, b)]
dropFlags [(String, b)]
argAndString
            String
"find" -> forall a. Int -> [a] -> [a]
take Int
1 forall a b. (a -> b) -> a -> b
$ forall a. Int -> [a] -> [a]
drop Int
1 forall a b. (a -> b) -> a -> b
$
                forall a. (a -> Bool) -> [a] -> [a]
dropWhile (\(String, b)
x -> forall a b. (a, b) -> a
fst (String, b)
x forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [String]
findExecFlags) [(String, b)]
argAndString
            String
_ -> []
      where
        firstNonFlag :: [(String, b)]
firstNonFlag = forall a. Int -> [a] -> [a]
take Int
1 forall a b. (a -> b) -> a -> b
$ forall {b}. [(String, b)] -> [(String, b)]
dropFlags [(String, b)]
argAndString
        findExecFlags :: [String]
findExecFlags = [String
"-exec", String
"-execdir", String
"-ok"]
        dropFlags :: [(String, b)] -> [(String, b)]
dropFlags = forall a. (a -> Bool) -> [a] -> [a]
dropWhile (\(String, b)
x -> String
"-" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` forall a b. (a, b) -> a
fst (String, b)
x)

    functionsAndAliases :: Map String Id
functionsAndAliases = forall k a. Ord k => Map k a -> Map k a -> Map k a
Map.union (Token -> Map String Id
functions Token
t) (Token -> Map String Id
aliases Token
t)

    patternContext :: Id -> String
patternContext Id
id =
        case Position -> Code
posLine forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a, b) -> a
fst forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup Id
id (Parameters -> Map Id (Position, Position)
tokenPositions Parameters
params) of
          Just Code
l -> String
" on line " forall a. Semigroup a => a -> a -> a
<> forall a. Show a => a -> String
show Code
l forall a. Semigroup a => a -> a -> a
<> String
"."
          Maybe Code
_      -> String
"."

    checkArg :: String -> Id -> (a, Token) -> m ()
checkArg String
cmd Id
cmdId (a
_, Token
arg) = forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
        String
literalArg <- Token -> Maybe String
getUnquotedLiteral Token
arg  -- only consider unquoted literals
        Id
definitionId <- forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup String
literalArg Map String Id
functionsAndAliases
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ do
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
arg) Code
2033
              String
"Shell functions can't be passed to external commands. Use separate script or sh -c."
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info Id
definitionId Code
2032 forall a b. (a -> b) -> a -> b
$
              String
"This function can't be invoked via " forall a. [a] -> [a] -> [a]
++ String
cmd forall a. [a] -> [a] -> [a]
++ Id -> String
patternContext Id
cmdId

prop_checkUnused0 :: Bool
prop_checkUnused0 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"var=foo; echo $var"
prop_checkUnused1 :: Bool
prop_checkUnused1 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"var=foo; echo $bar"
prop_checkUnused2 :: Bool
prop_checkUnused2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"var=foo; export var;"
prop_checkUnused3 :: Bool
prop_checkUnused3 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"for f in *; do echo '$f'; done"
prop_checkUnused4 :: Bool
prop_checkUnused4 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"local i=0"
prop_checkUnused5 :: Bool
prop_checkUnused5 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"read lol; echo $lol"
prop_checkUnused6 :: Bool
prop_checkUnused6 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"var=4; (( var++ ))"
prop_checkUnused7 :: Bool
prop_checkUnused7 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"var=2; $((var))"
prop_checkUnused8 :: Bool
prop_checkUnused8 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"var=2; var=3;"
prop_checkUnused9 :: Bool
prop_checkUnused9 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"read ''"
prop_checkUnused10 :: Bool
prop_checkUnused10 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"read -p 'test: '"
prop_checkUnused11 :: Bool
prop_checkUnused11 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"bar=5; export foo[$bar]=3"
prop_checkUnused12 :: Bool
prop_checkUnused12 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"read foo; echo ${!foo}"
prop_checkUnused13 :: Bool
prop_checkUnused13 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"x=(1); (( x[0] ))"
prop_checkUnused14 :: Bool
prop_checkUnused14 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"x=(1); n=0; echo ${x[n]}"
prop_checkUnused15 :: Bool
prop_checkUnused15 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"x=(1); n=0; (( x[n] ))"
prop_checkUnused16 :: Bool
prop_checkUnused16 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"foo=5; declare -x foo"
prop_checkUnused16b :: Bool
prop_checkUnused16b = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"f() { local -x foo; foo=42; bar; }; f"
prop_checkUnused17 :: Bool
prop_checkUnused17 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"read -i 'foo' -e -p 'Input: ' bar; $bar;"
prop_checkUnused18 :: Bool
prop_checkUnused18 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"a=1; arr=( [$a]=42 ); echo \"${arr[@]}\""
prop_checkUnused19 :: Bool
prop_checkUnused19 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"a=1; let b=a+1; echo $b"
prop_checkUnused20 :: Bool
prop_checkUnused20 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"a=1; PS1='$a'"
prop_checkUnused21 :: Bool
prop_checkUnused21 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"a=1; trap 'echo $a' INT"
prop_checkUnused22 :: Bool
prop_checkUnused22 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"a=1; [ -v a ]"
prop_checkUnused23 :: Bool
prop_checkUnused23 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"a=1; [ -R a ]"
prop_checkUnused24 :: Bool
prop_checkUnused24 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"mapfile -C a b; echo ${b[@]}"
prop_checkUnused25 :: Bool
prop_checkUnused25 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"readarray foo; echo ${foo[@]}"
prop_checkUnused26 :: Bool
prop_checkUnused26 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"declare -F foo"
prop_checkUnused27 :: Bool
prop_checkUnused27 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"var=3; [ var -eq 3 ]"
prop_checkUnused28 :: Bool
prop_checkUnused28 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"var=3; [[ var -eq 3 ]]"
prop_checkUnused29 :: Bool
prop_checkUnused29 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"var=(a b); declare -p var"
prop_checkUnused30 :: Bool
prop_checkUnused30 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"let a=1"
prop_checkUnused31 :: Bool
prop_checkUnused31 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"let 'a=1'"
prop_checkUnused32 :: Bool
prop_checkUnused32 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"let a=b=c; echo $a"
prop_checkUnused33 :: Bool
prop_checkUnused33 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"a=foo; [[ foo =~ ^{$a}$ ]]"
prop_checkUnused34 :: Bool
prop_checkUnused34 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"foo=1; (( t = foo )); echo $t"
prop_checkUnused35 :: Bool
prop_checkUnused35 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"a=foo; b=2; echo ${a:b}"
prop_checkUnused36 :: Bool
prop_checkUnused36 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"if [[ -v foo ]]; then true; fi"
prop_checkUnused37 :: Bool
prop_checkUnused37 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"fd=2; exec {fd}>&-"
prop_checkUnused38 :: Bool
prop_checkUnused38 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"(( a=42 ))"
prop_checkUnused39 :: Bool
prop_checkUnused39 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"declare -x -f foo"
prop_checkUnused40 :: Bool
prop_checkUnused40 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"arr=(1 2); num=2; echo \"${arr[@]:num}\""
prop_checkUnused41 :: Bool
prop_checkUnused41 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"@test 'foo' {\ntrue\n}\n"
prop_checkUnused42 :: Bool
prop_checkUnused42 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"DEFINE_string foo '' ''; echo \"${FLAGS_foo}\""
prop_checkUnused43 :: Bool
prop_checkUnused43 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"DEFINE_string foo '' ''"
prop_checkUnused44 :: Bool
prop_checkUnused44 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"DEFINE_string \"foo$ibar\" x y"
prop_checkUnused45 :: Bool
prop_checkUnused45 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"readonly foo=bar"
prop_checkUnused46 :: Bool
prop_checkUnused46 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"readonly foo=(bar)"
prop_checkUnused47 :: Bool
prop_checkUnused47 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"a=1; alias hello='echo $a'"
prop_checkUnused48 :: Bool
prop_checkUnused48 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"_a=1"
prop_checkUnused49 :: Bool
prop_checkUnused49 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"declare -A array; key=a; [[ -v array[$key] ]]"
prop_checkUnused50 :: Bool
prop_checkUnused50 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"foofunc() { :; }; typeset -fx foofunc"
prop_checkUnused51 :: Bool
prop_checkUnused51 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"x[y[z=1]]=1; echo ${x[@]}"

checkUnusedAssignments :: Parameters -> p -> [TokenComment]
checkUnusedAssignments Parameters
params p
t = forall w a. Writer w a -> w
execWriter (forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
(String, Token) -> f ()
warnFor [(String, Token)]
unused)
  where
    flow :: [StackData]
flow = Parameters -> [StackData]
variableFlow Parameters
params
    references :: Map String ()
references = forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl (forall a b c. (a -> b -> c) -> b -> a -> c
flip forall a b. (a -> b) -> a -> b
($)) Map String ()
defaultMap (forall a b. (a -> b) -> [a] -> [b]
map StackData -> Map String () -> Map String ()
insertRef [StackData]
flow)
    insertRef :: StackData -> Map String () -> Map String ()
insertRef (Reference (Token
base, Token
token, String
name)) =
        forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert (String -> String
stripSuffix String
name) ()
    insertRef StackData
_ = forall a. a -> a
id

    assignments :: Map String Token
assignments = forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl (forall a b c. (a -> b -> c) -> b -> a -> c
flip forall a b. (a -> b) -> a -> b
($)) forall k a. Map k a
Map.empty (forall a b. (a -> b) -> [a] -> [b]
map StackData -> Map String Token -> Map String Token
insertAssignment [StackData]
flow)
    insertAssignment :: StackData -> Map String Token -> Map String Token
insertAssignment (Assignment (Token
_, Token
token, String
name, DataType
_)) | String -> Bool
isVariableName String
name =
        forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert String
name Token
token
    insertAssignment StackData
_ = forall a. a -> a
id

    unused :: [(String, Token)]
unused = forall k a. Map k a -> [(k, a)]
Map.assocs forall a b. (a -> b) -> a -> b
$ forall k a b. Ord k => Map k a -> Map k b -> Map k a
Map.difference Map String Token
assignments Map String ()
references

    warnFor :: (String, Token) -> f ()
warnFor (String
name, Token
token) =
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (String
"_" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
name) forall a b. (a -> b) -> a -> b
$
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
token) Code
2034 forall a b. (a -> b) -> a -> b
$
                String
name forall a. [a] -> [a] -> [a]
++ String
" appears unused. Verify use (or export if used externally)."

    stripSuffix :: String -> String
stripSuffix = forall a. (a -> Bool) -> [a] -> [a]
takeWhile Char -> Bool
isVariableChar
    defaultMap :: Map String ()
defaultMap = forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList forall a b. (a -> b) -> a -> b
$ forall a b. [a] -> [b] -> [(a, b)]
zip [String]
internalVariables forall a b. (a -> b) -> a -> b
$ forall a. a -> [a]
repeat ()

prop_checkUnassignedReferences1 :: Bool
prop_checkUnassignedReferences1 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"echo $foo"
prop_checkUnassignedReferences2 :: Bool
prop_checkUnassignedReferences2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"foo=hello; echo $foo"
prop_checkUnassignedReferences3 :: Bool
prop_checkUnassignedReferences3 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"MY_VALUE=3; echo $MYVALUE"
prop_checkUnassignedReferences4 :: Bool
prop_checkUnassignedReferences4 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"RANDOM2=foo; echo $RANDOM"
prop_checkUnassignedReferences5 :: Bool
prop_checkUnassignedReferences5 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"declare -A foo=([bar]=baz); echo ${foo[bar]}"
prop_checkUnassignedReferences6 :: Bool
prop_checkUnassignedReferences6 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"foo=..; echo ${foo-bar}"
prop_checkUnassignedReferences7 :: Bool
prop_checkUnassignedReferences7 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"getopts ':h' foo; echo $foo"
prop_checkUnassignedReferences8 :: Bool
prop_checkUnassignedReferences8 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"let 'foo = 1'; echo $foo"
prop_checkUnassignedReferences9 :: Bool
prop_checkUnassignedReferences9 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"echo ${foo-bar}"
prop_checkUnassignedReferences10 :: Bool
prop_checkUnassignedReferences10 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"echo ${foo:?}"
prop_checkUnassignedReferences11 :: Bool
prop_checkUnassignedReferences11 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"declare -A foo; echo \"${foo[@]}\""
prop_checkUnassignedReferences12 :: Bool
prop_checkUnassignedReferences12 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"typeset -a foo; echo \"${foo[@]}\""
prop_checkUnassignedReferences13 :: Bool
prop_checkUnassignedReferences13 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"f() { local foo; echo $foo; }"
prop_checkUnassignedReferences14 :: Bool
prop_checkUnassignedReferences14 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"foo=; echo $foo"
prop_checkUnassignedReferences15 :: Bool
prop_checkUnassignedReferences15 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"f() { true; }; export -f f"
prop_checkUnassignedReferences16 :: Bool
prop_checkUnassignedReferences16 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"declare -A foo=( [a b]=bar ); echo ${foo[a b]}"
prop_checkUnassignedReferences17 :: Bool
prop_checkUnassignedReferences17 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"USERS=foo; echo $USER"
prop_checkUnassignedReferences18 :: Bool
prop_checkUnassignedReferences18 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"FOOBAR=42; export FOOBAR="
prop_checkUnassignedReferences19 :: Bool
prop_checkUnassignedReferences19 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"readonly foo=bar; echo $foo"
prop_checkUnassignedReferences20 :: Bool
prop_checkUnassignedReferences20 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"printf -v foo bar; echo $foo"
prop_checkUnassignedReferences21 :: Bool
prop_checkUnassignedReferences21 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"echo ${#foo}"
prop_checkUnassignedReferences22 :: Bool
prop_checkUnassignedReferences22 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"echo ${!os*}"
prop_checkUnassignedReferences23 :: Bool
prop_checkUnassignedReferences23 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"declare -a foo; foo[bar]=42;"
prop_checkUnassignedReferences24 :: Bool
prop_checkUnassignedReferences24 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"declare -A foo; foo[bar]=42;"
prop_checkUnassignedReferences25 :: Bool
prop_checkUnassignedReferences25 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"declare -A foo=(); foo[bar]=42;"
prop_checkUnassignedReferences26 :: Bool
prop_checkUnassignedReferences26 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"a::b() { foo; }; readonly -f a::b"
prop_checkUnassignedReferences27 :: Bool
prop_checkUnassignedReferences27 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
": ${foo:=bar}"
prop_checkUnassignedReferences28 :: Bool
prop_checkUnassignedReferences28 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"#!/bin/ksh\necho \"${.sh.version}\"\n"
prop_checkUnassignedReferences29 :: Bool
prop_checkUnassignedReferences29 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"if [[ -v foo ]]; then echo $foo; fi"
prop_checkUnassignedReferences30 :: Bool
prop_checkUnassignedReferences30 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"if [[ -v foo[3] ]]; then echo ${foo[3]}; fi"
prop_checkUnassignedReferences31 :: Bool
prop_checkUnassignedReferences31 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"X=1; if [[ -v foo[$X+42] ]]; then echo ${foo[$X+42]}; fi"
prop_checkUnassignedReferences32 :: Bool
prop_checkUnassignedReferences32 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"if [[ -v \"foo[1]\" ]]; then echo ${foo[@]}; fi"
prop_checkUnassignedReferences33 :: Bool
prop_checkUnassignedReferences33 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"f() { local -A foo; echo \"${foo[@]}\"; }"
prop_checkUnassignedReferences34 :: Bool
prop_checkUnassignedReferences34 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"declare -A foo; (( foo[bar] ))"
prop_checkUnassignedReferences35 :: Bool
prop_checkUnassignedReferences35 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"echo ${arr[foo-bar]:?fail}"
prop_checkUnassignedReferences36 :: Bool
prop_checkUnassignedReferences36 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"read -a foo -r <<<\"foo bar\"; echo \"$foo\""
prop_checkUnassignedReferences37 :: Bool
prop_checkUnassignedReferences37 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"var=howdy; printf -v 'array[0]' %s \"$var\"; printf %s \"${array[0]}\";"
prop_checkUnassignedReferences38 :: Bool
prop_checkUnassignedReferences38 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree (forall {p}. Bool -> Parameters -> p -> [TokenComment]
checkUnassignedReferences' Bool
True) String
"echo $VAR"
prop_checkUnassignedReferences39 :: Bool
prop_checkUnassignedReferences39 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"builtin export var=4; echo $var"
prop_checkUnassignedReferences40 :: Bool
prop_checkUnassignedReferences40 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
": ${foo=bar}"
prop_checkUnassignedReferences41 :: Bool
prop_checkUnassignedReferences41 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"mapfile -t files 123; echo \"${files[@]}\""
prop_checkUnassignedReferences42 :: Bool
prop_checkUnassignedReferences42 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"mapfile files -t; echo \"${files[@]}\""
prop_checkUnassignedReferences43 :: Bool
prop_checkUnassignedReferences43 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"mapfile --future files; echo \"${files[@]}\""
prop_checkUnassignedReferences_minusNPlain :: Bool
prop_checkUnassignedReferences_minusNPlain   = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"if [ -n \"$x\" ]; then echo $x; fi"
prop_checkUnassignedReferences_minusZPlain :: Bool
prop_checkUnassignedReferences_minusZPlain   = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"if [ -z \"$x\" ]; then echo \"\"; fi"
prop_checkUnassignedReferences_minusNBraced :: Bool
prop_checkUnassignedReferences_minusNBraced  = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"if [ -n \"${x}\" ]; then echo $x; fi"
prop_checkUnassignedReferences_minusZBraced :: Bool
prop_checkUnassignedReferences_minusZBraced  = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"if [ -z \"${x}\" ]; then echo \"\"; fi"
prop_checkUnassignedReferences_minusNDefault :: Bool
prop_checkUnassignedReferences_minusNDefault = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"if [ -n \"${x:-}\" ]; then echo $x; fi"
prop_checkUnassignedReferences_minusZDefault :: Bool
prop_checkUnassignedReferences_minusZDefault = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"if [ -z \"${x:-}\" ]; then echo \"\"; fi"
prop_checkUnassignedReferences50 :: Bool
prop_checkUnassignedReferences50 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"echo ${foo:+bar}"
prop_checkUnassignedReferences51 :: Bool
prop_checkUnassignedReferences51 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"echo ${foo:+$foo}"
prop_checkUnassignedReferences52 :: Bool
prop_checkUnassignedReferences52 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"wait -p pid; echo $pid"

checkUnassignedReferences :: Parameters -> p -> [TokenComment]
checkUnassignedReferences = forall {p}. Bool -> Parameters -> p -> [TokenComment]
checkUnassignedReferences' Bool
False
checkUnassignedReferences' :: Bool -> Parameters -> p -> [TokenComment]
checkUnassignedReferences' Bool
includeGlobals Parameters
params p
t = [TokenComment]
warnings
  where
    (Map String Token
readMap, Map String ()
writeMap) = forall s a. State s a -> s -> s
execState (forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
mapM forall {m :: * -> *}.
MonadState (Map String Token, Map String ()) m =>
StackData -> m ()
tally forall a b. (a -> b) -> a -> b
$ Parameters -> [StackData]
variableFlow Parameters
params) (forall k a. Map k a
Map.empty, forall k a. Map k a
Map.empty)
    defaultAssigned :: Map String ()
defaultAssigned = forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map (\String
a -> (String
a, ())) forall a b. (a -> b) -> a -> b
$ forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: * -> *) a. Foldable t => t a -> Bool
null) [String]
internalVariables

    tally :: StackData -> m ()
tally (Assignment (Token
_, Token
_, String
name, DataType
_))  =
        forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify (\(Map String Token
read, Map String ()
written) -> (Map String Token
read, forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert String
name () Map String ()
written))
    tally (Reference (Token
_, Token
place, String
name)) =
        forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify (\(Map String Token
read, Map String ()
written) -> (forall k a. Ord k => (a -> a -> a) -> k -> a -> Map k a -> Map k a
Map.insertWith (forall a b. a -> b -> a
const forall a. a -> a
id) String
name Token
place Map String Token
read, Map String ()
written))
    tally StackData
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

    unassigned :: [(String, Token)]
unassigned = forall k a. Map k a -> [(k, a)]
Map.toList forall a b. (a -> b) -> a -> b
$ forall k a b. Ord k => Map k a -> Map k b -> Map k a
Map.difference (forall k a b. Ord k => Map k a -> Map k b -> Map k a
Map.difference Map String Token
readMap Map String ()
writeMap) Map String ()
defaultAssigned
    writtenVars :: [String]
writtenVars = forall a. (a -> Bool) -> [a] -> [a]
filter String -> Bool
isVariableName forall a b. (a -> b) -> a -> b
$ forall k a. Map k a -> [k]
Map.keys Map String ()
writeMap

    getBestMatch :: String -> Maybe String
getBestMatch String
var = do
        (String
match, Int
score) <- forall a. [a] -> Maybe a
listToMaybe [(String, Int)]
best
        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ forall {a} {t :: * -> *} {p} {a}.
(Ord a, Num a, Foldable t) =>
p -> t a -> a -> Bool
goodMatch String
var String
match Int
score
        forall (m :: * -> *) a. Monad m => a -> m a
return String
match
      where
        matches :: [(String, Int)]
matches = forall a b. (a -> b) -> [a] -> [b]
map (\String
x -> (String
x, String -> String -> Int
match String
var String
x)) [String]
writtenVars
        best :: [(String, Int)]
best = forall a. (a -> a -> Ordering) -> [a] -> [a]
sortBy (forall a b. Ord a => (b -> a) -> b -> b -> Ordering
comparing forall a b. (a, b) -> b
snd) [(String, Int)]
matches
        goodMatch :: p -> t a -> a -> Bool
goodMatch p
var t a
match a
score =
            let l :: Int
l = forall (t :: * -> *) a. Foldable t => t a -> Int
length t a
match in
                Int
l forall a. Ord a => a -> a -> Bool
> Int
3 Bool -> Bool -> Bool
&& a
score forall a. Ord a => a -> a -> Bool
<= a
1
                Bool -> Bool -> Bool
|| Int
l forall a. Ord a => a -> a -> Bool
> Int
7 Bool -> Bool -> Bool
&& a
score forall a. Ord a => a -> a -> Bool
<= a
2

    isLocal :: String -> Bool
isLocal = forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Char -> Bool
isLower

    warningForGlobals :: String -> Token -> Maybe (m ())
warningForGlobals String
var Token
place = do
        String
match <- String -> Maybe String
getBestMatch String
var
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
place) Code
2153 forall a b. (a -> b) -> a -> b
$
            String
"Possible misspelling: " forall a. [a] -> [a] -> [a]
++ String
var forall a. [a] -> [a] -> [a]
++ String
" may not be assigned. Did you mean " forall a. [a] -> [a] -> [a]
++ String
match forall a. [a] -> [a] -> [a]
++ String
"?"

    warningForLocals :: String -> Token -> m (m ())
warningForLocals String
var Token
place =
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
place) Code
2154 forall a b. (a -> b) -> a -> b
$
            String
var forall a. [a] -> [a] -> [a]
++ String
" is referenced but not assigned" forall a. [a] -> [a] -> [a]
++ String
optionalTip forall a. [a] -> [a] -> [a]
++ String
"."
      where
        optionalTip :: String
optionalTip =
            if String
var forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
commonCommands
            then String
" (for output from commands, use \"$(" forall a. [a] -> [a] -> [a]
++ String
var forall a. [a] -> [a] -> [a]
++ String
" ..." forall a. [a] -> [a] -> [a]
++ String
")\" )"
            else forall a. a -> Maybe a -> a
fromMaybe String
"" forall a b. (a -> b) -> a -> b
$ do
                    String
match <- String -> Maybe String
getBestMatch String
var
                    forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ String
" (did you mean '" forall a. [a] -> [a] -> [a]
++ String
match forall a. [a] -> [a] -> [a]
++ String
"'?)"

    warningFor :: (String, Token) -> Maybe (m ())
warningFor (String
var, Token
place) = do
        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ String -> Bool
isVariableName String
var
        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ String -> Token -> Bool
isException String
var Token
place Bool -> Bool -> Bool
|| Token -> Bool
isGuarded Token
place
        (if Bool
includeGlobals Bool -> Bool -> Bool
|| String -> Bool
isLocal String
var
         then forall {m :: * -> *} {m :: * -> *}.
(MonadWriter [TokenComment] m, Monad m) =>
String -> Token -> m (m ())
warningForLocals
         else forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
String -> Token -> Maybe (m ())
warningForGlobals) String
var Token
place

    warnings :: [TokenComment]
warnings = forall w a. Writer w a -> w
execWriter forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: * -> *) (m :: * -> *) a.
(Traversable t, Monad m) =>
t (m a) -> m (t a)
sequence forall a b. (a -> b) -> a -> b
$ forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
(String, Token) -> Maybe (m ())
warningFor [(String, Token)]
unassigned

    -- Due to parsing, foo=( [bar]=baz ) parses 'bar' as a reference even for assoc arrays.
    -- Similarly, ${foo[bar baz]} may not be referencing bar/baz. Just skip these.
    -- We can also have ${foo:+$foo} should be treated like [[ -n $foo ]] && echo $foo
    isException :: String -> Token -> Bool
isException String
var Token
t = forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
shouldExclude forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) Token
t
      where
        shouldExclude :: Token -> Bool
shouldExclude Token
t =
            case Token
t of
                T_Array {} -> Bool
True
                (T_DollarBraced Id
_ Bool
_ Token
l) ->
                    let str :: String
str = forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
l
                        ref :: String
ref = String -> String
getBracedReference String
str
                        mod :: String
mod = String -> String
getBracedModifier String
str
                    in
                        -- Either we're used as an array index like ${arr[here]}
                        String
ref forall a. Eq a => a -> a -> Bool
/= String
var Bool -> Bool -> Bool
||
                        -- or the reference is guarded by a parent, ${here:+foo$here}
                        String
"+" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
mod Bool -> Bool -> Bool
|| String
":+" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
mod
                Token
_ -> Bool
False

    isGuarded :: Token -> Bool
isGuarded (T_DollarBraced Id
_ Bool
_ Token
v) =
        String
rest String -> Regex -> Bool
`matches` Regex
guardRegex
      where
        name :: String
name = forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
v
        rest :: String
rest = forall a. (a -> Bool) -> [a] -> [a]
dropWhile Char -> Bool
isVariableChar forall a b. (a -> b) -> a -> b
$ forall a. (a -> Bool) -> [a] -> [a]
dropWhile (forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
"#!") String
name
    isGuarded Token
_ = Bool
False
    --  :? or :- with optional array index and colon
    guardRegex :: Regex
guardRegex = String -> Regex
mkRegex String
"^(\\[.*\\])?:?[-?]"

    match :: String -> String -> Int
match String
var String
candidate =
        if String
var forall a. Eq a => a -> a -> Bool
/= String
candidate Bool -> Bool -> Bool
&& forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
toLower String
var forall a. Eq a => a -> a -> Bool
== forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
toLower String
candidate
        then Int
1
        else forall a. Eq a => [a] -> [a] -> Int
dist String
var String
candidate


prop_checkGlobsAsOptions1 :: Bool
prop_checkGlobsAsOptions1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobsAsOptions String
"rm *.txt"
prop_checkGlobsAsOptions2 :: Bool
prop_checkGlobsAsOptions2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobsAsOptions String
"ls ??.*"
prop_checkGlobsAsOptions3 :: Bool
prop_checkGlobsAsOptions3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobsAsOptions String
"rm -- *.txt"
prop_checkGlobsAsOptions4 :: Bool
prop_checkGlobsAsOptions4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobsAsOptions String
"*.txt"
prop_checkGlobsAsOptions5 :: Bool
prop_checkGlobsAsOptions5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobsAsOptions String
"echo 'Files:' *.txt"
prop_checkGlobsAsOptions6 :: Bool
prop_checkGlobsAsOptions6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobsAsOptions String
"printf '%s\\n' *"
checkGlobsAsOptions :: p -> Token -> f ()
checkGlobsAsOptions p
_ cmd :: Token
cmd@(T_SimpleCommand Id
_ [Token]
_ [Token]
args) =
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless ((forall a. a -> Maybe a -> a
fromMaybe String
"" forall a b. (a -> b) -> a -> b
$ Token -> Maybe String
getCommandBasename Token
cmd) forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"echo", String
"printf"]) forall a b. (a -> b) -> a -> b
$
        forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
check forall a b. (a -> b) -> a -> b
$ forall a. (a -> Bool) -> [a] -> [a]
takeWhile (Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> Bool
isEndOfArgs) (forall a. Int -> [a] -> [a]
drop Int
1 [Token]
args)
  where
    check :: Token -> m ()
check v :: Token
v@(T_NormalWord Id
_ (T_Glob Id
id String
s:[Token]
_)) | String
s forall a. Eq a => a -> a -> Bool
== String
"*" Bool -> Bool -> Bool
|| String
s forall a. Eq a => a -> a -> Bool
== String
"?" =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info Id
id Code
2035 String
"Use ./*glob* or -- *glob* so names with dashes won't become options."
    check Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

    isEndOfArgs :: Token -> Bool
isEndOfArgs Token
t =
        case forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
t of
            String
"--" -> Bool
True
            String
":::" -> Bool
True
            String
"::::" -> Bool
True
            String
_ -> Bool
False

checkGlobsAsOptions p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkWhileReadPitfalls1 :: Bool
prop_checkWhileReadPitfalls1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkWhileReadPitfalls String
"while read foo; do ssh $foo uptime; done < file"
prop_checkWhileReadPitfalls2 :: Bool
prop_checkWhileReadPitfalls2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkWhileReadPitfalls String
"while read -u 3 foo; do ssh $foo uptime; done 3< file"
prop_checkWhileReadPitfalls3 :: Bool
prop_checkWhileReadPitfalls3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkWhileReadPitfalls String
"while true; do ssh host uptime; done"
prop_checkWhileReadPitfalls4 :: Bool
prop_checkWhileReadPitfalls4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkWhileReadPitfalls String
"while read foo; do ssh $foo hostname < /dev/null; done"
prop_checkWhileReadPitfalls5 :: Bool
prop_checkWhileReadPitfalls5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkWhileReadPitfalls String
"while read foo; do echo ls | ssh $foo; done"
prop_checkWhileReadPitfalls6 :: Bool
prop_checkWhileReadPitfalls6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkWhileReadPitfalls String
"while read foo <&3; do ssh $foo; done 3< foo"
prop_checkWhileReadPitfalls7 :: Bool
prop_checkWhileReadPitfalls7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkWhileReadPitfalls String
"while read foo; do if true; then ssh $foo uptime; fi; done < file"
prop_checkWhileReadPitfalls8 :: Bool
prop_checkWhileReadPitfalls8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkWhileReadPitfalls String
"while read foo; do ssh -n $foo uptime; done < file"
prop_checkWhileReadPitfalls9 :: Bool
prop_checkWhileReadPitfalls9 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkWhileReadPitfalls String
"while read foo; do ffmpeg -i foo.mkv bar.mkv -an; done"
prop_checkWhileReadPitfalls10 :: Bool
prop_checkWhileReadPitfalls10 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkWhileReadPitfalls String
"while read foo; do mplayer foo.ogv > file; done"
prop_checkWhileReadPitfalls11 :: Bool
prop_checkWhileReadPitfalls11 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkWhileReadPitfalls String
"while read foo; do mplayer foo.ogv <<< q; done"
prop_checkWhileReadPitfalls12 :: Bool
prop_checkWhileReadPitfalls12 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkWhileReadPitfalls String
"while read foo\ndo\nmplayer foo.ogv << EOF\nq\nEOF\ndone"
prop_checkWhileReadPitfalls13 :: Bool
prop_checkWhileReadPitfalls13 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkWhileReadPitfalls String
"while read foo; do x=$(ssh host cmd); done"
prop_checkWhileReadPitfalls14 :: Bool
prop_checkWhileReadPitfalls14 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkWhileReadPitfalls String
"while read foo; do echo $(ssh host cmd) < /dev/null; done"
prop_checkWhileReadPitfalls15 :: Bool
prop_checkWhileReadPitfalls15 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkWhileReadPitfalls String
"while read foo; do ssh $foo cmd & done"

checkWhileReadPitfalls :: Parameters -> Token -> Writer [TokenComment] ()
checkWhileReadPitfalls Parameters
params (T_WhileExpression Id
id [Token
command] [Token]
contents)
        | Token -> Bool
isStdinReadCommand Token
command =
    forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> Writer [TokenComment] ()
checkMuncher [Token]
contents
  where
    -- Map of munching commands to a function that checks if the flags should exclude it
    munchers :: Map
  String (String -> Token -> Bool, String -> Token -> Fix, String)
munchers = forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList [
        (String
"ssh", (String -> Token -> Bool
hasFlag, String -> Token -> Fix
addFlag, String
"-n")),
        (String
"ffmpeg", (String -> Token -> Bool
hasArgument, String -> Token -> Fix
addFlag, String
"-nostdin")),
        (String
"mplayer", (String -> Token -> Bool
hasArgument, String -> Token -> Fix
addFlag, String
"-noconsolecontrols")),
        (String
"HandBrakeCLI", (\String
_ Token
_ -> Bool
False, String -> Token -> Fix
addRedirect, String
"< /dev/null"))
        ]
    -- Use flag parsing, e.g. "-an" -> "a", "n"
    hasFlag :: String -> Token -> Bool
hasFlag (Char
'-':String
flag) = forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
elem String
flag forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a -> b) -> [a] -> [b]
map forall a b. (a, b) -> b
snd forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [(Token, String)]
getAllFlags
    -- Simple string match, e.g. "-an" -> "-an"
    hasArgument :: String -> Token -> Bool
hasArgument String
arg = forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
elem String
arg forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe Token -> Maybe String
getLiteralString forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. HasCallStack => Maybe a -> a
fromJust forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> Maybe [Token]
getCommandArgv
    addFlag :: String -> Token -> Fix
addFlag String
string Token
cmd = [Replacement] -> Fix
fixWith [Id -> Parameters -> Code -> String -> Replacement
replaceEnd (Token -> Id
getId forall a b. (a -> b) -> a -> b
$ Token -> Token
getCommandTokenOrThis Token
cmd) Parameters
params Code
0 (Char
' 'forall a. a -> [a] -> [a]
:String
string)]
    addRedirect :: String -> Token -> Fix
addRedirect String
string Token
cmd = [Replacement] -> Fix
fixWith [Id -> Parameters -> Code -> String -> Replacement
replaceEnd (Token -> Id
getId Token
cmd) Parameters
params Code
0 (Char
' 'forall a. a -> [a] -> [a]
:String
string)]

    isStdinReadCommand :: Token -> Bool
isStdinReadCommand (T_Pipeline Id
_ [Token]
_ [T_Redirecting Id
id [Token]
redirs Token
cmd]) =
        let plaintext :: [String]
plaintext = Token -> [String]
oversimplify Token
cmd
        in forall {a}. a -> [a] -> a
headOrDefault String
"" [String]
plaintext forall a. Eq a => a -> a -> Bool
== String
"read"
            Bool -> Bool -> Bool
&& (String
"-u" forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [String]
plaintext)
            Bool -> Bool -> Bool
&& Bool -> Bool
not (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
stdinRedirect [Token]
redirs)
    isStdinReadCommand Token
_ = Bool
False

    checkMuncher :: Token -> Writer [TokenComment] ()
    checkMuncher :: Token -> Writer [TokenComment] ()
checkMuncher (T_Pipeline Id
_ [Token]
_ (T_Redirecting Id
_ [Token]
redirs Token
cmd:[Token]
_)) = do
        -- Check command substitutions regardless of the command
        case Token
cmd of
            T_SimpleCommand Id
_ [Token]
vars [Token]
args ->
                forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> Writer [TokenComment] ()
checkMuncher forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Token -> [[Token]]
getCommandSequences forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Token -> [Token]
getWords forall a b. (a -> b) -> a -> b
$ [Token]
vars forall a. [a] -> [a] -> [a]
++ [Token]
args
            Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()

        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
stdinRedirect [Token]
redirs) forall a b. (a -> b) -> a -> b
$ do
            -- Recurse into ifs/loops/groups/etc if this doesn't redirect
            forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> Writer [TokenComment] ()
checkMuncher forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ Token -> [[Token]]
getCommandSequences Token
cmd

            -- Check the actual command
            forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
                String
name <- Token -> Maybe String
getCommandBasename Token
cmd
                (String -> Token -> Bool
check, String -> Token -> Fix
fix, String
flag) <- forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup String
name Map
  String (String -> Token -> Bool, String -> Token -> Fix, String)
munchers
                forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ Bool -> Bool
not (String -> Token -> Bool
check String
flag Token
cmd)

                forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ do
                    forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info Id
id Code
2095 forall a b. (a -> b) -> a -> b
$
                        String
name forall a. [a] -> [a] -> [a]
++ String
" may swallow stdin, preventing this loop from working properly."
                    forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> Fix -> m ()
warnWithFix (Token -> Id
getId Token
cmd) Code
2095
                        (String
"Use " forall a. [a] -> [a] -> [a]
++ String
name forall a. [a] -> [a] -> [a]
++ String
" " forall a. [a] -> [a] -> [a]
++ String
flag forall a. [a] -> [a] -> [a]
++ String
" to prevent " forall a. [a] -> [a] -> [a]
++ String
name forall a. [a] -> [a] -> [a]
++ String
" from swallowing stdin.")
                        (String -> Token -> Fix
fix String
flag Token
cmd)
    checkMuncher (T_Backgrounded Id
_ Token
t) = Token -> Writer [TokenComment] ()
checkMuncher Token
t
    checkMuncher Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

    stdinRedirect :: Token -> Bool
stdinRedirect (T_FdRedirect Id
_ String
fd Token
op)
        | String
fd forall a. Eq a => a -> a -> Bool
== String
"0" = Bool
True
        | String
fd forall a. Eq a => a -> a -> Bool
== String
"" =
            case Token
op of
                T_IoFile Id
_ (T_Less Id
_) Token
_ -> Bool
True
                T_IoDuplicate Id
_ (T_LESSAND Id
_) String
_ -> Bool
True
                T_HereString Id
_ Token
_ -> Bool
True
                T_HereDoc {} -> Bool
True
                Token
_ -> Bool
False
    stdinRedirect Token
_ = Bool
False

    getWords :: Token -> [Token]
getWords Token
t =
        case Token
t of
            T_Assignment Id
_ AssignmentMode
_ String
_ [Token]
_ Token
x -> Token -> [Token]
getWordParts Token
x
            Token
_ -> Token -> [Token]
getWordParts Token
t
checkWhileReadPitfalls Parameters
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkPrefixAssign1 :: Bool
prop_checkPrefixAssign1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkPrefixAssignmentReference String
"var=foo echo $var"
prop_checkPrefixAssign2 :: Bool
prop_checkPrefixAssign2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkPrefixAssignmentReference String
"var=$(echo $var) cmd"
checkPrefixAssignmentReference :: Parameters -> Token -> m ()
checkPrefixAssignmentReference Parameters
params t :: Token
t@(T_DollarBraced Id
id Bool
_ Token
value) =
    forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
[Token] -> m ()
check [Token]
path
  where
    name :: String
name = String -> String
getBracedReference forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
value
    path :: [Token]
path = Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) Token
t
    idPath :: [Id]
idPath = forall a b. (a -> b) -> [a] -> [b]
map Token -> Id
getId [Token]
path

    check :: [Token] -> m ()
check [] = forall (m :: * -> *) a. Monad m => a -> m a
return ()
    check (Token
t:[Token]
rest) =
        case Token
t of
            T_SimpleCommand Id
_ [Token]
vars (Token
_:[Token]
_) -> forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkVar [Token]
vars
            Token
_ -> [Token] -> m ()
check [Token]
rest
    checkVar :: Token -> m ()
checkVar (T_Assignment Id
aId AssignmentMode
mode String
aName [] Token
value) |
            String
aName forall a. Eq a => a -> a -> Bool
== String
name Bool -> Bool -> Bool
&& (Id
aId forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [Id]
idPath) = do
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
aId Code
2097 String
"This assignment is only seen by the forked process."
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2098 String
"This expansion will not see the mentioned assignment."
    checkVar Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

checkPrefixAssignmentReference Parameters
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkCharRangeGlob1 :: Bool
prop_checkCharRangeGlob1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCharRangeGlob String
"ls *[:digit:].jpg"
prop_checkCharRangeGlob2 :: Bool
prop_checkCharRangeGlob2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCharRangeGlob String
"ls *[[:digit:]].jpg"
prop_checkCharRangeGlob3 :: Bool
prop_checkCharRangeGlob3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCharRangeGlob String
"ls [10-15]"
prop_checkCharRangeGlob4 :: Bool
prop_checkCharRangeGlob4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCharRangeGlob String
"ls [a-zA-Z]"
prop_checkCharRangeGlob5 :: Bool
prop_checkCharRangeGlob5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCharRangeGlob String
"tr -d [aa]" -- tr has 2060
prop_checkCharRangeGlob6 :: Bool
prop_checkCharRangeGlob6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCharRangeGlob String
"[[ $x == [!!]* ]]"
prop_checkCharRangeGlob7 :: Bool
prop_checkCharRangeGlob7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCharRangeGlob String
"[[ -v arr[keykey] ]]"
prop_checkCharRangeGlob8 :: Bool
prop_checkCharRangeGlob8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCharRangeGlob String
"[[ arr[keykey] -gt 1 ]]"
prop_checkCharRangeGlob9 :: Bool
prop_checkCharRangeGlob9 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCharRangeGlob String
"read arr[keykey]" -- tr has 2313
checkCharRangeGlob :: Parameters -> Token -> m ()
checkCharRangeGlob Parameters
p t :: Token
t@(T_Glob Id
id String
str) |
  String -> Bool
isCharClass String
str Bool -> Bool -> Bool
&& Bool -> Bool
not Bool
isIgnoredCommand Bool -> Bool -> Bool
&& Bool -> Bool
not (Token -> Bool
isDereferenced Token
t) =
    if String
":" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
contents
        Bool -> Bool -> Bool
&& String
":" forall a. Eq a => [a] -> [a] -> Bool
`isSuffixOf` String
contents
        Bool -> Bool -> Bool
&& String
contents forall a. Eq a => a -> a -> Bool
/= String
":"
    then forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2101 String
"Named class needs outer [], e.g. [[:digit:]]."
    else
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Char
'[' forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` String
contents Bool -> Bool -> Bool
&& Bool
hasDupes) forall a b. (a -> b) -> a -> b
$
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info Id
id Code
2102 String
"Ranges can only match single chars (mentioned due to duplicates)."
  where
    isCharClass :: String -> Bool
isCharClass String
str = String
"[" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
str Bool -> Bool -> Bool
&& String
"]" forall a. Eq a => [a] -> [a] -> Bool
`isSuffixOf` String
str
    contents :: String
contents = String -> String
dropNegation forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Int -> [a] -> [a]
drop Int
1 forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Int -> [a] -> [a]
take (forall (t :: * -> *) a. Foldable t => t a -> Int
length String
str forall a. Num a => a -> a -> a
- Int
1) forall a b. (a -> b) -> a -> b
$ String
str
    hasDupes :: Bool
hasDupes = forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any ((forall a. Ord a => a -> a -> Bool
>Int
1) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: * -> *) a. Foldable t => t a -> Int
length) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Eq a => [a] -> [[a]]
group forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Ord a => [a] -> [a]
sort forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. (a -> Bool) -> [a] -> [a]
filter (forall a. Eq a => a -> a -> Bool
/= Char
'-') forall a b. (a -> b) -> a -> b
$ String
contents
    dropNegation :: String -> String
dropNegation String
s =
        case String
s of
            Char
'!':String
rest -> String
rest
            Char
'^':String
rest -> String
rest
            String
x -> String
x

    isIgnoredCommand :: Bool
isIgnoredCommand = forall a. a -> Maybe a -> a
fromMaybe Bool
False forall a b. (a -> b) -> a -> b
$ do
        Token
cmd <- Map Id Token -> Token -> Maybe Token
getClosestCommand (Parameters -> Map Id Token
parentMap Parameters
p) Token
t
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Token -> (String -> Bool) -> Bool
isCommandMatch Token
cmd (forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"tr", String
"read"])

    -- Check if this is a dereferencing context like [[ -v array[operandhere] ]]
    isDereferenced :: Token -> Bool
isDereferenced = forall a. a -> Maybe a -> a
fromMaybe Bool
False forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, MonadPlus m) =>
t (m a) -> m a
msum forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a -> b) -> [a] -> [b]
map Token -> Maybe Bool
isDereferencingOp forall b c a. (b -> c) -> (a -> b) -> a -> c
. Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
p)
    isDereferencingOp :: Token -> Maybe Bool
isDereferencingOp Token
t =
        case Token
t of
            TC_Binary Id
_ ConditionType
DoubleBracket String
str Token
_ Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ String -> Bool
isDereferencingBinaryOp String
str
            TC_Unary Id
_ ConditionType
_ String
str Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ String
str forall a. Eq a => a -> a -> Bool
== String
"-v"
            T_SimpleCommand {} -> forall (m :: * -> *) a. Monad m => a -> m a
return Bool
False
            Token
_ -> forall a. Maybe a
Nothing
checkCharRangeGlob Parameters
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()



prop_checkCdAndBack1 :: Bool
prop_checkCdAndBack1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCdAndBack String
"for f in *; do cd $f; git pull; cd ..; done"
prop_checkCdAndBack2 :: Bool
prop_checkCdAndBack2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCdAndBack String
"for f in *; do cd $f || continue; git pull; cd ..; done"
prop_checkCdAndBack3 :: Bool
prop_checkCdAndBack3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCdAndBack String
"while [[ $PWD != / ]]; do cd ..; done"
prop_checkCdAndBack4 :: Bool
prop_checkCdAndBack4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCdAndBack String
"cd $tmp; foo; cd -"
prop_checkCdAndBack5 :: Bool
prop_checkCdAndBack5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCdAndBack String
"cd ..; foo; cd .."
prop_checkCdAndBack6 :: Bool
prop_checkCdAndBack6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCdAndBack String
"for dir in */; do cd \"$dir\"; some_cmd; cd ..; done"
prop_checkCdAndBack7 :: Bool
prop_checkCdAndBack7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCdAndBack String
"set -e; for dir in */; do cd \"$dir\"; some_cmd; cd ..; done"
prop_checkCdAndBack8 :: Bool
prop_checkCdAndBack8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCdAndBack String
"cd tmp\nfoo\n# shellcheck disable=SC2103\ncd ..\n"
checkCdAndBack :: Parameters -> Token -> f ()
checkCdAndBack Parameters
params Token
t =
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Parameters -> Bool
hasSetE Parameters
params) forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
[Token] -> m ()
doList forall a b. (a -> b) -> a -> b
$ Token -> [[Token]]
getCommandSequences Token
t
  where
    isCdRevert :: Token -> Bool
isCdRevert Token
t =
        case Token -> [String]
oversimplify Token
t of
            [String
_, String
p] -> String
p forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"..", String
"-"]
            [String]
_ -> Bool
False

    getCandidate :: Token -> Maybe Token
getCandidate (T_Annotation Id
_ [Annotation]
_ Token
x) = Token -> Maybe Token
getCandidate Token
x
    getCandidate (T_Pipeline Id
id [Token]
_ [Token
x]) | Token
x Token -> String -> Bool
`isCommand` String
"cd" = forall (m :: * -> *) a. Monad m => a -> m a
return Token
x
    getCandidate Token
_ = forall a. Maybe a
Nothing

    findCdPair :: [Token] -> Maybe Id
findCdPair [Token]
list =
        case [Token]
list of
            (Token
a:Token
b:[Token]
rest) ->
                if Token -> Bool
isCdRevert Token
b Bool -> Bool -> Bool
&& Bool -> Bool
not (Token -> Bool
isCdRevert Token
a)
                then forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Token -> Id
getId Token
b
                else [Token] -> Maybe Id
findCdPair (Token
bforall a. a -> [a] -> [a]
:[Token]
rest)
            [Token]
_ -> forall a. Maybe a
Nothing

    doList :: [Token] -> m ()
doList [Token]
list = forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
        Id
cd <- [Token] -> Maybe Id
findCdPair forall a b. (a -> b) -> a -> b
$ forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe Token -> Maybe Token
getCandidate [Token]
list
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info Id
cd Code
2103 String
"Use a ( subshell ) to avoid having to cd back."

prop_checkLoopKeywordScope1 :: Bool
prop_checkLoopKeywordScope1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkLoopKeywordScope String
"continue 2"
prop_checkLoopKeywordScope2 :: Bool
prop_checkLoopKeywordScope2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkLoopKeywordScope String
"for f; do ( break; ); done"
prop_checkLoopKeywordScope3 :: Bool
prop_checkLoopKeywordScope3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkLoopKeywordScope String
"if true; then continue; fi"
prop_checkLoopKeywordScope4 :: Bool
prop_checkLoopKeywordScope4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkLoopKeywordScope String
"while true; do break; done"
prop_checkLoopKeywordScope5 :: Bool
prop_checkLoopKeywordScope5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkLoopKeywordScope String
"if true; then break; fi"
prop_checkLoopKeywordScope6 :: Bool
prop_checkLoopKeywordScope6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkLoopKeywordScope String
"while true; do true | { break; }; done"
prop_checkLoopKeywordScope7 :: Bool
prop_checkLoopKeywordScope7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkLoopKeywordScope String
"#!/bin/ksh\nwhile true; do true | { break; }; done"
checkLoopKeywordScope :: Parameters -> Token -> m ()
checkLoopKeywordScope Parameters
params Token
t |
        Maybe String
name forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` forall a b. (a -> b) -> [a] -> [b]
map forall a. a -> Maybe a
Just [String
"continue", String
"break"] =
    if Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
isLoop [Token]
path
    then if forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
isFunction forall a b. (a -> b) -> a -> b
$ forall a. Int -> [a] -> [a]
take Int
1 [Token]
path
        -- breaking at a source/function invocation is an abomination. Let's ignore it.
        then forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
t) Code
2104 forall a b. (a -> b) -> a -> b
$ String
"In functions, use return instead of " forall a. [a] -> [a] -> [a]
++ forall a. HasCallStack => Maybe a -> a
fromJust Maybe String
name forall a. [a] -> [a] -> [a]
++ String
"."
        else forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
t) Code
2105 forall a b. (a -> b) -> a -> b
$ forall a. HasCallStack => Maybe a -> a
fromJust Maybe String
name forall a. [a] -> [a] -> [a]
++ String
" is only valid in loops."
    else case forall a b. (a -> b) -> [a] -> [b]
map Token -> Maybe String
subshellType forall a b. (a -> b) -> a -> b
$ forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> Bool
isFunction) [Token]
path of
        Just String
str:[Maybe String]
_ -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
t) Code
2106 forall a b. (a -> b) -> a -> b
$
            String
"This only exits the subshell caused by the " forall a. [a] -> [a] -> [a]
++ String
str forall a. [a] -> [a] -> [a]
++ String
"."
        [Maybe String]
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    name :: Maybe String
name = Token -> Maybe String
getCommandName Token
t
    path :: [Token]
path = let p :: [Token]
p = Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) Token
t in forall a. (a -> Bool) -> [a] -> [a]
filter Token -> Bool
relevant [Token]
p
    subshellType :: Token -> Maybe String
subshellType Token
t = case Parameters -> Token -> Scope
leadType Parameters
params Token
t of
        Scope
NoneScope -> forall a. Maybe a
Nothing
        SubshellScope String
str -> forall (m :: * -> *) a. Monad m => a -> m a
return String
str
    relevant :: Token -> Bool
relevant Token
t = Token -> Bool
isLoop Token
t Bool -> Bool -> Bool
|| Token -> Bool
isFunction Token
t Bool -> Bool -> Bool
|| forall a. Maybe a -> Bool
isJust (Token -> Maybe String
subshellType Token
t)
checkLoopKeywordScope Parameters
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkFunctionDeclarations1 :: Bool
prop_checkFunctionDeclarations1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkFunctionDeclarations String
"#!/bin/ksh\nfunction foo() { command foo --lol \"$@\"; }"
prop_checkFunctionDeclarations2 :: Bool
prop_checkFunctionDeclarations2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkFunctionDeclarations String
"#!/bin/dash\nfunction foo { lol; }"
prop_checkFunctionDeclarations3 :: Bool
prop_checkFunctionDeclarations3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkFunctionDeclarations String
"foo() { echo bar; }"
checkFunctionDeclarations :: Parameters -> Token -> m ()
checkFunctionDeclarations Parameters
params
        (T_Function Id
id (FunctionKeyword Bool
hasKeyword) (FunctionParentheses Bool
hasParens) String
_ Token
_) =
    case Parameters -> Shell
shellType Parameters
params of
        Shell
Bash -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
        Shell
Ksh ->
            forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Bool
hasKeyword Bool -> Bool -> Bool
&& Bool
hasParens) forall a b. (a -> b) -> a -> b
$
                forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2111 String
"ksh does not allow 'function' keyword and '()' at the same time."
        Shell
Dash -> m ()
forSh
        Shell
Sh   -> m ()
forSh

    where
        forSh :: m ()
forSh = do
            forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Bool
hasKeyword Bool -> Bool -> Bool
&& Bool
hasParens) forall a b. (a -> b) -> a -> b
$
                forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2112 String
"'function' keyword is non-standard. Delete it."
            forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Bool
hasKeyword Bool -> Bool -> Bool
&& Bool -> Bool
not Bool
hasParens) forall a b. (a -> b) -> a -> b
$
                forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2113 String
"'function' keyword is non-standard. Use 'foo()' instead of 'function foo'."
checkFunctionDeclarations Parameters
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()



prop_checkStderrPipe1 :: Bool
prop_checkStderrPipe1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkStderrPipe String
"#!/bin/ksh\nfoo |& bar"
prop_checkStderrPipe2 :: Bool
prop_checkStderrPipe2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkStderrPipe String
"#!/bin/bash\nfoo |& bar"
checkStderrPipe :: Parameters -> Token -> m ()
checkStderrPipe Parameters
params =
    case Parameters -> Shell
shellType Parameters
params of
        Shell
Ksh -> forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
match
        Shell
_ -> forall a b. a -> b -> a
const forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    match :: Token -> m ()
match (T_Pipe Id
id String
"|&") =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2118 String
"Ksh does not support |&. Use 2>&1 |."
    match Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkUnpassedInFunctions1 :: Bool
prop_checkUnpassedInFunctions1 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions String
"foo() { echo $1; }; foo"
prop_checkUnpassedInFunctions2 :: Bool
prop_checkUnpassedInFunctions2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions String
"foo() { echo $1; };"
prop_checkUnpassedInFunctions3 :: Bool
prop_checkUnpassedInFunctions3 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions String
"foo() { echo $lol; }; foo"
prop_checkUnpassedInFunctions4 :: Bool
prop_checkUnpassedInFunctions4 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions String
"foo() { echo $0; }; foo"
prop_checkUnpassedInFunctions5 :: Bool
prop_checkUnpassedInFunctions5 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions String
"foo() { echo $1; }; foo 'lol'; foo"
prop_checkUnpassedInFunctions6 :: Bool
prop_checkUnpassedInFunctions6 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions String
"foo() { set -- *; echo $1; }; foo"
prop_checkUnpassedInFunctions7 :: Bool
prop_checkUnpassedInFunctions7 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions String
"foo() { echo $1; }; foo; foo;"
prop_checkUnpassedInFunctions8 :: Bool
prop_checkUnpassedInFunctions8 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions String
"foo() { echo $((1)); }; foo;"
prop_checkUnpassedInFunctions9 :: Bool
prop_checkUnpassedInFunctions9 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions String
"foo() { echo $(($b)); }; foo;"
prop_checkUnpassedInFunctions10 :: Bool
prop_checkUnpassedInFunctions10 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions String
"foo() { echo $!; }; foo;"
prop_checkUnpassedInFunctions11 :: Bool
prop_checkUnpassedInFunctions11 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions String
"foo() { bar() { echo $1; }; bar baz; }; foo;"
prop_checkUnpassedInFunctions12 :: Bool
prop_checkUnpassedInFunctions12 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions String
"foo() { echo ${!var*}; }; foo;"
prop_checkUnpassedInFunctions13 :: Bool
prop_checkUnpassedInFunctions13 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions String
"# shellcheck disable=SC2120\nfoo() { echo $1; }\nfoo\n"
prop_checkUnpassedInFunctions14 :: Bool
prop_checkUnpassedInFunctions14 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions String
"foo() { echo $#; }; foo"
checkUnpassedInFunctions :: Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions Parameters
params Token
root =
    forall w a. Writer w a -> w
execWriter forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
[(String, Bool, Token)] -> f ()
warnForGroup [[(String, Bool, Token)]]
referenceGroups
  where
    functionMap :: Map.Map String Token
    functionMap :: Map String Token
functionMap = forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList forall a b. (a -> b) -> a -> b
$
        forall a b. (a -> b) -> [a] -> [b]
map (\t :: Token
t@(T_Function Id
_ FunctionKeyword
_ FunctionParentheses
_ String
name Token
_) -> (String
name,Token
t)) [Token]
functions
    functions :: [Token]
functions = forall w a. Writer w a -> w
execWriter forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> Token -> m Token
doAnalysis (forall w (m :: * -> *). MonadWriter w m => w -> m ()
tell forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Maybe a -> [a]
maybeToList forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> Maybe Token
findFunction) Token
root

    findFunction :: Token -> Maybe Token
findFunction t :: Token
t@(T_Function Id
id FunctionKeyword
_ FunctionParentheses
_ String
name Token
body)
        | forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Token -> StackData -> Bool
isPositionalReference Token
t) [StackData]
flow Bool -> Bool -> Bool
&& Bool -> Bool
not (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any StackData -> Bool
isPositionalAssignment [StackData]
flow)
        = forall (m :: * -> *) a. Monad m => a -> m a
return Token
t
        where flow :: [StackData]
flow = Parameters -> Token -> [StackData]
getVariableFlow Parameters
params Token
body
    findFunction Token
_ = forall a. Maybe a
Nothing

    isPositionalAssignment :: StackData -> Bool
isPositionalAssignment StackData
x =
        case StackData
x of
            Assignment (Token
_, Token
_, String
str, DataType
_) -> String -> Bool
isPositional String
str
            StackData
_ -> Bool
False
    isPositionalReference :: Token -> StackData -> Bool
isPositionalReference Token
function StackData
x =
        case StackData
x of
            Reference (Token
_, Token
t, String
str) -> String -> Bool
isPositional String
str Bool -> Bool -> Bool
&& Token
t Token -> Token -> Bool
`isDirectChildOf` Token
function
            StackData
_ -> Bool
False

    isDirectChildOf :: Token -> Token -> Bool
isDirectChildOf Token
child Token
parent = forall a. a -> Maybe a -> a
fromMaybe Bool
False forall a b. (a -> b) -> a -> b
$ do
        Token
function <- forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (\Token
x -> case Token
x of
            T_Function {} -> Bool
True
            T_Script {} -> Bool
True  -- for sourced files
            Token
_ -> Bool
False) forall a b. (a -> b) -> a -> b
$
                Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) Token
child
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Token -> Id
getId Token
parent forall a. Eq a => a -> a -> Bool
== Token -> Id
getId Token
function

    referenceList :: [(String, Bool, Token)]
    referenceList :: [(String, Bool, Token)]
referenceList = forall w a. Writer w a -> w
execWriter forall a b. (a -> b) -> a -> b
$
        forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> Token -> m Token
doAnalysis (forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> Maybe (WriterT [(String, Bool, Token)] Identity ())
checkCommand) Token
root
    checkCommand :: Token -> Maybe (Writer [(String, Bool, Token)] ())
    checkCommand :: Token -> Maybe (WriterT [(String, Bool, Token)] Identity ())
checkCommand t :: Token
t@(T_SimpleCommand Id
_ [Token]
_ (Token
cmd:[Token]
args)) = do
        String
str <- Token -> Maybe String
getLiteralString Token
cmd
        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ forall k a. Ord k => k -> Map k a -> Bool
Map.member String
str Map String Token
functionMap
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall w (m :: * -> *). MonadWriter w m => w -> m ()
tell [(String
str, forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Token]
args, Token
t)]
    checkCommand Token
_ = forall a. Maybe a
Nothing

    isPositional :: String -> Bool
isPositional String
str = String
str forall a. Eq a => a -> a -> Bool
== String
"*" Bool -> Bool -> Bool
|| String
str forall a. Eq a => a -> a -> Bool
== String
"@" Bool -> Bool -> Bool
|| String
str forall a. Eq a => a -> a -> Bool
== String
"#"
        Bool -> Bool -> Bool
|| (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isDigit String
str Bool -> Bool -> Bool
&& String
str forall a. Eq a => a -> a -> Bool
/= String
"0" Bool -> Bool -> Bool
&& String
str forall a. Eq a => a -> a -> Bool
/= String
"")

    isArgumentless :: (a, b, c) -> b
isArgumentless (a
_, b
b, c
_) = b
b
    referenceGroups :: [[(String, Bool, Token)]]
referenceGroups = forall k a. Map k a -> [a]
Map.elems forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr forall {k} {b} {c}.
Ord k =>
(k, b, c) -> Map k [(k, b, c)] -> Map k [(k, b, c)]
updateWith forall k a. Map k a
Map.empty [(String, Bool, Token)]
referenceList
    updateWith :: (k, b, c) -> Map k [(k, b, c)] -> Map k [(k, b, c)]
updateWith x :: (k, b, c)
x@(k
name, b
_, c
_) = forall k a. Ord k => (a -> a -> a) -> k -> a -> Map k a -> Map k a
Map.insertWith forall a. [a] -> [a] -> [a]
(++) k
name [(k, b, c)
x]

    warnForGroup :: [(String, Bool, Token)] -> f ()
warnForGroup [(String, Bool, Token)]
group =
        -- Allow ignoring SC2120 on the function to ignore all calls
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all forall {a} {b} {c}. (a, b, c) -> b
isArgumentless [(String, Bool, Token)]
group Bool -> Bool -> Bool
&& Bool -> Bool
not Bool
ignoring) forall a b. (a -> b) -> a -> b
$ do
            forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *} {b}.
MonadWriter [TokenComment] m =>
(String, b, Token) -> m ()
suggestParams [(String, Bool, Token)]
group
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Token -> String -> m ()
warnForDeclaration Token
func String
name
        where (String
name, Token
func) = forall {b} {c}. [(String, b, c)] -> (String, Token)
getFunction [(String, Bool, Token)]
group
              ignoring :: Bool
ignoring = Parameters -> Code -> Token -> Bool
shouldIgnoreCode Parameters
params Code
2120 Token
func

    suggestParams :: (String, b, Token) -> m ()
suggestParams (String
name, b
_, Token
thing) =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
thing) Code
2119 forall a b. (a -> b) -> a -> b
$
            String
"Use " forall a. [a] -> [a] -> [a]
++ (String -> String
e4m String
name) forall a. [a] -> [a] -> [a]
++ String
" \"$@\" if function's $1 should mean script's $1."
    warnForDeclaration :: Token -> String -> m ()
warnForDeclaration Token
func String
name =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
func) Code
2120 forall a b. (a -> b) -> a -> b
$
            String
name forall a. [a] -> [a] -> [a]
++ String
" references arguments, but none are ever passed."

    getFunction :: [(String, b, c)] -> (String, Token)
getFunction ((String
name, b
_, c
_):[(String, b, c)]
_) =
        (String
name, Map String Token
functionMap forall k a. Ord k => Map k a -> k -> a
Map.! String
name)


prop_checkOverridingPath1 :: Bool
prop_checkOverridingPath1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOverridingPath String
"PATH=\"$var/$foo\""
prop_checkOverridingPath2 :: Bool
prop_checkOverridingPath2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOverridingPath String
"PATH=\"mydir\""
prop_checkOverridingPath3 :: Bool
prop_checkOverridingPath3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOverridingPath String
"PATH=/cow/foo"
prop_checkOverridingPath4 :: Bool
prop_checkOverridingPath4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOverridingPath String
"PATH=/cow/foo/bin"
prop_checkOverridingPath5 :: Bool
prop_checkOverridingPath5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOverridingPath String
"PATH='/bin:/sbin'"
prop_checkOverridingPath6 :: Bool
prop_checkOverridingPath6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOverridingPath String
"PATH=\"$var/$foo\" cmd"
prop_checkOverridingPath7 :: Bool
prop_checkOverridingPath7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOverridingPath String
"PATH=$OLDPATH"
prop_checkOverridingPath8 :: Bool
prop_checkOverridingPath8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOverridingPath String
"PATH=$PATH:/stuff"
checkOverridingPath :: p -> Token -> m ()
checkOverridingPath p
_ (T_SimpleCommand Id
_ [Token]
vars []) =
    forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkVar [Token]
vars
  where
    checkVar :: Token -> m ()
checkVar (T_Assignment Id
id AssignmentMode
Assign String
"PATH" [] Token
word)
        | Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` String
string) [String
"/bin", String
"/sbin" ] = do
            forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Char
'/' forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
string Bool -> Bool -> Bool
&& Char
':' forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` String
string) forall a b. (a -> b) -> a -> b
$ forall {m :: * -> *}. MonadWriter [TokenComment] m => Id -> m ()
notify Id
id
            forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isLiteral Token
word Bool -> Bool -> Bool
&& Char
':' forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` String
string Bool -> Bool -> Bool
&& Char
'/' forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` String
string) forall a b. (a -> b) -> a -> b
$ forall {m :: * -> *}. MonadWriter [TokenComment] m => Id -> m ()
notify Id
id
        where string :: String
string = forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
word
    checkVar Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()
    notify :: Id -> m ()
notify Id
id = forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2123 String
"PATH is the shell search path. Use another name."
checkOverridingPath p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkTildeInPath1 :: Bool
prop_checkTildeInPath1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTildeInPath String
"PATH=\"$PATH:~/bin\""
prop_checkTildeInPath2 :: Bool
prop_checkTildeInPath2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTildeInPath String
"PATH='~foo/bin'"
prop_checkTildeInPath3 :: Bool
prop_checkTildeInPath3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTildeInPath String
"PATH=~/bin"
checkTildeInPath :: p -> Token -> m ()
checkTildeInPath p
_ (T_SimpleCommand Id
_ [Token]
vars [Token]
_) =
    forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkVar [Token]
vars
  where
    checkVar :: Token -> m ()
checkVar (T_Assignment Id
id AssignmentMode
Assign String
"PATH" [] (T_NormalWord Id
_ [Token]
parts))
        | forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (\Token
x -> Token -> Bool
isQuoted Token
x Bool -> Bool -> Bool
&& Token -> Bool
hasTilde Token
x) [Token]
parts =
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2147 String
"Literal tilde in PATH works poorly across programs."
    checkVar Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

    hasTilde :: Token -> Bool
hasTilde Token
t = Char
'~' forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` Token -> String
onlyLiteralString Token
t
    isQuoted :: Token -> Bool
isQuoted T_DoubleQuoted {} = Bool
True
    isQuoted T_SingleQuoted {} = Bool
True
    isQuoted Token
_ = Bool
False
checkTildeInPath p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkUnsupported3 :: Bool
prop_checkUnsupported3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnsupported String
"#!/bin/sh\ncase foo in bar) baz ;& esac"
prop_checkUnsupported4 :: Bool
prop_checkUnsupported4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnsupported String
"#!/bin/ksh\ncase foo in bar) baz ;;& esac"
prop_checkUnsupported5 :: Bool
prop_checkUnsupported5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnsupported String
"#!/bin/bash\necho \"${ ls; }\""
checkUnsupported :: Parameters -> Token -> f ()
checkUnsupported Parameters
params Token
t =
    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Shell]
support Bool -> Bool -> Bool
|| (Parameters -> Shell
shellType Parameters
params forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Shell]
support)) forall a b. (a -> b) -> a -> b
$
        forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
String -> f ()
report String
name
 where
    (String
name, [Shell]
support) = Token -> (String, [Shell])
shellSupport Token
t
    report :: String -> m ()
report String
s = forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
t) Code
2127 forall a b. (a -> b) -> a -> b
$
        String
"To use " forall a. [a] -> [a] -> [a]
++ String
s forall a. [a] -> [a] -> [a]
++ String
", specify #!/usr/bin/env " forall a. [a] -> [a] -> [a]
++
            (forall a. [a] -> [[a]] -> [a]
intercalate String
" or " forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a -> b) -> [a] -> [b]
map (forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
toLower forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Show a => a -> String
show) forall a b. (a -> b) -> a -> b
$ [Shell]
support)

-- TODO: Move more of these checks here
shellSupport :: Token -> (String, [Shell])
shellSupport Token
t =
  case Token
t of
    T_CaseExpression Id
_ Token
_ [(CaseType, [Token], [Token])]
list -> forall {t :: * -> *}. Foldable t => t CaseType -> (String, [Shell])
forCase (forall a b. (a -> b) -> [a] -> [b]
map (\(CaseType
a,[Token]
_,[Token]
_) -> CaseType
a) [(CaseType, [Token], [Token])]
list)
    T_DollarBraceCommandExpansion {} -> (String
"${ ..; } command expansion", [Shell
Ksh])
    Token
_ -> (String
"", [])
  where
    forCase :: t CaseType -> (String, [Shell])
forCase t CaseType
seps | CaseType
CaseContinue forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` t CaseType
seps = (String
"cases with ;;&", [Shell
Bash])
    forCase t CaseType
seps | CaseType
CaseFallThrough forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` t CaseType
seps = (String
"cases with ;&", [Shell
Bash, Shell
Ksh])
    forCase t CaseType
_ = (String
"", [])


groupWith :: (a -> b) -> [a] -> [[a]]
groupWith a -> b
f = forall a. (a -> a -> Bool) -> [a] -> [[a]]
groupBy (forall a. Eq a => a -> a -> Bool
(==) forall b c a. (b -> b -> c) -> (a -> b) -> a -> a -> c
`on` a -> b
f)

prop_checkMultipleAppends1 :: Bool
prop_checkMultipleAppends1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkMultipleAppends String
"foo >> file; bar >> file; baz >> file;"
prop_checkMultipleAppends2 :: Bool
prop_checkMultipleAppends2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkMultipleAppends String
"foo >> file; bar | grep f >> file; baz >> file;"
prop_checkMultipleAppends3 :: Bool
prop_checkMultipleAppends3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkMultipleAppends String
"foo < file; bar < file; baz < file;"
checkMultipleAppends :: p -> Token -> m ()
checkMultipleAppends p
params Token
t =
    forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
[Token] -> m ()
checkList forall a b. (a -> b) -> a -> b
$ Token -> [[Token]]
getCommandSequences Token
t
  where
    checkList :: [Token] -> m ()
checkList [Token]
list =
        forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *} {a}.
MonadWriter [TokenComment] m =>
[Maybe (a, Id)] -> m ()
checkGroup (forall {b} {a}. Eq b => (a -> b) -> [a] -> [[a]]
groupWith (forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap forall a b. (a, b) -> a
fst) forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map Token -> Maybe (Token, Id)
getTarget [Token]
list)
    checkGroup :: [Maybe (a, Id)] -> m ()
checkGroup (Just (a
_,Id
id):Maybe (a, Id)
_:Maybe (a, Id)
_:[Maybe (a, Id)]
_) =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style Id
id Code
2129
            String
"Consider using { cmd1; cmd2; } >> file instead of individual redirects."
    checkGroup [Maybe (a, Id)]
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()
    getTarget :: Token -> Maybe (Token, Id)
getTarget (T_Annotation Id
_ [Annotation]
_ Token
t) = Token -> Maybe (Token, Id)
getTarget Token
t
    getTarget (T_Pipeline Id
_ [Token]
_ args :: [Token]
args@(Token
_:[Token]
_)) = Token -> Maybe (Token, Id)
getTarget (forall a. [a] -> a
last [Token]
args)
    getTarget (T_Redirecting Id
id [Token]
list Token
_) = do
        Token
file <- forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe Token -> Maybe Token
getAppend [Token]
list forall {a}. [a] -> Int -> Maybe a
!!! Int
0
        forall (m :: * -> *) a. Monad m => a -> m a
return (Token
file, Id
id)
    getTarget Token
_ = forall a. Maybe a
Nothing
    getAppend :: Token -> Maybe Token
getAppend (T_FdRedirect Id
_ String
_ (T_IoFile Id
_ T_DGREAT {} Token
f)) = forall (m :: * -> *) a. Monad m => a -> m a
return Token
f
    getAppend Token
_ = forall a. Maybe a
Nothing


prop_checkSuspiciousIFS1 :: Bool
prop_checkSuspiciousIFS1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSuspiciousIFS String
"IFS=\"\\n\""
prop_checkSuspiciousIFS2 :: Bool
prop_checkSuspiciousIFS2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSuspiciousIFS String
"IFS=$'\\t'"
prop_checkSuspiciousIFS3 :: Bool
prop_checkSuspiciousIFS3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSuspiciousIFS String
"IFS=' \\t\\n'"
checkSuspiciousIFS :: Parameters -> Token -> m ()
checkSuspiciousIFS Parameters
params (T_Assignment Id
_ AssignmentMode
_ String
"IFS" [] Token
value) =
    forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
String -> f ()
check forall a b. (a -> b) -> a -> b
$ Token -> Maybe String
getLiteralString Token
value
  where
    hasDollarSingle :: Bool
hasDollarSingle = Parameters -> Shell
shellType Parameters
params forall a. Eq a => a -> a -> Bool
== Shell
Bash Bool -> Bool -> Bool
|| Parameters -> Shell
shellType Parameters
params forall a. Eq a => a -> a -> Bool
== Shell
Ksh
    n :: String
n = if Bool
hasDollarSingle then  String
"$'\\n'" else String
"'<literal linefeed here>'"
    t :: String
t = if Bool
hasDollarSingle then  String
"$'\\t'" else String
"\"$(printf '\\t')\""
    check :: String -> m ()
check String
value =
        case String
value of
            String
"\\n" -> forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
String -> f ()
suggest String
n
            String
"\\t" -> forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
String -> f ()
suggest String
t
            String
x | Char
'\\' forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
x -> forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
String -> f ()
suggest2 String
"a literal backslash"
            String
x | Char
'n' forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
x -> forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
String -> f ()
suggest2 String
"the literal letter 'n'"
            String
x | Char
't' forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
x -> forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
String -> f ()
suggest2 String
"the literal letter 't'"
            String
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
    suggest :: String -> m ()
suggest String
r = forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
value) Code
2141 forall a b. (a -> b) -> a -> b
$ String
"This backslash is literal. Did you mean IFS=" forall a. [a] -> [a] -> [a]
++ String
r forall a. [a] -> [a] -> [a]
++ String
" ?"
    suggest2 :: String -> m ()
suggest2 String
desc = forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
value) Code
2141 forall a b. (a -> b) -> a -> b
$ String
"This IFS value contains " forall a. [a] -> [a] -> [a]
++ String
desc forall a. [a] -> [a] -> [a]
++ String
". For tabs/linefeeds/escapes, use $'..', literal, or printf."
checkSuspiciousIFS Parameters
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkGrepQ1 :: Bool
prop_checkGrepQ1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkShouldUseGrepQ String
"[[ $(foo | grep bar) ]]"
prop_checkGrepQ2 :: Bool
prop_checkGrepQ2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkShouldUseGrepQ String
"[ -z $(fgrep lol) ]"
prop_checkGrepQ3 :: Bool
prop_checkGrepQ3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkShouldUseGrepQ String
"[ -n \"$(foo | zgrep lol)\" ]"
prop_checkGrepQ4 :: Bool
prop_checkGrepQ4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkShouldUseGrepQ String
"[ -z $(grep bar | cmd) ]"
prop_checkGrepQ5 :: Bool
prop_checkGrepQ5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkShouldUseGrepQ String
"rm $(ls | grep file)"
prop_checkGrepQ6 :: Bool
prop_checkGrepQ6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkShouldUseGrepQ String
"[[ -n $(pgrep foo) ]]"
checkShouldUseGrepQ :: p -> Token -> m ()
checkShouldUseGrepQ p
params Token
t =
    forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ case Token
t of
        TC_Nullary Id
id ConditionType
_ Token
token -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Bool -> Token -> Maybe (m ())
check Id
id Bool
True Token
token
        TC_Unary Id
id ConditionType
_ String
"-n" Token
token -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Bool -> Token -> Maybe (m ())
check Id
id Bool
True Token
token
        TC_Unary Id
id ConditionType
_ String
"-z" Token
token -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Bool -> Token -> Maybe (m ())
check Id
id Bool
False Token
token
        Token
_ -> forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
"not check"
  where
    check :: Id -> Bool -> Token -> Maybe (m ())
check Id
id Bool
bool Token
token = do
        String
name <- Token -> Maybe String
getFinalGrep Token
token
        let op :: String
op = if Bool
bool then String
"-n" else String
"-z"
        let flip :: String
flip = if Bool
bool then String
"" else String
"! "
        forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style Id
id Code
2143 forall a b. (a -> b) -> a -> b
$
            String
"Use " forall a. [a] -> [a] -> [a]
++ String
flip forall a. [a] -> [a] -> [a]
++ String
name forall a. [a] -> [a] -> [a]
++ String
" -q instead of " forall a. [a] -> [a] -> [a]
++
                String
"comparing output with [ " forall a. [a] -> [a] -> [a]
++ String
op forall a. [a] -> [a] -> [a]
++ String
" .. ]."

    getFinalGrep :: Token -> Maybe String
getFinalGrep Token
t = do
        [Token]
cmds <- forall {m :: * -> *}. MonadFail m => Token -> m [Token]
getPipeline Token
t
        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: * -> *) a. Foldable t => t a -> Bool
null forall a b. (a -> b) -> a -> b
$ [Token]
cmds
        String
name <- Token -> Maybe String
getCommandBasename forall a b. (a -> b) -> a -> b
$ forall a. [a] -> a
last [Token]
cmds
        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Bool
isGrep forall a b. (a -> b) -> a -> b
$ String
name
        forall (m :: * -> *) a. Monad m => a -> m a
return String
name
    getPipeline :: Token -> m [Token]
getPipeline Token
t =
        case Token
t of
            T_NormalWord Id
_ [Token
x] -> Token -> m [Token]
getPipeline Token
x
            T_DoubleQuoted Id
_ [Token
x] -> Token -> m [Token]
getPipeline Token
x
            T_DollarExpansion Id
_ [Token
x] -> Token -> m [Token]
getPipeline Token
x
            T_Pipeline Id
_ [Token]
_ [Token]
cmds -> forall (m :: * -> *) a. Monad m => a -> m a
return [Token]
cmds
            Token
_ -> forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
"unknown"
    isGrep :: String -> Bool
isGrep = (forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"grep", String
"egrep", String
"fgrep", String
"zgrep"])

prop_checkTestArgumentSplitting1 :: Bool
prop_checkTestArgumentSplitting1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkTestArgumentSplitting String
"[ -e *.mp3 ]"
prop_checkTestArgumentSplitting2 :: Bool
prop_checkTestArgumentSplitting2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkTestArgumentSplitting String
"[[ $a == *b* ]]"
prop_checkTestArgumentSplitting3 :: Bool
prop_checkTestArgumentSplitting3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkTestArgumentSplitting String
"[[ *.png == '' ]]"
prop_checkTestArgumentSplitting4 :: Bool
prop_checkTestArgumentSplitting4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkTestArgumentSplitting String
"[[ foo == f{o,oo,ooo} ]]"
prop_checkTestArgumentSplitting5 :: Bool
prop_checkTestArgumentSplitting5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkTestArgumentSplitting String
"[[ $@ ]]"
prop_checkTestArgumentSplitting6 :: Bool
prop_checkTestArgumentSplitting6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkTestArgumentSplitting String
"[ -e $@ ]"
prop_checkTestArgumentSplitting7 :: Bool
prop_checkTestArgumentSplitting7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkTestArgumentSplitting String
"[ $@ == $@ ]"
prop_checkTestArgumentSplitting8 :: Bool
prop_checkTestArgumentSplitting8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkTestArgumentSplitting String
"[[ $@ = $@ ]]"
prop_checkTestArgumentSplitting9 :: Bool
prop_checkTestArgumentSplitting9 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkTestArgumentSplitting String
"[[ foo =~ bar{1,2} ]]"
prop_checkTestArgumentSplitting10 :: Bool
prop_checkTestArgumentSplitting10 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkTestArgumentSplitting String
"[ \"$@\" ]"
prop_checkTestArgumentSplitting11 :: Bool
prop_checkTestArgumentSplitting11 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkTestArgumentSplitting String
"[[ \"$@\" ]]"
prop_checkTestArgumentSplitting12 :: Bool
prop_checkTestArgumentSplitting12 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkTestArgumentSplitting String
"[ *.png ]"
prop_checkTestArgumentSplitting13 :: Bool
prop_checkTestArgumentSplitting13 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkTestArgumentSplitting String
"[ \"$@\" == \"\" ]"
prop_checkTestArgumentSplitting14 :: Bool
prop_checkTestArgumentSplitting14 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkTestArgumentSplitting String
"[[ \"$@\" == \"\" ]]"
prop_checkTestArgumentSplitting15 :: Bool
prop_checkTestArgumentSplitting15 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkTestArgumentSplitting String
"[[ \"$*\" == \"\" ]]"
prop_checkTestArgumentSplitting16 :: Bool
prop_checkTestArgumentSplitting16 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkTestArgumentSplitting String
"[[ -v foo[123] ]]"
prop_checkTestArgumentSplitting17 :: Bool
prop_checkTestArgumentSplitting17 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkTestArgumentSplitting String
"#!/bin/ksh\n[ -e foo* ]"
prop_checkTestArgumentSplitting18 :: Bool
prop_checkTestArgumentSplitting18 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkTestArgumentSplitting String
"#!/bin/ksh\n[ -d foo* ]"
prop_checkTestArgumentSplitting19 :: Bool
prop_checkTestArgumentSplitting19 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkTestArgumentSplitting String
"[[ var[x] -eq 2*3 ]]"
prop_checkTestArgumentSplitting20 :: Bool
prop_checkTestArgumentSplitting20 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkTestArgumentSplitting String
"[ var[x] -eq 2 ]"
prop_checkTestArgumentSplitting21 :: Bool
prop_checkTestArgumentSplitting21 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkTestArgumentSplitting String
"[ 6 -eq 2*3 ]"
checkTestArgumentSplitting :: Parameters -> Token -> Writer [TokenComment] ()
checkTestArgumentSplitting :: Parameters -> Token -> Writer [TokenComment] ()
checkTestArgumentSplitting Parameters
params Token
t =
    case Token
t of
        (TC_Unary Id
_ ConditionType
typ String
op Token
token) | Token -> Bool
isGlob Token
token ->
            if String
op forall a. Eq a => a -> a -> Bool
== String
"-v"
            then
                forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (ConditionType
typ forall a. Eq a => a -> a -> Bool
== ConditionType
SingleBracket) forall a b. (a -> b) -> a -> b
$
                    forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
token) Code
2208 forall a b. (a -> b) -> a -> b
$
                      String
"Use [[ ]] or quote arguments to -v to avoid glob expansion."
            else
                if (ConditionType
typ forall a. Eq a => a -> a -> Bool
== ConditionType
SingleBracket Bool -> Bool -> Bool
&& Parameters -> Shell
shellType Parameters
params forall a. Eq a => a -> a -> Bool
== Shell
Ksh)
                then
                    -- Ksh appears to stop processing after unrecognized tokens, so operators
                    -- will effectively work with globs, but only the first match.
                    forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
op forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [[Char
'-', Char
c] | Char
c <- String
"bcdfgkprsuwxLhNOGRS" ]) forall a b. (a -> b) -> a -> b
$
                        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
token) Code
2245 forall a b. (a -> b) -> a -> b
$
                            String
op forall a. [a] -> [a] -> [a]
++ String
" only applies to the first expansion of this glob. Use a loop to check any/all."
                else
                    forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
token) Code
2144 forall a b. (a -> b) -> a -> b
$
                       String
op forall a. [a] -> [a] -> [a]
++ String
" doesn't work with globs. Use a for loop."

        (TC_Nullary Id
_ ConditionType
typ Token
token) -> do
            forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
ConditionType -> Token -> f ()
checkBraces ConditionType
typ Token
token
            forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
ConditionType -> Token -> f ()
checkGlobs ConditionType
typ Token
token
            forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (ConditionType
typ forall a. Eq a => a -> a -> Bool
== ConditionType
DoubleBracket) forall a b. (a -> b) -> a -> b
$
                forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
ConditionType -> Token -> f ()
checkArrays ConditionType
typ Token
token

        (TC_Unary Id
_ ConditionType
typ String
op Token
token) -> forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
ConditionType -> Token -> f ()
checkAll ConditionType
typ Token
token

        (TC_Binary Id
_ ConditionType
typ String
op Token
lhs Token
rhs) | String
op forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
arithmeticBinaryTestOps ->
            if ConditionType
typ forall a. Eq a => a -> a -> Bool
== ConditionType
DoubleBracket
            then
                forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (\Token
c -> do
                        forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
ConditionType -> Token -> f ()
checkArrays ConditionType
typ Token
c
                        forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
ConditionType -> Token -> f ()
checkBraces ConditionType
typ Token
c) [Token
lhs, Token
rhs]
            else
                forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (\Token
c -> do
                        forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
ConditionType -> Token -> f ()
checkNumericalGlob ConditionType
typ Token
c
                        forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
ConditionType -> Token -> f ()
checkArrays ConditionType
typ Token
c
                        forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
ConditionType -> Token -> f ()
checkBraces ConditionType
typ Token
c) [Token
lhs, Token
rhs]

        (TC_Binary Id
_ ConditionType
typ String
op Token
lhs Token
rhs) ->
            if String
op forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"=", String
"==", String
"!=", String
"=~"]
            then do
                forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
ConditionType -> Token -> f ()
checkAll ConditionType
typ Token
lhs
                forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
ConditionType -> Token -> f ()
checkArrays ConditionType
typ Token
rhs
                forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
ConditionType -> Token -> f ()
checkBraces ConditionType
typ Token
rhs
            else forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
ConditionType -> Token -> f ()
checkAll ConditionType
typ) [Token
lhs, Token
rhs]
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    checkAll :: ConditionType -> Token -> m ()
checkAll ConditionType
typ Token
token = do
        forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
ConditionType -> Token -> f ()
checkArrays ConditionType
typ Token
token
        forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
ConditionType -> Token -> f ()
checkBraces ConditionType
typ Token
token
        forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
ConditionType -> Token -> f ()
checkGlobs ConditionType
typ Token
token

    checkArrays :: ConditionType -> Token -> f ()
checkArrays ConditionType
typ Token
token =
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
isArrayExpansion forall a b. (a -> b) -> a -> b
$ Token -> [Token]
getWordParts Token
token) forall a b. (a -> b) -> a -> b
$
            if ConditionType
typ forall a. Eq a => a -> a -> Bool
== ConditionType
SingleBracket
            then forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
token) Code
2198 String
"Arrays don't work as operands in [ ]. Use a loop (or concatenate with * instead of @)."
            else forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
token) Code
2199 String
"Arrays implicitly concatenate in [[ ]]. Use a loop (or explicit * instead of @)."

    checkBraces :: ConditionType -> Token -> f ()
checkBraces ConditionType
typ Token
token =
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
isBraceExpansion forall a b. (a -> b) -> a -> b
$ Token -> [Token]
getWordParts Token
token) forall a b. (a -> b) -> a -> b
$
            if ConditionType
typ forall a. Eq a => a -> a -> Bool
== ConditionType
SingleBracket
            then forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
token) Code
2200 String
"Brace expansions don't work as operands in [ ]. Use a loop."
            else forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
token) Code
2201 String
"Brace expansion doesn't happen in [[ ]]. Use a loop."

    checkGlobs :: ConditionType -> Token -> f ()
checkGlobs ConditionType
typ Token
token =
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isGlob Token
token) forall a b. (a -> b) -> a -> b
$
            if ConditionType
typ forall a. Eq a => a -> a -> Bool
== ConditionType
SingleBracket
            then forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
token) Code
2202 String
"Globs don't work as operands in [ ]. Use a loop."
            else forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
token) Code
2203 String
"Globs are ignored in [[ ]] except right of =/!=. Use a loop."

    checkNumericalGlob :: ConditionType -> Token -> f ()
checkNumericalGlob ConditionType
SingleBracket Token
token =
        -- var[x] and x*2 look like globs
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Parameters -> Shell
shellType Parameters
params forall a. Eq a => a -> a -> Bool
/= Shell
Ksh Bool -> Bool -> Bool
&& Token -> Bool
isGlob Token
token) forall a b. (a -> b) -> a -> b
$
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
token) Code
2255 String
"[ ] does not apply arithmetic evaluation. Evaluate with $((..)) for numbers, or use string comparator for strings."


prop_checkReadWithoutR1 :: Bool
prop_checkReadWithoutR1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkReadWithoutR String
"read -a foo"
prop_checkReadWithoutR2 :: Bool
prop_checkReadWithoutR2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkReadWithoutR String
"read -ar foo"
prop_checkReadWithoutR3 :: Bool
prop_checkReadWithoutR3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkReadWithoutR String
"read -t 0"
prop_checkReadWithoutR4 :: Bool
prop_checkReadWithoutR4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkReadWithoutR String
"read -t 0 && read --d '' -r bar"
prop_checkReadWithoutR5 :: Bool
prop_checkReadWithoutR5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkReadWithoutR String
"read -t 0 foo < file.txt"
prop_checkReadWithoutR6 :: Bool
prop_checkReadWithoutR6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkReadWithoutR String
"read -u 3 -t 0"
checkReadWithoutR :: p -> Token -> m ()
checkReadWithoutR p
_ t :: Token
t@T_SimpleCommand {} | Token
t Token -> String -> Bool
`isUnqualifiedCommand` String
"read"
    Bool -> Bool -> Bool
&& String
"r" forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` forall a b. (a -> b) -> [a] -> [b]
map forall a b. (a, b) -> b
snd [(Token, String)]
flags Bool -> Bool -> Bool
&& Bool -> Bool
not Bool
has_t0 =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId forall a b. (a -> b) -> a -> b
$ Token -> Token
getCommandTokenOrThis Token
t) Code
2162 String
"read without -r will mangle backslashes."
  where
    flags :: [(Token, String)]
flags = Token -> [(Token, String)]
getAllFlags Token
t
    has_t0 :: Bool
has_t0 = forall a. a -> Maybe a
Just String
"0" forall a. Eq a => a -> a -> Bool
== do
        [(String, (Token, Token))]
parsed <- String -> [Token] -> Maybe [(String, (Token, Token))]
getGnuOpts String
flagsForRead forall a b. (a -> b) -> a -> b
$ Token -> [Token]
arguments Token
t
        (Token
_, Token
t) <- forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup String
"t" [(String, (Token, Token))]
parsed
        Token -> Maybe String
getLiteralString Token
t

checkReadWithoutR p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkUncheckedCd1 :: Bool
prop_checkUncheckedCd1 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd String
"cd ~/src; rm -r foo"
prop_checkUncheckedCd2 :: Bool
prop_checkUncheckedCd2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd String
"cd ~/src || exit; rm -r foo"
prop_checkUncheckedCd3 :: Bool
prop_checkUncheckedCd3 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd String
"set -e; cd ~/src; rm -r foo"
prop_checkUncheckedCd4 :: Bool
prop_checkUncheckedCd4 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd String
"if cd foo; then rm foo; fi"
prop_checkUncheckedCd5 :: Bool
prop_checkUncheckedCd5 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd String
"if true; then cd foo; fi"
prop_checkUncheckedCd6 :: Bool
prop_checkUncheckedCd6 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd String
"cd .."
prop_checkUncheckedCd7 :: Bool
prop_checkUncheckedCd7 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd String
"#!/bin/bash -e\ncd foo\nrm bar"
prop_checkUncheckedCd8 :: Bool
prop_checkUncheckedCd8 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd String
"set -o errexit; cd foo; rm bar"
prop_checkUncheckedCd9 :: Bool
prop_checkUncheckedCd9 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd String
"builtin cd ~/src; rm -r foo"
prop_checkUncheckedPushd1 :: Bool
prop_checkUncheckedPushd1 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd String
"pushd ~/src; rm -r foo"
prop_checkUncheckedPushd2 :: Bool
prop_checkUncheckedPushd2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd String
"pushd ~/src || exit; rm -r foo"
prop_checkUncheckedPushd3 :: Bool
prop_checkUncheckedPushd3 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd String
"set -e; pushd ~/src; rm -r foo"
prop_checkUncheckedPushd4 :: Bool
prop_checkUncheckedPushd4 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd String
"if pushd foo; then rm foo; fi"
prop_checkUncheckedPushd5 :: Bool
prop_checkUncheckedPushd5 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd String
"if true; then pushd foo; fi"
prop_checkUncheckedPushd6 :: Bool
prop_checkUncheckedPushd6 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd String
"pushd .."
prop_checkUncheckedPushd7 :: Bool
prop_checkUncheckedPushd7 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd String
"#!/bin/bash -e\npushd foo\nrm bar"
prop_checkUncheckedPushd8 :: Bool
prop_checkUncheckedPushd8 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd String
"set -o errexit; pushd foo; rm bar"
prop_checkUncheckedPushd9 :: Bool
prop_checkUncheckedPushd9 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd String
"pushd -n foo"
prop_checkUncheckedPopd1 :: Bool
prop_checkUncheckedPopd1 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd String
"popd; rm -r foo"
prop_checkUncheckedPopd2 :: Bool
prop_checkUncheckedPopd2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd String
"popd || exit; rm -r foo"
prop_checkUncheckedPopd3 :: Bool
prop_checkUncheckedPopd3 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd String
"set -e; popd; rm -r foo"
prop_checkUncheckedPopd4 :: Bool
prop_checkUncheckedPopd4 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd String
"if popd; then rm foo; fi"
prop_checkUncheckedPopd5 :: Bool
prop_checkUncheckedPopd5 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd String
"if true; then popd; fi"
prop_checkUncheckedPopd6 :: Bool
prop_checkUncheckedPopd6 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd String
"popd"
prop_checkUncheckedPopd7 :: Bool
prop_checkUncheckedPopd7 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd String
"#!/bin/bash -e\npopd\nrm bar"
prop_checkUncheckedPopd8 :: Bool
prop_checkUncheckedPopd8 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd String
"set -o errexit; popd; rm bar"
prop_checkUncheckedPopd9 :: Bool
prop_checkUncheckedPopd9 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd String
"popd -n foo"
prop_checkUncheckedPopd10 :: Bool
prop_checkUncheckedPopd10 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd String
"cd ../.."
prop_checkUncheckedPopd11 :: Bool
prop_checkUncheckedPopd11 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd String
"cd ../.././.."
prop_checkUncheckedPopd12 :: Bool
prop_checkUncheckedPopd12 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd String
"cd /"
prop_checkUncheckedPopd13 :: Bool
prop_checkUncheckedPopd13 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd String
"cd ../../.../.."

checkUncheckedCdPushdPopd :: Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd Parameters
params Token
root =
    if Parameters -> Bool
hasSetE Parameters
params then
        []
    else forall w a. Writer w a -> w
execWriter forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> Token -> m Token
doAnalysis forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkElement Token
root
  where
    checkElement :: Token -> m ()
checkElement t :: Token
t@T_SimpleCommand {}
        | String
name forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"cd", String
"pushd", String
"popd"]
            Bool -> Bool -> Bool
&& Bool -> Bool
not (Token -> Bool
isSafeDir Token
t)
            Bool -> Bool -> Bool
&& Bool -> Bool
not (String
name forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"pushd", String
"popd"] Bool -> Bool -> Bool
&& (String
"n" forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` forall a b. (a -> b) -> [a] -> [b]
map forall a b. (a, b) -> b
snd (Token -> [(Token, String)]
getAllFlags Token
t)))
            Bool -> Bool -> Bool
&& Bool -> Bool
not ([Token] -> Bool
isCondition forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) Token
t) =
                forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> Fix -> m ()
warnWithFix (Token -> Id
getId Token
t) Code
2164
                    (String
"Use '" forall a. [a] -> [a] -> [a]
++ String
name forall a. [a] -> [a] -> [a]
++ String
" ... || exit' or '" forall a. [a] -> [a] -> [a]
++ String
name forall a. [a] -> [a] -> [a]
++ String
" ... || return' in case " forall a. [a] -> [a] -> [a]
++ String
name forall a. [a] -> [a] -> [a]
++ String
" fails.")
                    ([Replacement] -> Fix
fixWith [Id -> Parameters -> Code -> String -> Replacement
replaceEnd (Token -> Id
getId Token
t) Parameters
params Code
0 String
" || exit"])
        where name :: String
name = Token -> String
getName Token
t
    checkElement Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()
    getName :: Token -> String
getName Token
t = forall a. a -> Maybe a -> a
fromMaybe String
"" forall a b. (a -> b) -> a -> b
$ Token -> Maybe String
getCommandName Token
t
    isSafeDir :: Token -> Bool
isSafeDir Token
t = case Token -> [String]
oversimplify Token
t of
          [String
_, String
str] -> String
str String -> Regex -> Bool
`matches` Regex
regex
          [String]
_ -> Bool
False
    regex :: Regex
regex = String -> Regex
mkRegex String
"^/*((\\.|\\.\\.)/+)*(\\.|\\.\\.)?$"

prop_checkLoopVariableReassignment1 :: Bool
prop_checkLoopVariableReassignment1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkLoopVariableReassignment String
"for i in *; do for i in *.bar; do true; done; done"
prop_checkLoopVariableReassignment2 :: Bool
prop_checkLoopVariableReassignment2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkLoopVariableReassignment String
"for i in *; do for((i=0; i<3; i++)); do true; done; done"
prop_checkLoopVariableReassignment3 :: Bool
prop_checkLoopVariableReassignment3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkLoopVariableReassignment String
"for i in *; do for j in *.bar; do true; done; done"
prop_checkLoopVariableReassignment4 :: Bool
prop_checkLoopVariableReassignment4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkLoopVariableReassignment String
"for _ in *; do for _ in *.bar; do true; done; done"
checkLoopVariableReassignment :: Parameters -> Token -> m ()
checkLoopVariableReassignment Parameters
params Token
token =
    forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ case Token
token of
        T_ForIn {} -> Maybe (m ())
check
        T_ForArithmetic {} -> Maybe (m ())
check
        Token
_ -> forall a. Maybe a
Nothing
  where
    check :: Maybe (m ())
check = do
        String
str <- Token -> Maybe String
loopVariable Token
token
        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ String
str forall a. Eq a => a -> a -> Bool
/= String
"_"
        Token
next <- forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (\Token
x -> Token -> Maybe String
loopVariable Token
x forall a. Eq a => a -> a -> Bool
== forall a. a -> Maybe a
Just String
str) [Token]
path
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ do
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
token) Code
2165 String
"This nested loop overrides the index variable of its parent."
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
next)  Code
2167 String
"This parent loop has its index variable overridden."
    path :: [Token]
path = forall a. Int -> [a] -> [a]
drop Int
1 forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) Token
token
    loopVariable :: Token -> Maybe String
    loopVariable :: Token -> Maybe String
loopVariable Token
t =
        case Token
t of
            T_ForIn Id
_ String
s [Token]
_ [Token]
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return String
s
            T_ForArithmetic Id
_
                (TA_Sequence Id
_
                    [TA_Assignment Id
_ String
"="
                        (TA_Variable Id
_ String
var [Token]
_ ) Token
_])
                            Token
_ Token
_ [Token]
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return String
var
            Token
_ -> forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
"not loop"

prop_checkTrailingBracket1 :: Bool
prop_checkTrailingBracket1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTrailingBracket String
"if -z n ]]; then true; fi "
prop_checkTrailingBracket2 :: Bool
prop_checkTrailingBracket2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTrailingBracket String
"if [[ -z n ]]; then true; fi "
prop_checkTrailingBracket3 :: Bool
prop_checkTrailingBracket3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTrailingBracket String
"a || b ] && thing"
prop_checkTrailingBracket4 :: Bool
prop_checkTrailingBracket4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTrailingBracket String
"run [ foo ]"
prop_checkTrailingBracket5 :: Bool
prop_checkTrailingBracket5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTrailingBracket String
"run bar ']'"
checkTrailingBracket :: p -> Token -> m ()
checkTrailingBracket p
_ Token
token =
    case Token
token of
        T_SimpleCommand Id
_ [Token]
_ tokens :: [Token]
tokens@(Token
_:[Token]
_) -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Token -> Token -> m ()
check (forall a. [a] -> a
last [Token]
tokens) Token
token
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    check :: Token -> Token -> m ()
check (T_NormalWord Id
id [T_Literal Id
_ String
str]) Token
command
        | String
str forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [ String
"]]", String
"]" ]
        Bool -> Bool -> Bool
&& String
opposite forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [String]
parameters
        = forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2171 forall a b. (a -> b) -> a -> b
$
            String
"Found trailing " forall a. [a] -> [a] -> [a]
++ String
str forall a. [a] -> [a] -> [a]
++ String
" outside test. Add missing " forall a. [a] -> [a] -> [a]
++ String
opposite forall a. [a] -> [a] -> [a]
++ String
" or quote if intentional."
        where
            opposite :: String
opposite = String -> String
invert String
str
            parameters :: [String]
parameters = Token -> [String]
oversimplify Token
command
    check Token
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()
    invert :: String -> String
invert String
s =
        case String
s of
            String
"]]" -> String
"[["
            String
"]" -> String
"["
            String
x -> String
x

prop_checkReturnAgainstZero1 :: Bool
prop_checkReturnAgainstZero1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkReturnAgainstZero String
"[ $? -eq 0 ]"
prop_checkReturnAgainstZero2 :: Bool
prop_checkReturnAgainstZero2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkReturnAgainstZero String
"[[ \"$?\" -gt 0 ]]"
prop_checkReturnAgainstZero3 :: Bool
prop_checkReturnAgainstZero3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkReturnAgainstZero String
"[[ 0 -ne $? ]]"
prop_checkReturnAgainstZero4 :: Bool
prop_checkReturnAgainstZero4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkReturnAgainstZero String
"[[ $? -eq 4 ]]"
prop_checkReturnAgainstZero5 :: Bool
prop_checkReturnAgainstZero5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkReturnAgainstZero String
"[[ 0 -eq $? ]]"
prop_checkReturnAgainstZero6 :: Bool
prop_checkReturnAgainstZero6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkReturnAgainstZero String
"[[ $R -eq 0 ]]"
prop_checkReturnAgainstZero7 :: Bool
prop_checkReturnAgainstZero7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkReturnAgainstZero String
"(( $? == 0 ))"
prop_checkReturnAgainstZero8 :: Bool
prop_checkReturnAgainstZero8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkReturnAgainstZero String
"(( $? ))"
prop_checkReturnAgainstZero9 :: Bool
prop_checkReturnAgainstZero9 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkReturnAgainstZero String
"(( ! $? ))"
prop_checkReturnAgainstZero10 :: Bool
prop_checkReturnAgainstZero10 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkReturnAgainstZero String
"x=$(( $? > 0 ))"
prop_checkReturnAgainstZero11 :: Bool
prop_checkReturnAgainstZero11 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkReturnAgainstZero String
"(( ! ! ! $? ))"
prop_checkReturnAgainstZero12 :: Bool
prop_checkReturnAgainstZero12 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkReturnAgainstZero String
"[ ! $? -eq 0 ]"
prop_checkReturnAgainstZero13 :: Bool
prop_checkReturnAgainstZero13 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkReturnAgainstZero String
"(( ! $? && $? > 42))"
prop_checkReturnAgainstZero14 :: Bool
prop_checkReturnAgainstZero14 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkReturnAgainstZero String
"[[ -e foo || $? -eq 0 ]]"
prop_checkReturnAgainstZero15 :: Bool
prop_checkReturnAgainstZero15 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkReturnAgainstZero String
"(( $?, n=1 ))"
prop_checkReturnAgainstZero16 :: Bool
prop_checkReturnAgainstZero16 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkReturnAgainstZero String
"(( $? || $? == 4 ))"
prop_checkReturnAgainstZero17 :: Bool
prop_checkReturnAgainstZero17 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkReturnAgainstZero String
"(( $? + 0 ))"
prop_checkReturnAgainstZero18 :: Bool
prop_checkReturnAgainstZero18 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkReturnAgainstZero String
"f() { if [ $? -eq 0 ]; then :; fi; }"
prop_checkReturnAgainstZero19 :: Bool
prop_checkReturnAgainstZero19 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkReturnAgainstZero String
"f() ( [ $? -eq 0 ] || exit 42; )"
prop_checkReturnAgainstZero20 :: Bool
prop_checkReturnAgainstZero20 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkReturnAgainstZero String
"f() { if :; then x; [ $? -eq 0 ] && exit; fi; }"
prop_checkReturnAgainstZero21 :: Bool
prop_checkReturnAgainstZero21 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkReturnAgainstZero String
"(( ( $? ) ))"
prop_checkReturnAgainstZero22 :: Bool
prop_checkReturnAgainstZero22 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkReturnAgainstZero String
"[[ ( $? -eq 0 ) ]]"
checkReturnAgainstZero :: Parameters -> Token -> f ()
checkReturnAgainstZero Parameters
params Token
token =
    case Token
token of
        TC_Binary Id
id ConditionType
_ String
op Token
lhs Token
rhs -> forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
String -> Token -> Token -> f ()
check String
op Token
lhs Token
rhs
        TA_Binary Id
id String
op Token
lhs Token
rhs
            | String
op forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
">", String
"<", String
">=", String
"<=", String
"==", String
"!="] -> forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
String -> Token -> Token -> f ()
check String
op Token
lhs Token
rhs
        TA_Unary Id
id op :: String
op@String
"!" Token
exp
            | Token -> Bool
isExitCode Token
exp -> forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
Bool -> Id -> f ()
message (String -> Bool
checksSuccessLhs String
op) (Token -> Id
getId Token
exp)
        TA_Sequence Id
_ [Token
exp]
            | Token -> Bool
isExitCode Token
exp -> forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
Bool -> Id -> f ()
message Bool
False (Token -> Id
getId Token
exp)
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    -- We don't want to warn about composite expressions like
    -- [[ $? -eq 0 || $? -eq 4 ]] since these can be annoying to rewrite.
    isOnlyTestInCommand :: Token -> Bool
isOnlyTestInCommand Token
t =
        case Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) Token
t of
            Token
_:(T_Condition {}):[Token]
_ -> Bool
True
            Token
_:(T_Arithmetic {}):[Token]
_ -> Bool
True
            Token
_:(TA_Sequence Id
_ [Token
_]):(T_Arithmetic {}):[Token]
_ -> Bool
True

            -- Some negations and groupings are also fine
            Token
_:next :: Token
next@(TC_Unary Id
_ ConditionType
_ String
"!" Token
_):[Token]
_ -> Token -> Bool
isOnlyTestInCommand Token
next
            Token
_:next :: Token
next@(TA_Unary Id
_ String
"!" Token
_):[Token]
_ -> Token -> Bool
isOnlyTestInCommand Token
next
            Token
_:next :: Token
next@(TC_Group {}):[Token]
_ -> Token -> Bool
isOnlyTestInCommand Token
next
            Token
_:next :: Token
next@(TA_Sequence Id
_ [Token
_]):[Token]
_ -> Token -> Bool
isOnlyTestInCommand Token
next
            Token
_:next :: Token
next@(TA_Parentesis Id
_ Token
_):[Token]
_ -> Token -> Bool
isOnlyTestInCommand Token
next
            [Token]
_ -> Bool
False

    -- TODO: Do better $? tracking and filter on whether
    -- the target command is in the same function
    getFirstCommandInFunction :: Token -> Token
getFirstCommandInFunction = Token -> Token
f
      where
        f :: Token -> Token
f Token
t = case Token
t of
            T_Function Id
_ FunctionKeyword
_ FunctionParentheses
_ String
_ Token
x -> Token -> Token
f Token
x
            T_BraceGroup Id
_ (Token
x:[Token]
_) -> Token -> Token
f Token
x
            T_Subshell Id
_ (Token
x:[Token]
_) -> Token -> Token
f Token
x
            T_Annotation Id
_ [Annotation]
_ Token
x -> Token -> Token
f Token
x
            T_AndIf Id
_ Token
x Token
_ -> Token -> Token
f Token
x
            T_OrIf Id
_ Token
x Token
_ -> Token -> Token
f Token
x
            T_Pipeline Id
_ [Token]
_ (Token
x:[Token]
_) -> Token -> Token
f Token
x
            T_Redirecting Id
_ [Token]
_ (T_IfExpression Id
_ (((Token
x:[Token]
_),[Token]
_):[([Token], [Token])]
_) [Token]
_) -> Token -> Token
f Token
x
            Token
x -> Token
x

    isFirstCommandInFunction :: Bool
isFirstCommandInFunction = forall a. a -> Maybe a -> a
fromMaybe Bool
False forall a b. (a -> b) -> a -> b
$ do
        let path :: [Token]
path = Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) Token
token
        Token
func <- forall a. [a] -> Maybe a
listToMaybe forall a b. (a -> b) -> a -> b
$ forall a. (a -> Bool) -> [a] -> [a]
filter Token -> Bool
isFunction [Token]
path
        Token
cmd <- Map Id Token -> Token -> Maybe Token
getClosestCommand (Parameters -> Map Id Token
parentMap Parameters
params) Token
token
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Token -> Id
getId Token
cmd forall a. Eq a => a -> a -> Bool
== Token -> Id
getId (Token -> Token
getFirstCommandInFunction Token
func)

    -- Is "$? op 0" trying to check if the command succeeded?
    checksSuccessLhs :: String -> Bool
checksSuccessLhs String
op = Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ String
op forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"-gt", String
"-ne", String
"!=", String
"!"]
    -- Is "0 op $?" trying to check if the command succeeded?
    checksSuccessRhs :: String -> Bool
checksSuccessRhs String
op = String
op forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [String
"-ne", String
"!="]

    check :: String -> Token -> Token -> f ()
check String
op Token
lhs Token
rhs =
        if Token -> Bool
isZero Token
rhs Bool -> Bool -> Bool
&& Token -> Bool
isExitCode Token
lhs
        then forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
Bool -> Id -> f ()
message (String -> Bool
checksSuccessLhs String
op) (Token -> Id
getId Token
lhs)
        else forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isZero Token
lhs Bool -> Bool -> Bool
&& Token -> Bool
isExitCode Token
rhs) forall a b. (a -> b) -> a -> b
$ forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
Bool -> Id -> f ()
message (String -> Bool
checksSuccessRhs String
op) (Token -> Id
getId Token
rhs)
    isZero :: Token -> Bool
isZero Token
t = Token -> Maybe String
getLiteralString Token
t forall a. Eq a => a -> a -> Bool
== forall a. a -> Maybe a
Just String
"0"
    isExitCode :: Token -> Bool
isExitCode Token
t =
        case Token -> [Token]
getWordParts Token
t of
            [T_DollarBraced Id
_ Bool
_ Token
l] -> forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat (Token -> [String]
oversimplify Token
l) forall a. Eq a => a -> a -> Bool
== String
"?"
            [Token]
_ -> Bool
False

    message :: Bool -> Id -> f ()
message Bool
forSuccess Id
id = forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isOnlyTestInCommand Token
token Bool -> Bool -> Bool
&& Bool -> Bool
not Bool
isFirstCommandInFunction) forall a b. (a -> b) -> a -> b
$ forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style Id
id Code
2181 forall a b. (a -> b) -> a -> b
$
        String
"Check exit code directly with e.g. 'if " forall a. [a] -> [a] -> [a]
++ (if Bool
forSuccess then String
"" else String
"! ") forall a. [a] -> [a] -> [a]
++ String
"mycmd;', not indirectly with $?."


prop_checkRedirectedNowhere1 :: Bool
prop_checkRedirectedNowhere1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectedNowhere String
"> file"
prop_checkRedirectedNowhere2 :: Bool
prop_checkRedirectedNowhere2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectedNowhere String
"> file | grep foo"
prop_checkRedirectedNowhere3 :: Bool
prop_checkRedirectedNowhere3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectedNowhere String
"grep foo | > bar"
prop_checkRedirectedNowhere4 :: Bool
prop_checkRedirectedNowhere4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectedNowhere String
"grep foo > bar"
prop_checkRedirectedNowhere5 :: Bool
prop_checkRedirectedNowhere5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectedNowhere String
"foo | grep bar > baz"
prop_checkRedirectedNowhere6 :: Bool
prop_checkRedirectedNowhere6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectedNowhere String
"var=$(value) 2> /dev/null"
prop_checkRedirectedNowhere7 :: Bool
prop_checkRedirectedNowhere7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectedNowhere String
"var=$(< file)"
prop_checkRedirectedNowhere8 :: Bool
prop_checkRedirectedNowhere8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectedNowhere String
"var=`< file`"
checkRedirectedNowhere :: Parameters -> Token -> m ()
checkRedirectedNowhere Parameters
params Token
token =
    case Token
token of
        T_Pipeline Id
_ [Token]
_ [Token
single] -> forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
            Token
redir <- Token -> Maybe Token
getDanglingRedirect Token
single
            forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ Token -> Bool
isInExpansion Token
token
            forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
redir) Code
2188 String
"This redirection doesn't have a command. Move to its command (or use 'true' as no-op)."

        T_Pipeline Id
_ [Token]
_ [Token]
list -> forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ [Token]
list forall a b. (a -> b) -> a -> b
$ \Token
x -> forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
            Token
redir <- Token -> Maybe Token
getDanglingRedirect Token
x
            forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
redir) Code
2189 String
"You can't have | between this redirection and the command it should apply to."

        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    isInExpansion :: Token -> Bool
isInExpansion Token
t =
        case forall a. Int -> [a] -> [a]
drop Int
1 forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) Token
t of
            T_DollarExpansion Id
_ [Token
_] : [Token]
_ -> Bool
True
            T_Backticked Id
_ [Token
_] : [Token]
_ -> Bool
True
            t :: Token
t@T_Annotation {} : [Token]
_ -> Token -> Bool
isInExpansion Token
t
            [Token]
_ -> Bool
False
    getDanglingRedirect :: Token -> Maybe Token
getDanglingRedirect Token
token =
        case Token
token of
            T_Redirecting Id
_ (Token
first:[Token]
_) (T_SimpleCommand Id
_ [] []) -> forall (m :: * -> *) a. Monad m => a -> m a
return Token
first
            Token
_ -> forall a. Maybe a
Nothing


prop_checkArrayAssignmentIndices1 :: Bool
prop_checkArrayAssignmentIndices1 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkArrayAssignmentIndices String
"declare -A foo; foo=(bar)"
prop_checkArrayAssignmentIndices2 :: Bool
prop_checkArrayAssignmentIndices2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkArrayAssignmentIndices String
"declare -a foo; foo=(bar)"
prop_checkArrayAssignmentIndices3 :: Bool
prop_checkArrayAssignmentIndices3 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkArrayAssignmentIndices String
"declare -A foo; foo=([i]=bar)"
prop_checkArrayAssignmentIndices4 :: Bool
prop_checkArrayAssignmentIndices4 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkArrayAssignmentIndices String
"typeset -A foo; foo+=(bar)"
prop_checkArrayAssignmentIndices5 :: Bool
prop_checkArrayAssignmentIndices5 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkArrayAssignmentIndices String
"arr=( [foo]= bar )"
prop_checkArrayAssignmentIndices6 :: Bool
prop_checkArrayAssignmentIndices6 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkArrayAssignmentIndices String
"arr=( [foo] = bar )"
prop_checkArrayAssignmentIndices7 :: Bool
prop_checkArrayAssignmentIndices7 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkArrayAssignmentIndices String
"arr=( var=value )"
prop_checkArrayAssignmentIndices8 :: Bool
prop_checkArrayAssignmentIndices8 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkArrayAssignmentIndices String
"arr=( [foo]=bar )"
prop_checkArrayAssignmentIndices9 :: Bool
prop_checkArrayAssignmentIndices9 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkArrayAssignmentIndices String
"arr=( [foo]=\"\" )"
prop_checkArrayAssignmentIndices10 :: Bool
prop_checkArrayAssignmentIndices10 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkArrayAssignmentIndices String
"declare -A arr; arr=( var=value )"
prop_checkArrayAssignmentIndices11 :: Bool
prop_checkArrayAssignmentIndices11 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkArrayAssignmentIndices String
"arr=( 1=value )"
prop_checkArrayAssignmentIndices12 :: Bool
prop_checkArrayAssignmentIndices12 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkArrayAssignmentIndices String
"arr=( $a=value )"
prop_checkArrayAssignmentIndices13 :: Bool
prop_checkArrayAssignmentIndices13 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkArrayAssignmentIndices String
"arr=( $((1+1))=value )"
checkArrayAssignmentIndices :: Parameters -> Token -> [TokenComment]
checkArrayAssignmentIndices Parameters
params Token
root =
    forall {w} {t}.
Monoid w =>
(t -> Token -> WriterT w Identity ()) -> t -> Token -> w
runNodeAnalysis forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
check Parameters
params Token
root
  where
    assocs :: [String]
assocs = Token -> [String]
getAssociativeArrays Token
root
    check :: p -> Token -> m ()
check p
_ Token
t =
        case Token
t of
            T_Assignment Id
_ AssignmentMode
_ String
name [] (T_Array Id
_ [Token]
list) ->
                let isAssoc :: Bool
isAssoc = String
name forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
assocs in
                    forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Bool -> Token -> m ()
checkElement Bool
isAssoc) [Token]
list
            Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()

    checkElement :: Bool -> Token -> m ()
checkElement Bool
isAssociative Token
t =
        case Token
t of
            T_IndexedElement Id
_ [Token]
_ (T_Literal Id
id String
"") ->
                forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2192 String
"This array element has no value. Remove spaces after = or use \"\" for empty string."
            T_IndexedElement {} ->
                forall (m :: * -> *) a. Monad m => a -> m a
return ()

            T_NormalWord Id
_ [Token]
parts ->
                let literalEquals :: [m ()]
literalEquals = do
                    T_Literal Id
id String
str <- [Token]
parts
                    let (String
before, String
after) = forall a. (a -> Bool) -> [a] -> ([a], [a])
break (Char
'=' forall a. Eq a => a -> a -> Bool
==) String
str
                    forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isDigit String
before Bool -> Bool -> Bool
&& Bool -> Bool
not (forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
after)
                    forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> Fix -> m ()
warnWithFix Id
id Code
2191 String
"The = here is literal. To assign by index, use ( [index]=value ) with no spaces. To keep as literal, quote it." (Id -> Parameters -> String -> Fix
surroundWith Id
id Parameters
params String
"\"")
                in
                    if forall (t :: * -> *) a. Foldable t => t a -> Bool
null [m ()]
literalEquals Bool -> Bool -> Bool
&& Bool
isAssociative
                    then forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
t) Code
2190 String
"Elements in associative arrays need index, e.g. array=( [index]=value ) ."
                    else forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ [m ()]
literalEquals

            Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkUnmatchableCases1 :: Bool
prop_checkUnmatchableCases1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnmatchableCases String
"case foo in bar) true; esac"
prop_checkUnmatchableCases2 :: Bool
prop_checkUnmatchableCases2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnmatchableCases String
"case foo-$bar in ??|*) true; esac"
prop_checkUnmatchableCases3 :: Bool
prop_checkUnmatchableCases3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnmatchableCases String
"case foo in foo) true; esac"
prop_checkUnmatchableCases4 :: Bool
prop_checkUnmatchableCases4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnmatchableCases String
"case foo-$bar in foo*|*bar|*baz*) true; esac"
prop_checkUnmatchableCases5 :: Bool
prop_checkUnmatchableCases5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnmatchableCases String
"case $f in *.txt) true;; f??.txt) false;; esac"
prop_checkUnmatchableCases6 :: Bool
prop_checkUnmatchableCases6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnmatchableCases String
"case $f in ?*) true;; *) false;; esac"
prop_checkUnmatchableCases7 :: Bool
prop_checkUnmatchableCases7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnmatchableCases String
"case $f in $(x)) true;; asdf) false;; esac"
prop_checkUnmatchableCases8 :: Bool
prop_checkUnmatchableCases8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnmatchableCases String
"case $f in cow) true;; bar|cow) false;; esac"
prop_checkUnmatchableCases9 :: Bool
prop_checkUnmatchableCases9 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnmatchableCases String
"case $f in x) true;;& x) false;; esac"
checkUnmatchableCases :: Parameters -> Token -> m ()
checkUnmatchableCases Parameters
params Token
t =
    case Token
t of
        T_CaseExpression Id
_ Token
word [(CaseType, [Token], [Token])]
list -> do
            -- Check all patterns for whether they can ever match
            let allpatterns :: [Token]
allpatterns  = forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap forall {a} {b} {c}. (a, b, c) -> b
snd3 [(CaseType, [Token], [Token])]
list
            -- Check only the non-fallthrough branches for shadowing
            let breakpatterns :: [Token]
breakpatterns = forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap forall {a} {b} {c}. (a, b, c) -> b
snd3 forall a b. (a -> b) -> a -> b
$ forall a. (a -> Bool) -> [a] -> [a]
filter (\(CaseType, [Token], [Token])
x -> forall {a} {b} {c}. (a, b, c) -> a
fst3 (CaseType, [Token], [Token])
x forall a. Eq a => a -> a -> Bool
== CaseType
CaseBreak) [(CaseType, [Token], [Token])]
list

            if Token -> Bool
isConstant Token
word
                then forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
word) Code
2194
                        String
"This word is constant. Did you forget the $ on a variable?"
                else forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
[PseudoGlob] -> Token -> f ()
check forall a b. (a -> b) -> a -> b
$ Token -> [PseudoGlob]
wordToPseudoGlob Token
word) [Token]
allpatterns

            let exactGlobs :: [(Token, Maybe [PseudoGlob])]
exactGlobs = forall {t} {b}. (t -> b) -> [t] -> [(t, b)]
tupMap Token -> Maybe [PseudoGlob]
wordToExactPseudoGlob [Token]
breakpatterns
            let fuzzyGlobs :: [(Token, [PseudoGlob])]
fuzzyGlobs = forall {t} {b}. (t -> b) -> [t] -> [(t, b)]
tupMap Token -> [PseudoGlob]
wordToPseudoGlob [Token]
breakpatterns
            let dominators :: [((Token, Maybe [PseudoGlob]), [(Token, [PseudoGlob])])]
dominators = forall a b. [a] -> [b] -> [(a, b)]
zip [(Token, Maybe [PseudoGlob])]
exactGlobs (forall a. [a] -> [[a]]
tails forall a b. (a -> b) -> a -> b
$ forall a. Int -> [a] -> [a]
drop Int
1 [(Token, [PseudoGlob])]
fuzzyGlobs)

            forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *} {t :: * -> *}.
(Foldable t, MonadWriter [TokenComment] m) =>
((Token, Maybe [PseudoGlob]), t (Token, [PseudoGlob])) -> m ()
checkDoms [((Token, Maybe [PseudoGlob]), [(Token, [PseudoGlob])])]
dominators

        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    fst3 :: (a, b, c) -> a
fst3 (a
x,b
_,c
_) = a
x
    snd3 :: (a, b, c) -> b
snd3 (a
_,b
x,c
_) = b
x
    tp :: Map Id (Position, Position)
tp = Parameters -> Map Id (Position, Position)
tokenPositions Parameters
params
    check :: [PseudoGlob] -> Token -> f ()
check [PseudoGlob]
target Token
candidate = forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless ([PseudoGlob] -> [PseudoGlob] -> Bool
pseudoGlobsCanOverlap [PseudoGlob]
target forall a b. (a -> b) -> a -> b
$ Token -> [PseudoGlob]
wordToPseudoGlob Token
candidate) forall a b. (a -> b) -> a -> b
$
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
candidate) Code
2195
            String
"This pattern will never match the case statement's word. Double check them."

    tupMap :: (t -> b) -> [t] -> [(t, b)]
tupMap t -> b
f [t]
l = forall a b. (a -> b) -> [a] -> [b]
map (\t
x -> (t
x, t -> b
f t
x)) [t]
l
    checkDoms :: ((Token, Maybe [PseudoGlob]), t (Token, [PseudoGlob])) -> m ()
checkDoms ((Token
glob, Just [PseudoGlob]
x), t (Token, [PseudoGlob])
rest) =
        forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (\(Token
_, [PseudoGlob]
p) -> [PseudoGlob]
x [PseudoGlob] -> [PseudoGlob] -> Bool
`pseudoGlobIsSuperSetof` [PseudoGlob]
p) t (Token, [PseudoGlob])
rest) forall a b. (a -> b) -> a -> b
$
            \(Token
first,[PseudoGlob]
_) -> do
                forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
glob) Code
2221 forall a b. (a -> b) -> a -> b
$ String
"This pattern always overrides a later one" forall a. Semigroup a => a -> a -> a
<> Id -> String
patternContext (Token -> Id
getId Token
first)
                forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
first) Code
2222 forall a b. (a -> b) -> a -> b
$ String
"This pattern never matches because of a previous pattern" forall a. Semigroup a => a -> a -> a
<> Id -> String
patternContext (Token -> Id
getId Token
glob)
      where
        patternContext :: Id -> String
        patternContext :: Id -> String
patternContext Id
id =
            case Position -> Code
posLine forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a, b) -> a
fst forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup Id
id Map Id (Position, Position)
tp of
              Just Code
l -> String
" on line " forall a. Semigroup a => a -> a -> a
<> forall a. Show a => a -> String
show Code
l forall a. Semigroup a => a -> a -> a
<> String
"."
              Maybe Code
_      -> String
"."
    checkDoms ((Token, Maybe [PseudoGlob]), t (Token, [PseudoGlob]))
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkSubshellAsTest1 :: Bool
prop_checkSubshellAsTest1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSubshellAsTest String
"( -e file )"
prop_checkSubshellAsTest2 :: Bool
prop_checkSubshellAsTest2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSubshellAsTest String
"( 1 -gt 2 )"
prop_checkSubshellAsTest3 :: Bool
prop_checkSubshellAsTest3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSubshellAsTest String
"( grep -c foo bar )"
prop_checkSubshellAsTest4 :: Bool
prop_checkSubshellAsTest4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSubshellAsTest String
"[ 1 -gt 2 ]"
prop_checkSubshellAsTest5 :: Bool
prop_checkSubshellAsTest5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSubshellAsTest String
"( -e file && -x file )"
prop_checkSubshellAsTest6 :: Bool
prop_checkSubshellAsTest6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSubshellAsTest String
"( -e file || -x file && -t 1 )"
prop_checkSubshellAsTest7 :: Bool
prop_checkSubshellAsTest7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSubshellAsTest String
"( ! -d file )"
checkSubshellAsTest :: p -> Token -> m ()
checkSubshellAsTest p
_ Token
t =
    case Token
t of
        T_Subshell Id
id [Token
w] -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Token -> m ()
check Id
id Token
w
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    check :: Id -> Token -> m ()
check Id
id Token
t = case Token
t of
        (T_Banged Id
_ Token
w) -> Id -> Token -> m ()
check Id
id Token
w
        (T_AndIf Id
_ Token
w Token
_) -> Id -> Token -> m ()
check Id
id Token
w
        (T_OrIf Id
_ Token
w Token
_) -> Id -> Token -> m ()
check Id
id Token
w
        (T_Pipeline Id
_ [Token]
_ [T_Redirecting Id
_ [Token]
_ (T_SimpleCommand Id
_ [] (Token
first:Token
second:[Token]
_))]) ->
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Token -> Token -> m ()
checkParams Id
id Token
first Token
second
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()


    checkParams :: Id -> Token -> Token -> m ()
checkParams Id
id Token
first Token
second = do
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
False (forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
unaryTestOps) forall a b. (a -> b) -> a -> b
$ Token -> Maybe String
getLiteralString Token
first) forall a b. (a -> b) -> a -> b
$
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2204 String
"(..) is a subshell. Did you mean [ .. ], a test expression?"
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
False (forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
binaryTestOps) forall a b. (a -> b) -> a -> b
$ Token -> Maybe String
getLiteralString Token
second) forall a b. (a -> b) -> a -> b
$
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2205 String
"(..) is a subshell. Did you mean [ .. ], a test expression?"


prop_checkSplittingInArrays1 :: Bool
prop_checkSplittingInArrays1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSplittingInArrays String
"a=( $var )"
prop_checkSplittingInArrays2 :: Bool
prop_checkSplittingInArrays2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSplittingInArrays String
"a=( $(cmd) )"
prop_checkSplittingInArrays3 :: Bool
prop_checkSplittingInArrays3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSplittingInArrays String
"a=( \"$var\" )"
prop_checkSplittingInArrays4 :: Bool
prop_checkSplittingInArrays4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSplittingInArrays String
"a=( \"$(cmd)\" )"
prop_checkSplittingInArrays5 :: Bool
prop_checkSplittingInArrays5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSplittingInArrays String
"a=( $! $$ $# )"
prop_checkSplittingInArrays6 :: Bool
prop_checkSplittingInArrays6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSplittingInArrays String
"a=( ${#arr[@]} )"
prop_checkSplittingInArrays7 :: Bool
prop_checkSplittingInArrays7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSplittingInArrays String
"a=( foo{1,2} )"
prop_checkSplittingInArrays8 :: Bool
prop_checkSplittingInArrays8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSplittingInArrays String
"a=( * )"
checkSplittingInArrays :: Parameters -> Token -> m ()
checkSplittingInArrays Parameters
params Token
t =
    case Token
t of
        T_Array Id
_ [Token]
elements -> forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
check [Token]
elements
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    check :: Token -> m ()
check Token
word = case Token
word of
        T_NormalWord Id
_ [Token]
parts -> forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkPart [Token]
parts
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
    checkPart :: Token -> m ()
checkPart Token
part = case Token
part of
        T_DollarExpansion Id
id [Token]
_ -> forall {m :: * -> *}. MonadWriter [TokenComment] m => Id -> m ()
forCommand Id
id
        T_DollarBraceCommandExpansion Id
id [Token]
_ -> forall {m :: * -> *}. MonadWriter [TokenComment] m => Id -> m ()
forCommand Id
id
        T_Backticked Id
id [Token]
_ -> forall {m :: * -> *}. MonadWriter [TokenComment] m => Id -> m ()
forCommand Id
id
        T_DollarBraced Id
id Bool
_ Token
str |
            Bool -> Bool
not (Token -> Bool
isCountingReference Token
part)
            Bool -> Bool -> Bool
&& Bool -> Bool
not (Token -> Bool
isQuotedAlternativeReference Token
part)
            Bool -> Bool -> Bool
&& String -> String
getBracedReference (forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
str) forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [String]
variablesWithoutSpaces
            -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2206 forall a b. (a -> b) -> a -> b
$
                if Parameters -> Shell
shellType Parameters
params forall a. Eq a => a -> a -> Bool
== Shell
Ksh
                then String
"Quote to prevent word splitting/globbing, or split robustly with read -A or while read."
                else String
"Quote to prevent word splitting/globbing, or split robustly with mapfile or read -a."
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()

    forCommand :: Id -> m ()
forCommand Id
id =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2207 forall a b. (a -> b) -> a -> b
$
            if Parameters -> Shell
shellType Parameters
params forall a. Eq a => a -> a -> Bool
== Shell
Ksh
            then String
"Prefer read -A or while read to split command output (or quote to avoid splitting)."
            else String
"Prefer mapfile or read -a to split command output (or quote to avoid splitting)."


prop_checkRedirectionToNumber1 :: Bool
prop_checkRedirectionToNumber1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkRedirectionToNumber String
"( 1 > 2 )"
prop_checkRedirectionToNumber2 :: Bool
prop_checkRedirectionToNumber2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkRedirectionToNumber String
"foo 1>2"
prop_checkRedirectionToNumber3 :: Bool
prop_checkRedirectionToNumber3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkRedirectionToNumber String
"echo foo > '2'"
prop_checkRedirectionToNumber4 :: Bool
prop_checkRedirectionToNumber4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkRedirectionToNumber String
"foo 1>&2"
checkRedirectionToNumber :: p -> Token -> m ()
checkRedirectionToNumber p
_ Token
t = case Token
t of
    T_IoFile Id
id Token
_ Token
word -> forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
        String
file <- Token -> Maybe String
getUnquotedLiteral Token
word
        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isDigit String
file
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2210 String
"This is a file redirection. Was it supposed to be a comparison or fd operation?"
    Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkGlobAsCommand1 :: Bool
prop_checkGlobAsCommand1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobAsCommand String
"foo*"
prop_checkGlobAsCommand2 :: Bool
prop_checkGlobAsCommand2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobAsCommand String
"$(var[i])"
prop_checkGlobAsCommand3 :: Bool
prop_checkGlobAsCommand3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobAsCommand String
"echo foo*"
checkGlobAsCommand :: p -> Token -> m ()
checkGlobAsCommand p
_ Token
t = case Token
t of
    T_SimpleCommand Id
_ [Token]
_ (Token
first:[Token]
_)
        | Token -> Bool
isGlob Token
first ->
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
first) Code
2211 String
"This is a glob used as a command name. Was it supposed to be in ${..}, array, or is it missing quoting?"
    Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkFlagAsCommand1 :: Bool
prop_checkFlagAsCommand1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkFlagAsCommand String
"-e file"
prop_checkFlagAsCommand2 :: Bool
prop_checkFlagAsCommand2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkFlagAsCommand String
"foo\n  --bar=baz"
prop_checkFlagAsCommand3 :: Bool
prop_checkFlagAsCommand3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkFlagAsCommand String
"'--myexec--' args"
prop_checkFlagAsCommand4 :: Bool
prop_checkFlagAsCommand4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkFlagAsCommand String
"var=cmd --arg"  -- Handled by SC2037
checkFlagAsCommand :: p -> Token -> m ()
checkFlagAsCommand p
_ Token
t = case Token
t of
    T_SimpleCommand Id
_ [] (Token
first:[Token]
_)
        | Token -> Bool
isUnquotedFlag Token
first ->
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
first) Code
2215 String
"This flag is used as a command name. Bad line break or missing [ .. ]?"
    Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkEmptyCondition1 :: Bool
prop_checkEmptyCondition1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkEmptyCondition String
"if [ ]; then ..; fi"
prop_checkEmptyCondition2 :: Bool
prop_checkEmptyCondition2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkEmptyCondition String
"[ foo -o bar ]"
checkEmptyCondition :: p -> Token -> m ()
checkEmptyCondition p
_ Token
t = case Token
t of
    TC_Empty Id
id ConditionType
_ -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style Id
id Code
2212 String
"Use 'false' instead of empty [/[[ conditionals."
    Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkPipeToNowhere1 :: Bool
prop_checkPipeToNowhere1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkPipeToNowhere String
"foo | echo bar"
prop_checkPipeToNowhere2 :: Bool
prop_checkPipeToNowhere2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkPipeToNowhere String
"basename < file.txt"
prop_checkPipeToNowhere3 :: Bool
prop_checkPipeToNowhere3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkPipeToNowhere String
"printf 'Lol' <<< str"
prop_checkPipeToNowhere4 :: Bool
prop_checkPipeToNowhere4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkPipeToNowhere String
"printf 'Lol' << eof\nlol\neof\n"
prop_checkPipeToNowhere5 :: Bool
prop_checkPipeToNowhere5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkPipeToNowhere String
"echo foo | xargs du"
prop_checkPipeToNowhere6 :: Bool
prop_checkPipeToNowhere6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkPipeToNowhere String
"ls | echo $(cat)"
prop_checkPipeToNowhere7 :: Bool
prop_checkPipeToNowhere7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkPipeToNowhere String
"echo foo | var=$(cat) ls"
prop_checkPipeToNowhere9 :: Bool
prop_checkPipeToNowhere9 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkPipeToNowhere String
"mv -i f . < /dev/stdin"
prop_checkPipeToNowhere10 :: Bool
prop_checkPipeToNowhere10 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkPipeToNowhere String
"ls > file | grep foo"
prop_checkPipeToNowhere11 :: Bool
prop_checkPipeToNowhere11 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkPipeToNowhere String
"ls | grep foo < file"
prop_checkPipeToNowhere12 :: Bool
prop_checkPipeToNowhere12 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkPipeToNowhere String
"ls > foo > bar"
prop_checkPipeToNowhere13 :: Bool
prop_checkPipeToNowhere13 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkPipeToNowhere String
"ls > foo 2> bar > baz"
prop_checkPipeToNowhere14 :: Bool
prop_checkPipeToNowhere14 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkPipeToNowhere String
"ls > foo &> bar"
prop_checkPipeToNowhere15 :: Bool
prop_checkPipeToNowhere15 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkPipeToNowhere String
"ls > foo 2> bar |& grep 'No space left'"
prop_checkPipeToNowhere16 :: Bool
prop_checkPipeToNowhere16 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkPipeToNowhere String
"echo World | cat << EOF\nhello $(cat)\nEOF\n"
prop_checkPipeToNowhere17 :: Bool
prop_checkPipeToNowhere17 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkPipeToNowhere String
"echo World | cat << 'EOF'\nhello $(cat)\nEOF\n"
prop_checkPipeToNowhere18 :: Bool
prop_checkPipeToNowhere18 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkPipeToNowhere String
"ls 1>&3 3>&1 3>&- | wc -l"
prop_checkPipeToNowhere19 :: Bool
prop_checkPipeToNowhere19 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkPipeToNowhere String
"find . -print0 | du --files0-from=/dev/stdin"
prop_checkPipeToNowhere20 :: Bool
prop_checkPipeToNowhere20 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkPipeToNowhere String
"find . | du --exclude-from=/dev/fd/0"

data PipeType = StdoutPipe | StdoutStderrPipe | NoPipe deriving (PipeType -> PipeType -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: PipeType -> PipeType -> Bool
$c/= :: PipeType -> PipeType -> Bool
== :: PipeType -> PipeType -> Bool
$c== :: PipeType -> PipeType -> Bool
Eq)
checkPipeToNowhere :: Parameters -> Token -> WriterT [TokenComment] Identity ()
checkPipeToNowhere :: Parameters -> Token -> Writer [TokenComment] ()
checkPipeToNowhere Parameters
params Token
t =
    case Token
t of
        T_Pipeline Id
_ [Token]
pipes [Token]
cmds ->
            forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
(PipeType, Token, PipeType) -> m ()
checkPipe forall a b. (a -> b) -> a -> b
$ forall {b}. [Token] -> [b] -> [(PipeType, b, PipeType)]
commandsWithContext [Token]
pipes [Token]
cmds
        T_Redirecting Id
_ [Token]
redirects Token
cmd | forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
redirectsStdin [Token]
redirects -> forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkRedir Token
cmd
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    checkPipe :: (PipeType, Token, PipeType) -> m ()
checkPipe (PipeType
input, Token
stage, PipeType
output) = do
        let hasConsumers :: Bool
hasConsumers = Token -> Bool
hasAdditionalConsumers Token
stage
        let hasProducers :: Bool
hasProducers = Token -> Bool
hasAdditionalProducers Token
stage

        forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
            Token
cmd <- Token -> Maybe Token
getCommand Token
stage
            String
name <- Token -> Maybe String
getCommandBasename Token
cmd
            forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ String
name forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
nonReadingCommands
            forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ Bool -> Bool
not Bool
hasConsumers Bool -> Bool -> Bool
&& PipeType
input forall a. Eq a => a -> a -> Bool
/= PipeType
NoPipe
            forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ String -> Token -> Bool
commandSpecificException String
name Token
cmd

            -- Confusing echo for cat is so common that it's worth a special case
            let suggestion :: String
suggestion =
                    if String
name forall a. Eq a => a -> a -> Bool
== String
"echo"
                    then String
"Did you want 'cat' instead?"
                    else String
"Wrong command or missing xargs?"
            forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
cmd) Code
2216 forall a b. (a -> b) -> a -> b
$
                String
"Piping to '" forall a. [a] -> [a] -> [a]
++ String
name forall a. [a] -> [a] -> [a]
++ String
"', a command that doesn't read stdin. " forall a. [a] -> [a] -> [a]
++ String
suggestion

        forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
            T_Redirecting Id
_ [Token]
redirs Token
cmd <- forall (m :: * -> *) a. Monad m => a -> m a
return Token
stage
            [[Code]]
fds <- forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
mapM forall {a}. (Num a, Read a) => Token -> Maybe [a]
getRedirectionFds [Token]
redirs

            let fdAndToken :: [(Integer, Token)]
                fdAndToken :: [(Code, Token)]
fdAndToken =
                  forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (\([Code]
list, Token
redir) -> forall a b. (a -> b) -> [a] -> [b]
map (\Code
n -> (Code
n, Token
redir)) [Code]
list) forall a b. (a -> b) -> a -> b
$
                    forall a b. [a] -> [b] -> [(a, b)]
zip [[Code]]
fds [Token]
redirs

            let fdMap :: Map Code [Token]
fdMap =
                  forall k a. Ord k => (a -> a -> a) -> [(k, a)] -> Map k a
Map.fromListWith forall a. [a] -> [a] -> [a]
(++) forall a b. (a -> b) -> a -> b
$
                    forall a b. (a -> b) -> [a] -> [b]
map (\(Code
a,Token
b) -> (Code
a,[Token
b])) [(Code, Token)]
fdAndToken

            let inputWarning :: m ()
inputWarning = forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
                    forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ PipeType
input forall a. Eq a => a -> a -> Bool
/= PipeType
NoPipe Bool -> Bool -> Bool
&& Bool -> Bool
not Bool
hasConsumers
                    (Token
override:[Token]
_) <- forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup Code
0 Map Code [Token]
fdMap
                    forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getOpId Token
override) Code
2259 forall a b. (a -> b) -> a -> b
$
                        String
"This redirection overrides piped input. To use both, merge or pass filenames."

            -- Only produce output warnings for regular pipes, since these are
            -- way more common, and  `foo > out 2> err |& foo` can still write
            -- to stderr if the files fail to open
            let outputWarning :: m ()
outputWarning = forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
                    forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ PipeType
output forall a. Eq a => a -> a -> Bool
== PipeType
StdoutPipe Bool -> Bool -> Bool
&& Bool -> Bool
not Bool
hasProducers
                    (Token
override:[Token]
_) <- forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup Code
1 Map Code [Token]
fdMap
                    forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getOpId Token
override) Code
2260 forall a b. (a -> b) -> a -> b
$
                        String
"This redirection overrides the output pipe. Use 'tee' to output to both."

            forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ do
                m ()
inputWarning
                m ()
outputWarning
                forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *} {a}.
(MonadWriter [TokenComment] m, Eq a, Num a, Show a) =>
(a, [Token]) -> m ()
warnAboutDupes forall a b. (a -> b) -> a -> b
$ forall k a. Map k a -> [(k, a)]
Map.assocs Map Code [Token]
fdMap

    commandSpecificException :: String -> Token -> Bool
commandSpecificException String
name Token
cmd =
        case String
name of
            String
"du" -> forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any ((forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"exclude-from", String
"files0-from"]) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a, b) -> b
snd) forall a b. (a -> b) -> a -> b
$ Token -> [(Token, String)]
getAllFlags Token
cmd
            String
_ -> Bool
False

    warnAboutDupes :: (a, [Token]) -> m ()
warnAboutDupes (a
n, list :: [Token]
list@(Token
_:Token
_:[Token]
_)) =
        forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ [Token]
list forall a b. (a -> b) -> a -> b
$ \Token
c -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getOpId Token
c) Code
2261 forall a b. (a -> b) -> a -> b
$
            String
"Multiple redirections compete for " forall a. [a] -> [a] -> [a]
++ forall {a}. (Eq a, Num a, Show a) => a -> String
str a
n forall a. [a] -> [a] -> [a]
++ String
". Use cat, tee, or pass filenames instead."
    warnAboutDupes (a, [Token])
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

    alternative :: String
alternative =
        if Parameters -> Shell
shellType Parameters
params forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Shell
Bash, Shell
Ksh]
        then String
"process substitutions or temp files"
        else String
"temporary files"

    str :: a -> String
str a
n =
        case a
n of
            a
0 -> String
"stdin"
            a
1 -> String
"stdout"
            a
2 -> String
"stderr"
            a
_ -> String
"FD " forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> String
show a
n

    checkRedir :: Token -> m ()
checkRedir Token
cmd = forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
        String
name <- Token -> Maybe String
getCommandBasename Token
cmd
        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ String
name forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
nonReadingCommands
        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ Token -> Bool
hasAdditionalConsumers Token
cmd
        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ String
name forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"cp", String
"mv", String
"rm"] Bool -> Bool -> Bool
&& Token
cmd Token -> String -> Bool
`hasFlag` String
"i"
        let suggestion :: String
suggestion =
                if String
name forall a. Eq a => a -> a -> Bool
== String
"echo"
                then String
"Did you want 'cat' instead?"
                else String
"Bad quoting, wrong command or missing xargs?"
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
cmd) Code
2217 forall a b. (a -> b) -> a -> b
$
            String
"Redirecting to '" forall a. [a] -> [a] -> [a]
++ String
name forall a. [a] -> [a] -> [a]
++ String
"', a command that doesn't read stdin. " forall a. [a] -> [a] -> [a]
++ String
suggestion

    -- Could any words in a SimpleCommand consume stdin (e.g. echo "$(cat)")?
    hasAdditionalConsumers :: Token -> Bool
hasAdditionalConsumers = (Token -> Bool) -> Token -> Bool
treeContains Token -> Bool
mayConsume
    -- Could any words in a SimpleCommand produce stdout? E.g. >(tee foo)
    hasAdditionalProducers :: Token -> Bool
hasAdditionalProducers = (Token -> Bool) -> Token -> Bool
treeContains Token -> Bool
mayProduce
    treeContains :: (Token -> Bool) -> Token -> Bool
treeContains Token -> Bool
pred Token
t = forall a. Maybe a -> Bool
isNothing forall a b. (a -> b) -> a -> b
$
        forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> Token -> m Token
doAnalysis (forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> Bool
pred) Token
t

    mayConsume :: Token -> Bool
mayConsume Token
t =
        case Token
t of
            T_ProcSub Id
_ String
"<" [Token]
_ -> Bool
True
            T_Backticked {} -> Bool
True
            T_DollarExpansion {} -> Bool
True
            Token
_ -> Bool
False

    mayProduce :: Token -> Bool
mayProduce Token
t =
        case Token
t of
            T_ProcSub Id
_ String
">" [Token]
_ -> Bool
True
            Token
_ -> Bool
False

    getOpId :: Token -> Id
getOpId Token
t =
        case Token
t of
            T_FdRedirect Id
_ String
_ Token
x -> Token -> Id
getOpId Token
x
            T_IoFile Id
_ Token
op Token
_ -> Token -> Id
getId Token
op
            Token
_ -> Token -> Id
getId Token
t

    getRedirectionFds :: Token -> Maybe [a]
getRedirectionFds Token
t =
        case Token
t of
            T_FdRedirect Id
_ String
"" Token
x -> forall {a}. Num a => Token -> Maybe [a]
getDefaultFds Token
x
            T_FdRedirect Id
_ String
"&" Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return [a
1, a
2]
            T_FdRedirect Id
_ String
num Token
x | forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isDigit String
num ->
                -- Don't report the number unless we know what it is.
                -- This avoids triggering on 3>&1 1>&3
                forall {a}. Num a => Token -> Maybe [a]
getDefaultFds Token
x forall (f :: * -> *) a b. Applicative f => f a -> f b -> f b
*> forall (m :: * -> *) a. Monad m => a -> m a
return [forall a. Read a => String -> a
read String
num]
            -- Don't bother with {fd}>42 and such
            Token
_ -> forall a. Maybe a
Nothing

    getDefaultFds :: Token -> Maybe [a]
getDefaultFds Token
redir =
        case Token
redir of
            T_HereDoc {} -> forall (m :: * -> *) a. Monad m => a -> m a
return [a
0]
            T_HereString {} -> forall (m :: * -> *) a. Monad m => a -> m a
return [a
0]
            T_IoFile Id
_ Token
op Token
_ ->
                case Token
op of
                    T_Less {} -> forall (m :: * -> *) a. Monad m => a -> m a
return [a
0]
                    T_Greater {} -> forall (m :: * -> *) a. Monad m => a -> m a
return [a
1]
                    T_DGREAT {} -> forall (m :: * -> *) a. Monad m => a -> m a
return [a
1]
                    T_GREATAND {} -> forall (m :: * -> *) a. Monad m => a -> m a
return [a
1, a
2]
                    T_CLOBBER {} -> forall (m :: * -> *) a. Monad m => a -> m a
return [a
1]
                    T_IoDuplicate Id
_ Token
op String
"-" -> Token -> Maybe [a]
getDefaultFds Token
op
                    Token
_ -> forall a. Maybe a
Nothing
            Token
_ -> forall a. Maybe a
Nothing

    redirectsStdin :: Token -> Bool
redirectsStdin Token
t =
        forall a. a -> Maybe a -> a
fromMaybe Bool
False forall a b. (a -> b) -> a -> b
$ do
            [Code]
fds <- forall {a}. (Num a, Read a) => Token -> Maybe [a]
getRedirectionFds Token
t
            forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Code
0 forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Code]
fds

    pipeType :: Token -> PipeType
pipeType Token
t =
        case Token
t of
            T_Pipe Id
_ String
"|" -> PipeType
StdoutPipe
            T_Pipe Id
_ String
"|&" -> PipeType
StdoutStderrPipe
            Token
_ -> PipeType
NoPipe

    commandsWithContext :: [Token] -> [b] -> [(PipeType, b, PipeType)]
commandsWithContext [Token]
pipes [b]
cmds =
        let pipeTypes :: [PipeType]
pipeTypes = forall a b. (a -> b) -> [a] -> [b]
map Token -> PipeType
pipeType [Token]
pipes
            inputs :: [PipeType]
inputs = PipeType
NoPipe forall a. a -> [a] -> [a]
: [PipeType]
pipeTypes
            outputs :: [PipeType]
outputs = [PipeType]
pipeTypes forall a. [a] -> [a] -> [a]
++ [PipeType
NoPipe]
        in
            forall a b c. [a] -> [b] -> [c] -> [(a, b, c)]
zip3 [PipeType]
inputs [b]
cmds [PipeType]
outputs

prop_checkUseBeforeDefinition1 :: Bool
prop_checkUseBeforeDefinition1 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {t}. t -> Token -> [TokenComment]
checkUseBeforeDefinition String
"f; f() { true; }"
prop_checkUseBeforeDefinition2 :: Bool
prop_checkUseBeforeDefinition2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {t}. t -> Token -> [TokenComment]
checkUseBeforeDefinition String
"f() { true; }; f"
prop_checkUseBeforeDefinition3 :: Bool
prop_checkUseBeforeDefinition3 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {t}. t -> Token -> [TokenComment]
checkUseBeforeDefinition String
"if ! mycmd --version; then mycmd() { true; }; fi"
prop_checkUseBeforeDefinition4 :: Bool
prop_checkUseBeforeDefinition4 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {t}. t -> Token -> [TokenComment]
checkUseBeforeDefinition String
"mycmd || mycmd() { f; }"
checkUseBeforeDefinition :: p -> Token -> [TokenComment]
checkUseBeforeDefinition p
_ Token
t =
    forall w a. Writer w a -> w
execWriter forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) s a. Monad m => StateT s m a -> s -> m a
evalStateT (forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}.
(MonadState (Map String Token) m, MonadWriter [TokenComment] m) =>
Token -> m ()
examine forall a b. (a -> b) -> a -> b
$ [Token]
revCommands) forall k a. Map k a
Map.empty
  where
    examine :: Token -> m ()
examine Token
t = case Token
t of
        T_Pipeline Id
_ [Token]
_ [T_Redirecting Id
_ [Token]
_ (T_Function Id
_ FunctionKeyword
_ FunctionParentheses
_ String
name Token
_)] ->
            forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify forall a b. (a -> b) -> a -> b
$ forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert String
name Token
t
        T_Annotation Id
_ [Annotation]
_ Token
w -> Token -> m ()
examine Token
w
        T_Pipeline Id
_ [Token]
_ [Token]
cmds -> do
            Map String Token
m <- forall s (m :: * -> *). MonadState s m => m s
get
            forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (forall k a. Map k a -> Bool
Map.null Map String Token
m) forall a b. (a -> b) -> a -> b
$
                forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (forall {m :: * -> *} {a}.
MonadWriter [TokenComment] m =>
Map String a -> Token -> m ()
checkUsage Map String Token
m) forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Token -> [Token]
recursiveSequences [Token]
cmds
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()

    checkUsage :: Map String a -> Token -> m ()
checkUsage Map String a
map Token
cmd = forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
        String
name <- Token -> Maybe String
getCommandName Token
cmd
        a
def <- forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup String
name Map String a
map
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
cmd) Code
2218
                String
"This function is only defined later. Move the definition up."

    revCommands :: [Token]
revCommands = forall a. [a] -> [a]
reverse forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ Token -> [[Token]]
getCommandSequences Token
t
    recursiveSequences :: Token -> [Token]
recursiveSequences Token
x =
        let list :: [Token]
list = forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ Token -> [[Token]]
getCommandSequences Token
x in
            if forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Token]
list
            then [Token
x]
            else forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Token -> [Token]
recursiveSequences [Token]
list

prop_checkForLoopGlobVariables1 :: Bool
prop_checkForLoopGlobVariables1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkForLoopGlobVariables String
"for i in $var/*.txt; do true; done"
prop_checkForLoopGlobVariables2 :: Bool
prop_checkForLoopGlobVariables2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkForLoopGlobVariables String
"for i in \"$var\"/*.txt; do true; done"
prop_checkForLoopGlobVariables3 :: Bool
prop_checkForLoopGlobVariables3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkForLoopGlobVariables String
"for i in $var; do true; done"
checkForLoopGlobVariables :: p -> Token -> m ()
checkForLoopGlobVariables p
_ Token
t =
    case Token
t of
        T_ForIn Id
_ String
_ [Token]
words [Token]
_ -> forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
check [Token]
words
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    check :: Token -> f ()
check (T_NormalWord Id
_ [Token]
parts) =
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
isGlob [Token]
parts) forall a b. (a -> b) -> a -> b
$
            forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
suggest forall a b. (a -> b) -> a -> b
$ forall a. (a -> Bool) -> [a] -> [a]
filter Token -> Bool
isQuoteableExpansion [Token]
parts
    suggest :: Token -> m ()
suggest Token
t = forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
t) Code
2231
        String
"Quote expansions in this for loop glob to prevent wordsplitting, e.g. \"$dir\"/*.txt ."


prop_checkSubshelledTests1 :: Bool
prop_checkSubshelledTests1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSubshelledTests String
"a && ( [ b ] || ! [ c ] )"
prop_checkSubshelledTests2 :: Bool
prop_checkSubshelledTests2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSubshelledTests String
"( [ a ] )"
prop_checkSubshelledTests3 :: Bool
prop_checkSubshelledTests3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSubshelledTests String
"( [ a ] && [ b ] || test c )"
prop_checkSubshelledTests4 :: Bool
prop_checkSubshelledTests4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSubshelledTests String
"( [ a ] && { [ b ] && [ c ]; } )"
prop_checkSubshelledTests5 :: Bool
prop_checkSubshelledTests5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSubshelledTests String
"( [[ ${var:=x} = y ]] )"
prop_checkSubshelledTests6 :: Bool
prop_checkSubshelledTests6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSubshelledTests String
"( [[ $((i++)) = 10 ]] )"
prop_checkSubshelledTests7 :: Bool
prop_checkSubshelledTests7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSubshelledTests String
"( [[ $((i+=1)) = 10 ]] )"
prop_checkSubshelledTests8 :: Bool
prop_checkSubshelledTests8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSubshelledTests String
"# shellcheck disable=SC2234\nf() ( [[ x ]] )"

checkSubshelledTests :: Parameters -> Token -> m ()
checkSubshelledTests Parameters
params Token
t =
    case Token
t of
        T_Subshell Id
id [Token]
list | forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Token -> Bool
isTestStructure [Token]
list Bool -> Bool -> Bool
&& (Bool -> Bool
not (Token -> Bool
hasAssignment Token
t))  ->
            case () of
                -- Special case for if (test) and while (test)
                ()
_ | [Token] -> Bool
isCompoundCondition (Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) Token
t) ->
                    forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style Id
id Code
2233 String
"Remove superfluous (..) around condition to avoid subshell overhead."

                -- Special case for ([ x ]), except for func() ( [ x ] )
                ()
_ | [Token] -> Bool
isSingleTest [Token]
list Bool -> Bool -> Bool
&& (Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ [Token] -> Bool
isFunctionBody (Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) Token
t)) ->
                    forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style Id
id Code
2234 String
"Remove superfluous (..) around test command to avoid subshell overhead."

                -- General case for ([ x ] || [ y ] && etc)
                ()
_ -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style Id
id Code
2235 String
"Use { ..; } instead of (..) to avoid subshell overhead."
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where

    isSingleTest :: [Token] -> Bool
isSingleTest [Token]
cmds =
        case [Token]
cmds of
            [Token
c] | Token -> Bool
isTestCommand Token
c -> Bool
True
            [Token]
_ -> Bool
False

    isFunctionBody :: [Token] -> Bool
isFunctionBody [Token]
path =
        case [Token]
path of
            (Token
_:Token
f:[Token]
_) -> Token -> Bool
isFunction Token
f
            [Token]
_ -> Bool
False

    isTestStructure :: Token -> Bool
isTestStructure Token
t =
        case Token
t of
            T_Banged Id
_ Token
t -> Token -> Bool
isTestStructure Token
t
            T_AndIf Id
_ Token
a Token
b -> Token -> Bool
isTestStructure Token
a Bool -> Bool -> Bool
&& Token -> Bool
isTestStructure Token
b
            T_OrIf  Id
_ Token
a Token
b -> Token -> Bool
isTestStructure Token
a Bool -> Bool -> Bool
&& Token -> Bool
isTestStructure Token
b
            T_Pipeline Id
_ [] [T_Redirecting Id
_ [Token]
_ Token
cmd] ->
                case Token
cmd of
                    T_BraceGroup Id
_ [Token]
ts -> forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Token -> Bool
isTestStructure [Token]
ts
                    T_Subshell   Id
_ [Token]
ts -> forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Token -> Bool
isTestStructure [Token]
ts
                    Token
_ -> Token -> Bool
isTestCommand Token
t
            Token
_ -> Token -> Bool
isTestCommand Token
t

    isTestCommand :: Token -> Bool
isTestCommand Token
t =
        case Token
t of
            T_Pipeline Id
_ [] [T_Redirecting Id
_ [Token]
_ Token
cmd] ->
                case Token
cmd of
                    T_Condition {} -> Bool
True
                    Token
_ -> Token
cmd Token -> String -> Bool
`isCommand` String
"test"
            Token
_ -> Bool
False

    -- Check if a T_Subshell is used as a condition, e.g. if ( test )
    -- This technically also triggers for `if true; then ( test ); fi`
    -- but it's still a valid suggestion.
    isCompoundCondition :: [Token] -> Bool
isCompoundCondition [Token]
chain =
        case forall a. (a -> Bool) -> [a] -> [a]
dropWhile Token -> Bool
skippable (forall a. Int -> [a] -> [a]
drop Int
1 [Token]
chain) of
            T_IfExpression {}    : [Token]
_ -> Bool
True
            T_WhileExpression {} : [Token]
_ -> Bool
True
            T_UntilExpression {} : [Token]
_ -> Bool
True
            [Token]
_ -> Bool
False

    hasAssignment :: Token -> Bool
hasAssignment Token
t = forall a. Maybe a -> Bool
isNothing forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> Token -> m Token
doAnalysis Token -> Maybe ()
guardNotAssignment Token
t
    guardNotAssignment :: Token -> Maybe ()
guardNotAssignment Token
t =
        case Token
t of
            TA_Assignment {} -> forall a. Maybe a
Nothing
            TA_Unary Id
_ String
s Token
_ -> forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ String
"++" forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` String
s Bool -> Bool -> Bool
|| String
"--" forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` String
s
            T_DollarBraced Id
_ Bool
_ Token
l ->
                let str :: String
str = forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
l
                    modifier :: String
modifier = String -> String
getBracedModifier String
str
                in
                    forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ String
"=" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
modifier Bool -> Bool -> Bool
|| String
":=" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
modifier
            T_DollarBraceCommandExpansion {} -> forall a. Maybe a
Nothing
            Token
_ -> forall a. a -> Maybe a
Just ()

    -- Skip any parent of a T_Subshell until we reach something interesting
    skippable :: Token -> Bool
skippable Token
t =
        case Token
t of
            T_Redirecting Id
_ [] Token
_ -> Bool
True
            T_Pipeline Id
_ [] [Token]
_ -> Bool
True
            T_Annotation {} -> Bool
True
            Token
_ -> Bool
False

prop_checkInvertedStringTest1 :: Bool
prop_checkInvertedStringTest1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkInvertedStringTest String
"[ ! -z $var ]"
prop_checkInvertedStringTest2 :: Bool
prop_checkInvertedStringTest2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkInvertedStringTest String
"! [[ -n $var ]]"
prop_checkInvertedStringTest3 :: Bool
prop_checkInvertedStringTest3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkInvertedStringTest String
"! [ -x $var ]"
prop_checkInvertedStringTest4 :: Bool
prop_checkInvertedStringTest4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkInvertedStringTest String
"[[ ! -w $var ]]"
prop_checkInvertedStringTest5 :: Bool
prop_checkInvertedStringTest5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkInvertedStringTest String
"[ -z $var ]"
checkInvertedStringTest :: p -> Token -> m ()
checkInvertedStringTest p
_ Token
t =
    case Token
t of
        TC_Unary Id
_ ConditionType
_ String
"!" (TC_Unary Id
_ ConditionType
_ String
op Token
_) ->
            case String
op of
                String
"-n" -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style (Token -> Id
getId Token
t) Code
2236 String
"Use -z instead of ! -n."
                String
"-z" -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style (Token -> Id
getId Token
t) Code
2236 String
"Use -n instead of ! -z."
                String
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
        T_Banged Id
_ (T_Pipeline Id
_ [Token]
_
          [T_Redirecting Id
_ [Token]
_ (T_Condition Id
_ ConditionType
_ (TC_Unary Id
_ ConditionType
_ String
op Token
_))]) ->
            case String
op of
                String
"-n" -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style (Token -> Id
getId Token
t) Code
2237 String
"Use [ -z .. ] instead of ! [ -n .. ]."
                String
"-z" -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style (Token -> Id
getId Token
t) Code
2237 String
"Use [ -n .. ] instead of ! [ -z .. ]."
                String
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkRedirectionToCommand1 :: Bool
prop_checkRedirectionToCommand1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkRedirectionToCommand String
"ls > rm"
prop_checkRedirectionToCommand2 :: Bool
prop_checkRedirectionToCommand2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkRedirectionToCommand String
"ls > 'rm'"
prop_checkRedirectionToCommand3 :: Bool
prop_checkRedirectionToCommand3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkRedirectionToCommand String
"ls > myfile"
checkRedirectionToCommand :: p -> Token -> m ()
checkRedirectionToCommand p
_ Token
t =
    case Token
t of
        T_IoFile Id
_ Token
_ (T_NormalWord Id
id [T_Literal Id
_ String
str]) | String
str forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
commonCommands
            Bool -> Bool -> Bool
&& String
str forall a. Eq a => a -> a -> Bool
/= String
"file" -> -- This would be confusing
                forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2238 String
"Redirecting to/from command name instead of file. Did you want pipes/xargs (or quote to ignore)?"
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkNullaryExpansionTest1 :: Bool
prop_checkNullaryExpansionTest1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNullaryExpansionTest String
"[[ $(a) ]]"
prop_checkNullaryExpansionTest2 :: Bool
prop_checkNullaryExpansionTest2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNullaryExpansionTest String
"[[ $a ]]"
prop_checkNullaryExpansionTest3 :: Bool
prop_checkNullaryExpansionTest3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNullaryExpansionTest String
"[[ $a=1 ]]"
prop_checkNullaryExpansionTest4 :: Bool
prop_checkNullaryExpansionTest4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNullaryExpansionTest String
"[[ -n $(a) ]]"
prop_checkNullaryExpansionTest5 :: Bool
prop_checkNullaryExpansionTest5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNullaryExpansionTest String
"[[ \"$a$b\" ]]"
prop_checkNullaryExpansionTest6 :: Bool
prop_checkNullaryExpansionTest6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNullaryExpansionTest String
"[[ `x` ]]"
checkNullaryExpansionTest :: Parameters -> Token -> m ()
checkNullaryExpansionTest Parameters
params Token
t =
    case Token
t of
        TC_Nullary Id
_ ConditionType
_ Token
word ->
            case Token -> [Token]
getWordParts Token
word of
                [Token
t] | Token -> Bool
isCommandSubstitution Token
t ->
                    forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> Fix -> m ()
styleWithFix Id
id Code
2243 String
"Prefer explicit -n to check for output (or run command without [/[[ to check for success)." Fix
fix

                -- If they're constant, you get SC2157 &co
                [Token]
x | forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> Bool
isConstant) [Token]
x ->
                    forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> Fix -> m ()
styleWithFix Id
id Code
2244 String
"Prefer explicit -n to check non-empty string (or use =/-ne to check boolean/integer)." Fix
fix
                [Token]
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
            where
                id :: Id
id = Token -> Id
getId Token
word
                fix :: Fix
fix = [Replacement] -> Fix
fixWith [Id -> Parameters -> Code -> String -> Replacement
replaceStart Id
id Parameters
params Code
0 String
"-n "]
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkDollarQuoteParen1 :: Bool
prop_checkDollarQuoteParen1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkDollarQuoteParen String
"$\"(foo)\""
prop_checkDollarQuoteParen2 :: Bool
prop_checkDollarQuoteParen2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkDollarQuoteParen String
"$\"{foo}\""
prop_checkDollarQuoteParen3 :: Bool
prop_checkDollarQuoteParen3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkDollarQuoteParen String
"\"$(foo)\""
prop_checkDollarQuoteParen4 :: Bool
prop_checkDollarQuoteParen4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkDollarQuoteParen String
"$\"..\""
checkDollarQuoteParen :: Parameters -> Token -> m ()
checkDollarQuoteParen Parameters
params Token
t =
    case Token
t of
        T_DollarDoubleQuoted Id
id ((T_Literal Id
_ (Char
c:String
_)):[Token]
_) | Char
c forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
"({" ->
            forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> Fix -> m ()
warnWithFix Id
id Code
2247 String
"Flip leading $ and \" if this should be a quoted substitution." (Id -> Fix
fix Id
id)
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    fix :: Id -> Fix
fix Id
id = [Replacement] -> Fix
fixWith [Id -> Parameters -> Code -> String -> Replacement
replaceStart Id
id Parameters
params Code
2 String
"\"$"]

prop_checkTranslatedStringVariable1 :: Bool
prop_checkTranslatedStringVariable1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkTranslatedStringVariable String
"foo_bar2=val; $\"foo_bar2\""
prop_checkTranslatedStringVariable2 :: Bool
prop_checkTranslatedStringVariable2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkTranslatedStringVariable String
"$\"foo_bar2\""
prop_checkTranslatedStringVariable3 :: Bool
prop_checkTranslatedStringVariable3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkTranslatedStringVariable String
"$\"..\""
prop_checkTranslatedStringVariable4 :: Bool
prop_checkTranslatedStringVariable4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkTranslatedStringVariable String
"var=val; $\"$var\""
prop_checkTranslatedStringVariable5 :: Bool
prop_checkTranslatedStringVariable5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkTranslatedStringVariable String
"foo=var; bar=val2; $\"foo bar\""
checkTranslatedStringVariable :: Parameters -> Token -> m ()
checkTranslatedStringVariable Parameters
params (T_DollarDoubleQuoted Id
id [T_Literal Id
_ String
s])
  | forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isVariableChar String
s
  Bool -> Bool -> Bool
&& forall k a. Ord k => k -> Map k a -> Bool
Map.member String
s Map String Token
assignments
  = forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> Fix -> m ()
warnWithFix Id
id Code
2256 String
"This translated string is the name of a variable. Flip leading $ and \" if this should be a quoted substitution." (Id -> Fix
fix Id
id)
  where
    assignments :: Map String Token
assignments = forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl (forall a b c. (a -> b -> c) -> b -> a -> c
flip forall a b. (a -> b) -> a -> b
($)) forall k a. Map k a
Map.empty (forall a b. (a -> b) -> [a] -> [b]
map StackData -> Map String Token -> Map String Token
insertAssignment forall a b. (a -> b) -> a -> b
$ Parameters -> [StackData]
variableFlow Parameters
params)
    insertAssignment :: StackData -> Map String Token -> Map String Token
insertAssignment (Assignment (Token
_, Token
token, String
name, DataType
_)) | String -> Bool
isVariableName String
name =
        forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert String
name Token
token
    insertAssignment StackData
_ = forall a. a -> a
Prelude.id
    fix :: Id -> Fix
fix Id
id = [Replacement] -> Fix
fixWith [Id -> Parameters -> Code -> String -> Replacement
replaceStart Id
id Parameters
params Code
2 String
"\"$"]
checkTranslatedStringVariable Parameters
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkDefaultCase1 :: Bool
prop_checkDefaultCase1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkDefaultCase String
"case $1 in a) true ;; esac"
prop_checkDefaultCase2 :: Bool
prop_checkDefaultCase2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkDefaultCase String
"case $1 in ?*?) true ;; *? ) true ;; esac"
prop_checkDefaultCase3 :: Bool
prop_checkDefaultCase3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkDefaultCase String
"case $1 in x|*) true ;; esac"
prop_checkDefaultCase4 :: Bool
prop_checkDefaultCase4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkDefaultCase String
"case $1 in **) true ;; esac"
checkDefaultCase :: p -> Token -> f ()
checkDefaultCase p
_ Token
t =
    case Token
t of
        T_CaseExpression Id
id Token
_ [(CaseType, [Token], [Token])]
list ->
            forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any forall {t :: * -> *} {a} {c}. Foldable t => (a, t Token, c) -> Bool
canMatchAny [(CaseType, [Token], [Token])]
list) forall a b. (a -> b) -> a -> b
$
                forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info Id
id Code
2249 String
"Consider adding a default *) case, even if it just exits with error."
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    canMatchAny :: (a, t Token, c) -> Bool
canMatchAny (a
_, t Token
list, c
_) = forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
canMatchAny' t Token
list
    -- hlint objects to 'pattern' as a variable name
    canMatchAny' :: Token -> Bool
canMatchAny' Token
pat = forall a. a -> Maybe a -> a
fromMaybe Bool
False forall a b. (a -> b) -> a -> b
$ do
        [PseudoGlob]
pg <- Token -> Maybe [PseudoGlob]
wordToExactPseudoGlob Token
pat
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ [PseudoGlob] -> [PseudoGlob] -> Bool
pseudoGlobIsSuperSetof [PseudoGlob]
pg [PseudoGlob
PGMany]

prop_checkUselessBang1 :: Bool
prop_checkUselessBang1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUselessBang String
"set -e; ! true; rest"
prop_checkUselessBang2 :: Bool
prop_checkUselessBang2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUselessBang String
"! true; rest"
prop_checkUselessBang3 :: Bool
prop_checkUselessBang3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUselessBang String
"set -e; while true; do ! true; done"
prop_checkUselessBang4 :: Bool
prop_checkUselessBang4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUselessBang String
"set -e; if ! true; then true; fi"
prop_checkUselessBang5 :: Bool
prop_checkUselessBang5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUselessBang String
"set -e; ( ! true )"
prop_checkUselessBang6 :: Bool
prop_checkUselessBang6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUselessBang String
"set -e; { ! true; }"
prop_checkUselessBang7 :: Bool
prop_checkUselessBang7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUselessBang String
"set -e; x() { ! [ x ]; }"
prop_checkUselessBang8 :: Bool
prop_checkUselessBang8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUselessBang String
"set -e; if { ! true; }; then true; fi"
prop_checkUselessBang9 :: Bool
prop_checkUselessBang9 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUselessBang String
"set -e; while ! true; do true; done"
checkUselessBang :: Parameters -> Token -> f ()
checkUselessBang Parameters
params Token
t = forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Parameters -> Bool
hasSetE Parameters
params) forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
check (Token -> [Token]
getNonReturningCommands Token
t)
  where
    check :: Token -> m ()
check Token
t =
        case Token
t of
            T_Banged Id
id Token
cmd | Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ [Token] -> Bool
isCondition (Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) Token
t) ->
                forall {a} {m :: * -> *}.
(NFData a, MonadWriter [a] m) =>
a -> m ()
addComment forall a b. (a -> b) -> a -> b
$ Severity -> Id -> Code -> String -> Fix -> TokenComment
makeCommentWithFix Severity
InfoC Id
id Code
2251
                        String
"This ! is not on a condition and skips errexit. Use `&& exit 1` instead, or make sure $? is checked."
                        ([Replacement] -> Fix
fixWith [Id -> Parameters -> Code -> String -> Replacement
replaceStart Id
id Parameters
params Code
1 String
"", Id -> Parameters -> Code -> String -> Replacement
replaceEnd (Token -> Id
getId Token
cmd) Parameters
params Code
0 String
" && exit 1"])
            Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()

    -- Get all the subcommands that aren't likely to be the return value
    getNonReturningCommands :: Token -> [Token]
    getNonReturningCommands :: Token -> [Token]
getNonReturningCommands Token
t =
        case Token
t of
            T_Script Id
_ Token
_ [Token]
list -> forall a. [a] -> [a]
dropLast [Token]
list
            T_BraceGroup Id
_ [Token]
list -> if Token -> Bool
isFunctionBody Token
t then forall a. [a] -> [a]
dropLast [Token]
list else [Token]
list
            T_Subshell Id
_ [Token]
list -> forall a. [a] -> [a]
dropLast [Token]
list
            T_WhileExpression Id
_ [Token]
conds [Token]
cmds -> forall a. [a] -> [a]
dropLast [Token]
conds forall a. [a] -> [a] -> [a]
++ [Token]
cmds
            T_UntilExpression Id
_ [Token]
conds [Token]
cmds -> forall a. [a] -> [a]
dropLast [Token]
conds forall a. [a] -> [a] -> [a]
++ [Token]
cmds
            T_ForIn Id
_ String
_ [Token]
_ [Token]
list -> [Token]
list
            T_ForArithmetic Id
_ Token
_ Token
_ Token
_ [Token]
list -> [Token]
list
            T_Annotation Id
_ [Annotation]
_ Token
t -> Token -> [Token]
getNonReturningCommands Token
t
            T_IfExpression Id
_ [([Token], [Token])]
conds [Token]
elses ->
                forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (forall a. [a] -> [a]
dropLast forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a, b) -> a
fst) [([Token], [Token])]
conds forall a. [a] -> [a] -> [a]
++ forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap forall a b. (a, b) -> b
snd [([Token], [Token])]
conds forall a. [a] -> [a] -> [a]
++ [Token]
elses
            Token
_ -> []

    isFunctionBody :: Token -> Bool
isFunctionBody Token
t =
        case Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) Token
t of
            Token
_:T_Function {}:[Token]
_-> Bool
True
            [Token]
_ -> Bool
False

    dropLast :: [a] -> [a]
dropLast [a]
t =
        case [a]
t of
            [a
_] -> []
            a
x:[a]
rest -> a
x forall a. a -> [a] -> [a]
: [a] -> [a]
dropLast [a]
rest
            [a]
_ -> []

prop_checkModifiedArithmeticInRedirection1 :: Bool
prop_checkModifiedArithmeticInRedirection1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkModifiedArithmeticInRedirection String
"ls > $((i++))"
prop_checkModifiedArithmeticInRedirection2 :: Bool
prop_checkModifiedArithmeticInRedirection2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkModifiedArithmeticInRedirection String
"cat < \"foo$((i++)).txt\""
prop_checkModifiedArithmeticInRedirection3 :: Bool
prop_checkModifiedArithmeticInRedirection3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkModifiedArithmeticInRedirection String
"while true; do true; done > $((i++))"
prop_checkModifiedArithmeticInRedirection4 :: Bool
prop_checkModifiedArithmeticInRedirection4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkModifiedArithmeticInRedirection String
"cat <<< $((i++))"
prop_checkModifiedArithmeticInRedirection5 :: Bool
prop_checkModifiedArithmeticInRedirection5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkModifiedArithmeticInRedirection String
"cat << foo\n$((i++))\nfoo\n"
prop_checkModifiedArithmeticInRedirection6 :: Bool
prop_checkModifiedArithmeticInRedirection6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkModifiedArithmeticInRedirection String
"#!/bin/dash\nls > $((i=i+1))"
checkModifiedArithmeticInRedirection :: Parameters -> Token -> f ()
checkModifiedArithmeticInRedirection Parameters
params Token
t = forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Parameters -> Shell
shellType Parameters
params forall a. Eq a => a -> a -> Bool
== Shell
Dash) forall a b. (a -> b) -> a -> b
$
    case Token
t of
        T_Redirecting Id
_ [Token]
redirs (T_SimpleCommand Id
_ [Token]
_ (Token
_:[Token]
_)) -> forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkRedirs [Token]
redirs
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    checkRedirs :: Token -> m ()
checkRedirs Token
t =
        case Token
t of
            T_FdRedirect Id
_ String
_ (T_IoFile Id
_ Token
_ Token
word) ->
                forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkArithmetic forall a b. (a -> b) -> a -> b
$ Token -> [Token]
getWordParts Token
word
            T_FdRedirect Id
_ String
_ (T_HereString Id
_ Token
word) ->
                forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkArithmetic forall a b. (a -> b) -> a -> b
$ Token -> [Token]
getWordParts Token
word
            T_FdRedirect Id
_ String
_ (T_HereDoc Id
_ Dashed
_ Quoted
_ String
_ [Token]
list) ->
                forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkArithmetic [Token]
list
            Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
    checkArithmetic :: Token -> m ()
checkArithmetic Token
t =
        case Token
t of
            T_DollarArithmetic Id
_ Token
x -> forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkModifying Token
x
            Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
    checkModifying :: Token -> m ()
checkModifying Token
t =
        case Token
t of
            TA_Sequence Id
_ [Token]
list -> forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
checkModifying [Token]
list
            TA_Unary Id
id String
s Token
_ | String
s forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"|++", String
"++|", String
"|--", String
"--|"] -> forall {m :: * -> *}. MonadWriter [TokenComment] m => Id -> m ()
warnFor Id
id
            TA_Assignment Id
id String
_ Token
_ Token
_ -> forall {m :: * -> *}. MonadWriter [TokenComment] m => Id -> m ()
warnFor Id
id
            TA_Binary Id
_ String
_ Token
x Token
y -> forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
checkModifying [Token
x ,Token
y]
            TA_Trinary Id
_ Token
x Token
y Token
z -> forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
checkModifying [Token
x, Token
y, Token
z]
            Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
    warnFor :: Id -> m ()
warnFor Id
id =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2257 String
"Arithmetic modifications in command redirections may be discarded. Do them separately."

prop_checkAliasUsedInSameParsingUnit1 :: Bool
prop_checkAliasUsedInSameParsingUnit1 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkAliasUsedInSameParsingUnit String
"alias x=y; x"
prop_checkAliasUsedInSameParsingUnit2 :: Bool
prop_checkAliasUsedInSameParsingUnit2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkAliasUsedInSameParsingUnit String
"alias x=y\nx"
prop_checkAliasUsedInSameParsingUnit3 :: Bool
prop_checkAliasUsedInSameParsingUnit3 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkAliasUsedInSameParsingUnit String
"{ alias x=y\nx\n}"
prop_checkAliasUsedInSameParsingUnit4 :: Bool
prop_checkAliasUsedInSameParsingUnit4 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkAliasUsedInSameParsingUnit String
"alias x=y; 'x';"
prop_checkAliasUsedInSameParsingUnit5 :: Bool
prop_checkAliasUsedInSameParsingUnit5 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkAliasUsedInSameParsingUnit String
":\n{\n#shellcheck disable=SC2262\nalias x=y\nx\n}"
prop_checkAliasUsedInSameParsingUnit6 :: Bool
prop_checkAliasUsedInSameParsingUnit6 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkAliasUsedInSameParsingUnit String
":\n{\n#shellcheck disable=SC2262\nalias x=y\nalias x=z\nx\n}" -- Only consider the first instance
checkAliasUsedInSameParsingUnit :: Parameters -> Token -> [TokenComment]
checkAliasUsedInSameParsingUnit :: Parameters -> Token -> [TokenComment]
checkAliasUsedInSameParsingUnit Parameters
params Token
root =
    let
        -- Get all root commands
        commands :: [Token]
commands = forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ Token -> [[Token]]
getCommandSequences Token
root
        -- Group them by whether they start on the same line where the previous one ended
        units :: [[Token]]
units = forall a. (a -> a -> Bool) -> [a] -> [[a]]
groupByLink Token -> Token -> Bool
followsOnLine [Token]
commands
    in
        forall w a. Writer w a -> w
execWriter forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ [Token] -> Writer [TokenComment] ()
checkUnit [[Token]]
units
  where
    lineSpan :: Id -> Maybe (Code, Code)
lineSpan Id
t =
        let m :: Map Id (Position, Position)
m = Parameters -> Map Id (Position, Position)
tokenPositions Parameters
params in do
            (Position
start, Position
end) <- forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup Id
t Map Id (Position, Position)
m
            forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ (Position -> Code
posLine Position
start, Position -> Code
posLine Position
end)

    followsOnLine :: Token -> Token -> Bool
followsOnLine Token
a Token
b = forall a. a -> Maybe a -> a
fromMaybe Bool
False forall a b. (a -> b) -> a -> b
$ do
        (Code
_, Code
end) <- Id -> Maybe (Code, Code)
lineSpan (Token -> Id
getId Token
a)
        (Code
start, Code
_) <- Id -> Maybe (Code, Code)
lineSpan (Token -> Id
getId Token
b)
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Code
end forall a. Eq a => a -> a -> Bool
== Code
start

    checkUnit :: [Token] -> Writer [TokenComment] ()
    checkUnit :: [Token] -> Writer [TokenComment] ()
checkUnit [Token]
unit = forall (m :: * -> *) s a. Monad m => StateT s m a -> s -> m a
evalStateT (forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> Token -> m Token
doAnalysis Token -> StateT (Map String Token) (Writer [TokenComment]) ()
findCommands) [Token]
unit) (forall k a. Map k a
Map.empty)

    findCommands :: Token -> StateT (Map.Map String Token) (Writer [TokenComment]) ()
    findCommands :: Token -> StateT (Map String Token) (Writer [TokenComment]) ()
findCommands Token
t = case Token
t of
            T_SimpleCommand Id
_ [Token]
_ (Token
cmd:[Token]
args) ->
                case Token -> Maybe String
getUnquotedLiteral Token
cmd of
                    Just String
"alias" ->
                        forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {f :: * -> *}.
MonadState (Map String Token) f =>
Token -> f ()
addAlias [Token]
args
                    Just String
name | Char
'/' forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` String
name -> do
                        Maybe Token
cmd <- forall s (m :: * -> *) a. MonadState s m => (s -> a) -> m a
gets (forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup String
name)
                        case Maybe Token
cmd of
                            Just Token
alias ->
                                forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Parameters -> Token -> Bool
isSourced Parameters
params Token
t Bool -> Bool -> Bool
|| Parameters -> Code -> Token -> Bool
shouldIgnoreCode Parameters
params Code
2262 Token
alias) forall a b. (a -> b) -> a -> b
$ do
                                    forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
alias) Code
2262 String
"This alias can't be defined and used in the same parsing unit. Use a function instead."
                                    forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
t) Code
2263 String
"Since they're in the same parsing unit, this command will not refer to the previously mentioned alias."
                            Maybe Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
                    Maybe String
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
            Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
    addAlias :: Token -> f ()
addAlias Token
arg = do
        let (String
name, String
value) = forall a. (a -> Bool) -> [a] -> ([a], [a])
break (forall a. Eq a => a -> a -> Bool
== Char
'=') forall a b. (a -> b) -> a -> b
$ String -> Token -> String
getLiteralStringDef String
"-" Token
arg
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String -> Bool
isVariableName String
name Bool -> Bool -> Bool
&& Bool -> Bool
not (forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
value)) forall a b. (a -> b) -> a -> b
$
            forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify (forall k a. Ord k => (a -> a -> a) -> k -> a -> Map k a -> Map k a
Map.insertWith (\Token
new Token
old -> Token
old) String
name Token
arg)

isSourced :: Parameters -> Token -> Bool
isSourced Parameters
params Token
t =
    let
        f :: Token -> Bool
f (T_SourceCommand {}) = Bool
True
        f Token
_ = Bool
False
    in
        forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
f forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) Token
t


-- Like groupBy, but compares pairs of adjacent elements, rather than against the first of the span
prop_groupByLink1 :: Bool
prop_groupByLink1 = forall a. (a -> a -> Bool) -> [a] -> [[a]]
groupByLink (\Code
a Code
b -> Code
aforall a. Num a => a -> a -> a
+Code
1 forall a. Eq a => a -> a -> Bool
== Code
b) [Code
1,Code
2,Code
3,Code
2,Code
3,Code
7,Code
8,Code
9] forall a. Eq a => a -> a -> Bool
== [[Code
1,Code
2,Code
3], [Code
2,Code
3], [Code
7,Code
8,Code
9]]
prop_groupByLink2 :: Bool
prop_groupByLink2 = forall a. (a -> a -> Bool) -> [a] -> [[a]]
groupByLink forall a. Eq a => a -> a -> Bool
(==) ([] :: [()]) forall a. Eq a => a -> a -> Bool
== []
groupByLink :: (a -> a -> Bool) -> [a] -> [[a]]
groupByLink :: forall a. (a -> a -> Bool) -> [a] -> [[a]]
groupByLink a -> a -> Bool
f [a]
list =
    case [a]
list of
        [] -> []
        (a
x:[a]
xs) -> forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr a -> (a -> [a] -> [[a]]) -> a -> [a] -> [[a]]
c forall {a}. a -> [a] -> [[a]]
n [a]
xs a
x []
  where
    c :: a -> (a -> [a] -> [[a]]) -> a -> [a] -> [[a]]
c a
next a -> [a] -> [[a]]
rest a
current [a]
span =
        if a -> a -> Bool
f a
current a
next
        then a -> [a] -> [[a]]
rest a
next (a
currentforall a. a -> [a] -> [a]
:[a]
span)
        else (forall a. [a] -> [a]
reverse forall a b. (a -> b) -> a -> b
$ a
currentforall a. a -> [a] -> [a]
:[a]
span) forall a. a -> [a] -> [a]
: a -> [a] -> [[a]]
rest a
next []
    n :: a -> [a] -> [[a]]
n a
current [a]
span = [forall a. [a] -> [a]
reverse (a
currentforall a. a -> [a] -> [a]
:[a]
span)]


prop_checkBlatantRecursion1 :: Bool
prop_checkBlatantRecursion1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkBlatantRecursion String
":(){ :|:& };:"
prop_checkBlatantRecursion2 :: Bool
prop_checkBlatantRecursion2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkBlatantRecursion String
"f() { f; }"
prop_checkBlatantRecursion3 :: Bool
prop_checkBlatantRecursion3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkBlatantRecursion String
"f() { command f; }"
prop_checkBlatantRecursion4 :: Bool
prop_checkBlatantRecursion4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify Parameters -> Token -> Writer [TokenComment] ()
checkBlatantRecursion String
"cd() { cd \"$lol/$1\" || exit; }"
prop_checkBlatantRecursion5 :: Bool
prop_checkBlatantRecursion5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkBlatantRecursion String
"cd() { [ -z \"$1\" ] || cd \"$1\"; }"
prop_checkBlatantRecursion6 :: Bool
prop_checkBlatantRecursion6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkBlatantRecursion String
"cd() { something; cd $1; }"
prop_checkBlatantRecursion7 :: Bool
prop_checkBlatantRecursion7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot Parameters -> Token -> Writer [TokenComment] ()
checkBlatantRecursion String
"cd() { builtin cd $1; }"
checkBlatantRecursion :: Parameters -> Token -> Writer [TokenComment] ()
checkBlatantRecursion :: Parameters -> Token -> Writer [TokenComment] ()
checkBlatantRecursion Parameters
params Token
t =
    case Token
t of
        T_Function Id
_ FunctionKeyword
_ FunctionParentheses
_ String
name Token
body ->
            case Token -> [[Token]]
getCommandSequences Token
body of
                    [Token
first : [Token]
_] -> String -> Token -> Writer [TokenComment] ()
checkList String
name Token
first
                    [[Token]]
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    checkList :: String -> Token -> Writer [TokenComment] ()
    checkList :: String -> Token -> Writer [TokenComment] ()
checkList String
name Token
t =
        case Token
t of
            T_Backgrounded Id
_ Token
t -> String -> Token -> Writer [TokenComment] ()
checkList String
name Token
t
            T_AndIf Id
_ Token
lhs Token
_ -> String -> Token -> Writer [TokenComment] ()
checkList String
name Token
lhs
            T_OrIf Id
_ Token
lhs Token
_ -> String -> Token -> Writer [TokenComment] ()
checkList String
name Token
lhs
            T_Pipeline Id
_ [Token]
_ [Token]
cmds -> forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (String -> Token -> Writer [TokenComment] ()
checkCommand String
name) [Token]
cmds
            Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()

    checkCommand :: String -> Token -> Writer [TokenComment] ()
    checkCommand :: String -> Token -> Writer [TokenComment] ()
checkCommand String
name Token
cmd = forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
        let (Maybe String
invokedM, Token
t) = Bool -> Token -> (Maybe String, Token)
getCommandNameAndToken Bool
True Token
cmd
        String
invoked <- Maybe String
invokedM
        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ String
name forall a. Eq a => a -> a -> Bool
== String
invoked
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$
            forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> Fix -> m ()
errWithFix (Token -> Id
getId Token
t) Code
2264
                (String
"This function unconditionally re-invokes itself. Missing 'command'?")
                ([Replacement] -> Fix
fixWith [Id -> Parameters -> Code -> String -> Replacement
replaceStart (Token -> Id
getId Token
t) Parameters
params Code
0 forall a b. (a -> b) -> a -> b
$ String
"command "])


prop_checkBadTestAndOr1 :: Bool
prop_checkBadTestAndOr1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkBadTestAndOr String
"[ x ] & [ y ]"
prop_checkBadTestAndOr2 :: Bool
prop_checkBadTestAndOr2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkBadTestAndOr String
"test -e foo & [ y ]"
prop_checkBadTestAndOr3 :: Bool
prop_checkBadTestAndOr3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkBadTestAndOr String
"[ x ] | [ y ]"
checkBadTestAndOr :: Parameters -> Token -> m ()
checkBadTestAndOr Parameters
params Token
t =
    case Token
t of
        T_Pipeline Id
_ [Token]
seps cmds :: [Token]
cmds@(Token
_:Token
_:[Token]
_) -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
[Token] -> [Token] -> m ()
checkOrs [Token]
seps [Token]
cmds
        T_Backgrounded Id
id Token
cmd -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Token -> m ()
checkAnds Id
id Token
cmd
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    checkOrs :: [Token] -> [Token] -> m ()
checkOrs [Token]
seps [Token]
cmds =
        let maybeSeps :: [Maybe Token]
maybeSeps = forall a b. (a -> b) -> [a] -> [b]
map forall a. a -> Maybe a
Just [Token]
seps
            commandWithSeps :: [(Maybe Token, Token, Maybe Token)]
commandWithSeps = forall a b c. [a] -> [b] -> [c] -> [(a, b, c)]
zip3 (forall a. Maybe a
Nothingforall a. a -> [a] -> [a]
:[Maybe Token]
maybeSeps) [Token]
cmds ([Maybe Token]
maybeSeps forall a. [a] -> [a] -> [a]
++ [forall a. Maybe a
Nothing])
        in
            forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
(Maybe Token, Token, Maybe Token) -> f ()
checkTest [(Maybe Token, Token, Maybe Token)]
commandWithSeps
    checkTest :: (Maybe Token, Token, Maybe Token) -> f ()
checkTest (Maybe Token
before, Token
cmd, Maybe Token
after) =
        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isTest Token
cmd) forall a b. (a -> b) -> a -> b
$ do
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Maybe Token -> m ()
checkPipe Maybe Token
before
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Maybe Token -> m ()
checkPipe Maybe Token
after

    checkPipe :: Maybe Token -> m ()
checkPipe Maybe Token
t =
        case Maybe Token
t of
            Just (T_Pipe Id
id String
"|") ->
                forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> Fix -> m ()
warnWithFix Id
id Code
2266 String
"Use || for logical OR. Single | will pipe." forall a b. (a -> b) -> a -> b
$
                    [Replacement] -> Fix
fixWith [Id -> Parameters -> Code -> String -> Replacement
replaceEnd Id
id Parameters
params Code
0 String
"|"]
            Maybe Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()

    checkAnds :: Id -> Token -> f ()
checkAnds Id
id Token
t =
        case Token
t of
            T_AndIf Id
_ Token
_ Token
rhs -> Id -> Token -> f ()
checkAnds Id
id Token
rhs
            T_OrIf Id
_ Token
_ Token
rhs -> Id -> Token -> f ()
checkAnds Id
id Token
rhs
            T_Pipeline Id
_ [Token]
_ [Token]
list | Bool -> Bool
not (forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Token]
list) -> Id -> Token -> f ()
checkAnds Id
id (forall a. [a] -> a
last [Token]
list)
            Token
cmd -> forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isTest Token
cmd) forall a b. (a -> b) -> a -> b
$
                forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> Fix -> m ()
errWithFix Id
id Code
2265 String
"Use && for logical AND. Single & will background and return true." forall a b. (a -> b) -> a -> b
$
                    ([Replacement] -> Fix
fixWith [Id -> Parameters -> Code -> String -> Replacement
replaceEnd Id
id Parameters
params Code
0 String
"&"])

    isTest :: Token -> Bool
isTest Token
t =
        case Token
t of
            T_Condition {} -> Bool
True
            T_SimpleCommand {} -> Token
t Token -> String -> Bool
`isCommand` String
"test"
            T_Redirecting Id
_ [Token]
_ Token
t -> Token -> Bool
isTest Token
t
            T_Annotation Id
_ [Annotation]
_ Token
t -> Token -> Bool
isTest Token
t
            Token
_ -> Bool
False

prop_checkComparisonWithLeadingX1 :: Bool
prop_checkComparisonWithLeadingX1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkComparisonWithLeadingX String
"[ x$foo = xlol ]"
prop_checkComparisonWithLeadingX2 :: Bool
prop_checkComparisonWithLeadingX2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkComparisonWithLeadingX String
"test x$foo = xlol"
prop_checkComparisonWithLeadingX3 :: Bool
prop_checkComparisonWithLeadingX3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkComparisonWithLeadingX String
"[ $foo = xbar ]"
prop_checkComparisonWithLeadingX4 :: Bool
prop_checkComparisonWithLeadingX4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkComparisonWithLeadingX String
"test $foo = xbar"
prop_checkComparisonWithLeadingX5 :: Bool
prop_checkComparisonWithLeadingX5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkComparisonWithLeadingX String
"[ \"x$foo\" = 'xlol' ]"
prop_checkComparisonWithLeadingX6 :: Bool
prop_checkComparisonWithLeadingX6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkComparisonWithLeadingX String
"[ x\"$foo\" = x'lol' ]"
checkComparisonWithLeadingX :: Parameters -> Token -> m ()
checkComparisonWithLeadingX Parameters
params Token
t =
    case Token
t of
        TC_Binary Id
id ConditionType
typ String
op Token
lhs Token
rhs | String
op forall a. Eq a => a -> a -> Bool
== String
"=" Bool -> Bool -> Bool
|| String
op forall a. Eq a => a -> a -> Bool
== String
"==" ->
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Token -> Token -> m ()
check Token
lhs Token
rhs
        T_SimpleCommand Id
_ [Token]
_ [Token
cmd, Token
lhs, Token
op, Token
rhs] |
            Token -> Maybe String
getLiteralString Token
cmd forall a. Eq a => a -> a -> Bool
== forall a. a -> Maybe a
Just String
"test" Bool -> Bool -> Bool
&&
                Token -> Maybe String
getLiteralString Token
op forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [forall a. a -> Maybe a
Just String
"=", forall a. a -> Maybe a
Just String
"=="] ->
                    forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Token -> Token -> m ()
check Token
lhs Token
rhs
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    msg :: String
msg = String
"Avoid x-prefix in comparisons as it no longer serves a purpose."
    check :: Token -> Token -> m ()
check Token
lhs Token
rhs = forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
        Replacement
l <- Token -> Maybe Replacement
fixLeadingX Token
lhs
        Replacement
r <- Token -> Maybe Replacement
fixLeadingX Token
rhs
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> Fix -> m ()
styleWithFix (Token -> Id
getId Token
lhs) Code
2268 String
msg forall a b. (a -> b) -> a -> b
$ [Replacement] -> Fix
fixWith [Replacement
l, Replacement
r]

    fixLeadingX :: Token -> Maybe Replacement
fixLeadingX Token
token =
         case Token -> [Token]
getWordParts Token
token of
            T_Literal Id
id (Char
'x':String
_):[Token]
_ ->
                case Token
token of
                    -- The side is a single, unquoted x, so we have to quote
                    T_NormalWord Id
_ [T_Literal Id
id String
"x"] ->
                        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Id -> Parameters -> Code -> String -> Replacement
replaceStart Id
id Parameters
params Code
1 String
"\"\""
                    -- Otherwise we can just delete it
                    Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Id -> Parameters -> Code -> String -> Replacement
replaceStart Id
id Parameters
params Code
1 String
""
            T_SingleQuoted Id
id (Char
'x':String
_):[Token]
_ ->
                -- Replace the single quote and x
                forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Id -> Parameters -> Code -> String -> Replacement
replaceStart Id
id Parameters
params Code
2 String
"'"
            [Token]
_ -> forall a. Maybe a
Nothing

prop_checkAssignToSelf1 :: Bool
prop_checkAssignToSelf1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkAssignToSelf String
"x=$x"
prop_checkAssignToSelf2 :: Bool
prop_checkAssignToSelf2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkAssignToSelf String
"x=${x}"
prop_checkAssignToSelf3 :: Bool
prop_checkAssignToSelf3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkAssignToSelf String
"x=\"$x\""
prop_checkAssignToSelf4 :: Bool
prop_checkAssignToSelf4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkAssignToSelf String
"x=$x mycmd"
checkAssignToSelf :: p -> Token -> m ()
checkAssignToSelf p
_ Token
t =
    case Token
t of
        T_SimpleCommand Id
_ [Token]
vars [] -> forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
check [Token]
vars
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    check :: Token -> f ()
check Token
t =
        case Token
t of
            T_Assignment Id
id AssignmentMode
Assign String
name [] Token
t ->
                case Token -> [Token]
getWordParts Token
t of
                    [T_DollarBraced Id
_ Bool
_ Token
b] -> do
                        forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (forall a. a -> Maybe a
Just String
name forall a. Eq a => a -> a -> Bool
== Token -> Maybe String
getLiteralString Token
b) forall a b. (a -> b) -> a -> b
$
                            forall {m :: * -> *}. MonadWriter [TokenComment] m => Id -> m ()
msg Id
id
                    [Token]
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
            Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
    msg :: Id -> m ()
msg Id
id = forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info Id
id Code
2269 String
"This variable is assigned to itself, so the assignment does nothing."


prop_checkEqualsInCommand1a :: Bool
prop_checkEqualsInCommand1a = (Parameters -> Token -> Writer [TokenComment] ())
-> [Code] -> String -> Bool
verifyCodes forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkEqualsInCommand [Code
2277] String
"#!/bin/bash\n0='foo'"
prop_checkEqualsInCommand2a :: Bool
prop_checkEqualsInCommand2a = (Parameters -> Token -> Writer [TokenComment] ())
-> [Code] -> String -> Bool
verifyCodes forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkEqualsInCommand [Code
2278] String
"#!/bin/ksh \n$0='foo'"
prop_checkEqualsInCommand3a :: Bool
prop_checkEqualsInCommand3a = (Parameters -> Token -> Writer [TokenComment] ())
-> [Code] -> String -> Bool
verifyCodes forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkEqualsInCommand [Code
2279] String
"#!/bin/dash\n${0}='foo'"
prop_checkEqualsInCommand4a :: Bool
prop_checkEqualsInCommand4a = (Parameters -> Token -> Writer [TokenComment] ())
-> [Code] -> String -> Bool
verifyCodes forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkEqualsInCommand [Code
2280] String
"#!/bin/sh  \n0='foo'"

prop_checkEqualsInCommand1b :: Bool
prop_checkEqualsInCommand1b = (Parameters -> Token -> Writer [TokenComment] ())
-> [Code] -> String -> Bool
verifyCodes forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkEqualsInCommand [Code
2270] String
"1='foo'"
prop_checkEqualsInCommand2b :: Bool
prop_checkEqualsInCommand2b = (Parameters -> Token -> Writer [TokenComment] ())
-> [Code] -> String -> Bool
verifyCodes forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkEqualsInCommand [Code
2270] String
"${2}='foo'"

prop_checkEqualsInCommand1c :: Bool
prop_checkEqualsInCommand1c = (Parameters -> Token -> Writer [TokenComment] ())
-> [Code] -> String -> Bool
verifyCodes forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkEqualsInCommand [Code
2271] String
"var$((n+1))=value"
prop_checkEqualsInCommand2c :: Bool
prop_checkEqualsInCommand2c = (Parameters -> Token -> Writer [TokenComment] ())
-> [Code] -> String -> Bool
verifyCodes forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkEqualsInCommand [Code
2271] String
"var${x}=value"
prop_checkEqualsInCommand3c :: Bool
prop_checkEqualsInCommand3c = (Parameters -> Token -> Writer [TokenComment] ())
-> [Code] -> String -> Bool
verifyCodes forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkEqualsInCommand [Code
2271] String
"var$((cmd))x='foo'"
prop_checkEqualsInCommand4c :: Bool
prop_checkEqualsInCommand4c = (Parameters -> Token -> Writer [TokenComment] ())
-> [Code] -> String -> Bool
verifyCodes forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkEqualsInCommand [Code
2271] String
"$(cmd)='foo'"

prop_checkEqualsInCommand1d :: Bool
prop_checkEqualsInCommand1d = (Parameters -> Token -> Writer [TokenComment] ())
-> [Code] -> String -> Bool
verifyCodes forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkEqualsInCommand [Code
2273] String
"======="
prop_checkEqualsInCommand2d :: Bool
prop_checkEqualsInCommand2d = (Parameters -> Token -> Writer [TokenComment] ())
-> [Code] -> String -> Bool
verifyCodes forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkEqualsInCommand [Code
2274] String
"======= Here ======="
prop_checkEqualsInCommand3d :: Bool
prop_checkEqualsInCommand3d = (Parameters -> Token -> Writer [TokenComment] ())
-> [Code] -> String -> Bool
verifyCodes forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkEqualsInCommand [Code
2275] String
"foo\n=42"

prop_checkEqualsInCommand1e :: Bool
prop_checkEqualsInCommand1e = (Parameters -> Token -> Writer [TokenComment] ())
-> [Code] -> String -> Bool
verifyCodes forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkEqualsInCommand [] String
"--foo=bar"
prop_checkEqualsInCommand2e :: Bool
prop_checkEqualsInCommand2e = (Parameters -> Token -> Writer [TokenComment] ())
-> [Code] -> String -> Bool
verifyCodes forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkEqualsInCommand [] String
"$(cmd)'=foo'"
prop_checkEqualsInCommand3e :: Bool
prop_checkEqualsInCommand3e = (Parameters -> Token -> Writer [TokenComment] ())
-> [Code] -> String -> Bool
verifyCodes forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkEqualsInCommand [Code
2276] String
"var${x}/=value"
prop_checkEqualsInCommand4e :: Bool
prop_checkEqualsInCommand4e = (Parameters -> Token -> Writer [TokenComment] ())
-> [Code] -> String -> Bool
verifyCodes forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkEqualsInCommand [Code
2276] String
"${}=value"
prop_checkEqualsInCommand5e :: Bool
prop_checkEqualsInCommand5e = (Parameters -> Token -> Writer [TokenComment] ())
-> [Code] -> String -> Bool
verifyCodes forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkEqualsInCommand [Code
2276] String
"${#x}=value"

prop_checkEqualsInCommand1f :: Bool
prop_checkEqualsInCommand1f = (Parameters -> Token -> Writer [TokenComment] ())
-> [Code] -> String -> Bool
verifyCodes forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkEqualsInCommand [Code
2281] String
"$var=foo"
prop_checkEqualsInCommand2f :: Bool
prop_checkEqualsInCommand2f = (Parameters -> Token -> Writer [TokenComment] ())
-> [Code] -> String -> Bool
verifyCodes forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkEqualsInCommand [Code
2281] String
"$a=$b"
prop_checkEqualsInCommand3f :: Bool
prop_checkEqualsInCommand3f = (Parameters -> Token -> Writer [TokenComment] ())
-> [Code] -> String -> Bool
verifyCodes forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkEqualsInCommand [Code
2281] String
"${var}=foo"
prop_checkEqualsInCommand4f :: Bool
prop_checkEqualsInCommand4f = (Parameters -> Token -> Writer [TokenComment] ())
-> [Code] -> String -> Bool
verifyCodes forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkEqualsInCommand [Code
2281] String
"${var[42]}=foo"
prop_checkEqualsInCommand5f :: Bool
prop_checkEqualsInCommand5f = (Parameters -> Token -> Writer [TokenComment] ())
-> [Code] -> String -> Bool
verifyCodes forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkEqualsInCommand [Code
2281] String
"$var+=foo"

prop_checkEqualsInCommand1g :: Bool
prop_checkEqualsInCommand1g = (Parameters -> Token -> Writer [TokenComment] ())
-> [Code] -> String -> Bool
verifyCodes forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkEqualsInCommand [Code
2282] String
"411toppm=true"

checkEqualsInCommand :: Parameters -> Token -> m ()
checkEqualsInCommand Parameters
params Token
originalToken =
    case Token
originalToken of
        T_SimpleCommand Id
_ [Token]
_ (Token
word:[Token]
_) -> forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
check Token
word
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    hasEquals :: Token -> Bool
hasEquals Token
t =
        case Token
t of
            T_Literal Id
_ String
s -> Char
'=' forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
s
            Token
_ -> Bool
False

    check :: Token -> m ()
check t :: Token
t@(T_NormalWord Id
_ [Token]
list) | forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
hasEquals [Token]
list =
        case forall a. (a -> Bool) -> [a] -> ([a], [a])
break Token -> Bool
hasEquals [Token]
list of
            ([Token]
leading, (Token
eq:[Token]
_)) -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Token -> [Token] -> Token -> m ()
msg Token
t ([Token] -> [Token]
stripSinglePlus [Token]
leading) Token
eq
            ([Token], [Token])
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
    check Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

    -- This is a workaround for the parser adding + and = as separate literals
    stripSinglePlus :: [Token] -> [Token]
stripSinglePlus [Token]
l =
        case forall a. [a] -> [a]
reverse [Token]
l of
            (T_Literal Id
_ String
"+"):[Token]
rest -> forall a. [a] -> [a]
reverse [Token]
rest
            [Token]
_ -> [Token]
l

    positionalAssignmentRe :: Regex
positionalAssignmentRe = String -> Regex
mkRegex String
"^[0-9][0-9]?="
    positionalMsg :: Id -> m ()
positionalMsg Id
id =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2270 String
"To assign positional parameters, use 'set -- first second ..' (or use [ ] to compare)."
    indirectionMsg :: Id -> m ()
indirectionMsg Id
id =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2271 String
"For indirection, use arrays, declare \"var$n=value\", or (for sh) read/eval."
    badComparisonMsg :: Id -> m ()
badComparisonMsg Id
id =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2272 String
"Command name contains ==. For comparison, use [ \"$var\" = value ]."
    conflictMarkerMsg :: Id -> m ()
conflictMarkerMsg Id
id =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2273 String
"Sequence of ===s found. Merge conflict or intended as a commented border?"
    borderMsg :: Id -> m ()
borderMsg Id
id =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2274 String
"Command name starts with ===. Intended as a commented border?"
    prefixMsg :: Id -> m ()
prefixMsg Id
id =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2275 String
"Command name starts with =. Bad line break?"
    genericMsg :: Id -> m ()
genericMsg Id
id =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2276 String
"This is interpreted as a command name containing '='. Bad assignment or comparison?"
    assign0Msg :: Id -> Fix -> m ()
assign0Msg Id
id Fix
bashfix =
        case Parameters -> Shell
shellType Parameters
params of
            Shell
Bash -> forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> Fix -> m ()
errWithFix Id
id Code
2277 String
"Use BASH_ARGV0 to assign to $0 in bash (or use [ ] to compare)." Fix
bashfix
            Shell
Ksh -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2278 String
"$0 can't be assigned in Ksh (but it does reflect the current function)."
            Shell
Dash -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2279 String
"$0 can't be assigned in Dash. This becomes a command name."
            Shell
_ -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2280 String
"$0 can't be assigned this way, and there is no portable alternative."
    leadingNumberMsg :: Id -> m ()
leadingNumberMsg Id
id =
        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2282 String
"Variable names can't start with numbers, so this is interpreted as a command."

    isExpansion :: Token -> Bool
isExpansion Token
t =
        case Token
t of
            T_Arithmetic {} -> Bool
True
            Token
_ -> Token -> Bool
isQuoteableExpansion Token
t

    isConflictMarker :: Token -> Bool
isConflictMarker Token
cmd = forall a. a -> Maybe a -> a
fromMaybe Bool
False forall a b. (a -> b) -> a -> b
$ do
        String
str <- Token -> Maybe String
getUnquotedLiteral Token
cmd
        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (forall a. Eq a => a -> a -> Bool
== Char
'=') String
str
        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. Foldable t => t a -> Int
length String
str forall a. Ord a => a -> a -> Bool
>= Int
4 Bool -> Bool -> Bool
&& forall (t :: * -> *) a. Foldable t => t a -> Int
length String
str forall a. Ord a => a -> a -> Bool
<= Int
12 -- Git uses 7 but who knows
        forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True

    mayBeVariableName :: [Token] -> Bool
mayBeVariableName [Token]
l = forall a. a -> Maybe a -> a
fromMaybe Bool
False forall a b. (a -> b) -> a -> b
$ do
        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
isQuotes [Token]
l
        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
willBecomeMultipleArgs [Token]
l
        String
str <- forall (m :: * -> *).
Monad m =>
(Token -> m String) -> Token -> m String
getLiteralStringExt (\Token
_ -> forall a. a -> Maybe a
Just String
"x") (Id -> [Token] -> Token
T_NormalWord (Int -> Id
Id Int
0) [Token]
l)
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ String -> Bool
isVariableName String
str

    isLeadingNumberVar :: String -> Bool
isLeadingNumberVar String
s =
        let lead :: String
lead = forall a. (a -> Bool) -> [a] -> [a]
takeWhile (forall a. Eq a => a -> a -> Bool
/= Char
'=') String
s
        in Bool -> Bool
not (forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
lead) Bool -> Bool -> Bool
&& Char -> Bool
isDigit (forall a. [a] -> a
head String
lead)
            Bool -> Bool -> Bool
&& forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isVariableChar String
lead Bool -> Bool -> Bool
&& Bool -> Bool
not (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isDigit String
lead)

    msg :: Token -> [Token] -> Token -> m ()
msg Token
cmd [Token]
leading (T_Literal Id
litId String
s) = do
        -- There are many different cases, and the order of the branches matter.
        case [Token]
leading of
            -- --foo=42
            [] | String
"-" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
s -> -- There's SC2215 for these
                forall (m :: * -> *) a. Monad m => a -> m a
return ()

            -- ======Hello======
            [] | String
"=" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
s ->
                case Token
originalToken of
                    T_SimpleCommand Id
_ [] [Token
word] | Token -> Bool
isConflictMarker Token
word ->
                        forall {m :: * -> *}. MonadWriter [TokenComment] m => Id -> m ()
conflictMarkerMsg (Token -> Id
getId Token
originalToken)
                    Token
_ | String
"===" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
s -> forall {m :: * -> *}. MonadWriter [TokenComment] m => Id -> m ()
borderMsg (Token -> Id
getId Token
originalToken)
                    Token
_ -> forall {m :: * -> *}. MonadWriter [TokenComment] m => Id -> m ()
prefixMsg (Token -> Id
getId Token
cmd)

            -- '$var==42'
            [Token]
_ | String
"==" forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` String
s ->
                forall {m :: * -> *}. MonadWriter [TokenComment] m => Id -> m ()
badComparisonMsg (Token -> Id
getId Token
cmd)

            -- '${foo[x]}=42' and '$foo=42'
            [T_DollarBraced Id
id Bool
braced Token
l] | String
"=" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
s -> do
                let variableStr :: String
variableStr = forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
l
                let variableReference :: String
variableReference = String -> String
getBracedReference String
variableStr
                let variableModifier :: String
variableModifier = String -> String
getBracedModifier String
variableStr
                let isPlain :: Bool
isPlain = String -> Bool
isVariableName String
variableStr
                let isPositional :: Bool
isPositional = forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isDigit String
variableStr

                let isArray :: Bool
isArray = String
variableReference forall a. Eq a => a -> a -> Bool
/= String
""
                                Bool -> Bool -> Bool
&& String
"[" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
variableModifier
                                Bool -> Bool -> Bool
&& String
"]" forall a. Eq a => [a] -> [a] -> Bool
`isSuffixOf` String
variableModifier

                case () of
                    -- '$foo=bar' should already have caused a parse-time SC1066
                    -- _ | not braced && isPlain ->
                    --    return ()

                    ()
_ | String
variableStr forall a. Eq a => a -> a -> Bool
== String
"" -> -- Don't try to fix ${}=foo
                        forall {m :: * -> *}. MonadWriter [TokenComment] m => Id -> m ()
genericMsg (Token -> Id
getId Token
cmd)

                    -- '$#=42' or '${#var}=42'
                    ()
_ | String
"#" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
variableStr ->
                        forall {m :: * -> *}. MonadWriter [TokenComment] m => Id -> m ()
genericMsg (Token -> Id
getId Token
cmd)

                    -- '${0}=42'
                    ()
_ | String
variableStr forall a. Eq a => a -> a -> Bool
== String
"0" ->
                        forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Fix -> m ()
assign0Msg Id
id forall a b. (a -> b) -> a -> b
$ [Replacement] -> Fix
fixWith [Id -> Parameters -> String -> Replacement
replaceToken Id
id Parameters
params String
"BASH_ARGV0"]

                    -- '$2=2'
                    ()
_ | Bool
isPositional ->
                        forall {m :: * -> *}. MonadWriter [TokenComment] m => Id -> m ()
positionalMsg Id
id

                    ()
_ | Bool
isArray Bool -> Bool -> Bool
|| Bool
isPlain ->
                        forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> Fix -> m ()
errWithFix Id
id Code
2281
                            (String
"Don't use " forall a. [a] -> [a] -> [a]
++ (if Bool
braced then String
"${}" else String
"$") forall a. [a] -> [a] -> [a]
++ String
" on the left side of assignments.") forall a b. (a -> b) -> a -> b
$
                                [Replacement] -> Fix
fixWith forall a b. (a -> b) -> a -> b
$
                                    if Bool
braced
                                      then [ Id -> Parameters -> Code -> String -> Replacement
replaceStart Id
id Parameters
params Code
2 String
"", Id -> Parameters -> Code -> String -> Replacement
replaceEnd Id
id Parameters
params Code
1 String
"" ]
                                      else [ Id -> Parameters -> Code -> String -> Replacement
replaceStart Id
id Parameters
params Code
1 String
"" ]

                    ()
_ -> forall {m :: * -> *}. MonadWriter [TokenComment] m => Id -> m ()
indirectionMsg Id
id

            -- 2=42
            [] | String
s String -> Regex -> Bool
`matches` Regex
positionalAssignmentRe ->
                if String
"0=" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
s
                then
                    forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Fix -> m ()
assign0Msg Id
litId forall a b. (a -> b) -> a -> b
$ [Replacement] -> Fix
fixWith [Id -> Parameters -> Code -> String -> Replacement
replaceStart Id
litId Parameters
params Code
1 String
"BASH_ARGV0"]
                else
                    forall {m :: * -> *}. MonadWriter [TokenComment] m => Id -> m ()
positionalMsg Id
litId

            -- 9foo=42
            [] | String -> Bool
isLeadingNumberVar String
s ->
                forall {m :: * -> *}. MonadWriter [TokenComment] m => Id -> m ()
leadingNumberMsg (Token -> Id
getId Token
cmd)

            -- var${foo}x=42
            (Token
_:[Token]
_) | [Token] -> Bool
mayBeVariableName [Token]
leading Bool -> Bool -> Bool
&& (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isVariableChar forall a b. (a -> b) -> a -> b
$ forall a. (a -> Bool) -> [a] -> [a]
takeWhile (forall a. Eq a => a -> a -> Bool
/= Char
'=') String
s) ->
                forall {m :: * -> *}. MonadWriter [TokenComment] m => Id -> m ()
indirectionMsg (Token -> Id
getId Token
cmd)

            [Token]
_ -> forall {m :: * -> *}. MonadWriter [TokenComment] m => Id -> m ()
genericMsg (Token -> Id
getId Token
cmd)


prop_checkSecondArgIsComparison1 :: Bool
prop_checkSecondArgIsComparison1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSecondArgIsComparison String
"foo = $bar"
prop_checkSecondArgIsComparison2 :: Bool
prop_checkSecondArgIsComparison2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSecondArgIsComparison String
"$foo = $bar"
prop_checkSecondArgIsComparison3 :: Bool
prop_checkSecondArgIsComparison3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSecondArgIsComparison String
"2f == $bar"
prop_checkSecondArgIsComparison4 :: Bool
prop_checkSecondArgIsComparison4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSecondArgIsComparison String
"'var' =$bar"
prop_checkSecondArgIsComparison5 :: Bool
prop_checkSecondArgIsComparison5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSecondArgIsComparison String
"foo ='$bar'"
prop_checkSecondArgIsComparison6 :: Bool
prop_checkSecondArgIsComparison6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSecondArgIsComparison String
"$foo =$bar"
prop_checkSecondArgIsComparison7 :: Bool
prop_checkSecondArgIsComparison7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSecondArgIsComparison String
"2f ==$bar"
prop_checkSecondArgIsComparison8 :: Bool
prop_checkSecondArgIsComparison8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSecondArgIsComparison String
"'var' =$bar"
prop_checkSecondArgIsComparison9 :: Bool
prop_checkSecondArgIsComparison9 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSecondArgIsComparison String
"var += $(foo)"
prop_checkSecondArgIsComparison10 :: Bool
prop_checkSecondArgIsComparison10 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSecondArgIsComparison String
"var +=$(foo)"
checkSecondArgIsComparison :: p -> Token -> m ()
checkSecondArgIsComparison p
_ Token
t =
    case Token
t of
        T_SimpleCommand Id
_ [Token]
_ (Token
lhs:Token
arg:[Token]
_) -> forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
            String
argString <- Token -> Maybe String
getLeadingUnquotedString Token
arg
            case String
argString of
                Char
'=':Char
'=':Char
'=':Char
'=':String
_ -> forall a. Maybe a
Nothing -- Don't warn about `echo ======` and such
                Char
'+':Char
'=':String
_ ->
                        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
headId Token
t) Code
2285 forall a b. (a -> b) -> a -> b
$
                            String
"Remove spaces around += to assign (or quote '+=' if literal)."
                Char
'=':Char
'=':String
_ ->
                        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
t) Code
2284 forall a b. (a -> b) -> a -> b
$
                            String
"Use [ x = y ] to compare values (or quote '==' if literal)."
                Char
'=':String
_ ->
                        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
headId Token
arg) Code
2283 forall a b. (a -> b) -> a -> b
$
                            String
"Remove spaces around = to assign (or use [ ] to compare, or quote '=' if literal)."
                String
_ -> forall a. Maybe a
Nothing
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    -- We don't pinpoint exactly, but this helps cases like foo =$bar
    headId :: Token -> Id
headId Token
t =
        case Token
t of
            T_NormalWord Id
_ (Token
x:[Token]
_) -> Token -> Id
getId Token
x
            Token
_ -> Token -> Id
getId Token
t


prop_checkCommandWithTrailingSymbol1 :: Bool
prop_checkCommandWithTrailingSymbol1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCommandWithTrailingSymbol String
"/"
prop_checkCommandWithTrailingSymbol2 :: Bool
prop_checkCommandWithTrailingSymbol2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCommandWithTrailingSymbol String
"/foo/ bar/baz"
prop_checkCommandWithTrailingSymbol3 :: Bool
prop_checkCommandWithTrailingSymbol3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCommandWithTrailingSymbol String
"/"
prop_checkCommandWithTrailingSymbol4 :: Bool
prop_checkCommandWithTrailingSymbol4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCommandWithTrailingSymbol String
"/*"
prop_checkCommandWithTrailingSymbol5 :: Bool
prop_checkCommandWithTrailingSymbol5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCommandWithTrailingSymbol String
"$foo/$bar"
prop_checkCommandWithTrailingSymbol6 :: Bool
prop_checkCommandWithTrailingSymbol6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCommandWithTrailingSymbol String
"foo, bar"
prop_checkCommandWithTrailingSymbol7 :: Bool
prop_checkCommandWithTrailingSymbol7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCommandWithTrailingSymbol String
". foo.sh"
prop_checkCommandWithTrailingSymbol8 :: Bool
prop_checkCommandWithTrailingSymbol8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCommandWithTrailingSymbol String
": foo"
prop_checkCommandWithTrailingSymbol9 :: Bool
prop_checkCommandWithTrailingSymbol9 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCommandWithTrailingSymbol String
"/usr/bin/python[23] file.py"

checkCommandWithTrailingSymbol :: p -> Token -> m ()
checkCommandWithTrailingSymbol p
_ Token
t =
    case Token
t of
        T_SimpleCommand Id
_ [Token]
_ (Token
cmd:[Token]
_) ->
            let str :: String
str = forall a. HasCallStack => Maybe a -> a
fromJust forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *).
Monad m =>
(Token -> m String) -> Token -> m String
getLiteralStringExt (\Token
_ -> forall a. a -> Maybe a
Just String
"x") Token
cmd
                last :: Char
last = forall {a}. a -> [a] -> a
lastOrDefault Char
'x' String
str
            in
                case String
str of
                    String
"." -> forall (m :: * -> *) a. Monad m => a -> m a
return ()  -- The . command
                    String
":" -> forall (m :: * -> *) a. Monad m => a -> m a
return ()  -- The : command
                    String
" " -> forall (m :: * -> *) a. Monad m => a -> m a
return ()  -- Probably caught by SC1101
                    String
"//" -> forall (m :: * -> *) a. Monad m => a -> m a
return () -- Probably caught by SC1127
                    String
"" -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
cmd) Code
2286 String
"This empty string is interpreted as a command name. Double check syntax (or use 'true' as a no-op)."
                    String
_ | Char
last forall a. Eq a => a -> a -> Bool
== Char
'/' -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
cmd) Code
2287 String
"This is interpreted as a command name ending with '/'. Double check syntax."
                    String
_ | Char
last forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
"\\.,([{<>}])#\"\'% " -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
cmd) Code
2288 (String
"This is interpreted as a command name ending with " forall a. [a] -> [a] -> [a]
++ (Char -> String
format Char
last) forall a. [a] -> [a] -> [a]
++ String
". Double check syntax.")
                    String
_ | Char
'\t' forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
str -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
cmd) Code
2289 String
"This is interpreted as a command name containing a tab. Double check syntax."
                    String
_ | Char
'\n' forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
str -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
cmd) Code
2289 String
"This is interpreted as a command name containing a linefeed. Double check syntax."
                    String
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    format :: Char -> String
format Char
x =
        case Char
x of
            Char
' ' -> String
"space"
            Char
'\'' -> String
"apostrophe"
            Char
'\"' -> String
"doublequote"
            Char
x -> Char
'\'' forall a. a -> [a] -> [a]
: Char
x forall a. a -> [a] -> [a]
: String
"\'"


prop_checkRequireDoubleBracket1 :: Bool
prop_checkRequireDoubleBracket1 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkRequireDoubleBracket String
"[ -x foo ]"
prop_checkRequireDoubleBracket2 :: Bool
prop_checkRequireDoubleBracket2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkRequireDoubleBracket String
"[ foo -o bar ]"
prop_checkRequireDoubleBracket3 :: Bool
prop_checkRequireDoubleBracket3 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkRequireDoubleBracket String
"#!/bin/sh\n[ -x foo ]"
prop_checkRequireDoubleBracket4 :: Bool
prop_checkRequireDoubleBracket4 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkRequireDoubleBracket String
"[[ -x foo ]]"
checkRequireDoubleBracket :: Parameters -> Token -> [TokenComment]
checkRequireDoubleBracket Parameters
params =
    if Parameters -> Bool
isBashLike Parameters
params
    then forall {w} {t :: * -> *} {t} {b}.
(Monoid w, Foldable t) =>
t (t -> Token -> WriterT w Identity b) -> t -> Token -> w
nodeChecksToTreeCheck [forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
check] Parameters
params
    else forall a b. a -> b -> a
const []
  where
    check :: p -> Token -> m ()
check p
_ Token
t = case Token
t of
        T_Condition Id
id ConditionType
SingleBracket Token
_ ->
            forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> Fix -> m ()
styleWithFix Id
id Code
2292 String
"Prefer [[ ]] over [ ] for tests in Bash/Ksh." (Token -> Fix
fixFor Token
t)
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()

    fixFor :: Token -> Fix
fixFor Token
t = [Replacement] -> Fix
fixWith forall a b. (a -> b) -> a -> b
$
        if Token -> Bool
isSimple Token
t
        then
            [
                Id -> Parameters -> Code -> String -> Replacement
replaceStart (Token -> Id
getId Token
t) Parameters
params Code
0 String
"[",
                Id -> Parameters -> Code -> String -> Replacement
replaceEnd (Token -> Id
getId Token
t) Parameters
params Code
0 String
"]"
            ]
        else []

    -- We don't tag operators like < and -o well enough to replace them,
    -- so just handle the simple cases.
    isSimple :: Token -> Bool
isSimple Token
t = case Token
t of
        T_Condition Id
_ ConditionType
_ Token
s -> Token -> Bool
isSimple Token
s
        TC_Binary Id
_ ConditionType
_ String
op Token
_ Token
_ -> Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (\Char
x -> Char
x forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
op) String
"<>"
        TC_Unary {} -> Bool
True
        TC_Nullary {} -> Bool
True
        Token
_ -> Bool
False


prop_checkUnquotedParameterExpansionPattern1 :: Bool
prop_checkUnquotedParameterExpansionPattern1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedParameterExpansionPattern  String
"echo \"${var#$x}\""
prop_checkUnquotedParameterExpansionPattern2 :: Bool
prop_checkUnquotedParameterExpansionPattern2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedParameterExpansionPattern  String
"echo \"${var%%$(x)}\""
prop_checkUnquotedParameterExpansionPattern3 :: Bool
prop_checkUnquotedParameterExpansionPattern3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedParameterExpansionPattern  String
"echo \"${var[#$x]}\""
prop_checkUnquotedParameterExpansionPattern4 :: Bool
prop_checkUnquotedParameterExpansionPattern4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedParameterExpansionPattern  String
"echo \"${var%\"$x\"}\""

checkUnquotedParameterExpansionPattern :: Parameters -> Token -> f ()
checkUnquotedParameterExpansionPattern Parameters
params Token
x =
    case Token
x of
        T_DollarBraced Id
_ Bool
True word :: Token
word@(T_NormalWord Id
_ (T_Literal Id
_ String
s : rest :: [Token]
rest@(Token
_:[Token]
_))) -> do
            let modifier :: String
modifier = String -> String
getBracedModifier forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
word
            forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
"%" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
modifier Bool -> Bool -> Bool
|| String
"#" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
modifier) forall a b. (a -> b) -> a -> b
$
                forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
check [Token]
rest
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    check :: Token -> m ()
check Token
t =
        case Token
t of
            T_DollarBraced {} -> forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
inform Token
t
            T_DollarExpansion {} -> forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
inform Token
t
            T_Backticked {} -> forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
inform Token
t
            Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()

    inform :: Token -> m ()
inform Token
t =
        forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> Fix -> m ()
infoWithFix (Token -> Id
getId Token
t) Code
2295
            String
"Expansions inside ${..} need to be quoted separately, otherwise they match as patterns." forall a b. (a -> b) -> a -> b
$
                Id -> Parameters -> String -> Fix
surroundWith (Token -> Id
getId Token
t) Parameters
params String
"\""


prop_checkArrayValueUsedAsIndex1 :: Bool
prop_checkArrayValueUsedAsIndex1 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkArrayValueUsedAsIndex  String
"for i in ${arr[@]}; do echo ${arr[i]}; done"
prop_checkArrayValueUsedAsIndex2 :: Bool
prop_checkArrayValueUsedAsIndex2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkArrayValueUsedAsIndex  String
"for i in ${arr[@]}; do echo ${arr[$i]}; done"
prop_checkArrayValueUsedAsIndex3 :: Bool
prop_checkArrayValueUsedAsIndex3 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkArrayValueUsedAsIndex  String
"for i in ${arr[@]}; do echo $((arr[i])); done"
prop_checkArrayValueUsedAsIndex4 :: Bool
prop_checkArrayValueUsedAsIndex4 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkArrayValueUsedAsIndex  String
"for i in ${arr1[@]} ${arr2[@]}; do echo ${arr1[$i]}; done"
prop_checkArrayValueUsedAsIndex5 :: Bool
prop_checkArrayValueUsedAsIndex5 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree forall {p}. Parameters -> p -> [TokenComment]
checkArrayValueUsedAsIndex  String
"for i in ${arr1[@]} ${arr2[@]}; do echo ${arr2[$i]}; done"
prop_checkArrayValueUsedAsIndex7 :: Bool
prop_checkArrayValueUsedAsIndex7 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkArrayValueUsedAsIndex  String
"for i in ${arr[@]}; do echo ${arr[K]}; done"
prop_checkArrayValueUsedAsIndex8 :: Bool
prop_checkArrayValueUsedAsIndex8 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkArrayValueUsedAsIndex  String
"for i in ${arr[@]}; do i=42; echo ${arr[i]}; done"
prop_checkArrayValueUsedAsIndex9 :: Bool
prop_checkArrayValueUsedAsIndex9 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree forall {p}. Parameters -> p -> [TokenComment]
checkArrayValueUsedAsIndex  String
"for i in ${arr[@]}; do echo ${arr2[i]}; done"

checkArrayValueUsedAsIndex :: Parameters -> p -> [TokenComment]
checkArrayValueUsedAsIndex Parameters
params p
_ =
    forall t v.
(Token -> Token -> String -> State t [v])
-> (Token -> Token -> String -> DataType -> State t [v])
-> t
-> [StackData]
-> [v]
doVariableFlowAnalysis forall {m :: * -> *} {t :: * -> *} {p}.
(MonadState (Map String (Token, t (Token, String))) m,
 Foldable t) =>
p -> Token -> String -> m [TokenComment]
read forall {k} {m :: * -> *} {p} {a}.
(MonadState (Map k (Token, [(Token, String)])) m, Ord k) =>
Token -> p -> k -> DataType -> m [a]
write forall k a. Map k a
Map.empty (Parameters -> [StackData]
variableFlow Parameters
params)
  where
    write :: Token -> p -> k -> DataType -> m [a]
write loop :: Token
loop@T_ForIn {}  p
_ k
name (DataString (SourceFrom [Token]
words)) = do
        forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify forall a b. (a -> b) -> a -> b
$ forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert k
name (Token
loop, forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe Token -> Maybe (Token, String)
f [Token]
words)
        forall (m :: * -> *) a. Monad m => a -> m a
return []
      where
        f :: Token -> Maybe (Token, String)
f Token
x = do
            String
name <- Token -> Maybe String
getArrayName Token
x
            forall (m :: * -> *) a. Monad m => a -> m a
return (Token
x, String
name)

    write Token
_ p
_ k
name DataType
_ = do
        forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify forall a b. (a -> b) -> a -> b
$ forall k a. Ord k => k -> Map k a -> Map k a
Map.delete k
name
        forall (m :: * -> *) a. Monad m => a -> m a
return []

    read :: p -> Token -> String -> m [TokenComment]
read p
_ Token
t String
name = do
        Map String (Token, t (Token, String))
varMap <- forall s (m :: * -> *). MonadState s m => m s
get
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall a. a -> Maybe a -> a
fromMaybe [] forall a b. (a -> b) -> a -> b
$ do
            (Token
loop, t (Token, String)
arrays) <- forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup String
name Map String (Token, t (Token, String))
varMap
            (Token
arrayRef, String
arrayName) <- String -> Token -> Maybe (Token, String)
getArrayIfUsedAsIndex String
name Token
t
            -- Is this one of the 'for' arrays?
            (Token
loopWord, String
_) <- forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find ((forall a. Eq a => a -> a -> Bool
==String
arrayName) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a, b) -> b
snd) t (Token, String)
arrays
            -- Are we still in this loop?
            forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ Token -> Id
getId Token
loop forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` forall a b. (a -> b) -> [a] -> [b]
map Token -> Id
getId (Map Id Token -> Token -> [Token]
getPath Map Id Token
parents Token
t)
            forall (m :: * -> *) a. Monad m => a -> m a
return [
                Severity -> Id -> Code -> String -> TokenComment
makeComment Severity
WarningC (Token -> Id
getId Token
loopWord) Code
2302 String
"This loops over values. To loop over keys, use \"${!array[@]}\".",
                Severity -> Id -> Code -> String -> TokenComment
makeComment Severity
WarningC (Token -> Id
getId Token
arrayRef) Code
2303 forall a b. (a -> b) -> a -> b
$ (String -> String
e4m String
name) forall a. [a] -> [a] -> [a]
++ String
" is an array value, not a key. Use directly or loop over keys instead."
                ]

    parents :: Map Id Token
parents = Parameters -> Map Id Token
parentMap Parameters
params

    getArrayName :: Token -> Maybe String
    getArrayName :: Token -> Maybe String
getArrayName Token
t = do
        [T_DollarBraced Id
_ Bool
_ Token
l] <- forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Token -> [Token]
getWordParts Token
t
        let str :: String
str = forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
l
        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ String -> String
getBracedModifier String
str forall a. Eq a => a -> a -> Bool
== String
"[@]" Bool -> Bool -> Bool
&& Bool -> Bool
not (String
"!" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
str)
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ String -> String
getBracedReference String
str

    -- This is much uglier than it should be
    getArrayIfUsedAsIndex :: String -> Token -> Maybe (Token, String)
    getArrayIfUsedAsIndex :: String -> Token -> Maybe (Token, String)
getArrayIfUsedAsIndex String
name Token
t =
        case Token
t of
            T_DollarBraced Id
_ Bool
_ Token
list -> do
                let ref :: String
ref = String -> String
getBracedReference forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
list
                forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ String
ref forall a. Eq a => a -> a -> Bool
== String
name
                -- We found a $name. Look up the chain to see if it's ${arr[$name]}
                list :: Token
list@T_NormalWord {} <- forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup (Token -> Id
getId Token
t) Map Id Token
parents
                (T_DollarBraced Id
_ Bool
_ Token
parentList) <- forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup (Token -> Id
getId Token
list) Map Id Token
parents
                (T_Literal Id
_ String
head : Token
index : T_Literal Id
_ String
tail : [Token]
_) <- forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Token -> [Token]
getWordParts Token
parentList
                let str :: String
str = forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
list
                let modifier :: String
modifier = String -> String
getBracedModifier String
str
                forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ Token -> Id
getId Token
index forall a. Eq a => a -> a -> Bool
== Token -> Id
getId Token
t
                forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ String
"[${VAR}]" forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
modifier
                forall (m :: * -> *) a. Monad m => a -> m a
return (Token
t, String -> String
getBracedReference String
str)

            T_NormalWord Id
wordId [Token]
list -> do
                    -- We found just name. Check if it's part of ${something[name]}
                    parent :: Token
parent@(T_DollarBraced Id
_ Bool
_ Token
parentList) <- forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup Id
wordId Map Id Token
parents
                    let str :: String
str = forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
t
                    let modifier :: String
modifier = String -> String
getBracedModifier String
str
                    forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ (String
"[" forall a. [a] -> [a] -> [a]
++ String
name forall a. [a] -> [a] -> [a]
++ String
"]") forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
modifier
                    forall (m :: * -> *) a. Monad m => a -> m a
return (Token
parent, String -> String
getBracedReference String
str)

            TA_Variable Id
indexId String
ref [] -> do
                -- We found arithmetic name. See if it's part of arithmetic arr[name]
                forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ String
ref forall a. Eq a => a -> a -> Bool
== String
name
                (TA_Sequence Id
seqId [Token
element]) <- forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup Id
indexId Map Id Token
parents
                forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ Token -> Id
getId Token
element forall a. Eq a => a -> a -> Bool
== Id
indexId
                parent :: Token
parent@(TA_Variable Id
arrayId String
arrayName [Token
element]) <- forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup Id
seqId Map Id Token
parents
                forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall a b. (a -> b) -> a -> b
$ Token -> Id
getId Token
element forall a. Eq a => a -> a -> Bool
== Id
seqId
                forall (m :: * -> *) a. Monad m => a -> m a
return (Token
parent, String
arrayName)

            Token
_ -> forall a. Maybe a
Nothing

prop_checkSetESuppressed1 :: Bool
prop_checkSetESuppressed1  = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
checkSetESuppressed String
"set -e; f(){ :; }; x=$(f)"
prop_checkSetESuppressed2 :: Bool
prop_checkSetESuppressed2  = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSetESuppressed String
"f(){ :; }; x=$(f)"
prop_checkSetESuppressed3 :: Bool
prop_checkSetESuppressed3  = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSetESuppressed String
"set -e; f(){ :; }; x=$(set -e; f)"
prop_checkSetESuppressed4 :: Bool
prop_checkSetESuppressed4  = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
checkSetESuppressed String
"set -e; f(){ :; }; baz=$(set -e; f) || :"
prop_checkSetESuppressed5 :: Bool
prop_checkSetESuppressed5  = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSetESuppressed String
"set -e; f(){ :; }; baz=$(echo \"\") || :"
prop_checkSetESuppressed6 :: Bool
prop_checkSetESuppressed6  = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
checkSetESuppressed String
"set -e; f(){ :; }; f && echo"
prop_checkSetESuppressed7 :: Bool
prop_checkSetESuppressed7  = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
checkSetESuppressed String
"set -e; f(){ :; }; f || echo"
prop_checkSetESuppressed8 :: Bool
prop_checkSetESuppressed8  = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSetESuppressed String
"set -e; f(){ :; }; echo && f"
prop_checkSetESuppressed9 :: Bool
prop_checkSetESuppressed9  = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSetESuppressed String
"set -e; f(){ :; }; echo || f"
prop_checkSetESuppressed10 :: Bool
prop_checkSetESuppressed10 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
checkSetESuppressed String
"set -e; f(){ :; }; ! f"
prop_checkSetESuppressed11 :: Bool
prop_checkSetESuppressed11 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
checkSetESuppressed String
"set -e; f(){ :; }; if f; then :; fi"
prop_checkSetESuppressed12 :: Bool
prop_checkSetESuppressed12 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
checkSetESuppressed String
"set -e; f(){ :; }; if set -e; f; then :; fi"
prop_checkSetESuppressed13 :: Bool
prop_checkSetESuppressed13 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
checkSetESuppressed String
"set -e; f(){ :; }; while f; do :; done"
prop_checkSetESuppressed14 :: Bool
prop_checkSetESuppressed14 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
checkSetESuppressed String
"set -e; f(){ :; }; while set -e; f; do :; done"
prop_checkSetESuppressed15 :: Bool
prop_checkSetESuppressed15 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
checkSetESuppressed String
"set -e; f(){ :; }; until f; do :; done"
prop_checkSetESuppressed16 :: Bool
prop_checkSetESuppressed16 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
checkSetESuppressed String
"set -e; f(){ :; }; until set -e; f; do :; done"
prop_checkSetESuppressed17 :: Bool
prop_checkSetESuppressed17 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSetESuppressed String
"set -e; f(){ :; }; g(){ :; }; g f"
prop_checkSetESuppressed18 :: Bool
prop_checkSetESuppressed18 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSetESuppressed String
"set -e; shopt -s inherit_errexit; f(){ :; }; x=$(f)"
prop_checkSetESuppressed19 :: Bool
prop_checkSetESuppressed19 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSetESuppressed String
"set -e; set -o posix; f(){ :; }; x=$(f)"
checkSetESuppressed :: Parameters -> Token -> [TokenComment]
checkSetESuppressed Parameters
params Token
t =
    if Parameters -> Bool
hasSetE Parameters
params then forall {w} {t}.
Monoid w =>
(t -> Token -> WriterT w Identity ()) -> t -> Token -> w
runNodeAnalysis forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkNode Parameters
params Token
t else []
  where
    checkNode :: p -> Token -> f ()
checkNode p
_ (T_SimpleCommand Id
_ [Token]
_ (Token
cmd:[Token]
_)) = forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isFunction Token
cmd) (forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkCmd Token
cmd)
    checkNode p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

    functions_ :: Map String Id
functions_ = Token -> Map String Id
functions Token
t

    isFunction :: Token -> Bool
isFunction Token
cmd = forall a. Maybe a -> Bool
isJust forall a b. (a -> b) -> a -> b
$ do
        String
literalArg <- Token -> Maybe String
getUnquotedLiteral Token
cmd
        forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup String
literalArg Map String Id
functions_

    checkCmd :: Token -> m ()
checkCmd Token
cmd = forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
[Token] -> m ()
go forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) Token
cmd
      where
        go :: [Token] -> m ()
go (Token
child:Token
parent:[Token]
rest) = do
            case Token
parent of
                T_Banged Id
_ Token
condition   | Token
child Token -> [Token] -> Bool
`isIn` [Token
condition] -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
String -> Token -> m ()
informConditional String
"a ! condition" Token
cmd
                T_AndIf  Id
_ Token
condition Token
_ | Token
child Token -> [Token] -> Bool
`isIn` [Token
condition] -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
String -> Token -> m ()
informConditional String
"an && condition" Token
cmd
                T_OrIf   Id
_ Token
condition Token
_ | Token
child Token -> [Token] -> Bool
`isIn` [Token
condition] -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
String -> Token -> m ()
informConditional String
"an || condition" Token
cmd
                T_IfExpression    Id
_ [([Token], [Token])]
condition [Token]
_ | Token
child Token -> [Token] -> Bool
`isIn` forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap forall a b. (a, b) -> a
fst [([Token], [Token])]
condition -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
String -> Token -> m ()
informConditional String
"an 'if' condition" Token
cmd
                T_UntilExpression Id
_ [Token]
condition [Token]
_ | Token
child Token -> [Token] -> Bool
`isIn` [Token]
condition -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
String -> Token -> m ()
informConditional String
"an 'until' condition" Token
cmd
                T_WhileExpression Id
_ [Token]
condition [Token]
_ | Token
child Token -> [Token] -> Bool
`isIn` [Token]
condition -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
String -> Token -> m ()
informConditional String
"a 'while' condition" Token
cmd
                T_DollarExpansion {} | Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ Token -> Bool
errExitEnabled Token
parent -> forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
informUninherited Token
cmd
                T_Backticked      {} | Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ Token -> Bool
errExitEnabled Token
parent -> forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
informUninherited Token
cmd
                Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
            [Token] -> m ()
go (Token
parentforall a. a -> [a] -> [a]
:[Token]
rest)
        go [Token]
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

        informConditional :: String -> Token -> m ()
informConditional String
condType Token
t =
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
t) Code
2310 (
                String
"This function is invoked in " forall a. [a] -> [a] -> [a]
++ String
condType forall a. [a] -> [a] -> [a]
++ String
" so set -e " forall a. [a] -> [a] -> [a]
++
                String
"will be disabled. Invoke separately if failures should " forall a. [a] -> [a] -> [a]
++
                String
"cause the script to exit.")
        informUninherited :: Token -> m ()
informUninherited Token
t =
            forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
t) Code
2311 (
                String
"Bash implicitly disabled set -e for this function " forall a. [a] -> [a] -> [a]
++
                String
"invocation because it's inside a command substitution. " forall a. [a] -> [a] -> [a]
++
                String
"Add set -e; before it or enable inherit_errexit.")
        errExitEnabled :: Token -> Bool
errExitEnabled Token
t = Parameters -> Bool
hasInheritErrexit Parameters
params Bool -> Bool -> Bool
|| Token -> Bool
containsSetE Token
t
        isIn :: Token -> [Token] -> Bool
isIn Token
t [Token]
cmds = Token -> Id
getId Token
t forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` forall a b. (a -> b) -> [a] -> [b]
map Token -> Id
getId [Token]
cmds


prop_checkExtraMaskedReturns1 :: Bool
prop_checkExtraMaskedReturns1  = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"cat < <(ls)"
prop_checkExtraMaskedReturns2 :: Bool
prop_checkExtraMaskedReturns2  = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"read -ra arr <(ls)"
prop_checkExtraMaskedReturns3 :: Bool
prop_checkExtraMaskedReturns3  = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"ls >(cat)"
prop_checkExtraMaskedReturns4 :: Bool
prop_checkExtraMaskedReturns4  = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"false | true"
prop_checkExtraMaskedReturns5 :: Bool
prop_checkExtraMaskedReturns5  = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"set -o pipefail; false | true"
prop_checkExtraMaskedReturns6 :: Bool
prop_checkExtraMaskedReturns6  = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"false | true || true"
prop_checkExtraMaskedReturns7 :: Bool
prop_checkExtraMaskedReturns7  = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"true $(false)"
prop_checkExtraMaskedReturns8 :: Bool
prop_checkExtraMaskedReturns8  = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"x=$(false)$(true)"
prop_checkExtraMaskedReturns9 :: Bool
prop_checkExtraMaskedReturns9  = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"x=$(false)true"
prop_checkExtraMaskedReturns10 :: Bool
prop_checkExtraMaskedReturns10 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"x=`false``false`"
prop_checkExtraMaskedReturns11 :: Bool
prop_checkExtraMaskedReturns11 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"x=\"$(false)$(true)\""
prop_checkExtraMaskedReturns12 :: Bool
prop_checkExtraMaskedReturns12 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"x=\"$(false)\"\"$(true)\""
prop_checkExtraMaskedReturns13 :: Bool
prop_checkExtraMaskedReturns13 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"true <<<$(false)"
prop_checkExtraMaskedReturns14 :: Bool
prop_checkExtraMaskedReturns14 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"echo asdf | false"
prop_checkExtraMaskedReturns15 :: Bool
prop_checkExtraMaskedReturns15 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"readonly x=$(false)"
prop_checkExtraMaskedReturns16 :: Bool
prop_checkExtraMaskedReturns16 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"readarray -t files < <(ls)"
prop_checkExtraMaskedReturns17 :: Bool
prop_checkExtraMaskedReturns17 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"x=( $(false) false )"
prop_checkExtraMaskedReturns18 :: Bool
prop_checkExtraMaskedReturns18 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"x=( $(false) $(false) )"
prop_checkExtraMaskedReturns19 :: Bool
prop_checkExtraMaskedReturns19 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"x=( $(false) [4]=false )"
prop_checkExtraMaskedReturns20 :: Bool
prop_checkExtraMaskedReturns20 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"x=( $(false) [4]=$(false) )"
prop_checkExtraMaskedReturns21 :: Bool
prop_checkExtraMaskedReturns21 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"cat << foo\n $(false)\nfoo"
prop_checkExtraMaskedReturns22 :: Bool
prop_checkExtraMaskedReturns22 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"[[ $(false) ]]"
prop_checkExtraMaskedReturns23 :: Bool
prop_checkExtraMaskedReturns23 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"x=$(false) y=z"
prop_checkExtraMaskedReturns24 :: Bool
prop_checkExtraMaskedReturns24 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"x=$(( $(date +%s) ))"
prop_checkExtraMaskedReturns25 :: Bool
prop_checkExtraMaskedReturns25 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"echo $(( $(date +%s) ))"
prop_checkExtraMaskedReturns26 :: Bool
prop_checkExtraMaskedReturns26 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"x=( $(false) )"
prop_checkExtraMaskedReturns27 :: Bool
prop_checkExtraMaskedReturns27 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"x=$(false) false"
prop_checkExtraMaskedReturns28 :: Bool
prop_checkExtraMaskedReturns28 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"x=$(false) y=$(false)"
prop_checkExtraMaskedReturns29 :: Bool
prop_checkExtraMaskedReturns29 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"false < <(set -e)"
prop_checkExtraMaskedReturns30 :: Bool
prop_checkExtraMaskedReturns30 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"false < <(shopt -s cdspell)"
prop_checkExtraMaskedReturns31 :: Bool
prop_checkExtraMaskedReturns31 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"false < <(dirname \"${BASH_SOURCE[0]}\")"
prop_checkExtraMaskedReturns32 :: Bool
prop_checkExtraMaskedReturns32 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"false < <(basename \"${BASH_SOURCE[0]}\")"
prop_checkExtraMaskedReturns33 :: Bool
prop_checkExtraMaskedReturns33 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"{ false || true; } | true"
prop_checkExtraMaskedReturns34 :: Bool
prop_checkExtraMaskedReturns34 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"{ false || :; } | true"
prop_checkExtraMaskedReturns35 :: Bool
prop_checkExtraMaskedReturns35 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"f() { local -r x=$(false); }"
prop_checkExtraMaskedReturns36 :: Bool
prop_checkExtraMaskedReturns36 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"time false"
prop_checkExtraMaskedReturns37 :: Bool
prop_checkExtraMaskedReturns37 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"time $(time false)"
prop_checkExtraMaskedReturns38 :: Bool
prop_checkExtraMaskedReturns38 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns String
"x=$(time time time false) time $(time false)"

checkExtraMaskedReturns :: Parameters -> Token -> [TokenComment]
checkExtraMaskedReturns Parameters
params Token
t =
    forall {w} {t}.
Monoid w =>
(t -> Token -> WriterT w Identity ()) -> t -> Token -> w
runNodeAnalysis forall {p}. p -> Token -> Writer [TokenComment] ()
findMaskingNodes Parameters
params (Token -> Token
removeTransparentCommands Token
t)
  where
    findMaskingNodes :: p -> Token -> Writer [TokenComment] ()
findMaskingNodes p
_ (T_Arithmetic Id
_ Token
list) = [Token] -> Writer [TokenComment] ()
findMaskedNodesInList [Token
list]
    findMaskingNodes p
_ (T_Array Id
_ [Token]
list) = [Token] -> Writer [TokenComment] ()
findMaskedNodesInList forall a b. (a -> b) -> a -> b
$ [Token] -> [Token]
allButLastSimpleCommands [Token]
list
    findMaskingNodes p
_ (T_Condition Id
_ ConditionType
_ Token
condition) = [Token] -> Writer [TokenComment] ()
findMaskedNodesInList [Token
condition]
    findMaskingNodes p
_ (T_DoubleQuoted Id
_ [Token]
list) = [Token] -> Writer [TokenComment] ()
findMaskedNodesInList forall a b. (a -> b) -> a -> b
$ [Token] -> [Token]
allButLastSimpleCommands [Token]
list
    findMaskingNodes p
_ (T_HereDoc Id
_ Dashed
_ Quoted
_ String
_ [Token]
list) = [Token] -> Writer [TokenComment] ()
findMaskedNodesInList [Token]
list
    findMaskingNodes p
_ (T_HereString Id
_ Token
word) = [Token] -> Writer [TokenComment] ()
findMaskedNodesInList [Token
word]
    findMaskingNodes p
_ (T_NormalWord Id
_ [Token]
parts) = [Token] -> Writer [TokenComment] ()
findMaskedNodesInList forall a b. (a -> b) -> a -> b
$ [Token] -> [Token]
allButLastSimpleCommands [Token]
parts
    findMaskingNodes p
_ (T_Pipeline Id
_ [Token]
_ [Token]
cmds) | Bool -> Bool
not (Parameters -> Bool
hasPipefail Parameters
params) = [Token] -> Writer [TokenComment] ()
findMaskedNodesInList forall a b. (a -> b) -> a -> b
$ [Token] -> [Token]
allButLastSimpleCommands [Token]
cmds
    findMaskingNodes p
_ (T_ProcSub Id
_ String
_ [Token]
list) = [Token] -> Writer [TokenComment] ()
findMaskedNodesInList [Token]
list
    findMaskingNodes p
_ (T_SimpleCommand Id
_ [Token]
assigns (Token
_:[Token]
args)) = [Token] -> Writer [TokenComment] ()
findMaskedNodesInList forall a b. (a -> b) -> a -> b
$ [Token]
assigns forall a. [a] -> [a] -> [a]
++ [Token]
args
    findMaskingNodes p
_ (T_SimpleCommand Id
_ [Token]
assigns []) = [Token] -> Writer [TokenComment] ()
findMaskedNodesInList forall a b. (a -> b) -> a -> b
$ [Token] -> [Token]
allButLastSimpleCommands [Token]
assigns
    findMaskingNodes p
_ Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

    findMaskedNodesInList :: [Token] -> Writer [TokenComment] ()
findMaskedNodesInList = forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> Token -> m Token
doAnalysis forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
findMaskedNodes)

    isMaskedNode :: Token -> Bool
isMaskedNode Token
t = Bool -> Bool
not (Token -> Bool
isHarmlessCommand Token
t Bool -> Bool -> Bool
|| Token -> Bool
isCheckedElsewhere Token
t Bool -> Bool -> Bool
|| Token -> Bool
isMaskDeliberate Token
t)
    findMaskedNodes :: Token -> f ()
findMaskedNodes t :: Token
t@(T_SimpleCommand Id
_ [Token]
_ (Token
_:[Token]
_)) = forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isMaskedNode Token
t) forall a b. (a -> b) -> a -> b
$ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
inform Token
t
    findMaskedNodes t :: Token
t@T_Condition {} = forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isMaskedNode Token
t) forall a b. (a -> b) -> a -> b
$ forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
inform Token
t
    findMaskedNodes Token
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()

    containsSimpleCommand :: Token -> Bool
containsSimpleCommand Token
t = forall a. Maybe a -> Bool
isNothing forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> Token -> m Token
doAnalysis forall {m :: * -> *}. MonadFail m => Token -> m ()
go Token
t
      where
        go :: Token -> m ()
go Token
t = case Token
t of
            T_SimpleCommand {} -> forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
""
            Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()

    allButLastSimpleCommands :: [Token] -> [Token]
allButLastSimpleCommands [Token]
cmds =
        if forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Token]
simpleCommands then [] else forall a. [a] -> [a]
init [Token]
simpleCommands
      where
        simpleCommands :: [Token]
simpleCommands = forall a. (a -> Bool) -> [a] -> [a]
filter Token -> Bool
containsSimpleCommand [Token]
cmds

    removeTransparentCommands :: Token -> Token
removeTransparentCommands Token
t =
        (Token -> Token) -> Token -> Token
doTransform Token -> Token
go Token
t
      where
        go :: Token -> Token
go cmd :: Token
cmd@(T_SimpleCommand Id
id [Token]
assigns (Token
_:[Token]
args)) | Token -> Bool
isTransparentCommand Token
cmd
          = Id -> [Token] -> [Token] -> Token
T_SimpleCommand Id
id [Token]
assigns [Token]
args
        go Token
t = Token
t

    inform :: Token -> m ()
inform Token
t = forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
t) Code
2312 (String
"Consider invoking this command "
        forall a. [a] -> [a] -> [a]
++ String
"separately to avoid masking its return value (or use '|| true' "
        forall a. [a] -> [a] -> [a]
++ String
"to ignore).")

    isMaskDeliberate :: Token -> Bool
isMaskDeliberate Token
t = (Token -> Token -> Bool) -> Token -> Bool
hasParent forall {p}. p -> Token -> Bool
isOrIf Token
t
      where
        isOrIf :: p -> Token -> Bool
isOrIf p
_ (T_OrIf Id
_ Token
_ (T_Pipeline Id
_ [Token]
_ [T_Redirecting Id
_ [Token]
_ Token
cmd]))
            = Token -> Maybe String
getCommandBasename Token
cmd forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [forall a. a -> Maybe a
Just String
"true", forall a. a -> Maybe a
Just String
":"]
        isOrIf p
_ Token
_ = Bool
False

    isCheckedElsewhere :: Token -> Bool
isCheckedElsewhere Token
t = (Token -> Token -> Bool) -> Token -> Bool
hasParent forall {p}. Token -> p -> Bool
isDeclaringCommand Token
t
      where
        isDeclaringCommand :: Token -> p -> Bool
isDeclaringCommand Token
t p
_ = forall a. a -> Maybe a -> a
fromMaybe Bool
False forall a b. (a -> b) -> a -> b
$ do
            Token
cmd <- Token -> Maybe Token
getCommand Token
t
            String
basename <- Token -> Maybe String
getCommandBasename Token
cmd
            forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$
                case String
basename of
                    -- local -r x=$(false) is intentionally ignored for SC2155
                    String
"local" | String
"r" forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` (forall a b. (a -> b) -> [a] -> [b]
map forall a b. (a, b) -> b
snd forall a b. (a -> b) -> a -> b
$ Token -> [(Token, String)]
getAllFlags Token
cmd) -> Bool
False
                    String
_ -> String
basename forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
declaringCommands

    isHarmlessCommand :: Token -> Bool
isHarmlessCommand Token
t = forall a. a -> Maybe a -> a
fromMaybe Bool
False forall a b. (a -> b) -> a -> b
$ do
        String
basename <- Token -> Maybe String
getCommandBasename Token
t
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ String
basename forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [
            String
"echo"
            ,String
"basename"
            ,String
"dirname"
            ,String
"printf"
            ,String
"set"
            ,String
"shopt"
            ]

    isTransparentCommand :: Token -> Bool
isTransparentCommand Token
t = forall a. a -> Maybe a -> a
fromMaybe Bool
False forall a b. (a -> b) -> a -> b
$ do
        String
basename <- Token -> Maybe String
getCommandBasename Token
t
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ String
basename forall a. Eq a => a -> a -> Bool
== String
"time"

    parentChildPairs :: Token -> [(Token, Token)]
parentChildPairs Token
t = forall {a}. [a] -> [(a, a)]
go forall a b. (a -> b) -> a -> b
$ Parameters -> Token -> [Token]
parents Parameters
params Token
t
      where
        go :: [a] -> [(a, a)]
go (a
child:a
parent:[a]
rest) = (a
parent, a
child)forall a. a -> [a] -> [a]
:[a] -> [(a, a)]
go (a
parentforall a. a -> [a] -> [a]
:[a]
rest)
        go [a]
_ = []

    hasParent :: (Token -> Token -> Bool) -> Token -> Bool
hasParent Token -> Token -> Bool
pred Token
t = forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry Token -> Token -> Bool
pred) (Token -> [(Token, Token)]
parentChildPairs Token
t)


-- hard error on negated command that is not last
prop_checkBatsTestDoesNotUseNegation1 :: Bool
prop_checkBatsTestDoesNotUseNegation1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkBatsTestDoesNotUseNegation String
"#!/usr/bin/env/bats\n@test \"name\" { ! true;  false; }"
prop_checkBatsTestDoesNotUseNegation2 :: Bool
prop_checkBatsTestDoesNotUseNegation2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkBatsTestDoesNotUseNegation String
"#!/usr/bin/env/bats\n@test \"name\" { ! [[ -e test ]]; false; }"
prop_checkBatsTestDoesNotUseNegation3 :: Bool
prop_checkBatsTestDoesNotUseNegation3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkBatsTestDoesNotUseNegation String
"#!/usr/bin/env/bats\n@test \"name\" { ! [ -e test ]; false; }"
-- acceptable formats:
--     using run
prop_checkBatsTestDoesNotUseNegation4 :: Bool
prop_checkBatsTestDoesNotUseNegation4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkBatsTestDoesNotUseNegation String
"#!/usr/bin/env/bats\n@test \"name\" { run ! true; }"
--     using || false
prop_checkBatsTestDoesNotUseNegation5 :: Bool
prop_checkBatsTestDoesNotUseNegation5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkBatsTestDoesNotUseNegation String
"#!/usr/bin/env/bats\n@test \"name\" { ! [[ -e test ]] || false; }"
prop_checkBatsTestDoesNotUseNegation6 :: Bool
prop_checkBatsTestDoesNotUseNegation6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkBatsTestDoesNotUseNegation String
"#!/usr/bin/env/bats\n@test \"name\" { ! [ -e test ] || false; }"
-- only style warning when last command
prop_checkBatsTestDoesNotUseNegation7 :: Bool
prop_checkBatsTestDoesNotUseNegation7 = (Parameters -> Token -> Writer [TokenComment] ())
-> [Code] -> String -> Bool
verifyCodes forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkBatsTestDoesNotUseNegation [Code
2314] String
"#!/usr/bin/env/bats\n@test \"name\" { ! true; }"
prop_checkBatsTestDoesNotUseNegation8 :: Bool
prop_checkBatsTestDoesNotUseNegation8 = (Parameters -> Token -> Writer [TokenComment] ())
-> [Code] -> String -> Bool
verifyCodes forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkBatsTestDoesNotUseNegation [Code
2315] String
"#!/usr/bin/env/bats\n@test \"name\" { ! [[ -e test ]]; }"
prop_checkBatsTestDoesNotUseNegation9 :: Bool
prop_checkBatsTestDoesNotUseNegation9 = (Parameters -> Token -> Writer [TokenComment] ())
-> [Code] -> String -> Bool
verifyCodes forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkBatsTestDoesNotUseNegation [Code
2315] String
"#!/usr/bin/env/bats\n@test \"name\" { ! [ -e test ]; }"

checkBatsTestDoesNotUseNegation :: Parameters -> Token -> m ()
checkBatsTestDoesNotUseNegation Parameters
params Token
t =
    case Token
t of
        T_BatsTest Id
_ String
_ (T_BraceGroup Id
_ [Token]
commands) -> forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
[Token] -> Token -> m ()
check [Token]
commands) [Token]
commands
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    check :: [Token] -> Token -> m ()
check [Token]
commands Token
t =
        case Token
t of
            T_Banged Id
id (T_Pipeline Id
_ [Token]
_ [T_Redirecting Id
_ [Token]
_ (T_Condition Id
idCondition ConditionType
_ Token
_)]) ->
                                if Token
t forall {t}. Eq t => t -> [t] -> Bool
`isLastOf` [Token]
commands
                                then forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style Id
id Code
2315 String
"In Bats, ! will not fail the test if it is not the last command anymore. Fold the `!` into the conditional!"
                                else forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err   Id
id Code
2315 String
"In Bats, ! does not cause a test failure. Fold the `!` into the conditional!"

            T_Banged Id
id Token
cmd -> if Token
t forall {t}. Eq t => t -> [t] -> Bool
`isLastOf` [Token]
commands
                                then forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> Fix -> m ()
styleWithFix Id
id Code
2314 String
"In Bats, ! will not fail the test if it is not the last command anymore. Use `run ! ` (on Bats >= 1.5.0) instead."
                                                ([Replacement] -> Fix
fixWith [Id -> Parameters -> Code -> String -> Replacement
replaceStart Id
id Parameters
params Code
0 String
"run "])
                                else forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> Fix -> m ()
errWithFix   Id
id Code
2314 String
"In Bats, ! does not cause a test failure. Use 'run ! ' (on Bats >= 1.5.0) instead."
                                                ([Replacement] -> Fix
fixWith [Id -> Parameters -> Code -> String -> Replacement
replaceStart Id
id Parameters
params Code
0 String
"run "])
            Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
    isLastOf :: t -> [t] -> Bool
isLastOf t
t [t]
commands =
        case [t]
commands of
            [t
x] -> t
x forall a. Eq a => a -> a -> Bool
== t
t
            t
x:[t]
rest -> t -> [t] -> Bool
isLastOf t
t [t]
rest
            [] -> Bool
False


prop_checkCommandIsUnreachable1 :: Bool
prop_checkCommandIsUnreachable1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCommandIsUnreachable String
"foo; bar; exit; baz"
prop_checkCommandIsUnreachable2 :: Bool
prop_checkCommandIsUnreachable2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCommandIsUnreachable String
"die() { exit; }; foo; bar; die; baz"
prop_checkCommandIsUnreachable3 :: Bool
prop_checkCommandIsUnreachable3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCommandIsUnreachable String
"foo; bar || exit; baz"
checkCommandIsUnreachable :: Parameters -> Token -> m ()
checkCommandIsUnreachable Parameters
params Token
t =
    case Token
t of
        T_Pipeline {} -> forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
            ProgramState
state <- CFGAnalysis -> Id -> Maybe ProgramState
CF.getIncomingState (Parameters -> CFGAnalysis
cfgAnalysis Parameters
params) Id
id
            forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ ProgramState -> Bool
CF.stateIsReachable ProgramState
state
            forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ Parameters -> Token -> Bool
isSourced Parameters
params Token
t
            forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info Id
id Code
2317 String
"Command appears to be unreachable. Check usage (or ignore if invoked indirectly)."
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where id :: Id
id = Token -> Id
getId Token
t


prop_checkOverwrittenExitCode1 :: Bool
prop_checkOverwrittenExitCode1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkOverwrittenExitCode String
"x; [ $? -eq 1 ] || [ $? -eq 2 ]"
prop_checkOverwrittenExitCode2 :: Bool
prop_checkOverwrittenExitCode2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkOverwrittenExitCode String
"x; [ $? -eq 1 ]"
prop_checkOverwrittenExitCode3 :: Bool
prop_checkOverwrittenExitCode3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkOverwrittenExitCode String
"x; echo \"Exit is $?\"; [ $? -eq 0 ]"
prop_checkOverwrittenExitCode4 :: Bool
prop_checkOverwrittenExitCode4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkOverwrittenExitCode String
"x; [ $? -eq 0 ] && echo Success"
prop_checkOverwrittenExitCode5 :: Bool
prop_checkOverwrittenExitCode5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkOverwrittenExitCode String
"x; if [ $? -eq 0 ]; then var=$?; fi"
prop_checkOverwrittenExitCode6 :: Bool
prop_checkOverwrittenExitCode6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkOverwrittenExitCode String
"x; [ $? -gt 0 ] && fail=$?"
prop_checkOverwrittenExitCode7 :: Bool
prop_checkOverwrittenExitCode7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkOverwrittenExitCode String
"[ 1 -eq 2 ]; status=$?"
prop_checkOverwrittenExitCode8 :: Bool
prop_checkOverwrittenExitCode8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkOverwrittenExitCode String
"[ 1 -eq 2 ]; exit $?"
checkOverwrittenExitCode :: Parameters -> Token -> m ()
checkOverwrittenExitCode Parameters
params Token
t =
    case Token
t of
        T_DollarBraced Id
id Bool
_ Token
val | Token -> Maybe String
getLiteralString Token
val forall a. Eq a => a -> a -> Bool
== forall a. a -> Maybe a
Just String
"?" -> forall {m :: * -> *}. MonadWriter [TokenComment] m => Id -> m ()
check Id
id
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    check :: Id -> m ()
check Id
id = forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ forall a b. (a -> b) -> a -> b
$ do
        ProgramState
state <- CFGAnalysis -> Id -> Maybe ProgramState
CF.getIncomingState (Parameters -> CFGAnalysis
cfgAnalysis Parameters
params) Id
id
        let exitCodeIds :: Set Id
exitCodeIds = ProgramState -> Set Id
CF.exitCodes ProgramState
state
        forall (f :: * -> *). Alternative f => Bool -> f ()
guard forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ forall a. Set a -> Bool
S.null Set Id
exitCodeIds

        let idToToken :: Map Id Token
idToToken = Parameters -> Map Id Token
idMap Parameters
params
        [Token]
exitCodeTokens <- forall (t :: * -> *) (m :: * -> *) a.
(Traversable t, Monad m) =>
t (m a) -> m (t a)
sequence forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map (\Id
k -> forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup Id
k Map Id Token
idToToken) forall a b. (a -> b) -> a -> b
$ forall a. Set a -> [a]
S.toList Set Id
exitCodeIds
        forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ do
            forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Token -> Bool
isCondition [Token]
exitCodeTokens Bool -> Bool -> Bool
&& Bool -> Bool
not (forall {t :: * -> *}. Foldable t => Token -> t Id -> Bool
usedUnconditionally Token
t Set Id
exitCodeIds)) forall a b. (a -> b) -> a -> b
$
                forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2319 String
"This $? refers to a condition, not a command. Assign to a variable to avoid it being overwritten."
            forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Token -> Bool
isPrinting [Token]
exitCodeTokens) forall a b. (a -> b) -> a -> b
$
                forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2320 String
"This $? refers to echo/printf, not a previous command. Assign to variable to avoid it being overwritten."

    isCondition :: Token -> Bool
isCondition Token
t =
        case Token
t of
            T_Condition {} -> Bool
True
            T_SimpleCommand {} -> Token -> Maybe String
getCommandName Token
t forall a. Eq a => a -> a -> Bool
== forall a. a -> Maybe a
Just String
"test"
            Token
_ -> Bool
False

    -- If we don't do anything based on the condition, assume we wanted the condition itself
    -- This helps differentiate `x; [ $? -gt 0 ] && exit $?` vs `[ cond ]; exit $?`
    usedUnconditionally :: Token -> t Id -> Bool
usedUnconditionally Token
t t Id
testIds =
        forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (\Id
c -> CFGAnalysis -> Id -> Id -> Bool
CF.doesPostDominate (Parameters -> CFGAnalysis
cfgAnalysis Parameters
params) (Token -> Id
getId Token
t) Id
c) t Id
testIds

    isPrinting :: Token -> Bool
isPrinting Token
t =
        case Token -> Maybe String
getCommandBasename Token
t of
            Just String
"echo" -> Bool
True
            Just String
"printf" -> Bool
True
            Maybe String
_ -> Bool
False


prop_checkUnnecessaryArithmeticExpansionIndex1 :: Bool
prop_checkUnnecessaryArithmeticExpansionIndex1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnnecessaryArithmeticExpansionIndex String
"a[$((1+1))]=n"
prop_checkUnnecessaryArithmeticExpansionIndex2 :: Bool
prop_checkUnnecessaryArithmeticExpansionIndex2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnnecessaryArithmeticExpansionIndex String
"a[1+1]=n"
prop_checkUnnecessaryArithmeticExpansionIndex3 :: Bool
prop_checkUnnecessaryArithmeticExpansionIndex3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnnecessaryArithmeticExpansionIndex String
"a[$(echo $((1+1)))]=n"
prop_checkUnnecessaryArithmeticExpansionIndex4 :: Bool
prop_checkUnnecessaryArithmeticExpansionIndex4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnnecessaryArithmeticExpansionIndex String
"declare -A a; a[$((1+1))]=val"
checkUnnecessaryArithmeticExpansionIndex :: Parameters -> Token -> m ()
checkUnnecessaryArithmeticExpansionIndex Parameters
params Token
t =
    case Token
t of
        T_Assignment Id
_ AssignmentMode
mode String
var [TA_Sequence Id
_ [ TA_Expansion Id
_ [expansion :: Token
expansion@(T_DollarArithmetic Id
id Token
_)]]] Token
val ->
            forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> Fix -> m ()
styleWithFix Id
id Code
2321 String
"Array indices are already arithmetic contexts. Prefer removing the $(( and ))." forall a b. (a -> b) -> a -> b
$ Id -> Fix
fix Id
id
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()

  where
    fix :: Id -> Fix
fix Id
id =
        [Replacement] -> Fix
fixWith [
            Id -> Parameters -> Code -> String -> Replacement
replaceStart Id
id Parameters
params Code
3 String
"", -- Remove "$(("
            Id -> Parameters -> Code -> String -> Replacement
replaceEnd Id
id Parameters
params Code
2 String
""    -- Remove "))"
        ]


prop_checkUnnecessaryParens1 :: Bool
prop_checkUnnecessaryParens1 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnnecessaryParens String
"echo $(( ((1+1)) ))"
prop_checkUnnecessaryParens2 :: Bool
prop_checkUnnecessaryParens2 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnnecessaryParens String
"x[((1+1))+1]=1"
prop_checkUnnecessaryParens3 :: Bool
prop_checkUnnecessaryParens3 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnnecessaryParens String
"x[(1+1)]=1"
prop_checkUnnecessaryParens4 :: Bool
prop_checkUnnecessaryParens4 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnnecessaryParens String
"$(( (x) ))"
prop_checkUnnecessaryParens5 :: Bool
prop_checkUnnecessaryParens5 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verify forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnnecessaryParens String
"(( (x) ))"
prop_checkUnnecessaryParens6 :: Bool
prop_checkUnnecessaryParens6 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnnecessaryParens String
"x[(1+1)+1]=1"
prop_checkUnnecessaryParens7 :: Bool
prop_checkUnnecessaryParens7 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnnecessaryParens String
"(( (1*1)+1 ))"
prop_checkUnnecessaryParens8 :: Bool
prop_checkUnnecessaryParens8 = (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnnecessaryParens String
"(( (1)+1 ))"
checkUnnecessaryParens :: Parameters -> Token -> m ()
checkUnnecessaryParens Parameters
params Token
t =
    case Token
t of
        T_DollarArithmetic Id
_ Token
t -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
String -> Token -> m ()
checkLeading String
"$(( (x) )) is the same as $(( x ))" Token
t
        T_ForArithmetic Id
_ Token
x Token
y Token
z [Token]
_ -> forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
String -> Token -> m ()
checkLeading String
"for (((x); (y); (z))) is the same as for ((x; y; z))")  [Token
x,Token
y,Token
z]
        T_Assignment Id
_ AssignmentMode
_ String
_ [Token
t] Token
_ -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
String -> Token -> m ()
checkLeading String
"a[(x)] is the same as a[x]" Token
t
        T_Arithmetic Id
_ Token
t -> forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
String -> Token -> m ()
checkLeading String
"(( (x) )) is the same as (( x ))" Token
t
        TA_Parentesis Id
_ (TA_Sequence Id
_ [ TA_Parentesis Id
id Token
_ ]) ->
            forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> Fix -> m ()
styleWithFix Id
id Code
2322 String
"In arithmetic contexts, ((x)) is the same as (x). Prefer only one layer of parentheses." forall a b. (a -> b) -> a -> b
$ Id -> Fix
fix Id
id
        Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where

    checkLeading :: String -> Token -> m ()
checkLeading String
str Token
t =
        case Token
t of
            TA_Sequence Id
_ [TA_Parentesis Id
id Token
_ ] -> forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> Fix -> m ()
styleWithFix Id
id Code
2323 (String
str forall a. [a] -> [a] -> [a]
++ String
". Prefer not wrapping in additional parentheses.") forall a b. (a -> b) -> a -> b
$ Id -> Fix
fix Id
id
            Token
_ -> forall (m :: * -> *) a. Monad m => a -> m a
return ()

    fix :: Id -> Fix
fix Id
id =
        [Replacement] -> Fix
fixWith [
            Id -> Parameters -> Code -> String -> Replacement
replaceStart Id
id Parameters
params Code
1 String
"", -- Remove "("
            Id -> Parameters -> Code -> String -> Replacement
replaceEnd Id
id Parameters
params Code
1 String
""    -- Remove ")"
        ]


return []
runTests :: IO Bool
runTests =  $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |])