{-
    Copyright 2012-2019 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 (runAnalytics, optionalChecks, ShellCheck.Analytics.runTests) where

import ShellCheck.AST
import ShellCheck.ASTLib
import ShellCheck.AnalyzerLib hiding (producesComments)
import ShellCheck.Data
import ShellCheck.Parser
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
import Control.Monad.Reader
import Data.Char
import Data.Functor
import Data.Function (on)
import Data.List
import Data.Maybe
import Data.Ord
import Debug.Trace
import qualified Data.Map.Strict as Map
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 = [
    [Parameters -> Token -> WriterT [TokenComment] Identity ()]
-> Parameters -> Token -> [TokenComment]
forall w (t :: * -> *) t b.
(Monoid w, Foldable t) =>
t (t -> Token -> WriterT w Identity b) -> t -> Token -> w
nodeChecksToTreeCheck [Parameters -> Token -> WriterT [TokenComment] Identity ()]
nodeChecks
    ,Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
subshellAssignmentCheck
    ,Parameters -> Token -> [TokenComment]
checkSpacefulness
    ,Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkQuotesInLiterals
    ,Parameters -> Token -> [TokenComment]
forall t. t -> Token -> [TokenComment]
checkShebangParameters
    ,Parameters -> Token -> [TokenComment]
forall t. t -> Token -> [TokenComment]
checkFunctionsUsedExternally
    ,Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments
    ,Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions
    ,Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkArrayWithoutIndex
    ,Parameters -> Token -> [TokenComment]
checkShebang
    ,Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences
    ,Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd
    ,Parameters -> Token -> [TokenComment]
checkArrayAssignmentIndices
    ,Parameters -> Token -> [TokenComment]
forall t. t -> Token -> [TokenComment]
checkUseBeforeDefinition
    ]

runAnalytics :: AnalysisSpec -> [TokenComment]
runAnalytics :: AnalysisSpec -> [TokenComment]
runAnalytics AnalysisSpec
options =
    AnalysisSpec
-> [Parameters -> Token -> [TokenComment]] -> [TokenComment]
runList AnalysisSpec
options [Parameters -> Token -> [TokenComment]]
treeChecks [TokenComment] -> [TokenComment] -> [TokenComment]
forall a. [a] -> [a] -> [a]
++ AnalysisSpec
-> [Parameters -> Token -> [TokenComment]] -> [TokenComment]
runList AnalysisSpec
options [Parameters -> Token -> [TokenComment]]
optionalChecks
  where
    root :: Token
root = AnalysisSpec -> Token
asScript AnalysisSpec
options
    optionals :: [String]
optionals = Token -> [String]
getEnableDirectives Token
root [String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++ AnalysisSpec -> [String]
asOptionalChecks AnalysisSpec
options
    optionalChecks :: [Parameters -> Token -> [TokenComment]]
optionalChecks =
        if String
"all" String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
optionals
        then ((CheckDescription, Parameters -> Token -> [TokenComment])
 -> Parameters -> Token -> [TokenComment])
-> [(CheckDescription, Parameters -> Token -> [TokenComment])]
-> [Parameters -> Token -> [TokenComment]]
forall a b. (a -> b) -> [a] -> [b]
map (CheckDescription, Parameters -> Token -> [TokenComment])
-> Parameters -> Token -> [TokenComment]
forall a b. (a, b) -> b
snd [(CheckDescription, Parameters -> Token -> [TokenComment])]
optionalTreeChecks
        else (String -> Maybe (Parameters -> Token -> [TokenComment]))
-> [String] -> [Parameters -> Token -> [TokenComment]]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (\String
c -> String
-> Map String (Parameters -> Token -> [TokenComment])
-> Maybe (Parameters -> Token -> [TokenComment])
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup String
c Map String (Parameters -> Token -> [TokenComment])
optionalCheckMap) [String]
optionals

runList :: AnalysisSpec -> [Parameters -> Token -> [TokenComment]]
    -> [TokenComment]
runList :: AnalysisSpec
-> [Parameters -> Token -> [TokenComment]] -> [TokenComment]
runList AnalysisSpec
spec [Parameters -> Token -> [TokenComment]]
list = [TokenComment]
notes
    where
        root :: Token
root = AnalysisSpec -> Token
asScript AnalysisSpec
spec
        params :: Parameters
params = AnalysisSpec -> Parameters
makeParameters AnalysisSpec
spec
        notes :: [TokenComment]
notes = ((Parameters -> Token -> [TokenComment]) -> [TokenComment])
-> [Parameters -> Token -> [TokenComment]] -> [TokenComment]
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]]
list

getEnableDirectives :: Token -> [String]
getEnableDirectives Token
root =
    case Token
root of
        T_Annotation Id
_ [Annotation]
list Token
_ -> (Annotation -> Maybe String) -> [Annotation] -> [String]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe Annotation -> Maybe String
getEnable [Annotation]
list
        Token
_ -> []
  where
    getEnable :: Annotation -> Maybe String
getEnable Annotation
t =
        case Annotation
t of
            EnableComment String
s -> String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return String
s
            Annotation
_ -> Maybe String
forall a. Maybe a
Nothing

checkList :: t (t -> [b]) -> t -> [b]
checkList t (t -> [b])
l t
t = ((t -> [b]) -> [b]) -> t (t -> [b]) -> [b]
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 = Writer w Token -> w
forall w a. Writer w a -> w
execWriter ((Token -> WriterT w Identity ()) -> Token -> Writer w Token
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 =
    (t -> Token -> WriterT w Identity ()) -> t -> Token -> w
forall w t.
Monoid w =>
(t -> Token -> WriterT w Identity ()) -> t -> Token -> w
runNodeAnalysis
        (\t
p Token
t -> (((t -> Token -> WriterT w Identity b) -> WriterT w Identity b)
-> t (t -> Token -> WriterT w Identity b) -> WriterT w Identity ()
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) ((Token -> WriterT w Identity b) -> WriterT w Identity b)
-> ((t -> Token -> WriterT w Identity b)
    -> Token -> WriterT w Identity b)
-> (t -> Token -> WriterT w Identity b)
-> WriterT w Identity b
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 -> WriterT [TokenComment] Identity ()]
nodeChecks = [
    Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkUuoc
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkPipePitfalls
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkForInQuoted
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkForInLs
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkShorthandIf
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkDollarStar
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedDollarAt
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkStderrRedirect
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkUnquotedN
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleBracketOperators
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkDoubleBracketOperators
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkLiteralBreakingTest
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkConstantNullary
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkDivBeforeMult
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkArithmeticDeref
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkArithmeticBadOctal
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkComparisonAgainstGlob
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkCommarrays
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkOrNeq
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkEchoWc
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkConstantIfs
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkPipedAssignment
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkAssignAteCommand
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkUuoeVar
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkQuotedCondRegex
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkForInCat
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkFindExec
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkValidCondOps
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkGlobbedRegex
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkTestRedirects
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkIndirectExpansion
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkPS1Assignments
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkBackticks
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkInexplicablyUnquoted
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkTildeInQuotes
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkLonelyDotDash
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkSpuriousExec
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkSpuriousExpansion
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkDollarBrackets
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkSshHereDoc
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkGlobsAsOptions
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkWhileReadPitfalls
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkArithmeticOpCommand
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCharRangeGlob
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedExpansions
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectToSame
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkPrefixAssignmentReference
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkLoopKeywordScope
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCdAndBack
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkWrongArithmeticAssignment
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkConditionalAndOrs
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkFunctionDeclarations
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkStderrPipe
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkOverridingPath
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkArrayAsString
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnsupported
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkMultipleAppends
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSuspiciousIFS
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkShouldUseGrepQ
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkConcatenatedDollarAt
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkTildeInPath
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkMaskedReturns
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkReadWithoutR
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkLoopVariableReassignment
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkTrailingBracket
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkReturnAgainstZero
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectedNowhere
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnmatchableCases
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkSubshellAsTest
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSplittingInArrays
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkRedirectionToNumber
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkGlobAsCommand
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkFlagAsCommand
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkEmptyCondition
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
checkPipeToNowhere
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkForLoopGlobVariables
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSubshelledTests
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkInvertedStringTest
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkRedirectionToCommand
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkDollarQuoteParen
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUselessBang
    ]

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


prop_verifyOptionalExamples :: Bool
prop_verifyOptionalExamples = ((CheckDescription, Parameters -> Token -> [TokenComment]) -> Bool)
-> [(CheckDescription, Parameters -> Token -> [TokenComment])]
-> Bool
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\""
    }, Parameters -> Token -> [TokenComment]
checkVerboseSpacefulness)

    ,(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\" ]"
    }, [Parameters -> Token -> WriterT [TokenComment] Identity ()]
-> Parameters -> Token -> [TokenComment]
forall w (t :: * -> *) t b.
(Monoid w, Foldable t) =>
t (t -> Token -> WriterT w Identity b) -> t -> Token -> w
nodeChecksToTreeCheck [Parameters -> Token -> WriterT [TokenComment] Identity ()
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"
    }, [Parameters -> Token -> WriterT [TokenComment] Identity ()]
-> Parameters -> Token -> [TokenComment]
forall w (t :: * -> *) t b.
(Monoid w, Foldable t) =>
t (t -> Token -> WriterT w Identity b) -> t -> Token -> w
nodeChecksToTreeCheck [Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
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}"
    }, [Parameters -> Token -> WriterT [TokenComment] Identity ()]
-> Parameters -> Token -> [TokenComment]
forall w (t :: * -> *) t b.
(Monoid w, Foldable t) =>
t (t -> Token -> WriterT w Identity b) -> t -> Token -> w
nodeChecksToTreeCheck [Parameters -> Token -> WriterT [TokenComment] Identity ()
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"
    }, Bool -> Parameters -> Token -> [TokenComment]
forall p. Bool -> Parameters -> p -> [TokenComment]
checkUnassignedReferences' Bool
True)
    ]

optionalCheckMap :: Map.Map String (Parameters -> Token -> [TokenComment])
optionalCheckMap :: Map String (Parameters -> Token -> [TokenComment])
optionalCheckMap = [(String, Parameters -> Token -> [TokenComment])]
-> Map String (Parameters -> Token -> [TokenComment])
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList ([(String, Parameters -> Token -> [TokenComment])]
 -> Map String (Parameters -> Token -> [TokenComment]))
-> [(String, Parameters -> Token -> [TokenComment])]
-> Map String (Parameters -> Token -> [TokenComment])
forall a b. (a -> b) -> a -> b
$ ((CheckDescription, Parameters -> Token -> [TokenComment])
 -> (String, Parameters -> Token -> [TokenComment]))
-> [(CheckDescription, Parameters -> Token -> [TokenComment])]
-> [(String, Parameters -> Token -> [TokenComment])]
forall a b. (a -> b) -> [a] -> [b]
map (CheckDescription, Parameters -> Token -> [TokenComment])
-> (String, Parameters -> Token -> [TokenComment])
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
'*' Char -> t Char -> Bool
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 -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
f String
s = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Maybe Bool
checkNode Parameters -> Token -> WriterT [TokenComment] Identity ()
f String
s Maybe Bool -> Maybe Bool -> Bool
forall a. Eq a => a -> a -> Bool
== Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
True

verifyNot :: (Parameters -> Token -> Writer [TokenComment] ()) -> String -> Bool
verifyNot :: (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
f String
s = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Maybe Bool
checkNode Parameters -> Token -> WriterT [TokenComment] Identity ()
f String
s Maybe Bool -> Maybe Bool -> Bool
forall a. Eq a => a -> a -> Bool
== Bool -> Maybe 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 Maybe Bool -> Maybe Bool -> Bool
forall a. Eq a => a -> a -> Bool
== Bool -> Maybe 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 Maybe Bool -> Maybe Bool -> Bool
forall a. Eq a => a -> a -> Bool
== Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
False

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

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


checkNode :: (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Maybe Bool
checkNode Parameters -> Token -> WriterT [TokenComment] Identity ()
f = (Parameters -> Token -> [TokenComment]) -> String -> Maybe Bool
producesComments ((Parameters -> Token -> WriterT [TokenComment] Identity ())
-> Parameters -> Token -> [TokenComment]
forall w t.
Monoid w =>
(t -> Token -> WriterT w Identity ()) -> t -> Token -> w
runNodeAnalysis Parameters -> Token -> WriterT [TokenComment] Identity ()
f)
producesComments :: (Parameters -> Token -> [TokenComment]) -> String -> Maybe Bool
producesComments :: (Parameters -> Token -> [TokenComment]) -> String -> Maybe Bool
producesComments Parameters -> Token -> [TokenComment]
f String
s = do
        let pr :: ParseResult
pr = String -> ParseResult
pScript String
s
        ParseResult -> Maybe Token
prRoot ParseResult
pr
        let spec :: AnalysisSpec
spec = ParseResult -> AnalysisSpec
defaultSpec ParseResult
pr
        let params :: Parameters
params = AnalysisSpec -> Parameters
makeParameters AnalysisSpec
spec
        Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> Maybe Bool)
-> ([TokenComment] -> Bool) -> [TokenComment] -> Maybe Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not (Bool -> Bool)
-> ([TokenComment] -> Bool) -> [TokenComment] -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [TokenComment] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null ([TokenComment] -> Maybe Bool) -> [TokenComment] -> Maybe Bool
forall a b. (a -> b) -> a -> b
$
            AnalysisSpec -> Parameters -> [TokenComment] -> [TokenComment]
filterByAnnotation AnalysisSpec
spec Parameters
params ([TokenComment] -> [TokenComment])
-> [TokenComment] -> [TokenComment]
forall a b. (a -> b) -> a -> b
$
                AnalysisSpec
-> [Parameters -> Token -> [TokenComment]] -> [TokenComment]
runList AnalysisSpec
spec [Parameters -> Token -> [TokenComment]
f]

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

hasFloatingPoint :: Parameters -> Bool
hasFloatingPoint Parameters
params = Parameters -> Shell
shellType Parameters
params Shell -> Shell -> Bool
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 Id -> [Id] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` (Token -> Id) -> [Token] -> [Id]
forall a b. (a -> b) -> [a] -> [b]
map Token -> Id
getId (Token -> [Token]
getConditionChildren Token
parent) Bool -> Bool -> Bool
|| [Token] -> Bool
isCondition (Token
parentToken -> [Token] -> [Token]
forall 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 -> (([Token], [Token]) -> [Token]) -> [([Token], [Token])] -> [Token]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (Int -> [Token] -> [Token]
forall a. Int -> [a] -> [a]
take Int
1 ([Token] -> [Token])
-> (([Token], [Token]) -> [Token]) -> ([Token], [Token]) -> [Token]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Token] -> [Token]
forall a. [a] -> [a]
reverse ([Token] -> [Token])
-> (([Token], [Token]) -> [Token]) -> ([Token], [Token]) -> [Token]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ([Token], [Token]) -> [Token]
forall a b. (a, b) -> a
fst) [([Token], [Token])]
conditions
            T_WhileExpression Id
id [Token]
c [Token]
l -> Int -> [Token] -> [Token]
forall a. Int -> [a] -> [a]
take Int
1 ([Token] -> [Token]) -> ([Token] -> [Token]) -> [Token] -> [Token]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Token] -> [Token]
forall a. [a] -> [a]
reverse ([Token] -> [Token]) -> [Token] -> [Token]
forall a b. (a -> b) -> a -> b
$ [Token]
c
            T_UntilExpression Id
id [Token]
c [Token]
l -> Int -> [Token] -> [Token]
forall a. Int -> [a] -> [a]
take Int
1 ([Token] -> [Token]) -> ([Token] -> [Token]) -> [Token] -> [Token]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Token] -> [Token]
forall a. [a] -> [a]
reverse ([Token] -> [Token]) -> [Token] -> [Token]
forall a b. (a -> b) -> a -> b
$ [Token]
c
            Token
_ -> []

-- helpers to build replacements
replaceStart :: Id -> Parameters -> Integer -> String -> Replacement
replaceStart Id
id Parameters
params Integer
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 Map Id (Position, Position) -> Id -> (Position, Position)
forall k a. Ord k => Map k a -> k -> a
Map.! Id
id
        new_end :: Position
new_end = Position
start {
            posColumn :: Integer
posColumn = Position -> Integer
posColumn Position
start Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ Integer
n
        }
        depth :: Int
depth = [Token] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length ([Token] -> Int) -> [Token] -> Int
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 -> Integer -> String -> Replacement
replaceEnd Id
id Parameters
params Integer
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 Map Id (Position, Position) -> Id -> (Position, Position)
forall k a. Ord k => Map k a -> k -> a
Map.! Id
id
        new_start :: Position
new_start = Position
end {
            posColumn :: Integer
posColumn = Position -> Integer
posColumn Position
end Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
- Integer
n
        }
        new_end :: Position
new_end = Position
end {
            posColumn :: Integer
posColumn = Position -> Integer
posColumn Position
end
        }
        depth :: Int
depth = [Token] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length ([Token] -> Int) -> [Token] -> Int
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
    }
surroundWidth :: Id -> Parameters -> String -> Fix
surroundWidth Id
id Parameters
params String
s = [Replacement] -> Fix
fixWith [Id -> Parameters -> Integer -> String -> Replacement
replaceStart Id
id Parameters
params Integer
0 String
s, Id -> Parameters -> Integer -> String -> Replacement
replaceEnd Id
id Parameters
params Integer
0 String
s]
fixWith :: [Replacement] -> Fix
fixWith [Replacement]
fixes = Fix
newFix { fixReplacements :: [Replacement]
fixReplacements = [Replacement]
fixes }

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

prop_checkPipedAssignment1 :: Bool
prop_checkPipedAssignment1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkPipedAssignment String
"A=ls | grep foo"
prop_checkPipedAssignment2 :: Bool
prop_checkPipedAssignment2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkPipedAssignment String
"A=foo cmd | grep foo"
prop_checkPipedAssignment3 :: Bool
prop_checkPipedAssignment3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
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]
_)) =
    Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2036 String
"If you wanted to assign the output of the pipeline, use a=$(b | c) ."
checkPipedAssignment p
_ Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkAssignAteCommand1 :: Bool
prop_checkAssignAteCommand1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkAssignAteCommand String
"A=ls -l"
prop_checkAssignAteCommand2 :: Bool
prop_checkAssignAteCommand2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkAssignAteCommand String
"A=ls --sort=$foo"
prop_checkAssignAteCommand3 :: Bool
prop_checkAssignAteCommand3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkAssignAteCommand String
"A=cat foo | grep bar"
prop_checkAssignAteCommand4 :: Bool
prop_checkAssignAteCommand4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkAssignAteCommand String
"A=foo ls -l"
prop_checkAssignAteCommand5 :: Bool
prop_checkAssignAteCommand5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkAssignAteCommand String
"PAGER=cat grep bar"
prop_checkAssignAteCommand6 :: Bool
prop_checkAssignAteCommand6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkAssignAteCommand String
"PAGER=\"cat\" grep bar"
prop_checkAssignAteCommand7 :: Bool
prop_checkAssignAteCommand7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
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
        Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
id Integer
2037 String
"To assign the output of a command, use var=$(cmd) ."
    else
        -- Check if it's a known, unquoted command name.
        Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Maybe String -> Bool
isCommonCommand (Maybe String -> Bool) -> Maybe String -> Bool
forall a b. (a -> b) -> a -> b
$ Token -> Maybe String
getUnquotedLiteral Token
assignmentTerm) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
            Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2209 String
"Use var=$(command) to assign output (or quote to assign string)."
  where
    isCommonCommand :: Maybe String -> Bool
isCommonCommand (Just String
s) = String
s String -> [String] -> Bool
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 = Bool -> Maybe Bool -> Bool
forall a. a -> Maybe a -> a
fromMaybe Bool
False (Maybe Bool -> Bool) -> Maybe Bool -> Bool
forall a b. (a -> b) -> a -> b
$ do
        Token
head <- [Token]
list [Token] -> Int -> Maybe Token
forall a. [a] -> Int -> Maybe a
!!! Int
0
        Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> Maybe Bool) -> Bool -> Maybe Bool
forall a b. (a -> b) -> a -> b
$ Token -> Bool
isGlob Token
head Bool -> Bool -> Bool
|| Token -> Bool
isUnquotedFlag Token
head

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

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

prop_checkWrongArit :: Bool
prop_checkWrongArit = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkWrongArithmeticAssignment String
"i=i+1"
prop_checkWrongArit2 :: Bool
prop_checkWrongArit2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
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:[]) []) =
  m () -> Maybe (m ()) -> m ()
forall a. a -> Maybe a -> a
fromMaybe (() -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()) (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
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 [String] -> Int -> Maybe String
forall a. [a] -> Int -> Maybe a
!!! Int
0
    String
op <- [String]
match [String] -> Int -> Maybe String
forall a. [a] -> Int -> Maybe a
!!! Int
1
    String -> Map String () -> Maybe ()
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup String
var Map String ()
references
    m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ()))
-> (String -> m ()) -> String -> Maybe (m ())
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn (Token -> Id
getId Token
val) Integer
2100 (String -> Maybe (m ())) -> String -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$
        String
"Use $((..)) for arithmetics, e.g. i=$((i " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
op String -> String -> String
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 = (Map String ()
 -> (Map String () -> Map String ()) -> Map String ())
-> Map String ()
-> [Map String () -> Map String ()]
-> Map String ()
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl (((Map String () -> Map String ())
 -> Map String () -> Map String ())
-> Map String ()
-> (Map String () -> Map String ())
-> Map String ()
forall a b c. (a -> b -> c) -> b -> a -> c
flip (Map String () -> Map String ()) -> Map String () -> Map String ()
forall a b. (a -> b) -> a -> b
($)) Map String ()
forall k a. Map k a
Map.empty ((StackData -> Map String () -> Map String ())
-> [StackData] -> [Map String () -> Map String ()]
forall a b. (a -> b) -> [a] -> [b]
map StackData -> Map String () -> Map String ()
insertRef ([StackData] -> [Map String () -> Map String ()])
-> [StackData] -> [Map String () -> Map String ()]
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
_)) =
        String -> () -> Map String () -> Map String ()
forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert String
name ()
    insertRef StackData
_ = Map String () -> Map String ()
forall a. a -> a
Prelude.id

    getNormalString :: Token -> Maybe String
getNormalString (T_NormalWord Id
_ [Token]
words) = do
        [String]
parts <- (Maybe [String] -> Maybe String -> Maybe [String])
-> Maybe [String] -> [Maybe String] -> Maybe [String]
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl (([String] -> String -> [String])
-> Maybe [String] -> Maybe String -> Maybe [String]
forall (m :: * -> *) a1 a2 r.
Monad m =>
(a1 -> a2 -> r) -> m a1 -> m a2 -> m r
liftM2 (\[String]
x String
y -> [String]
x [String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++ [String
y])) ([String] -> Maybe [String]
forall a. a -> Maybe a
Just []) ([Maybe String] -> Maybe [String])
-> [Maybe String] -> Maybe [String]
forall a b. (a -> b) -> a -> b
$ (Token -> Maybe String) -> [Token] -> [Maybe String]
forall a b. (a -> b) -> [a] -> [b]
map Token -> Maybe String
getLiterals [Token]
words
        String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return (String -> Maybe String) -> String -> Maybe String
forall a b. (a -> b) -> a -> b
$ [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [String]
parts
    getNormalString Token
_ = Maybe String
forall a. Maybe a
Nothing

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


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

prop_checkPipePitfalls3 :: Bool
prop_checkPipePitfalls3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkPipePitfalls String
"ls | grep -v mp3"
prop_checkPipePitfalls4 :: Bool
prop_checkPipePitfalls4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkPipePitfalls String
"find . -print0 | xargs -0 foo"
prop_checkPipePitfalls5 :: Bool
prop_checkPipePitfalls5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkPipePitfalls String
"ls -N | foo"
prop_checkPipePitfalls6 :: Bool
prop_checkPipePitfalls6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkPipePitfalls String
"find . | xargs foo"
prop_checkPipePitfalls7 :: Bool
prop_checkPipePitfalls7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkPipePitfalls String
"find . -printf '%s\\n' | xargs foo"
prop_checkPipePitfalls8 :: Bool
prop_checkPipePitfalls8 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkPipePitfalls String
"foo | grep bar | wc -l"
prop_checkPipePitfalls9 :: Bool
prop_checkPipePitfalls9 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkPipePitfalls String
"foo | grep -o bar | wc -l"
prop_checkPipePitfalls10 :: Bool
prop_checkPipePitfalls10 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkPipePitfalls String
"foo | grep -o bar | wc"
prop_checkPipePitfalls11 :: Bool
prop_checkPipePitfalls11 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkPipePitfalls String
"foo | grep bar | wc"
prop_checkPipePitfalls12 :: Bool
prop_checkPipePitfalls12 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkPipePitfalls String
"foo | grep -o bar | wc -c"
prop_checkPipePitfalls13 :: Bool
prop_checkPipePitfalls13 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkPipePitfalls String
"foo | grep bar | wc -c"
prop_checkPipePitfalls14 :: Bool
prop_checkPipePitfalls14 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkPipePitfalls String
"foo | grep -o bar | wc -cmwL"
prop_checkPipePitfalls15 :: Bool
prop_checkPipePitfalls15 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkPipePitfalls String
"foo | grep bar | wc -cmwL"
prop_checkPipePitfalls16 :: Bool
prop_checkPipePitfalls16 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkPipePitfalls String
"foo | grep -r bar | wc -l"
checkPipePitfalls :: p -> Token -> m ()
checkPipePitfalls p
_ (T_Pipeline Id
id [Token]
_ [Token]
commands) = do
    [String] -> ([Token] -> m ()) -> m Bool
forall (m :: * -> *) b.
Monad m =>
[String] -> ([Token] -> m b) -> m Bool
for [String
"find", String
"xargs"] (([Token] -> m ()) -> m Bool) -> ([Token] -> m ()) -> m Bool
forall a b. (a -> b) -> a -> b
$
        \(Token
find:Token
xargs:[Token]
_) ->
          let args :: [String]
args = Token -> [String]
oversimplify Token
xargs [String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++ Token -> [String]
oversimplify Token
find
          in
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless ((([String] -> Bool) -> Bool) -> [[String] -> Bool] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (([String] -> Bool) -> [String] -> Bool
forall a b. (a -> b) -> a -> b
$ [String]
args) [
                Char -> [String] -> Bool
forall (t :: * -> *). Foldable t => Char -> t String -> Bool
hasShortParameter Char
'0',
                String -> [String] -> Bool
forall (t :: * -> *). Foldable t => String -> t String -> Bool
hasParameter String
"null",
                String -> [String] -> Bool
forall (t :: * -> *). Foldable t => String -> t String -> Bool
hasParameter String
"print0",
                String -> [String] -> Bool
forall (t :: * -> *). Foldable t => String -> t String -> Bool
hasParameter String
"printf"
              ]) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn (Token -> Id
getId Token
find) Integer
2038
                      String
"Use -print0/-0 or -exec + to allow for non-alphanumeric filenames."

    [String] -> (Id -> m ()) -> m Bool
forall (m :: * -> *). Monad m => [String] -> (Id -> m ()) -> m Bool
for' [String
"ps", String
"grep"] ((Id -> m ()) -> m Bool) -> (Id -> m ()) -> m Bool
forall a b. (a -> b) -> a -> b
$
        \Id
x -> Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
info Id
x Integer
2009 String
"Consider using pgrep instead of grepping ps output."

    [String] -> ([Token] -> m ()) -> m Bool
forall (m :: * -> *) b.
Monad m =>
[String] -> ([Token] -> m b) -> m Bool
for [String
"grep", String
"wc"] (([Token] -> m ()) -> m Bool) -> ([Token] -> m ()) -> m Bool
forall a b. (a -> b) -> a -> b
$
        \(Token
grep:Token
wc:[Token]
_) ->
            let flagsGrep :: [String]
flagsGrep = [String] -> Maybe [String] -> [String]
forall a. a -> Maybe a -> a
fromMaybe [] (Maybe [String] -> [String]) -> Maybe [String] -> [String]
forall a b. (a -> b) -> a -> b
$ ((Token, String) -> String) -> [(Token, String)] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (Token, String) -> String
forall a b. (a, b) -> b
snd ([(Token, String)] -> [String])
-> (Token -> [(Token, String)]) -> Token -> [String]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [(Token, String)]
getAllFlags (Token -> [String]) -> Maybe Token -> Maybe [String]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Token -> Maybe Token
getCommand Token
grep
                flagsWc :: [String]
flagsWc = [String] -> Maybe [String] -> [String]
forall a. a -> Maybe a -> a
fromMaybe [] (Maybe [String] -> [String]) -> Maybe [String] -> [String]
forall a b. (a -> b) -> a -> b
$ ((Token, String) -> String) -> [(Token, String)] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (Token, String) -> String
forall a b. (a, b) -> b
snd ([(Token, String)] -> [String])
-> (Token -> [(Token, String)]) -> Token -> [String]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [(Token, String)]
getAllFlags (Token -> [String]) -> Maybe Token -> Maybe [String]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Token -> Maybe Token
getCommand Token
wc
            in
                Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless ((String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"o", String
"only-matching", String
"r", String
"R", String
"recursive"]) [String]
flagsGrep Bool -> Bool -> Bool
|| (String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (String -> [String] -> Bool
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
|| [String] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [String]
flagsWc) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
                    Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
style (Token -> Id
getId Token
grep) Integer
2126 String
"Consider using grep -c instead of grep|wc -l."

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

indexOfSublists :: [String] -> [String] -> [t]
indexOfSublists [String]
sub = t -> [String] -> [t]
forall t. Num t => t -> [String] -> [t]
f t
0
  where
    f :: t -> [String] -> [t]
f t
_ [] = []
    f t
n a :: [String]
a@(String
r:[String]
rest) =
        let others :: [t]
others = t -> [String] -> [t]
f (t
nt -> t -> t
forall a. Num a => a -> a -> a
+t
1) [String]
rest in
            if [String] -> [String] -> Bool
match [String]
sub [String]
a
              then t
nt -> [t] -> [t]
forall a. a -> [a] -> [a]
:[t]
others
              else [t]
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 String -> String -> Bool
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 Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
forall t. t -> Token -> [TokenComment]
checkShebangParameters String
"#! /bin/sh  -l "
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 -> Integer -> String -> TokenComment
makeComment Severity
ErrorC Id
id Integer
2096 String
"On most OS, shebangs can only specify a single parameter." | [String] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length (String -> [String]
words String
sb) Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
2]

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"
checkShebang :: Parameters -> Token -> [TokenComment]
checkShebang Parameters
params (T_Annotation Id
_ [Annotation]
list Token
t) =
    if (Annotation -> Bool) -> [Annotation] -> Bool
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]
_) = WriterT [TokenComment] Identity () -> [TokenComment]
forall w a. Writer w a -> w
execWriter (WriterT [TokenComment] Identity () -> [TokenComment])
-> WriterT [TokenComment] Identity () -> [TokenComment]
forall a b. (a -> b) -> a -> b
$ do
    Bool
-> WriterT [TokenComment] Identity ()
-> WriterT [TokenComment] Identity ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Parameters -> Bool
shellTypeSpecified Parameters
params) (WriterT [TokenComment] Identity ()
 -> WriterT [TokenComment] Identity ())
-> WriterT [TokenComment] Identity ()
-> WriterT [TokenComment] Identity ()
forall a b. (a -> b) -> a -> b
$ do
        Bool
-> WriterT [TokenComment] Identity ()
-> WriterT [TokenComment] Identity ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
sb String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"") (WriterT [TokenComment] Identity ()
 -> WriterT [TokenComment] Identity ())
-> WriterT [TokenComment] Identity ()
-> WriterT [TokenComment] Identity ()
forall a b. (a -> b) -> a -> b
$
            Id -> Integer -> String -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
id Integer
2148 String
"Tips depend on target shell and yours is unknown. Add a shebang."
        Bool
-> WriterT [TokenComment] Identity ()
-> WriterT [TokenComment] Identity ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String -> String
executableFromShebang String
sb String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"ash") (WriterT [TokenComment] Identity ()
 -> WriterT [TokenComment] Identity ())
-> WriterT [TokenComment] Identity ()
-> WriterT [TokenComment] Identity ()
forall a b. (a -> b) -> a -> b
$
            Id -> Integer -> String -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2187 String
"Ash scripts will be checked as Dash. Add '# shellcheck shell=dash' to silence."
    Bool
-> WriterT [TokenComment] Identity ()
-> WriterT [TokenComment] Identity ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (String -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
sb) (WriterT [TokenComment] Identity ()
 -> WriterT [TokenComment] Identity ())
-> WriterT [TokenComment] Identity ()
-> WriterT [TokenComment] Identity ()
forall a b. (a -> b) -> a -> b
$ do
        Bool
-> WriterT [TokenComment] Identity ()
-> WriterT [TokenComment] Identity ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (String
"/" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
sb) (WriterT [TokenComment] Identity ()
 -> WriterT [TokenComment] Identity ())
-> WriterT [TokenComment] Identity ()
-> WriterT [TokenComment] Identity ()
forall a b. (a -> b) -> a -> b
$
            Id -> Integer -> String -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
id Integer
2239 String
"Ensure the shebang uses an absolute path to the interpreter."
        case String -> [String]
words String
sb of
            String
first:[String]
_ ->
                Bool
-> WriterT [TokenComment] Identity ()
-> WriterT [TokenComment] Identity ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
"/" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isSuffixOf` String
first) (WriterT [TokenComment] Identity ()
 -> WriterT [TokenComment] Identity ())
-> WriterT [TokenComment] Identity ()
-> WriterT [TokenComment] Identity ()
forall a b. (a -> b) -> a -> b
$
                    Id -> Integer -> String -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
id Integer
2246 String
"This shebang specifies a directory. Ensure the interpreter is a file."


prop_checkForInQuoted :: Bool
prop_checkForInQuoted = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkForInQuoted String
"for f in \"$(ls)\"; do echo foo; done"
prop_checkForInQuoted2 :: Bool
prop_checkForInQuoted2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkForInQuoted String
"for f in \"$@\"; do echo foo; done"
prop_checkForInQuoted2a :: Bool
prop_checkForInQuoted2a = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkForInQuoted String
"for f in *.mp3; do echo foo; done"
prop_checkForInQuoted2b :: Bool
prop_checkForInQuoted2b = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkForInQuoted String
"for f in \"*.mp3\"; do echo foo; done"
prop_checkForInQuoted3 :: Bool
prop_checkForInQuoted3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkForInQuoted String
"for f in 'find /'; do true; done"
prop_checkForInQuoted4 :: Bool
prop_checkForInQuoted4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkForInQuoted String
"for f in 1,2,3; do true; done"
prop_checkForInQuoted4a :: Bool
prop_checkForInQuoted4a = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkForInQuoted String
"for f in foo{1,2,3}; do true; done"
prop_checkForInQuoted5 :: Bool
prop_checkForInQuoted5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkForInQuoted String
"for f in ls; do true; done"
prop_checkForInQuoted6 :: Bool
prop_checkForInQuoted6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkForInQuoted String
"for f in \"${!arr}\"; do true; done"
checkForInQuoted :: p -> Token -> f ()
checkForInQuoted p
_ (T_ForIn Id
_ String
f [T_NormalWord Id
_ [word :: Token
word@(T_DoubleQuoted Id
id [Token]
list)]] [Token]
_) =
    Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ((Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (\Token
x -> Token -> Bool
willSplit Token
x Bool -> Bool -> Bool
&& Bool -> Bool
not (Token -> Bool
mayBecomeMultipleArgs Token
x)) [Token]
list
            Bool -> Bool -> Bool
|| ((String -> Bool) -> Maybe String -> Maybe Bool
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap String -> Bool
forall (t :: * -> *). Foldable t => t Char -> Bool
wouldHaveBeenGlob (Token -> Maybe String
getLiteralString Token
word) Maybe Bool -> Maybe Bool -> Bool
forall a. Eq a => a -> a -> Bool
== Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
True)) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
        Id -> Integer -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
id Integer
2066 String
"Since you double quoted this, it will not word split, and the loop will only run once."
checkForInQuoted p
_ (T_ForIn Id
_ String
f [T_NormalWord Id
_ [T_SingleQuoted Id
id String
_]] [Token]
_) =
    Id -> Integer -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2041 String
"This is a literal string. To run as a command, use $(..) instead of '..' . "
checkForInQuoted p
_ (T_ForIn Id
_ String
f [T_NormalWord Id
_ [T_Literal Id
id String
s]] [Token]
_) =
    if Char
',' Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
s
      then Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Char
'{' Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
s) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
            Id -> Integer -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2042 String
"Use spaces, not commas, to separate loop elements."
      else Id -> Integer -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2043 String
"This loop will only ever run once for a constant value. Did you perhaps mean to loop over dir/*, $var or $(cmd)?"
checkForInQuoted p
_ Token
_ = () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkForInCat1 :: Bool
prop_checkForInCat1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkForInCat String
"for f in $(cat foo); do stuff; done"
prop_checkForInCat1a :: Bool
prop_checkForInCat1a= (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkForInCat String
"for f in `cat foo`; do stuff; done"
prop_checkForInCat2 :: Bool
prop_checkForInCat2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkForInCat String
"for f in $(cat foo | grep lol); do stuff; done"
prop_checkForInCat2a :: Bool
prop_checkForInCat2a= (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkForInCat String
"for f in `cat foo | grep lol`; do stuff; done"
prop_checkForInCat3 :: Bool
prop_checkForInCat3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
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]
_) = (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
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])
        | (Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Token -> Bool
isLineBased [Token]
r =
            Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
info Id
id Integer
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
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    isLineBased :: Token -> Bool
isLineBased Token
cmd = (String -> Bool) -> [String] -> Bool
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
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkForInLs :: Bool
prop_checkForInLs = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkForInLs String
"for f in $(ls *.mp3); do mplayer \"$f\"; done"
prop_checkForInLs2 :: Bool
prop_checkForInLs2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkForInLs String
"for f in `ls *.mp3`; do mplayer \"$f\"; done"
prop_checkForInLs3 :: Bool
prop_checkForInLs3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkForInLs String
"for f in `find / -name '*.mp3'`; do mplayer \"$f\"; done"
checkForInLs :: p -> Token -> m ()
checkForInLs p
_ = Token -> m ()
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]
_) =
        Id -> String -> Token -> m ()
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]
_) =
        Id -> String -> Token -> m ()
forall (m :: * -> *) p.
MonadWriter [TokenComment] m =>
Id -> p -> Token -> m ()
check Id
id String
f Token
x
   try Token
_ = () -> m ()
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 -> Integer -> String -> m ()
warntype = if (String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (String
"-" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf`) [String]
n then Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn else Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err in
          Id -> Integer -> String -> m ()
warntype Id
id Integer
2045 String
"Iterating over ls output is fragile. Use globs."
      (String
"find":[String]
_) -> Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2044 String
"For loops over find output are fragile. Use find -exec or a while read loop."
      [String]
_ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkFindExec1 :: Bool
prop_checkFindExec1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkFindExec String
"find / -name '*.php' -exec rm {};"
prop_checkFindExec2 :: Bool
prop_checkFindExec2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkFindExec String
"find / -exec touch {} && ls {} \\;"
prop_checkFindExec3 :: Bool
prop_checkFindExec3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkFindExec String
"find / -execdir cat {} | grep lol +"
prop_checkFindExec4 :: Bool
prop_checkFindExec4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkFindExec String
"find / -name '*.php' -exec foo {} +"
prop_checkFindExec5 :: Bool
prop_checkFindExec5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkFindExec String
"find / -execdir bash -c 'a && b' \\;"
prop_checkFindExec6 :: Bool
prop_checkFindExec6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
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 <- [Token] -> Bool -> m Bool
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
[Token] -> Bool -> m Bool
broken [Token]
r Bool
False
    Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when Bool
c (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
        let wordId :: Id
wordId = Token -> Id
getId (Token -> Id) -> Token -> Id
forall a b. (a -> b) -> a -> b
$ [Token] -> Token
forall a. [a] -> a
last [Token]
t in
            Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
wordId Integer
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 = Bool -> m Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
v
    broken (Token
w:[Token]
r) Bool
v = do
        Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when Bool
v ((Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
warnFor ([Token] -> m ()) -> [Token] -> m ()
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 =
        Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when(Token -> Bool
shouldWarn Token
x) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
            Id -> Integer -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
info (Token -> Id
getId Token
x) Integer
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
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkUnquotedExpansions1 :: Bool
prop_checkUnquotedExpansions1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedExpansions String
"rm $(ls)"
prop_checkUnquotedExpansions1a :: Bool
prop_checkUnquotedExpansions1a= (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedExpansions String
"rm `ls`"
prop_checkUnquotedExpansions2 :: Bool
prop_checkUnquotedExpansions2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedExpansions String
"rm foo$(date)"
prop_checkUnquotedExpansions3 :: Bool
prop_checkUnquotedExpansions3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedExpansions String
"[ $(foo) == cow ]"
prop_checkUnquotedExpansions3a :: Bool
prop_checkUnquotedExpansions3a= (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedExpansions String
"[ ! $(foo) ]"
prop_checkUnquotedExpansions4 :: Bool
prop_checkUnquotedExpansions4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedExpansions String
"[[ $(foo) == cow ]]"
prop_checkUnquotedExpansions5 :: Bool
prop_checkUnquotedExpansions5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
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 -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedExpansions String
"$(cmd)"
prop_checkUnquotedExpansions7 :: Bool
prop_checkUnquotedExpansions7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedExpansions String
"cat << foo\n$(ls)\nfoo"
prop_checkUnquotedExpansions8 :: Bool
prop_checkUnquotedExpansions8 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedExpansions String
"set -- $(seq 1 4)"
prop_checkUnquotedExpansions9 :: Bool
prop_checkUnquotedExpansions9 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedExpansions String
"echo foo `# inline comment`"
checkUnquotedExpansions :: Parameters -> Token -> f ()
checkUnquotedExpansions Parameters
params =
    Token -> f ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
check
  where
    check :: Token -> f ()
check t :: Token
t@(T_DollarExpansion Id
_ [Token]
c) = Token -> [Token] -> f ()
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) = Token -> [Token] -> f ()
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) = Token -> [Token] -> f ()
forall (f :: * -> *) (t :: * -> *) a.
(Foldable t, MonadWriter [TokenComment] f) =>
Token -> t a -> f ()
examine Token
t [Token]
c
    check Token
_ = () -> f ()
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 =
        Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (t a -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null t a
contents Bool -> Bool -> Bool
|| Token -> Bool
shouldBeSplit Token
t Bool -> Bool -> Bool
|| Map Id Token -> Token -> Bool
isQuoteFree Map Id Token
tree Token
t Bool -> Bool -> Bool
|| Map Id Token -> Token -> Bool
usedAsCommandName Map Id Token
tree Token
t) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
            Id -> Integer -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn (Token -> Id
getId Token
t) Integer
2046 String
"Quote this to prevent word splitting."

    shouldBeSplit :: Token -> Bool
shouldBeSplit Token
t =
        Token -> Maybe String
getCommandNameFromExpansion Token
t Maybe String -> Maybe String -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Maybe String
forall a. a -> Maybe a
Just String
"seq"


prop_checkRedirectToSame :: Bool
prop_checkRedirectToSame = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectToSame String
"cat foo > foo"
prop_checkRedirectToSame2 :: Bool
prop_checkRedirectToSame2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
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 -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
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 -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectToSame String
"foo /dev/null > /dev/null"
prop_checkRedirectToSame5 :: Bool
prop_checkRedirectToSame5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectToSame String
"foo > bar 2> bar"
prop_checkRedirectToSame6 :: Bool
prop_checkRedirectToSame6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectToSame String
"echo foo > foo"
prop_checkRedirectToSame7 :: Bool
prop_checkRedirectToSame7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
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 -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectToSame String
"while read -r line; do _=\"$fname\"; done <\"$fname\""
checkRedirectToSame :: Parameters -> Token -> m ()
checkRedirectToSame Parameters
params s :: Token
s@(T_Pipeline Id
_ [Token]
_ [Token]
list) =
    (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (\Token
l -> ((Token -> m Token) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (\Token
x -> (Token -> m ()) -> Token -> m Token
forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> Token -> m Token
doAnalysis (Token -> Token -> m ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
Token -> Token -> f ()
checkOccurrences Token
x) Token
l) ([Token] -> [Token]
getAllRedirs [Token]
list))) [Token]
list
  where
    note :: Id -> TokenComment
note Id
x = Severity -> Id -> Integer -> String -> TokenComment
makeComment Severity
InfoC Id
x Integer
2094
                String
"Make sure not to read and write the same file in the same pipeline."
    checkOccurrences :: Token -> Token -> f ()
checkOccurrences t :: Token
t@(T_NormalWord Id
exceptId [Token]
x) u :: Token
u@(T_NormalWord Id
newId [Token]
y) =
        Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Id
exceptId Id -> Id -> Bool
forall a. Eq a => a -> a -> Bool
/= Id
newId
                Bool -> Bool -> Bool
&& [Token]
x [Token] -> [Token] -> Bool
forall a. Eq a => a -> a -> Bool
== [Token]
y
                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 ((Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
isHarmlessCommand [Token
t,Token
u])
                Bool -> Bool -> Bool
&& Bool -> Bool
not ((Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
containsAssignment [Token
u])) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$ do
            TokenComment -> f ()
forall a (m :: * -> *). (NFData a, MonadWriter [a] m) => a -> m ()
addComment (TokenComment -> f ()) -> TokenComment -> f ()
forall a b. (a -> b) -> a -> b
$ Id -> TokenComment
note Id
newId
            TokenComment -> f ()
forall a (m :: * -> *). (NFData a, MonadWriter [a] m) => a -> m ()
addComment (TokenComment -> f ()) -> TokenComment -> f ()
forall a b. (a -> b) -> a -> b
$ Id -> TokenComment
note Id
exceptId
    checkOccurrences Token
_ Token
_ = () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    getAllRedirs :: [Token] -> [Token]
getAllRedirs = (Token -> [Token]) -> [Token] -> [Token]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (\Token
t ->
        case Token
t of
            T_Redirecting Id
_ [Token]
ls Token
_ -> (Token -> [Token]) -> [Token] -> [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/" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat (Token -> [String]
oversimplify Token
x)
    isOutput :: Token -> Bool
isOutput Token
t =
        case Int -> [Token] -> [Token]
forall a. Int -> [a] -> [a]
drop Int
1 ([Token] -> [Token]) -> [Token] -> [Token]
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 = Bool -> Maybe Bool -> Bool
forall a. a -> Maybe a -> a
fromMaybe Bool
False (Maybe Bool -> Bool) -> Maybe Bool -> Bool
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
        Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> Maybe Bool) -> Bool -> Maybe Bool
forall a b. (a -> b) -> a -> b
$ String
name String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"echo", String
"printf", String
"sponge"]
    containsAssignment :: Token -> Bool
containsAssignment Token
arg = Bool -> Maybe Bool -> Bool
forall a. a -> Maybe a -> a
fromMaybe Bool
False (Maybe Bool -> Bool) -> Maybe Bool -> Bool
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
        Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> Maybe Bool) -> Bool -> Maybe Bool
forall a b. (a -> b) -> a -> b
$ Token -> Bool
isAssignment Token
cmd

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


prop_checkShorthandIf :: Bool
prop_checkShorthandIf  = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkShorthandIf String
"[[ ! -z file ]] && scp file host || rm file"
prop_checkShorthandIf2 :: Bool
prop_checkShorthandIf2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkShorthandIf String
"[[ ! -z file ]] && { scp file host || echo 'Eek'; }"
prop_checkShorthandIf3 :: Bool
prop_checkShorthandIf3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkShorthandIf String
"foo && bar || echo baz"
prop_checkShorthandIf4 :: Bool
prop_checkShorthandIf4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkShorthandIf String
"foo && a=b || a=c"
prop_checkShorthandIf5 :: Bool
prop_checkShorthandIf5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkShorthandIf String
"foo && rm || printf b"
prop_checkShorthandIf6 :: Bool
prop_checkShorthandIf6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkShorthandIf String
"if foo && bar || baz; then true; fi"
prop_checkShorthandIf7 :: Bool
prop_checkShorthandIf7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkShorthandIf String
"while foo && bar || baz; do true; done"
prop_checkShorthandIf8 :: Bool
prop_checkShorthandIf8 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
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_AndIf Id
id Token
_ (T_OrIf Id
_ Token
_ (T_Pipeline Id
_ [Token]
_ [Token]
t)))
        | Bool -> Bool
not ([Token] -> Bool
isOk [Token]
t Bool -> Bool -> Bool
|| Bool
inCondition) =
    Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
info Id
id Integer
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
|| Bool -> Maybe Bool -> Bool
forall a. a -> Maybe a -> a
fromMaybe Bool
False (do
        String
name <- Token -> Maybe String
getCommandBasename Token
t
        Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> Maybe Bool) -> Bool -> Maybe Bool
forall a b. (a -> b) -> a -> b
$ String
name String -> [String] -> Bool
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 ([Token] -> Bool) -> [Token] -> Bool
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
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkDollarStar :: Bool
prop_checkDollarStar = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkDollarStar String
"for f in $*; do ..; done"
prop_checkDollarStar2 :: Bool
prop_checkDollarStar2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkDollarStar String
"a=$*"
prop_checkDollarStar3 :: Bool
prop_checkDollarStar3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkDollarStar String
"[[ $* = 'a b' ]]"
checkDollarStar :: Parameters -> Token -> f ()
checkDollarStar Parameters
p t :: Token
t@(T_NormalWord Id
_ [b :: Token
b@(T_DollarBraced Id
id Bool
_ Token
_)])
      | Token -> String
bracedString Token
b String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"*"  =
    Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Map Id Token -> Token -> Bool
isStrictlyQuoteFree (Parameters -> Map Id Token
parentMap Parameters
p) Token
t) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
        Id -> Integer -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2048 String
"Use \"$@\" (with quotes) to prevent whitespace problems."
checkDollarStar Parameters
_ Token
_ = () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()


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

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

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

prop_checkArrayWithoutIndex1 :: Bool
prop_checkArrayWithoutIndex1 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkArrayWithoutIndex String
"foo=(a b); echo $foo"
prop_checkArrayWithoutIndex2 :: Bool
prop_checkArrayWithoutIndex2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkArrayWithoutIndex String
"coproc tail -f log; echo $COPROC"
prop_checkArrayWithoutIndex5 :: Bool
prop_checkArrayWithoutIndex5 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkArrayWithoutIndex String
"a[0]=foo; echo $a"
prop_checkArrayWithoutIndex6 :: Bool
prop_checkArrayWithoutIndex6 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkArrayWithoutIndex String
"echo $PIPESTATUS"
prop_checkArrayWithoutIndex7 :: Bool
prop_checkArrayWithoutIndex7 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkArrayWithoutIndex String
"a=(a b); a+=c"
prop_checkArrayWithoutIndex8 :: Bool
prop_checkArrayWithoutIndex8 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkArrayWithoutIndex String
"declare -a foo; foo=bar;"
prop_checkArrayWithoutIndex9 :: Bool
prop_checkArrayWithoutIndex9 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkArrayWithoutIndex String
"read -ra arr <<< 'foo bar'; echo \"$arr\""
checkArrayWithoutIndex :: Parameters -> p -> [TokenComment]
checkArrayWithoutIndex Parameters
params p
_ =
    (Token -> Token -> String -> State (Map String ()) [TokenComment])
-> (Token
    -> Token
    -> String
    -> DataType
    -> State (Map String ()) [TokenComment])
-> Map String ()
-> [StackData]
-> [TokenComment]
forall t v.
(Token -> Token -> String -> State t [v])
-> (Token -> Token -> String -> DataType -> State t [v])
-> t
-> [StackData]
-> [v]
doVariableFlowAnalysis Token -> Token -> String -> State (Map String ()) [TokenComment]
forall (m :: * -> *) a p p.
MonadState (Map String a) m =>
p -> Token -> p -> m [TokenComment]
readF Token
-> Token
-> String
-> DataType
-> State (Map String ()) [TokenComment]
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 = [(String, ())] -> Map String ()
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList ([(String, ())] -> Map String ())
-> [(String, ())] -> Map String ()
forall a b. (a -> b) -> a -> b
$ (String -> (String, ())) -> [String] -> [(String, ())]
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 <- m (Map String a)
forall s (m :: * -> *). MonadState s m => m s
get
        [TokenComment] -> m [TokenComment]
forall (m :: * -> *) a. Monad m => a -> m a
return ([TokenComment] -> m [TokenComment])
-> (Maybe TokenComment -> [TokenComment])
-> Maybe TokenComment
-> m [TokenComment]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Maybe TokenComment -> [TokenComment]
forall a. Maybe a -> [a]
maybeToList (Maybe TokenComment -> m [TokenComment])
-> Maybe TokenComment -> m [TokenComment]
forall a b. (a -> b) -> a -> b
$ do
            String
name <- Token -> Maybe String
getLiteralString Token
token
            a
assigned <- String -> Map String a -> Maybe a
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup String
name Map String a
map
            TokenComment -> Maybe TokenComment
forall (m :: * -> *) a. Monad m => a -> m a
return (TokenComment -> Maybe TokenComment)
-> TokenComment -> Maybe TokenComment
forall a b. (a -> b) -> a -> b
$ Severity -> Id -> Integer -> String -> TokenComment
makeComment Severity
WarningC Id
id Integer
2128
                    String
"Expanding an array without an index only gives the first element."
    readF p
_ Token
_ p
_ = [TokenComment] -> m [TokenComment]
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 <- (Map String () -> Bool) -> m Bool
forall s (m :: * -> *) a. MonadState s m => (s -> a) -> m a
gets (Maybe () -> Bool
forall a. Maybe a -> Bool
isJust (Maybe () -> Bool)
-> (Map String () -> Maybe ()) -> Map String () -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Map String () -> Maybe ()
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup String
name)
        [TokenComment] -> m [TokenComment]
forall (m :: * -> *) a. Monad m => a -> m a
return ([TokenComment] -> m [TokenComment])
-> [TokenComment] -> m [TokenComment]
forall a b. (a -> b) -> a -> b
$ if Bool -> Bool
not Bool
isArray then [] else
            case AssignmentMode
mode of
                AssignmentMode
Assign -> [Severity -> Id -> Integer -> String -> TokenComment
makeComment Severity
WarningC Id
id Integer
2178 String
"Variable was used as an array but is now assigned a string."]
                AssignmentMode
Append -> [Severity -> Id -> Integer -> String -> TokenComment
makeComment Severity
WarningC Id
id Integer
2179 String
"Use array+=(\"item\") to append items to an array."]

    writeF p
_ Token
t String
name (DataArray DataSource
_) = do
        (Map String () -> Map String ()) -> m ()
forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify (String -> () -> Map String () -> Map String ()
forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert String
name ())
        [TokenComment] -> m [TokenComment]
forall (m :: * -> *) a. Monad m => a -> m a
return []
    writeF p
_ Token
expr String
name DataType
_ = do
        if Token -> Bool
isIndexed Token
expr
          then (Map String () -> Map String ()) -> m ()
forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify (String -> () -> Map String () -> Map String ()
forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert String
name ())
          else (Map String () -> Map String ()) -> m ()
forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify (String -> Map String () -> Map String ()
forall k a. Ord k => k -> Map k a -> Map k a
Map.delete String
name)
        [TokenComment] -> m [TokenComment]
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 -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkStderrRedirect String
"test 2>&1 > cow"
prop_checkStderrRedirect2 :: Bool
prop_checkStderrRedirect2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkStderrRedirect String
"test > cow 2>&1"
prop_checkStderrRedirect3 :: Bool
prop_checkStderrRedirect3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkStderrRedirect String
"test 2>&1 > file | grep stderr"
prop_checkStderrRedirect4 :: Bool
prop_checkStderrRedirect4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkStderrRedirect String
"errors=$(test 2>&1 > file)"
prop_checkStderrRedirect5 :: Bool
prop_checkStderrRedirect5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkStderrRedirect String
"read < <(test 2>&1 > file)"
prop_checkStderrRedirect6 :: Bool
prop_checkStderrRedirect6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkStderrRedirect String
"foo | bar 2>&1 > /dev/null"
prop_checkStderrRedirect7 :: Bool
prop_checkStderrRedirect7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
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
_ -> () -> f ()
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) -> [Token] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Token]
list Int -> Int -> Bool
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) ([Token] -> Token
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 = (Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
usesOutput ([Token] -> Bool) -> [Token] -> Bool
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 = Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless Bool
isCaptured (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
        Id -> Integer -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2069 String
"To redirect stdout+stderr, 2>&1 must be last (or use '{ cmd > file; } 2>&1' to clarify)."

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

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


prop_checkSingleQuotedVariables :: Bool
prop_checkSingleQuotedVariables  = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"echo '$foo'"
prop_checkSingleQuotedVariables2 :: Bool
prop_checkSingleQuotedVariables2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"echo 'lol$1.jpg'"
prop_checkSingleQuotedVariables3 :: Bool
prop_checkSingleQuotedVariables3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"sed 's/foo$/bar/'"
prop_checkSingleQuotedVariables3a :: Bool
prop_checkSingleQuotedVariables3a= (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"sed 's/${foo}/bar/'"
prop_checkSingleQuotedVariables3b :: Bool
prop_checkSingleQuotedVariables3b= (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"sed 's/$(echo cow)/bar/'"
prop_checkSingleQuotedVariables3c :: Bool
prop_checkSingleQuotedVariables3c= (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"sed 's/$((1+foo))/bar/'"
prop_checkSingleQuotedVariables4 :: Bool
prop_checkSingleQuotedVariables4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"awk '{print $1}'"
prop_checkSingleQuotedVariables5 :: Bool
prop_checkSingleQuotedVariables5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"trap 'echo $SECONDS' EXIT"
prop_checkSingleQuotedVariables6 :: Bool
prop_checkSingleQuotedVariables6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"sed -n '$p'"
prop_checkSingleQuotedVariables6a :: Bool
prop_checkSingleQuotedVariables6a= (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"sed -n '$pattern'"
prop_checkSingleQuotedVariables7 :: Bool
prop_checkSingleQuotedVariables7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"PS1='$PWD \\$ '"
prop_checkSingleQuotedVariables8 :: Bool
prop_checkSingleQuotedVariables8 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"find . -exec echo '$1' {} +"
prop_checkSingleQuotedVariables9 :: Bool
prop_checkSingleQuotedVariables9 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"find . -exec awk '{print $1}' {} \\;"
prop_checkSingleQuotedVariables10 :: Bool
prop_checkSingleQuotedVariables10= (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"echo '`pwd`'"
prop_checkSingleQuotedVariables11 :: Bool
prop_checkSingleQuotedVariables11= (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"sed '${/lol/d}'"
prop_checkSingleQuotedVariables12 :: Bool
prop_checkSingleQuotedVariables12= (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"eval 'echo $1'"
prop_checkSingleQuotedVariables13 :: Bool
prop_checkSingleQuotedVariables13= (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"busybox awk '{print $1}'"
prop_checkSingleQuotedVariables14 :: Bool
prop_checkSingleQuotedVariables14= (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"[ -v 'bar[$foo]' ]"
prop_checkSingleQuotedVariables15 :: Bool
prop_checkSingleQuotedVariables15= (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"git filter-branch 'test $GIT_COMMIT'"
prop_checkSingleQuotedVariables16 :: Bool
prop_checkSingleQuotedVariables16= (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"git '$a'"
prop_checkSingleQuotedVariables17 :: Bool
prop_checkSingleQuotedVariables17= (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"rename 's/(.)a/$1/g' *"
prop_checkSingleQuotedVariables18 :: Bool
prop_checkSingleQuotedVariables18= (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"echo '``'"
prop_checkSingleQuotedVariables19 :: Bool
prop_checkSingleQuotedVariables19= (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleQuotedVariables String
"echo '```'"

checkSingleQuotedVariables :: Parameters -> Token -> f ()
checkSingleQuotedVariables Parameters
params t :: Token
t@(T_SingleQuoted Id
id String
s) =
    Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
s String -> Regex -> Bool
`matches` Regex
re) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
        if String
"sed" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
commandName
        then Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (String
s String -> Regex -> Bool
`matches` Regex
sedContra) f ()
showMessage
        else Bool -> f () -> f ()
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 = Id -> Integer -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
info Id
id Integer
2016
        String
"Expressions don't expand in single quotes, use double quotes for that."
    commandName :: String
commandName = String -> Maybe String -> String
forall a. a -> Maybe a -> a
fromMaybe String
"" (Maybe String -> String) -> Maybe String -> 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
        String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return (String -> Maybe String) -> String -> Maybe String
forall a b. (a -> b) -> a -> b
$ if String
name String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"find" then Token -> String
getFindCommand Token
cmd else if String
name String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"git" then Token -> String
getGitCommand Token
cmd else String
name

    isProbablyOk :: Bool
isProbablyOk =
            (Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
isOkAssignment (Int -> [Token] -> [Token]
forall a. Int -> [a] -> [a]
take Int
3 ([Token] -> [Token]) -> [Token] -> [Token]
forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> [Token]
getPath Map Id Token
parents Token
t)
            Bool -> Bool -> Bool
|| String
commandName String -> [String] -> Bool
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
"dpkg-query"
                ,String
"jq"  -- could also check that user provides --arg
                ,String
"rename"
                ,String
"unset"
                ,String
"git filter-branch"
                ]
            Bool -> Bool -> Bool
|| String
"awk" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isSuffixOf` String
commandName
            Bool -> Bool -> Bool
|| String
"perl" String -> String -> Bool
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 String -> [String] -> Bool
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 = (Token -> Maybe String) -> [Token] -> [Maybe String]
forall a b. (a -> b) -> [a] -> [b]
map Token -> Maybe String
getLiteralString [Token]
words
            cmd :: [Maybe String]
cmd  = (Maybe String -> Bool) -> [Maybe String] -> [Maybe String]
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (\Maybe String
x -> Maybe String
x Maybe String -> Maybe String -> Bool
forall a. Eq a => a -> a -> Bool
/= String -> Maybe String
forall a. a -> Maybe a
Just String
"-exec" Bool -> Bool -> Bool
&& Maybe String
x Maybe String -> Maybe String -> Bool
forall a. Eq a => a -> a -> Bool
/= String -> Maybe String
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) -> String -> Maybe String -> String
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 (Token -> Maybe String) -> [Token] -> [Maybe String]
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"
checkSingleQuotedVariables Parameters
_ Token
_ = () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkUnquotedN :: Bool
prop_checkUnquotedN = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkUnquotedN String
"if [ -n $foo ]; then echo cow; fi"
prop_checkUnquotedN2 :: Bool
prop_checkUnquotedN2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkUnquotedN String
"[ -n $cow ]"
prop_checkUnquotedN3 :: Bool
prop_checkUnquotedN3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkUnquotedN String
"[[ -n $foo ]] && echo cow"
prop_checkUnquotedN4 :: Bool
prop_checkUnquotedN4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkUnquotedN String
"[ -n $cow -o -t 1 ]"
prop_checkUnquotedN5 :: Bool
prop_checkUnquotedN5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkUnquotedN String
"[ -n \"$@\" ]"
checkUnquotedN :: p -> Token -> m ()
checkUnquotedN p
_ (TC_Unary Id
_ ConditionType
SingleBracket String
"-n" (T_NormalWord Id
id [Token
t])) | Token -> Bool
willSplit Token
t =
       Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
id Integer
2070 String
"-n doesn't work with unquoted arguments. Quote or use [[ ]]."
checkUnquotedN p
_ Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkNumberComparisons1 :: Bool
prop_checkNumberComparisons1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[[ $foo < 3 ]]"
prop_checkNumberComparisons2 :: Bool
prop_checkNumberComparisons2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[[ 0 >= $(cmd) ]]"
prop_checkNumberComparisons3 :: Bool
prop_checkNumberComparisons3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[[ $foo ]] > 3"
prop_checkNumberComparisons4 :: Bool
prop_checkNumberComparisons4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[[ $foo > 2.72 ]]"
prop_checkNumberComparisons5 :: Bool
prop_checkNumberComparisons5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[[ $foo -le 2.72 ]]"
prop_checkNumberComparisons6 :: Bool
prop_checkNumberComparisons6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[[ 3.14 -eq $foo ]]"
prop_checkNumberComparisons7 :: Bool
prop_checkNumberComparisons7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[[ 3.14 == $foo ]]"
prop_checkNumberComparisons8 :: Bool
prop_checkNumberComparisons8 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[ foo <= bar ]"
prop_checkNumberComparisons9 :: Bool
prop_checkNumberComparisons9 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[ foo \\>= bar ]"
prop_checkNumberComparisons11 :: Bool
prop_checkNumberComparisons11 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[ $foo -eq 'N' ]"
prop_checkNumberComparisons12 :: Bool
prop_checkNumberComparisons12 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[ x$foo -gt x${N} ]"
prop_checkNumberComparisons13 :: Bool
prop_checkNumberComparisons13 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[ $foo > $bar ]"
prop_checkNumberComparisons14 :: Bool
prop_checkNumberComparisons14 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[[ foo < bar ]]"
prop_checkNumberComparisons15 :: Bool
prop_checkNumberComparisons15 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNumberComparisons String
"[ $foo '>' $bar ]"
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
        Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String -> Bool
isLtGt String
op) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
          Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
id Integer
2071 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
            String
op String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" is for string comparisons. Use " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> String
eqv String
op String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" instead."
        Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String -> Bool
isLeGe String
op Bool -> Bool -> Bool
&& Bool
hasStringComparison) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
            Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
id Integer
2071 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
op String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" is not a valid operator. " String -> String -> String
forall a. [a] -> [a] -> [a]
++
              String
"Use " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> String
eqv String
op String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" ."
      else do
        Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String -> Bool
isLeGe String
op Bool -> Bool -> Bool
|| String -> Bool
isLtGt String
op) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
            (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
checkDecimals [Token
lhs, Token
rhs]

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

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

    Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
op String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"-lt", String
"-gt", String
"-le", String
"-ge", String
"-eq"]) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ do
        (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
checkDecimals [Token
lhs, Token
rhs]
        Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (ConditionType
typ ConditionType -> ConditionType -> Bool
forall a. Eq a => a -> a -> Bool
== ConditionType
SingleBracket) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
            [Token] -> m ()
checkStrings [Token
lhs, Token
rhs]

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

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

      checkStrings :: [Token] -> m ()
checkStrings =
        (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
stringError ([Token] -> m ()) -> ([Token] -> [Token]) -> [Token] -> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> [Token] -> [Token]
forall a. Int -> [a] -> [a]
take Int
1 ([Token] -> [Token]) -> ([Token] -> [Token]) -> [Token] -> [Token]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Token -> Bool) -> [Token] -> [Token]
forall a. (a -> Bool) -> [a] -> [a]
filter Token -> Bool
isNonNum

      isNonNum :: Token -> Bool
isNonNum Token
t = Bool -> Maybe Bool -> Bool
forall a. a -> Maybe a -> a
fromMaybe Bool
False (Maybe Bool -> Bool) -> Maybe Bool -> Bool
forall a b. (a -> b) -> a -> b
$ do
        String
s <- (Token -> Maybe String) -> Token -> Maybe String
getLiteralStringExt (Maybe String -> Token -> Maybe String
forall a b. a -> b -> a
const (Maybe String -> Token -> Maybe String)
-> Maybe String -> Token -> Maybe String
forall a b. (a -> b) -> a -> b
$ String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return String
"") Token
t
        Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> Maybe Bool) -> (String -> Bool) -> String -> Maybe Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not (Bool -> Bool) -> (String -> Bool) -> String -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> Bool) -> String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
numChar (String -> Maybe Bool) -> String -> Maybe Bool
forall a b. (a -> b) -> a -> b
$ String
s
      numChar :: Char -> Bool
numChar Char
x = Char -> Bool
isDigit Char
x Bool -> Bool -> Bool
|| Char
x Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
"+-. "

      stringError :: Token -> m ()
stringError Token
t = Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err (Token -> Id
getId Token
t) Integer
2170 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
          String
"Numerical " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
op String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" does not dereference in [..]. Expand or use string operator."

      isNum :: Token -> Bool
isNum Token
t =
        case Token -> [String]
oversimplify Token
t of
            [String
v] -> (Char -> Bool) -> String -> Bool
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] -> Maybe [String] -> Bool
forall a. Maybe a -> Bool
isJust (Maybe [String] -> Bool) -> Maybe [String] -> Bool
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 ConditionType -> ConditionType -> Bool
forall a. Eq a => a -> a -> Bool
== ConditionType
SingleBracket then String
"\\" else String
""
      seqv :: String -> String
seqv String
"-ge" = String
"! a " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
esc String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"< b"
      seqv String
"-gt" = String
esc String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
">"
      seqv String
"-le" = String
"! a " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
esc String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"> b"
      seqv String
"-lt" = String
esc String -> String -> String
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
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

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

prop_checkDoubleBracketOperators1 :: Bool
prop_checkDoubleBracketOperators1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkDoubleBracketOperators String
"[[ 3 \\< 4 ]]"
prop_checkDoubleBracketOperators3 :: Bool
prop_checkDoubleBracketOperators3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
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 ConditionType -> ConditionType -> Bool
forall a. Eq a => a -> a -> Bool
== ConditionType
DoubleBracket Bool -> Bool -> Bool
&& String
op String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"\\<", String
"\\>"] =
        Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
id Integer
2075 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
"Escaping " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
op String -> String -> String
forall a. [a] -> [a] -> [a]
++String
" is required in [..], but invalid in [[..]]"
checkDoubleBracketOperators p
_ Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkConditionalAndOrs1 :: Bool
prop_checkConditionalAndOrs1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkConditionalAndOrs String
"[ foo && bar ]"
prop_checkConditionalAndOrs2 :: Bool
prop_checkConditionalAndOrs2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkConditionalAndOrs String
"[[ foo -o bar ]]"
prop_checkConditionalAndOrs3 :: Bool
prop_checkConditionalAndOrs3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkConditionalAndOrs String
"[[ foo || bar ]]"
prop_checkConditionalAndOrs4 :: Bool
prop_checkConditionalAndOrs4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkConditionalAndOrs String
"[ foo -a bar ]"
prop_checkConditionalAndOrs5 :: Bool
prop_checkConditionalAndOrs5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
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
_) ->
            Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
id Integer
2107 String
"Instead of [ a && b ], use [ a ] && [ b ]."
        (TC_And Id
id ConditionType
DoubleBracket String
"-a" Token
_ Token
_) ->
            Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
id Integer
2108 String
"In [[..]], use && instead of -a."
        (TC_Or Id
id ConditionType
SingleBracket String
"||" Token
_ Token
_) ->
            Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
id Integer
2109 String
"Instead of [ a || b ], use [ a ] || [ b ]."
        (TC_Or Id
id ConditionType
DoubleBracket String
"-o" Token
_ Token
_) ->
            Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
id Integer
2110 String
"In [[..]], use || instead of -o."

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

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

prop_checkQuotedCondRegex1 :: Bool
prop_checkQuotedCondRegex1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkQuotedCondRegex String
"[[ $foo =~ \"bar.*\" ]]"
prop_checkQuotedCondRegex2 :: Bool
prop_checkQuotedCondRegex2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkQuotedCondRegex String
"[[ $foo =~ '(cow|bar)' ]]"
prop_checkQuotedCondRegex3 :: Bool
prop_checkQuotedCondRegex3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkQuotedCondRegex String
"[[ $foo =~ $foo ]]"
prop_checkQuotedCondRegex4 :: Bool
prop_checkQuotedCondRegex4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkQuotedCondRegex String
"[[ $foo =~ \"bar\" ]]"
prop_checkQuotedCondRegex5 :: Bool
prop_checkQuotedCondRegex5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkQuotedCondRegex String
"[[ $foo =~ 'cow bar' ]]"
prop_checkQuotedCondRegex6 :: Bool
prop_checkQuotedCondRegex6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
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]
_] -> Token -> f ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
error Token
rhs
        T_NormalWord Id
id [T_SingleQuoted Id
_ String
_] -> Token -> f ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
error Token
rhs
        Token
_ -> () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    error :: Token -> f ()
error Token
t =
        Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Token -> Bool
isConstantNonRe Token
t) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
            Id -> Integer -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err (Token -> Id
getId Token
t) Integer
2076
                String
"Don't quote right-hand side of =~, it'll match literally rather than as a regex."
    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 = Bool -> Maybe Bool -> Bool
forall a. a -> Maybe a -> a
fromMaybe Bool
False (Maybe Bool -> Bool) -> Maybe Bool -> Bool
forall a b. (a -> b) -> a -> b
$ do
        String
s <- Token -> Maybe String
getLiteralString Token
t
        Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> Maybe Bool) -> (Bool -> Bool) -> Bool -> Maybe Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not (Bool -> Maybe Bool) -> Bool -> Maybe Bool
forall a b. (a -> b) -> a -> b
$ String -> Bool
hasMetachars String
s
checkQuotedCondRegex p
_ Token
_ = () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

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


prop_checkConstantIfs1 :: Bool
prop_checkConstantIfs1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkConstantIfs String
"[[ foo != bar ]]"
prop_checkConstantIfs2a :: Bool
prop_checkConstantIfs2a= (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkConstantIfs String
"[ n -le 4 ]"
prop_checkConstantIfs2b :: Bool
prop_checkConstantIfs2b= (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkConstantIfs String
"[[ n -le 4 ]]"
prop_checkConstantIfs3 :: Bool
prop_checkConstantIfs3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkConstantIfs String
"[[ $n -le 4 && n != 2 ]]"
prop_checkConstantIfs4 :: Bool
prop_checkConstantIfs4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkConstantIfs String
"[[ $n -le 3 ]]"
prop_checkConstantIfs5 :: Bool
prop_checkConstantIfs5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkConstantIfs String
"[[ $n -le $n ]]"
prop_checkConstantIfs6 :: Bool
prop_checkConstantIfs6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkConstantIfs String
"[[ a -ot b ]]"
prop_checkConstantIfs7 :: Bool
prop_checkConstantIfs7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkConstantIfs String
"[ a -nt b ]"
prop_checkConstantIfs8 :: Bool
prop_checkConstantIfs8 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkConstantIfs String
"[[ ~foo == '~foo' ]]"
prop_checkConstantIfs9 :: Bool
prop_checkConstantIfs9 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkConstantIfs String
"[[ *.png == [a-z] ]]"
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  Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2050 String
"This expression is constant. Did you forget the $ on a variable?"
        else Id -> String -> Token -> Token -> m ()
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 String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [ String
"-lt", String
"-gt", String
"-le", String
"-ge", String
"-eq", String
"-ne" ]
            Bool -> Bool -> Bool
&& ConditionType
typ ConditionType -> ConditionType -> Bool
forall a. Eq a => a -> a -> Bool
== ConditionType
DoubleBracket
        Bool -> Bool -> Bool
|| String
op String -> [String] -> Bool
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 =
        Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
op String -> [String] -> Bool
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)) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
            Id -> Integer -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2193 String
"The arguments to this comparison can never be equal. Make sure your syntax is correct."
checkConstantIfs p
_ Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

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

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

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

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

prop_checkForDecimals1 :: Bool
prop_checkForDecimals1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForDecimals String
"((3.14*c))"
prop_checkForDecimals2 :: Bool
prop_checkForDecimals2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForDecimals String
"foo[1.2]=bar"
prop_checkForDecimals3 :: Bool
prop_checkForDecimals3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
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]
_) = Maybe (m ()) -> m ()
forall (m :: * -> *). Monad m => Maybe (m ()) -> m ()
potentially (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
    Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
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 String -> Int -> Maybe Char
forall a. [a] -> Int -> Maybe a
!!! Int
0
    Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ Char -> Bool
isDigit Char
first Bool -> Bool -> Bool
&& Char
'.' Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
str
    m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
id Integer
2079 String
"(( )) doesn't support decimals. Use bc or awk."
checkForDecimals Parameters
_ Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkDivBeforeMult :: Bool
prop_checkDivBeforeMult = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkDivBeforeMult String
"echo $((c/n*100))"
prop_checkDivBeforeMult2 :: Bool
prop_checkDivBeforeMult2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkDivBeforeMult String
"echo $((c*100/n))"
prop_checkDivBeforeMult3 :: Bool
prop_checkDivBeforeMult3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
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 Token -> Token -> Bool
forall a. Eq a => a -> a -> Bool
/= Token
y =
        Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
info Id
id Integer
2017 String
"Increase precision by replacing a/b*c with a*c/b."
checkDivBeforeMult Parameters
_ Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

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

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

prop_checkArithmeticBadOctal1 :: Bool
prop_checkArithmeticBadOctal1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkArithmeticBadOctal String
"(( 0192 ))"
prop_checkArithmeticBadOctal2 :: Bool
prop_checkArithmeticBadOctal2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkArithmeticBadOctal String
"(( 0x192 ))"
prop_checkArithmeticBadOctal3 :: Bool
prop_checkArithmeticBadOctal3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkArithmeticBadOctal String
"(( 1 ^ 0777 ))"
checkArithmeticBadOctal :: p -> Token -> m ()
checkArithmeticBadOctal p
_ t :: Token
t@(TA_Expansion Id
id [Token]
_) = Maybe (m ()) -> m ()
forall (m :: * -> *). Monad m => Maybe (m ()) -> m ()
potentially (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
    String
str <- Token -> Maybe String
getLiteralString Token
t
    Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String
str String -> Regex -> Bool
`matches` Regex
octalRE
    m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
id Integer
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
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkComparisonAgainstGlob :: Bool
prop_checkComparisonAgainstGlob = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkComparisonAgainstGlob String
"[[ $cow == $bar ]]"
prop_checkComparisonAgainstGlob2 :: Bool
prop_checkComparisonAgainstGlob2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkComparisonAgainstGlob String
"[[ $cow == \"$bar\" ]]"
prop_checkComparisonAgainstGlob3 :: Bool
prop_checkComparisonAgainstGlob3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkComparisonAgainstGlob String
"[ $cow = *foo* ]"
prop_checkComparisonAgainstGlob4 :: Bool
prop_checkComparisonAgainstGlob4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkComparisonAgainstGlob String
"[ $cow = foo ]"
prop_checkComparisonAgainstGlob5 :: Bool
prop_checkComparisonAgainstGlob5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkComparisonAgainstGlob String
"[[ $cow != $bar ]]"
prop_checkComparisonAgainstGlob6 :: Bool
prop_checkComparisonAgainstGlob6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
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 String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"=", String
"==", String
"!="] =
        Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2053 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
"Quote the right-hand side of " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
op String -> String -> String
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 String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"=", String
"==", String
"!="] Bool -> Bool -> Bool
&& Token -> Bool
isGlob Token
word =
    Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err (Token -> Id
getId Token
word) Integer
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
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkCommarrays1 :: Bool
prop_checkCommarrays1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkCommarrays String
"a=(1, 2)"
prop_checkCommarrays2 :: Bool
prop_checkCommarrays2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkCommarrays String
"a+=(1,2,3)"
prop_checkCommarrays3 :: Bool
prop_checkCommarrays3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkCommarrays String
"cow=(1 \"foo,bar\" 3)"
prop_checkCommarrays4 :: Bool
prop_checkCommarrays4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkCommarrays String
"cow=('one,' 'two')"
prop_checkCommarrays5 :: Bool
prop_checkCommarrays5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkCommarrays String
"a=([a]=b, [c]=d)"
prop_checkCommarrays6 :: Bool
prop_checkCommarrays6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkCommarrays String
"a=([a]=b,[c]=d,[e]=f)"
prop_checkCommarrays7 :: Bool
prop_checkCommarrays7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkCommarrays String
"a=(1,2)"
checkCommarrays :: p -> Token -> f ()
checkCommarrays p
_ (T_Array Id
id [Token]
l) =
    Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ((Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (String -> Bool
isCommaSeparated (String -> Bool) -> (Token -> String) -> Token -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> String
literal) [Token]
l) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
        Id -> Integer -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
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) = (Token -> String) -> [Token] -> String
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 = Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
elem Char
','
checkCommarrays p
_ Token
_ = () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkOrNeq1 :: Bool
prop_checkOrNeq1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkOrNeq String
"if [[ $lol -ne cow || $lol -ne foo ]]; then echo foo; fi"
prop_checkOrNeq2 :: Bool
prop_checkOrNeq2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkOrNeq String
"(( a!=lol || a!=foo ))"
prop_checkOrNeq3 :: Bool
prop_checkOrNeq3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkOrNeq String
"[ \"$a\" != lol || \"$a\" != foo ]"
prop_checkOrNeq4 :: Bool
prop_checkOrNeq4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkOrNeq String
"[ a != $cow || b != $foo ]"
prop_checkOrNeq5 :: Bool
prop_checkOrNeq5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkOrNeq String
"[[ $a != /home || $a != */public_html/* ]]"
prop_checkOrNeq6 :: Bool
prop_checkOrNeq6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkOrNeq String
"[ $a != a ] || [ $a != b ]"
prop_checkOrNeq7 :: Bool
prop_checkOrNeq7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkOrNeq String
"[ $a != a ] || [ $a != b ] || true"
prop_checkOrNeq8 :: Bool
prop_checkOrNeq8 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkOrNeq String
"[[ $a != x || $a != x ]]"
-- 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 String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
op2 Bool -> Bool -> Bool
&& (String
op1 String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"-ne" Bool -> Bool -> Bool
|| String
op1 String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"!=")) Bool -> Bool -> Bool
&& Token
lhs1 Token -> Token -> Bool
forall a. Eq a => a -> a -> Bool
== Token
lhs2 Bool -> Bool -> Bool
&& Token
rhs1 Token -> Token -> Bool
forall a. Eq a => a -> a -> Bool
/= Token
rhs2 Bool -> Bool -> Bool
&& Bool -> Bool
not ((Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
isGlob [Token
rhs1,Token
rhs2]) =
        Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2055 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
"You probably wanted " String -> String -> String
forall a. [a] -> [a] -> [a]
++ (if ConditionType
typ ConditionType -> ConditionType -> Bool
forall a. Eq a => a -> a -> Bool
== ConditionType
SingleBracket then String
"-a" else String
"&&") String -> String -> 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 Token -> Token -> Bool
forall a. Eq a => a -> a -> Bool
== Token
word2 =
        Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
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) = Maybe (m ()) -> m ()
forall (m :: * -> *). Monad m => Maybe (m ()) -> m ()
potentially (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
    (Token
lhs1, String
op1, Token
rhs1) <- Token -> Maybe (Token, String, Token)
forall (m :: * -> *).
MonadFail m =>
Token -> m (Token, String, Token)
getExpr Token
lhs
    (Token
lhs2, String
op2, Token
rhs2) <- Token -> Maybe (Token, String, Token)
forall (m :: * -> *).
MonadFail m =>
Token -> m (Token, String, Token)
getExpr Token
rhs
    Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String
op1 String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
op2 Bool -> Bool -> Bool
&& String
op1 String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"-ne", String
"!="]
    Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ Token
lhs1 Token -> Token -> Bool
forall a. Eq a => a -> a -> Bool
== Token
lhs2 Bool -> Bool -> Bool
&& Token
rhs1 Token -> Token -> Bool
forall a. Eq a => a -> a -> Bool
/= Token
rhs2
    Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> (Bool -> Bool) -> Bool -> Maybe ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ (Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
isGlob [Token
rhs1, Token
rhs2]
    m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2252 String
"You probably wanted && here, otherwise it's always true."
  where
    getExpr :: Token -> m (Token, String, Token)
getExpr Token
x =
        case Token
x of
            T_OrIf Id
_ Token
lhs Token
_ -> Token -> m (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 -> m (Token, String, Token)
getExpr Token
x
            T_Redirecting Id
_ [Token]
_ Token
c -> Token -> m (Token, String, Token)
getExpr Token
c
            T_Condition Id
_ ConditionType
_ Token
c -> Token -> m (Token, String, Token)
getExpr Token
c
            TC_Binary Id
_ ConditionType
_ String
op Token
lhs Token
rhs -> (Token, String, Token) -> m (Token, String, Token)
forall (m :: * -> *) a. Monad m => a -> m a
return (Token
lhs, String
op, Token
rhs)
            Token
_ -> String -> m (Token, String, Token)
forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
""

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


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

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

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


prop_checkTestRedirects1 :: Bool
prop_checkTestRedirects1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkTestRedirects String
"test 3 > 1"
prop_checkTestRedirects2 :: Bool
prop_checkTestRedirects2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkTestRedirects String
"test 3 \\> 1"
prop_checkTestRedirects3 :: Bool
prop_checkTestRedirects3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkTestRedirects String
"/usr/bin/test $var > $foo"
prop_checkTestRedirects4 :: Bool
prop_checkTestRedirects4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
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" =
    (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
check [Token]
redirs
  where
    check :: Token -> f ()
check Token
t =
        Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
suspicious Token
t) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
            Id -> Integer -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn (Token -> Id
getId Token
t) Integer
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 String -> String -> Bool
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
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkPS11 :: Bool
prop_checkPS11 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkPS1Assignments String
"PS1='\\033[1;35m\\$ '"
prop_checkPS11a :: Bool
prop_checkPS11a= (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkPS1Assignments String
"export PS1='\\033[1;35m\\$ '"
prop_checkPSf2 :: Bool
prop_checkPSf2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkPS1Assignments String
"PS1='\\h \\e[0m\\$ '"
prop_checkPS13 :: Bool
prop_checkPS13 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkPS1Assignments String
"PS1=$'\\x1b[c '"
prop_checkPS14 :: Bool
prop_checkPS14 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkPS1Assignments String
"PS1=$'\\e[3m; '"
prop_checkPS14a :: Bool
prop_checkPS14a= (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkPS1Assignments String
"export PS1=$'\\e[3m; '"
prop_checkPS15 :: Bool
prop_checkPS15 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkPS1Assignments String
"PS1='\\[\\033[1;35m\\]\\$ '"
prop_checkPS16 :: Bool
prop_checkPS16 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkPS1Assignments String
"PS1='\\[\\e1m\\e[1m\\]\\$ '"
prop_checkPS17 :: Bool
prop_checkPS17 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkPS1Assignments String
"PS1='e033x1B'"
prop_checkPS18 :: Bool
prop_checkPS18 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkPS1Assignments String
"PS1='\\[\\e\\]'"
checkPS1Assignments :: p -> Token -> f ()
checkPS1Assignments p
_ (T_Assignment Id
_ AssignmentMode
_ String
"PS1" [Token]
_ Token
word) = Token -> f ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
warnFor Token
word
  where
    warnFor :: Token -> f ()
warnFor Token
word =
        let contents :: String
contents = [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
word in
            Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String -> Bool
containsUnescaped String
contents) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
                Id -> Integer -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
info (Token -> Id
getId Token
word) Integer
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
           Maybe [String] -> Bool
forall a. Maybe a -> Bool
isJust (Maybe [String] -> Bool) -> Maybe [String] -> Bool
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
_ = () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkBackticks1 :: Bool
prop_checkBackticks1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkBackticks String
"echo `foo`"
prop_checkBackticks2 :: Bool
prop_checkBackticks2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkBackticks String
"echo $(foo)"
prop_checkBackticks3 :: Bool
prop_checkBackticks3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
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 ([Token] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Token]
list) =
    TokenComment -> m ()
forall a (m :: * -> *). (NFData a, MonadWriter [a] m) => a -> m ()
addComment (TokenComment -> m ()) -> TokenComment -> m ()
forall a b. (a -> b) -> a -> b
$
        Severity -> Id -> Integer -> String -> Fix -> TokenComment
makeCommentWithFix Severity
StyleC Id
id Integer
2006  String
"Use $(...) notation instead of legacy backticked `...`."
            ([Replacement] -> Fix
fixWith [Id -> Parameters -> Integer -> String -> Replacement
replaceStart Id
id Parameters
params Integer
1 String
"$(", Id -> Parameters -> Integer -> String -> Replacement
replaceEnd Id
id Parameters
params Integer
1 String
")"])
checkBackticks Parameters
_ Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkIndirectExpansion1 :: Bool
prop_checkIndirectExpansion1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkIndirectExpansion String
"${foo$n}"
prop_checkIndirectExpansion2 :: Bool
prop_checkIndirectExpansion2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkIndirectExpansion String
"${foo//$n/lol}"
prop_checkIndirectExpansion3 :: Bool
prop_checkIndirectExpansion3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkIndirectExpansion String
"${$#}"
prop_checkIndirectExpansion4 :: Bool
prop_checkIndirectExpansion4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkIndirectExpansion String
"${var${n}_$((i%2))}"
prop_checkIndirectExpansion5 :: Bool
prop_checkIndirectExpansion5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkIndirectExpansion String
"${bar}"
checkIndirectExpansion :: p -> Token -> f ()
checkIndirectExpansion p
_ (T_DollarBraced Id
i Bool
_ (T_NormalWord Id
_ [Token]
contents)) =
    Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ([Token] -> Bool
isIndirection [Token]
contents) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
        Id -> Integer -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
i Integer
2082 String
"To expand via indirection, use arrays, ${!name} or (for sh only) eval."
  where
    isIndirection :: [Token] -> Bool
isIndirection [Token]
vars =
        let list :: [Bool]
list = (Token -> Maybe Bool) -> [Token] -> [Bool]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe Token -> Maybe Bool
isIndirectionPart [Token]
vars in
            Bool -> Bool
not ([Bool] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Bool]
list) Bool -> Bool -> 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 Id
_ [Token]
_ ->  Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
True
                  T_Backticked Id
_ [Token]
_ ->       Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
True
                  T_DollarBraced Id
_ Bool
_ Token
_ ->     Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
True
                  T_DollarArithmetic Id
_ Token
_ -> Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
True
                  T_Literal Id
_ String
s -> if (Char -> Bool) -> String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isVariableChar String
s
                                    then Maybe Bool
forall a. Maybe a
Nothing
                                    else Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
False
                  Token
_ -> Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
False

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

prop_checkInexplicablyUnquoted1 :: Bool
prop_checkInexplicablyUnquoted1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkInexplicablyUnquoted String
"echo 'var='value';'"
prop_checkInexplicablyUnquoted2 :: Bool
prop_checkInexplicablyUnquoted2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkInexplicablyUnquoted String
"'foo'*"
prop_checkInexplicablyUnquoted3 :: Bool
prop_checkInexplicablyUnquoted3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkInexplicablyUnquoted String
"wget --user-agent='something'"
prop_checkInexplicablyUnquoted4 :: Bool
prop_checkInexplicablyUnquoted4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkInexplicablyUnquoted String
"echo \"VALUES (\"id\")\""
prop_checkInexplicablyUnquoted5 :: Bool
prop_checkInexplicablyUnquoted5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkInexplicablyUnquoted String
"\"$dir\"/\"$file\""
prop_checkInexplicablyUnquoted6 :: Bool
prop_checkInexplicablyUnquoted6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkInexplicablyUnquoted String
"\"$dir\"some_stuff\"$file\""
prop_checkInexplicablyUnquoted7 :: Bool
prop_checkInexplicablyUnquoted7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkInexplicablyUnquoted String
"${dir/\"foo\"/\"bar\"}"
prop_checkInexplicablyUnquoted8 :: Bool
prop_checkInexplicablyUnquoted8 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkInexplicablyUnquoted String
"  'foo'\\\n  'bar'"
prop_checkInexplicablyUnquoted9 :: Bool
prop_checkInexplicablyUnquoted9 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkInexplicablyUnquoted String
"[[ $x =~ \"foo\"(\"bar\"|\"baz\") ]]"
checkInexplicablyUnquoted :: Parameters -> Token -> m ()
checkInexplicablyUnquoted Parameters
params (T_NormalWord Id
id [Token]
tokens) = ([Token] -> m ()) -> [[Token]] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ [Token] -> m ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
[Token] -> f ()
check ([Token] -> [[Token]]
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 (String -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
str) Bool -> Bool -> Bool
&& (Char -> Bool) -> String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isAlphaNum String
str =
        Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
info Id
id Integer
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]
_ -> Id -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Id -> m ()
warnAboutExpansion Id
id
            T_DollarBraced Id
id Bool
_ Token
_ -> Id -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Id -> m ()
warnAboutExpansion Id
id
            T_Literal Id
id String
s ->
                Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless ([Token] -> Bool
quotesSingleThing [Token]
a Bool -> Bool -> Bool
&& [Token] -> Bool
quotesSingleThing [Token]
b Bool -> Bool -> Bool
|| [Token] -> Bool
isRegex (Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) Token
trapped)) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
                    Id -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Id -> m ()
warnAboutLiteral Id
id
            Token
_ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

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

    -- Regexes for [[ .. =~ re ]] are parsed with metacharacters like ()| as unquoted
    -- literals, so avoid overtriggering on these.
    isRegex :: [Token] -> Bool
isRegex [Token]
t =
        case [Token]
t of
            (T_Redirecting {} : [Token]
_) -> Bool
False
            (Token
a:(TC_Binary Id
_ ConditionType
_ String
"=~" Token
lhs Token
rhs):[Token]
rest) -> Token -> Id
getId Token
a Id -> Id -> Bool
forall a. Eq a => a -> a -> Bool
== Token -> Id
getId Token
rhs
            Token
_:[Token]
rest -> [Token] -> Bool
isRegex [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 =
        Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2027 String
"The surrounding quotes actually unquote this. Remove or escape them."
    warnAboutLiteral :: Id -> m ()
warnAboutLiteral Id
id =
        Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2140 String
"Word is of the form \"A\"B\"C\" (B indicated). Did you mean \"ABC\" or \"A\\\"B\\\"C\"?"
checkInexplicablyUnquoted Parameters
_ Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

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

prop_checkLonelyDotDash1 :: Bool
prop_checkLonelyDotDash1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkLonelyDotDash String
"./ file"
prop_checkLonelyDotDash2 :: Bool
prop_checkLonelyDotDash2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
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
"./" =
        Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
id Integer
2083 String
"Don't add spaces after the slash in './file'."
checkLonelyDotDash p
_ Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkSpuriousExec1 :: Bool
prop_checkSpuriousExec1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkSpuriousExec String
"exec foo; true"
prop_checkSpuriousExec2 :: Bool
prop_checkSpuriousExec2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkSpuriousExec String
"if a; then exec b; exec c; fi"
prop_checkSpuriousExec3 :: Bool
prop_checkSpuriousExec3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkSpuriousExec String
"echo cow; exec foo"
prop_checkSpuriousExec4 :: Bool
prop_checkSpuriousExec4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkSpuriousExec String
"if a; then exec b; fi"
prop_checkSpuriousExec5 :: Bool
prop_checkSpuriousExec5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkSpuriousExec String
"exec > file; cmd"
prop_checkSpuriousExec6 :: Bool
prop_checkSpuriousExec6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkSpuriousExec String
"exec foo > file; cmd"
prop_checkSpuriousExec7 :: Bool
prop_checkSpuriousExec7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkSpuriousExec String
"exec file; echo failed; exit 3"
prop_checkSpuriousExec8 :: Bool
prop_checkSpuriousExec8 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkSpuriousExec String
"exec {origout}>&1- >tmp.log 2>&1; bar"
prop_checkSpuriousExec9 :: Bool
prop_checkSpuriousExec9 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkSpuriousExec String
"for file in rc.d/*; do exec \"$file\"; done"
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
        (([Token], [Token]) -> m ()) -> [([Token], [Token])] -> m ()
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
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

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

    doList :: [Token] -> Bool -> m ()
doList = [Token] -> Bool -> m ()
doList' ([Token] -> Bool -> m ())
-> ([Token] -> [Token]) -> [Token] -> Bool -> m ()
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' t :: [Token]
t@(Token
current:Token
following:[Token]
_) Bool
False = do
        Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
commentIfExec Token
current
        [Token] -> Bool -> m ()
doList ([Token] -> [Token]
forall a. [a] -> [a]
tail [Token]
t) Bool
False
    doList' (Token
current:[Token]
tail) Bool
True = do
        Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
commentIfExec Token
current
        [Token] -> Bool -> m ()
doList [Token]
tail Bool
True
    doList' [Token]
_ Bool
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

    commentIfExec :: Token -> m ()
commentIfExec (T_Pipeline Id
id [Token]
_ [Token]
list) =
      (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
commentIfExec ([Token] -> m ()) -> [Token] -> m ()
forall a b. (a -> b) -> a -> b
$ Int -> [Token] -> [Token]
forall a. Int -> [a] -> [a]
take Int
1 [Token]
list
    commentIfExec (T_Redirecting Id
_ [Token]
_ f :: Token
f@(
      T_SimpleCommand Id
id [Token]
_ (Token
cmd:Token
arg:[Token]
_))) =
        Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token
f Token -> String -> Bool
`isUnqualifiedCommand` String
"exec") (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
          Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2093
            String
"Remove \"exec \" if script should continue after this command."
    commentIfExec Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkSpuriousExpansion1 :: Bool
prop_checkSpuriousExpansion1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkSpuriousExpansion String
"if $(true); then true; fi"
prop_checkSpuriousExpansion2 :: Bool
prop_checkSpuriousExpansion2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkSpuriousExpansion String
"while \"$(cmd)\"; do :; done"
prop_checkSpuriousExpansion3 :: Bool
prop_checkSpuriousExpansion3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkSpuriousExpansion String
"$(cmd) --flag1 --flag2"
prop_checkSpuriousExpansion4 :: Bool
prop_checkSpuriousExpansion4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkSpuriousExpansion String
"$((i++))"
checkSpuriousExpansion :: p -> Token -> m ()
checkSpuriousExpansion p
_ (T_SimpleCommand Id
_ [Token]
_ [T_NormalWord Id
_ [Token
word]]) = Token -> m ()
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]
_ ->
            Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2091 String
"Remove surrounding $() to avoid executing output."
        T_Backticked Id
id [Token]
_ ->
            Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2092 String
"Remove backticks to avoid executing output."
        T_DollarArithmetic Id
id Token
_ ->
            Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
id Integer
2084 String
"Remove '$' or use '_=$((expr))' to avoid executing output."
        T_DoubleQuoted Id
id [Token
subword] -> Token -> m ()
check Token
subword
        Token
_ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
checkSpuriousExpansion p
_ Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()


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

prop_checkSshHereDoc1 :: Bool
prop_checkSshHereDoc1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkSshHereDoc String
"ssh host << foo\necho $PATH\nfoo"
prop_checkSshHereDoc2 :: Bool
prop_checkSshHereDoc2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
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" =
    (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
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 ((Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Token -> Bool
isConstant [Token]
tokens) =
        Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2087 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
"Quote '" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
token String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"' to make here document expansions happen on the server side rather than on the client."
    checkHereDoc Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
checkSshHereDoc p
_ Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

--- Subshell detection
prop_subshellAssignmentCheck :: Bool
prop_subshellAssignmentCheck = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree     Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
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    Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"( A=foo; ); rm $A"
prop_subshellAssignmentCheck4 :: Bool
prop_subshellAssignmentCheck4 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"( A=foo; rm $A; )"
prop_subshellAssignmentCheck5 :: Bool
prop_subshellAssignmentCheck5 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
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    Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"( export lol=$(ls); ); echo $lol;"
prop_subshellAssignmentCheck6a :: Bool
prop_subshellAssignmentCheck6a= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"( typeset -a lol=a; ); echo $lol;"
prop_subshellAssignmentCheck7 :: Bool
prop_subshellAssignmentCheck7 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
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    Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"n=3 & echo $((n++))"
prop_subshellAssignmentCheck9 :: Bool
prop_subshellAssignmentCheck9 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"read n & n=foo$n"
prop_subshellAssignmentCheck10 :: Bool
prop_subshellAssignmentCheck10 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"(( n <<= 3 )) & (( n |= 4 )) &"
prop_subshellAssignmentCheck11 :: Bool
prop_subshellAssignmentCheck11 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"(set -e); echo $@"
prop_subshellAssignmentCheck17 :: Bool
prop_subshellAssignmentCheck17 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"( exec {n}>&2; ); echo $n"
prop_subshellAssignmentCheck19 :: Bool
prop_subshellAssignmentCheck19 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
subshellAssignmentCheck String
"@test 'foo' { a=1; }\n@test 'bar' { echo $a; }\n"
subshellAssignmentCheck :: Parameters -> p -> [TokenComment]
subshellAssignmentCheck Parameters
params p
t =
    let flow :: [StackData]
flow = Parameters -> [StackData]
variableFlow Parameters
params
        check :: WriterT [TokenComment] Identity ()
check = [StackData]
-> [(String, [(Token, Token, String, DataType)])]
-> Map String VariableState
-> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
[StackData]
-> [(String, [(Token, Token, String, DataType)])]
-> Map String VariableState
-> m ()
findSubshelled [StackData]
flow [(String
"oops",[])] Map String VariableState
forall k a. Map k a
Map.empty
    in WriterT [TokenComment] Identity () -> [TokenComment]
forall w a. Writer w a -> w
execWriter WriterT [TokenComment] Identity ()
check


findSubshelled :: [StackData]
-> [(String, [(Token, Token, String, DataType)])]
-> Map String VariableState
-> m ()
findSubshelled [] [(String, [(Token, Token, String, DataType)])]
_ Map String VariableState
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
findSubshelled (Assignment x :: (Token, Token, String, DataType)
x@(Token
_, Token
_, String
str, DataType
_):[StackData]
rest) ((String
reason,[(Token, Token, String, DataType)]
scope):[(String, [(Token, Token, String, DataType)])]
lol) Map String VariableState
deadVars =
    [StackData]
-> [(String, [(Token, Token, String, DataType)])]
-> Map String VariableState
-> m ()
findSubshelled [StackData]
rest ((String
reason, (Token, Token, String, DataType)
x(Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
-> [(Token, Token, String, DataType)]
forall a. a -> [a] -> [a]
:[(Token, Token, String, DataType)]
scope)(String, [(Token, Token, String, DataType)])
-> [(String, [(Token, Token, String, DataType)])]
-> [(String, [(Token, Token, String, DataType)])]
forall a. a -> [a] -> [a]
:[(String, [(Token, Token, String, DataType)])]
lol) (Map String VariableState -> m ())
-> Map String VariableState -> m ()
forall a b. (a -> b) -> a -> b
$ String
-> VariableState
-> Map String VariableState
-> Map String VariableState
forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert String
str VariableState
Alive Map String VariableState
deadVars
findSubshelled (Reference (Token
_, Token
readToken, String
str):[StackData]
rest) [(String, [(Token, Token, String, DataType)])]
scopes Map String VariableState
deadVars = do
    Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (String -> Bool
shouldIgnore String
str) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ case VariableState
-> String -> Map String VariableState -> VariableState
forall k a. Ord k => a -> k -> Map k a -> a
Map.findWithDefault VariableState
Alive String
str Map String VariableState
deadVars of
        VariableState
Alive -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
        Dead Token
writeToken String
reason -> do
                    Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
info (Token -> Id
getId Token
writeToken) Integer
2030 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
"Modification of " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
str String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" is local (to subshell caused by "String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
reason String -> String -> String
forall a. [a] -> [a] -> [a]
++String
")."
                    Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
info (Token -> Id
getId Token
readToken) Integer
2031 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
str String -> String -> String
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 String -> [String] -> Bool
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,[])(String, [(Token, Token, String, DataType)])
-> [(String, [(Token, Token, String, DataType)])]
-> [(String, [(Token, Token, String, DataType)])]
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 (Map String VariableState -> m ())
-> Map String VariableState -> m ()
forall a b. (a -> b) -> a -> b
$
        (Map String VariableState
 -> (Token, Token, String, DataType) -> Map String VariableState)
-> Map String VariableState
-> [(Token, Token, String, DataType)]
-> Map String VariableState
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl (\Map String VariableState
m (Token
_, Token
token, String
var, DataType
_) ->
            String
-> VariableState
-> Map String VariableState
-> Map String VariableState
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 :: (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 = State t [v] -> t -> [v]
forall s a. State s a -> s -> a
evalState (
    ([v] -> StackData -> State t [v])
-> [v] -> [StackData] -> State t [v]
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;  [v] -> State t [v]
forall (m :: * -> *) a. Monad m => a -> m a
return ([v] -> State t [v]) -> [v] -> State t [v]
forall a b. (a -> b) -> a -> b
$ [v]
l [v] -> [v] -> [v]
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
_ = [v] -> State t [v]
forall (m :: * -> *) a. Monad m => a -> m a
return []

---- Check whether variables could have spaces/globs
prop_checkSpacefulness1 :: Bool
prop_checkSpacefulness1 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"a='cow moo'; echo $a"
prop_checkSpacefulness2 :: Bool
prop_checkSpacefulness2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"a='cow moo'; [[ $a ]]"
prop_checkSpacefulness3 :: Bool
prop_checkSpacefulness3 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"a='cow*.mp3'; echo \"$a\""
prop_checkSpacefulness4 :: Bool
prop_checkSpacefulness4 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"for f in *.mp3; do echo $f; done"
prop_checkSpacefulness4a :: Bool
prop_checkSpacefulness4a= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"foo=3; foo=$(echo $foo)"
prop_checkSpacefulness5 :: Bool
prop_checkSpacefulness5 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"a='*'; b=$a; c=lol${b//foo/bar}; echo $c"
prop_checkSpacefulness6 :: Bool
prop_checkSpacefulness6 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"a=foo$(lol); echo $a"
prop_checkSpacefulness7 :: Bool
prop_checkSpacefulness7 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"a=foo\\ bar; rm $a"
prop_checkSpacefulness8 :: Bool
prop_checkSpacefulness8 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"a=foo\\ bar; a=foo; rm $a"
prop_checkSpacefulness10 :: Bool
prop_checkSpacefulness10= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"rm $1"
prop_checkSpacefulness11 :: Bool
prop_checkSpacefulness11= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"rm ${10//foo/bar}"
prop_checkSpacefulness12 :: Bool
prop_checkSpacefulness12= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"(( $1 + 3 ))"
prop_checkSpacefulness13 :: Bool
prop_checkSpacefulness13= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"if [[ $2 -gt 14 ]]; then true; fi"
prop_checkSpacefulness14 :: Bool
prop_checkSpacefulness14= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"foo=$3 env"
prop_checkSpacefulness15 :: Bool
prop_checkSpacefulness15= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"local foo=$1"
prop_checkSpacefulness16 :: Bool
prop_checkSpacefulness16= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"declare foo=$1"
prop_checkSpacefulness17 :: Bool
prop_checkSpacefulness17= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"echo foo=$1"
prop_checkSpacefulness18 :: Bool
prop_checkSpacefulness18= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"$1 --flags"
prop_checkSpacefulness19 :: Bool
prop_checkSpacefulness19= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"echo $PWD"
prop_checkSpacefulness20 :: Bool
prop_checkSpacefulness20= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"n+='foo bar'"
prop_checkSpacefulness21 :: Bool
prop_checkSpacefulness21= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"select foo in $bar; do true; done"
prop_checkSpacefulness22 :: Bool
prop_checkSpacefulness22= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"echo $\"$1\""
prop_checkSpacefulness23 :: Bool
prop_checkSpacefulness23= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"a=(1); echo ${a[@]}"
prop_checkSpacefulness24 :: Bool
prop_checkSpacefulness24= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"a='a    b'; cat <<< $a"
prop_checkSpacefulness25 :: Bool
prop_checkSpacefulness25= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"a='s/[0-9]//g'; sed $a"
prop_checkSpacefulness26 :: Bool
prop_checkSpacefulness26= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"a='foo bar'; echo {1,2,$a}"
prop_checkSpacefulness27 :: Bool
prop_checkSpacefulness27= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"echo ${a:+'foo'}"
prop_checkSpacefulness28 :: Bool
prop_checkSpacefulness28= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"exec {n}>&1; echo $n"
prop_checkSpacefulness29 :: Bool
prop_checkSpacefulness29= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"n=$(stuff); exec {n}>&-;"
prop_checkSpacefulness30 :: Bool
prop_checkSpacefulness30= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"file='foo bar'; echo foo > $file;"
prop_checkSpacefulness31 :: Bool
prop_checkSpacefulness31= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"echo \"`echo \\\"$1\\\"`\""
prop_checkSpacefulness32 :: Bool
prop_checkSpacefulness32= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"var=$1; [ -v var ]"
prop_checkSpacefulness33 :: Bool
prop_checkSpacefulness33= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"for file; do echo $file; done"
prop_checkSpacefulness34 :: Bool
prop_checkSpacefulness34= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"declare foo$n=$1"
prop_checkSpacefulness35 :: Bool
prop_checkSpacefulness35= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"echo ${1+\"$1\"}"
prop_checkSpacefulness36 :: Bool
prop_checkSpacefulness36= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"arg=$#; echo $arg"
prop_checkSpacefulness37 :: Bool
prop_checkSpacefulness37= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness String
"@test 'status' {\n [ $status -eq 0 ]\n}"
prop_checkSpacefulness37v :: Bool
prop_checkSpacefulness37v = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkVerboseSpacefulness String
"@test 'status' {\n [ $status -eq 0 ]\n}"

-- This is slightly awkward because we want to support structured
-- optional checks based on nearly the same logic
checkSpacefulness :: Parameters -> Token -> [TokenComment]
checkSpacefulness Parameters
params = (Bool -> Token -> String -> WriterT [TokenComment] Identity ())
-> Parameters -> Token -> [TokenComment]
checkSpacefulness' Bool -> Token -> String -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
Bool -> Token -> p -> f ()
onFind Parameters
params
  where
    emit :: a -> m ()
emit a
x = [a] -> m ()
forall w (m :: * -> *). MonadWriter w m => w -> m ()
tell [a
x]
    onFind :: Bool -> Token -> p -> f ()
onFind Bool
spaces Token
token p
_ =
        Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when Bool
spaces (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
            if Map Id Token -> Token -> Bool
isDefaultAssignment (Parameters -> Map Id Token
parentMap Parameters
params) Token
token
            then
                TokenComment -> f ()
forall a (m :: * -> *). MonadWriter [a] m => a -> m ()
emit (TokenComment -> f ()) -> TokenComment -> f ()
forall a b. (a -> b) -> a -> b
$ Severity -> Id -> Integer -> String -> TokenComment
makeComment Severity
InfoC (Token -> Id
getId Token
token) Integer
2223
                         String
"This default assignment may cause DoS due to globbing. Quote it."
            else
                TokenComment -> f ()
forall a (m :: * -> *). MonadWriter [a] m => a -> m ()
emit (TokenComment -> f ()) -> TokenComment -> f ()
forall a b. (a -> b) -> a -> b
$ Severity -> Id -> Integer -> String -> Fix -> TokenComment
makeCommentWithFix Severity
InfoC (Token -> Id
getId Token
token) Integer
2086
                         String
"Double quote to prevent globbing and word splitting."
                         (Parameters -> Token -> Fix
addDoubleQuotesAround Parameters
params Token
token)

    isDefaultAssignment :: Map Id Token -> Token -> Bool
isDefaultAssignment Map Id Token
parents Token
token =
        let modifier :: String
modifier = String -> String
getBracedModifier (String -> String) -> String -> String
forall a b. (a -> b) -> a -> b
$ Token -> String
bracedString Token
token in
            (String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (String -> String -> Bool
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


prop_checkSpacefulness4v :: Bool
prop_checkSpacefulness4v= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkVerboseSpacefulness String
"foo=3; foo=$(echo $foo)"
prop_checkSpacefulness8v :: Bool
prop_checkSpacefulness8v= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkVerboseSpacefulness String
"a=foo\\ bar; a=foo; rm $a"
prop_checkSpacefulness28v :: Bool
prop_checkSpacefulness28v = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkVerboseSpacefulness String
"exec {n}>&1; echo $n"
prop_checkSpacefulness36v :: Bool
prop_checkSpacefulness36v = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkVerboseSpacefulness String
"arg=$#; echo $arg"
checkVerboseSpacefulness :: Parameters -> Token -> [TokenComment]
checkVerboseSpacefulness Parameters
params = (Bool -> Token -> String -> WriterT [TokenComment] Identity ())
-> Parameters -> Token -> [TokenComment]
checkSpacefulness' Bool -> Token -> String -> WriterT [TokenComment] Identity ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
Bool -> Token -> String -> f ()
onFind Parameters
params
  where
    onFind :: Bool -> Token -> String -> f ()
onFind Bool
spaces Token
token String
name =
        Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Bool -> Bool
not Bool
spaces Bool -> Bool -> Bool
&& String
name String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [String]
specialVariablesWithoutSpaces) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
            [TokenComment] -> f ()
forall w (m :: * -> *). MonadWriter w m => w -> m ()
tell [Severity -> Id -> Integer -> String -> Fix -> TokenComment
makeCommentWithFix Severity
StyleC (Token -> Id
getId Token
token) Integer
2248
                    String
"Prefer double quoting even when variables don't contain special characters."
                    (Parameters -> Token -> Fix
addDoubleQuotesAround Parameters
params Token
token)]

addDoubleQuotesAround :: Parameters -> Token -> Fix
addDoubleQuotesAround Parameters
params Token
token = (Id -> Parameters -> String -> Fix
surroundWidth (Token -> Id
getId Token
token) Parameters
params String
"\"")
checkSpacefulness'
    :: (Bool -> Token -> String -> Writer [TokenComment] ()) ->
            Parameters -> Token -> [TokenComment]
checkSpacefulness' :: (Bool -> Token -> String -> WriterT [TokenComment] Identity ())
-> Parameters -> Token -> [TokenComment]
checkSpacefulness' Bool -> Token -> String -> WriterT [TokenComment] Identity ()
onFind Parameters
params Token
t =
    (Token
 -> Token -> String -> State (Map String Bool) [TokenComment])
-> (Token
    -> Token
    -> String
    -> DataType
    -> State (Map String Bool) [TokenComment])
-> Map String Bool
-> [StackData]
-> [TokenComment]
forall t v.
(Token -> Token -> String -> State t [v])
-> (Token -> Token -> String -> DataType -> State t [v])
-> t
-> [StackData]
-> [v]
doVariableFlowAnalysis Token -> Token -> String -> State (Map String Bool) [TokenComment]
forall (m :: * -> *) p.
MonadState (Map String Bool) m =>
p -> Token -> String -> m [TokenComment]
readF Token
-> Token
-> String
-> DataType
-> State (Map String Bool) [TokenComment]
forall (m :: * -> *) p p a.
MonadState (Map String Bool) m =>
p -> p -> String -> DataType -> m [a]
writeF ([(String, Bool)] -> Map String Bool
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList [(String, Bool)]
defaults) (Parameters -> [StackData]
variableFlow Parameters
params)
  where
    defaults :: [(String, Bool)]
defaults = [String] -> [Bool] -> [(String, Bool)]
forall a b. [a] -> [b] -> [(a, b)]
zip [String]
variablesWithoutSpaces (Bool -> [Bool]
forall a. a -> [a]
repeat Bool
False)

    hasSpaces :: k -> m Bool
hasSpaces k
name = (Map k Bool -> Bool) -> m Bool
forall s (m :: * -> *) a. MonadState s m => (s -> a) -> m a
gets (Bool -> k -> Map k Bool -> Bool
forall k a. Ord k => a -> k -> Map k a -> a
Map.findWithDefault Bool
True k
name)

    setSpaces :: k -> a -> m ()
setSpaces k
name a
bool =
        (Map k a -> Map k a) -> m ()
forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify ((Map k a -> Map k a) -> m ()) -> (Map k a -> Map k a) -> m ()
forall a b. (a -> b) -> a -> b
$ k -> a -> Map k a -> Map k a
forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert k
name a
bool

    readF :: p -> Token -> String -> m [TokenComment]
readF p
_ Token
token String
name = do
        Bool
spaces <- String -> m Bool
forall k (m :: * -> *).
(MonadState (Map k Bool) m, Ord k) =>
k -> m Bool
hasSpaces String
name
        let needsQuoting :: Bool
needsQuoting =
                  Token -> Bool
isExpansion Token
token
                  Bool -> Bool -> Bool
&& 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 (Map Id Token -> Token -> Bool
isQuoteFree 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)

        [TokenComment] -> m [TokenComment]
forall (m :: * -> *) a. Monad m => a -> m a
return ([TokenComment] -> m [TokenComment])
-> (WriterT [TokenComment] Identity () -> [TokenComment])
-> WriterT [TokenComment] Identity ()
-> m [TokenComment]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. WriterT [TokenComment] Identity () -> [TokenComment]
forall w a. Writer w a -> w
execWriter (WriterT [TokenComment] Identity () -> m [TokenComment])
-> WriterT [TokenComment] Identity () -> m [TokenComment]
forall a b. (a -> b) -> a -> b
$ Bool
-> WriterT [TokenComment] Identity ()
-> WriterT [TokenComment] Identity ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when Bool
needsQuoting (WriterT [TokenComment] Identity ()
 -> WriterT [TokenComment] Identity ())
-> WriterT [TokenComment] Identity ()
-> WriterT [TokenComment] Identity ()
forall a b. (a -> b) -> a -> b
$ Bool -> Token -> String -> WriterT [TokenComment] Identity ()
onFind Bool
spaces Token
token String
name

      where
        emit :: a -> m ()
emit a
x = [a] -> m ()
forall w (m :: * -> *). MonadWriter w m => w -> m ()
tell [a
x]

    writeF :: p -> p -> String -> DataType -> m [a]
writeF p
_ p
_ String
name (DataString DataSource
SourceExternal) = String -> Bool -> m ()
forall k a (m :: * -> *).
(MonadState (Map k a) m, Ord k) =>
k -> a -> m ()
setSpaces String
name Bool
True m () -> m [a] -> m [a]
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> [a] -> m [a]
forall (m :: * -> *) a. Monad m => a -> m a
return []
    writeF p
_ p
_ String
name (DataString DataSource
SourceInteger) = String -> Bool -> m ()
forall k a (m :: * -> *).
(MonadState (Map k a) m, Ord k) =>
k -> a -> m ()
setSpaces String
name Bool
False m () -> m [a] -> m [a]
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> [a] -> m [a]
forall (m :: * -> *) a. Monad m => a -> m a
return []

    writeF p
_ p
_ String
name (DataString (SourceFrom [Token]
vals)) = do
        Map String Bool
map <- m (Map String Bool)
forall s (m :: * -> *). MonadState s m => m s
get
        String -> Bool -> m ()
forall k a (m :: * -> *).
(MonadState (Map k a) m, Ord k) =>
k -> a -> m ()
setSpaces String
name
            ((String -> Bool) -> [Token] -> Bool
isSpacefulWord (\String
x -> Bool -> String -> Map String Bool -> Bool
forall k a. Ord k => a -> k -> Map k a -> a
Map.findWithDefault Bool
True String
x Map String Bool
map) [Token]
vals)
        [a] -> m [a]
forall (m :: * -> *) a. Monad m => a -> m a
return []

    writeF p
_ p
_ String
_ DataType
_ = [a] -> m [a]
forall (m :: * -> *) a. Monad m => a -> m a
return []

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

    isExpansion :: Token -> Bool
isExpansion Token
t =
        case Token
t of
            (T_DollarBraced Id
_ Bool
_ Token
_ ) -> Bool
True
            Token
_ -> Bool
False

    isSpacefulWord :: (String -> Bool) -> [Token] -> Bool
    isSpacefulWord :: (String -> Bool) -> [Token] -> Bool
isSpacefulWord String -> Bool
f = (Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any ((String -> Bool) -> Token -> Bool
isSpaceful String -> Bool
f)
    isSpaceful :: (String -> Bool) -> Token -> Bool
    isSpaceful :: (String -> Bool) -> Token -> Bool
isSpaceful String -> Bool
spacefulF 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
          T_Literal Id
_ String
s      -> String
s String -> String -> Bool
forall (t :: * -> *) (t :: * -> *) a.
(Foldable t, Foldable t, Eq a) =>
t a -> t a -> Bool
`containsAny` String
globspace
          T_SingleQuoted Id
_ String
s -> String
s String -> String -> Bool
forall (t :: * -> *) (t :: * -> *) a.
(Foldable t, Foldable t, Eq a) =>
t a -> t a -> Bool
`containsAny` String
globspace
          T_DollarBraced Id
_ Bool
_ Token
_ -> String -> Bool
spacefulF (String -> Bool) -> String -> Bool
forall a b. (a -> b) -> a -> b
$ String -> String
getBracedReference (String -> String) -> String -> String
forall a b. (a -> b) -> a -> b
$ Token -> String
bracedString Token
x
          T_NormalWord Id
_ [Token]
w   -> (String -> Bool) -> [Token] -> Bool
isSpacefulWord String -> Bool
spacefulF [Token]
w
          T_DoubleQuoted Id
_ [Token]
w -> (String -> Bool) -> [Token] -> Bool
isSpacefulWord String -> Bool
spacefulF [Token]
w
          Token
_ -> Bool
False
      where
        globspace :: String
globspace = String
"*?[] \t\n"
        containsAny :: t a -> t a -> Bool
containsAny t a
s = (a -> Bool) -> t a -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (a -> t a -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` t a
s)

prop_CheckVariableBraces1 :: Bool
prop_CheckVariableBraces1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkVariableBraces String
"a='123'; echo $a"
prop_CheckVariableBraces2 :: Bool
prop_CheckVariableBraces2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkVariableBraces String
"a='123'; echo ${a}"
prop_CheckVariableBraces3 :: Bool
prop_CheckVariableBraces3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkVariableBraces String
"#shellcheck disable=SC2016\necho '$a'"
prop_CheckVariableBraces4 :: Bool
prop_CheckVariableBraces4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkVariableBraces String
"echo $* $1"
checkVariableBraces :: Parameters -> Token -> f ()
checkVariableBraces Parameters
params Token
t =
    case Token
t of
        T_DollarBraced Id
id Bool
False Token
_ ->
            Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (String
name String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
unbracedVariables) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
                Id -> Integer -> String -> Fix -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> Fix -> m ()
styleWithFix Id
id Integer
2250
                    String
"Prefer putting braces around variable references even when not strictly required."
                    (Token -> Fix
fixFor Token
t)

        Token
_ -> () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    name :: String
name = String -> String
getBracedReference (String -> String) -> String -> String
forall a b. (a -> b) -> a -> b
$ Token -> String
bracedString Token
t
    fixFor :: Token -> Fix
fixFor Token
token = [Replacement] -> Fix
fixWith [Id -> Parameters -> Integer -> String -> Replacement
replaceStart (Token -> Id
getId Token
token) Parameters
params Integer
1 String
"${"
                           ,Id -> Parameters -> Integer -> String -> Replacement
replaceEnd (Token -> Id
getId Token
token) Parameters
params Integer
0 String
"}"]

prop_checkQuotesInLiterals1 :: Bool
prop_checkQuotesInLiterals1 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkQuotesInLiterals String
"param='--foo=\"bar\"'; app $param"
prop_checkQuotesInLiterals1a :: Bool
prop_checkQuotesInLiterals1a= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkQuotesInLiterals String
"param=\"--foo='lolbar'\"; app $param"
prop_checkQuotesInLiterals2 :: Bool
prop_checkQuotesInLiterals2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkQuotesInLiterals String
"param='--foo=\"bar\"'; app \"$param\""
prop_checkQuotesInLiterals3 :: Bool
prop_checkQuotesInLiterals3 =(Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkQuotesInLiterals String
"param=('--foo='); app \"${param[@]}\""
prop_checkQuotesInLiterals4 :: Bool
prop_checkQuotesInLiterals4 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkQuotesInLiterals String
"param=\"--foo='lolbar'\"; eval app $param"
prop_checkQuotesInLiterals6 :: Bool
prop_checkQuotesInLiterals6 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkQuotesInLiterals String
"param='my\\ file'; rm $param"
prop_checkQuotesInLiterals8 :: Bool
prop_checkQuotesInLiterals8 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkQuotesInLiterals String
"param=\"/foo/'bar baz'/etc\"; rm ${#param}"
checkQuotesInLiterals :: Parameters -> p -> [TokenComment]
checkQuotesInLiterals Parameters
params p
t =
    (Token -> Token -> String -> State (Map String Id) [TokenComment])
-> (Token
    -> Token
    -> String
    -> DataType
    -> State (Map String Id) [TokenComment])
-> Map String Id
-> [StackData]
-> [TokenComment]
forall t v.
(Token -> Token -> String -> State t [v])
-> (Token -> Token -> String -> DataType -> State t [v])
-> t
-> [StackData]
-> [v]
doVariableFlowAnalysis Token -> Token -> String -> State (Map String Id) [TokenComment]
forall (m :: * -> *) k p.
(Ord k, MonadState (Map k Id) m) =>
p -> Token -> k -> m [TokenComment]
readF Token
-> Token
-> String
-> DataType
-> State (Map String Id) [TokenComment]
forall p p a.
p -> p -> String -> DataType -> StateT (Map String Id) Identity [a]
writeF Map String Id
forall k a. Map k a
Map.empty (Parameters -> [StackData]
variableFlow Parameters
params)
  where
    getQuotes :: k -> f (Maybe a)
getQuotes k
name = (Map k a -> Maybe a) -> f (Map k a) -> f (Maybe a)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (k -> Map k a -> Maybe a
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup k
name) f (Map k a)
forall s (m :: * -> *). MonadState s m => m s
get
    setQuotes :: k -> a -> m ()
setQuotes k
name a
ref = (Map k a -> Map k a) -> m ()
forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify ((Map k a -> Map k a) -> m ()) -> (Map k a -> Map k a) -> m ()
forall a b. (a -> b) -> a -> b
$ k -> a -> Map k a -> Map k a
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 = (Map String Id -> Map String Id)
-> StateT (Map String Id) Identity ()
forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify ((Map String Id -> Map String Id)
 -> StateT (Map String Id) Identity ())
-> (String -> Map String Id -> Map String Id)
-> String
-> StateT (Map String Id) Identity ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Map String Id -> Map String Id
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 <- StateT (Map String Id) Identity (Map String Id)
forall s (m :: * -> *). MonadState s m => m s
get
        let quotedVars :: Maybe Id
quotedVars = [Maybe Id] -> Maybe Id
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, MonadPlus m) =>
t (m a) -> m a
msum ([Maybe Id] -> Maybe Id) -> [Maybe Id] -> Maybe Id
forall a b. (a -> b) -> a -> b
$ (Token -> Maybe Id) -> [Token] -> [Maybe Id]
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 -> String -> Id -> StateT (Map String Id) Identity ()
forall k a (m :: * -> *).
(MonadState (Map k a) m, Ord k) =>
k -> a -> m ()
setQuotes String
name Id
x
        [a] -> StateT (Map String Id) Identity [a]
forall (m :: * -> *) a. Monad m => a -> m a
return []
    writeF p
_ p
_ String
_ DataType
_ = [a] -> StateT (Map String Id) Identity [a]
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
        String -> Map String Id -> Maybe Id
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup ([String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> (Token -> [String]) -> Token -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> [String]
oversimplify (Token -> String) -> Token -> String
forall a b. (a -> b) -> a -> b
$ Token
t) Map String Id
map
    forToken Map String Id
quoteMap (T_DoubleQuoted Id
id [Token]
tokens) =
        [Maybe Id] -> Maybe Id
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, MonadPlus m) =>
t (m a) -> m a
msum ([Maybe Id] -> Maybe Id) -> [Maybe Id] -> Maybe Id
forall a b. (a -> b) -> a -> b
$ (Token -> Maybe Id) -> [Token] -> [Maybe Id]
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) =
        [Maybe Id] -> Maybe Id
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, MonadPlus m) =>
t (m a) -> m a
msum ([Maybe Id] -> Maybe Id) -> [Maybe Id] -> Maybe Id
forall a b. (a -> b) -> a -> b
$ (Token -> Maybe Id) -> [Token] -> [Maybe Id]
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 ([String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
t)
        then Id -> Maybe Id
forall (m :: * -> *) a. Monad m => a -> m a
return (Id -> Maybe Id) -> Id -> Maybe Id
forall a b. (a -> b) -> a -> b
$ Token -> Id
getId Token
t
        else Maybe Id
forall a. Maybe a
Nothing

    squashesQuotes :: Token -> Bool
squashesQuotes Token
t =
        case Token
t of
            T_DollarBraced Id
id Bool
_ Token
_ -> String
"#" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` Token -> String
bracedString Token
t
            Token
_ -> Bool
False

    readF :: p -> Token -> k -> m [TokenComment]
readF p
_ Token
expr k
name = do
        Maybe Id
assignment <- k -> m (Maybe Id)
forall (f :: * -> *) k a.
(Ord k, MonadState (Map k a) f) =>
k -> f (Maybe a)
getQuotes k
name
        [TokenComment] -> m [TokenComment]
forall (m :: * -> *) a. Monad m => a -> m a
return
          (if Maybe Id -> Bool
forall a. Maybe a -> Bool
isJust Maybe Id
assignment
              Bool -> Bool -> Bool
&& Bool -> Bool
not (Map Id Token -> String -> Token -> Bool
isParamTo Map Id Token
parents String
"eval" Token
expr)
              Bool -> Bool -> Bool
&& Bool -> Bool
not (Map Id Token -> Token -> Bool
isQuoteFree Map Id Token
parents Token
expr)
              Bool -> Bool -> Bool
&& Bool -> Bool
not (Token -> Bool
squashesQuotes Token
expr)
              then [
                  Severity -> Id -> Integer -> String -> TokenComment
makeComment Severity
WarningC (Maybe Id -> Id
forall a. HasCallStack => Maybe a -> a
fromJust Maybe Id
assignment) Integer
2089 (String -> TokenComment) -> String -> TokenComment
forall a b. (a -> b) -> a -> b
$
                      String
"Quotes/backslashes will be treated literally. " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
suggestion,
                  Severity -> Id -> Integer -> String -> TokenComment
makeComment Severity
WarningC (Token -> Id
getId Token
expr) Integer
2090
                      String
"Quotes/backslashes in this variable will not be respected."
                ]
              else [])
    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]
forall t. t -> Token -> [TokenComment]
checkFunctionsUsedExternally String
"foo() { :; }; sudo foo"
prop_checkFunctionsUsedExternally2 :: Bool
prop_checkFunctionsUsedExternally2 =
  (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall t. t -> Token -> [TokenComment]
checkFunctionsUsedExternally String
"alias f='a'; xargs -0 f"
prop_checkFunctionsUsedExternally2b :: Bool
prop_checkFunctionsUsedExternally2b=
  (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall t. t -> Token -> [TokenComment]
checkFunctionsUsedExternally String
"alias f='a'; find . -type f"
prop_checkFunctionsUsedExternally2c :: Bool
prop_checkFunctionsUsedExternally2c=
  (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall t. t -> 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]
forall t. t -> Token -> [TokenComment]
checkFunctionsUsedExternally String
"f() { :; }; echo f"
prop_checkFunctionsUsedExternally4 :: Bool
prop_checkFunctionsUsedExternally4 =
  (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall t. t -> Token -> [TokenComment]
checkFunctionsUsedExternally String
"foo() { :; }; sudo \"foo\""
prop_checkFunctionsUsedExternally5 :: Bool
prop_checkFunctionsUsedExternally5 =
  (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall t. t -> Token -> [TokenComment]
checkFunctionsUsedExternally String
"foo() { :; }; ssh host foo"
prop_checkFunctionsUsedExternally6 :: Bool
prop_checkFunctionsUsedExternally6 =
  (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall t. t -> Token -> [TokenComment]
checkFunctionsUsedExternally String
"foo() { :; }; ssh host echo foo"
prop_checkFunctionsUsedExternally7 :: Bool
prop_checkFunctionsUsedExternally7 =
  (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall t. t -> Token -> [TokenComment]
checkFunctionsUsedExternally String
"install() { :; }; sudo apt-get install foo"
checkFunctionsUsedExternally :: p -> Token -> [TokenComment]
checkFunctionsUsedExternally p
params Token
t =
    (p -> Token -> WriterT [TokenComment] Identity ())
-> p -> Token -> [TokenComment]
forall w t.
Monoid w =>
(t -> Token -> WriterT w Identity ()) -> t -> Token -> w
runNodeAnalysis p -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkCommand p
params Token
t
  where
    checkCommand :: p -> Token -> m ()
checkCommand p
_ t :: Token
t@(T_SimpleCommand Id
_ [Token]
_ (Token
cmd:[Token]
args)) =
        case Token -> Maybe String
getCommandBasename Token
t of
            Just String
name -> do
                let argStrings :: [(String, Token)]
argStrings = (Token -> (String, Token)) -> [Token] -> [(String, Token)]
forall a b. (a -> b) -> [a] -> [b]
map (\Token
x -> (String -> Maybe String -> String
forall a. a -> Maybe a -> a
fromMaybe String
"" (Maybe String -> String) -> Maybe String -> String
forall a b. (a -> b) -> a -> b
$ Token -> Maybe String
getLiteralString Token
x, Token
x)) [Token]
args
                let candidates :: [(String, Token)]
candidates = String -> [(String, Token)] -> [(String, Token)]
forall b. String -> [(String, b)] -> [(String, b)]
getPotentialCommands String
name [(String, Token)]
argStrings
                ((String, Token) -> m ()) -> [(String, Token)] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (String -> (String, Token) -> m ()
forall (m :: * -> *) a.
MonadWriter [TokenComment] m =>
String -> (a, Token) -> m ()
checkArg String
name) [(String, Token)]
candidates
            Maybe String
_ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    checkCommand p
_ Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

    -- 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" -> Int -> [(String, b)] -> [(String, b)]
forall a. Int -> [a] -> [a]
take Int
1 ([(String, b)] -> [(String, b)]) -> [(String, b)] -> [(String, b)]
forall a b. (a -> b) -> a -> b
$ Int -> [(String, b)] -> [(String, b)]
forall a. Int -> [a] -> [a]
drop Int
1 ([(String, b)] -> [(String, b)]) -> [(String, b)] -> [(String, b)]
forall a b. (a -> b) -> a -> b
$ [(String, b)] -> [(String, b)]
forall b. [(String, b)] -> [(String, b)]
dropFlags [(String, b)]
argAndString
            String
"find" -> Int -> [(String, b)] -> [(String, b)]
forall a. Int -> [a] -> [a]
take Int
1 ([(String, b)] -> [(String, b)]) -> [(String, b)] -> [(String, b)]
forall a b. (a -> b) -> a -> b
$ Int -> [(String, b)] -> [(String, b)]
forall a. Int -> [a] -> [a]
drop Int
1 ([(String, b)] -> [(String, b)]) -> [(String, b)] -> [(String, b)]
forall a b. (a -> b) -> a -> b
$
                ((String, b) -> Bool) -> [(String, b)] -> [(String, b)]
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (\(String, b)
x -> (String, b) -> String
forall a b. (a, b) -> a
fst (String, b)
x String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [String]
findExecFlags) [(String, b)]
argAndString
            String
_ -> []
      where
        firstNonFlag :: [(String, b)]
firstNonFlag = Int -> [(String, b)] -> [(String, b)]
forall a. Int -> [a] -> [a]
take Int
1 ([(String, b)] -> [(String, b)]) -> [(String, b)] -> [(String, b)]
forall a b. (a -> b) -> a -> b
$ [(String, b)] -> [(String, 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 = ((String, b) -> Bool) -> [(String, b)] -> [(String, b)]
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (\(String, b)
x -> String
"-" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` (String, b) -> String
forall a b. (a, b) -> a
fst (String, b)
x)

    -- Make a map from functions/aliases to definition IDs
    analyse :: (Token -> StateT [a] Identity ()) -> Token -> [a]
analyse Token -> StateT [a] Identity ()
f Token
t = State [a] Token -> [a] -> [a]
forall s a. State s a -> s -> s
execState ((Token -> StateT [a] Identity ()) -> Token -> State [a] Token
forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> Token -> m Token
doAnalysis Token -> StateT [a] Identity ()
f Token
t) []
    functions :: Map String Id
functions = [(String, Id)] -> Map String Id
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList ([(String, Id)] -> Map String Id)
-> [(String, Id)] -> Map String Id
forall a b. (a -> b) -> a -> b
$ (Token -> StateT [(String, Id)] Identity ())
-> Token -> [(String, Id)]
forall a. (Token -> StateT [a] Identity ()) -> Token -> [a]
analyse Token -> StateT [(String, Id)] Identity ()
forall (m :: * -> *). MonadState [(String, Id)] m => Token -> m ()
findFunctions Token
t
    findFunctions :: Token -> m ()
findFunctions (T_Function Id
id FunctionKeyword
_ FunctionParentheses
_ String
name Token
_) = ([(String, Id)] -> [(String, Id)]) -> m ()
forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify ((String
name, Id
id)(String, Id) -> [(String, Id)] -> [(String, Id)]
forall a. a -> [a] -> [a]
:)
    findFunctions t :: Token
t@(T_SimpleCommand Id
id [Token]
_ (Token
_:[Token]
args))
        | Token
t Token -> String -> Bool
`isUnqualifiedCommand` String
"alias" = (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall (m :: * -> *). MonadState [(String, Id)] m => Token -> m ()
getAlias [Token]
args
    findFunctions Token
_ = () -> m ()
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 Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Char
'=' Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
string) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
            ([(String, Id)] -> [(String, Id)]) -> f ()
forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify (((Char -> Bool) -> String -> String
forall a. (a -> Bool) -> [a] -> [a]
takeWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
'=') String
string, Token -> Id
getId Token
arg)(String, Id) -> [(String, Id)] -> [(String, Id)]
forall a. a -> [a] -> [a]
:)

    checkArg :: String -> (a, Token) -> m ()
checkArg String
cmd (a
_, Token
arg) = Maybe (m ()) -> m ()
forall (m :: * -> *). Monad m => Maybe (m ()) -> m ()
potentially (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
        String
literalArg <- Token -> Maybe String
getUnquotedLiteral Token
arg  -- only consider unquoted literals
        Id
definitionId <- String -> Map String Id -> Maybe Id
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup String
literalArg Map String Id
functions
        m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ do
            Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn (Token -> Id
getId Token
arg) Integer
2033
              String
"Shell functions can't be passed to external commands."
            Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
info Id
definitionId Integer
2032 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
              String
"Use own script or sh -c '..' to run this from " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
cmd String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"."

prop_checkUnused0 :: Bool
prop_checkUnused0 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"var=foo; echo $var"
prop_checkUnused1 :: Bool
prop_checkUnused1 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"var=foo; echo $bar"
prop_checkUnused2 :: Bool
prop_checkUnused2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"var=foo; export var;"
prop_checkUnused3 :: Bool
prop_checkUnused3 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"local i=0"
prop_checkUnused5 :: Bool
prop_checkUnused5 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"read lol; echo $lol"
prop_checkUnused6 :: Bool
prop_checkUnused6 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"var=4; (( var++ ))"
prop_checkUnused7 :: Bool
prop_checkUnused7 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"var=2; $((var))"
prop_checkUnused8 :: Bool
prop_checkUnused8 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"var=2; var=3;"
prop_checkUnused9 :: Bool
prop_checkUnused9 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"read ''"
prop_checkUnused10 :: Bool
prop_checkUnused10= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"read -p 'test: '"
prop_checkUnused11 :: Bool
prop_checkUnused11= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"bar=5; export foo[$bar]=3"
prop_checkUnused12 :: Bool
prop_checkUnused12= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"read foo; echo ${!foo}"
prop_checkUnused13 :: Bool
prop_checkUnused13= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"x=(1); (( x[0] ))"
prop_checkUnused14 :: Bool
prop_checkUnused14= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"x=(1); n=0; (( x[n] ))"
prop_checkUnused16 :: Bool
prop_checkUnused16= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"foo=5; declare -x foo"
prop_checkUnused17 :: Bool
prop_checkUnused17= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"a=1; PS1='$a'"
prop_checkUnused21 :: Bool
prop_checkUnused21= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"a=1; trap 'echo $a' INT"
prop_checkUnused22 :: Bool
prop_checkUnused22= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"a=1; [ -v a ]"
prop_checkUnused23 :: Bool
prop_checkUnused23= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"a=1; [ -R a ]"
prop_checkUnused24 :: Bool
prop_checkUnused24= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"mapfile -C a b; echo ${b[@]}"
prop_checkUnused25 :: Bool
prop_checkUnused25= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"readarray foo; echo ${foo[@]}"
prop_checkUnused26 :: Bool
prop_checkUnused26= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"declare -F foo"
prop_checkUnused27 :: Bool
prop_checkUnused27= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"var=3; [ var -eq 3 ]"
prop_checkUnused28 :: Bool
prop_checkUnused28= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"var=3; [[ var -eq 3 ]]"
prop_checkUnused29 :: Bool
prop_checkUnused29= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"var=(a b); declare -p var"
prop_checkUnused30 :: Bool
prop_checkUnused30= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"let a=1"
prop_checkUnused31 :: Bool
prop_checkUnused31= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"let 'a=1'"
prop_checkUnused32 :: Bool
prop_checkUnused32= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"let a=b=c; echo $a"
prop_checkUnused33 :: Bool
prop_checkUnused33= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"a=foo; [[ foo =~ ^{$a}$ ]]"
prop_checkUnused34 :: Bool
prop_checkUnused34= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"foo=1; (( t = foo )); echo $t"
prop_checkUnused35 :: Bool
prop_checkUnused35= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"if [[ -v foo ]]; then true; fi"
prop_checkUnused37 :: Bool
prop_checkUnused37= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"fd=2; exec {fd}>&-"
prop_checkUnused38 :: Bool
prop_checkUnused38= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"(( a=42 ))"
prop_checkUnused39 :: Bool
prop_checkUnused39= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"declare -x -f foo"
prop_checkUnused40 :: Bool
prop_checkUnused40= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"@test 'foo' {\ntrue\n}\n"
prop_checkUnused42 :: Bool
prop_checkUnused42= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"DEFINE_string foo '' ''; echo \"${FLAGS_foo}\""
prop_checkUnused43 :: Bool
prop_checkUnused43= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"DEFINE_string foo '' ''"
prop_checkUnused44 :: Bool
prop_checkUnused44= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"DEFINE_string \"foo$ibar\" x y"
prop_checkUnused45 :: Bool
prop_checkUnused45= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"readonly foo=bar"
prop_checkUnused46 :: Bool
prop_checkUnused46= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments String
"readonly foo=(bar)"
checkUnusedAssignments :: Parameters -> p -> [TokenComment]
checkUnusedAssignments Parameters
params p
t = WriterT [TokenComment] Identity () -> [TokenComment]
forall w a. Writer w a -> w
execWriter (((String, Token) -> WriterT [TokenComment] Identity ())
-> [(String, Token)] -> WriterT [TokenComment] Identity ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (String, Token) -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
(String, Token) -> m ()
warnFor [(String, Token)]
unused)
  where
    flow :: [StackData]
flow = Parameters -> [StackData]
variableFlow Parameters
params
    references :: Map String ()
references = (Map String ()
 -> (Map String () -> Map String ()) -> Map String ())
-> Map String ()
-> [Map String () -> Map String ()]
-> Map String ()
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl (((Map String () -> Map String ())
 -> Map String () -> Map String ())
-> Map String ()
-> (Map String () -> Map String ())
-> Map String ()
forall a b c. (a -> b -> c) -> b -> a -> c
flip (Map String () -> Map String ()) -> Map String () -> Map String ()
forall a b. (a -> b) -> a -> b
($)) Map String ()
defaultMap ((StackData -> Map String () -> Map String ())
-> [StackData] -> [Map String () -> Map String ()]
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)) =
        String -> () -> Map String () -> Map String ()
forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert (String -> String
stripSuffix String
name) ()
    insertRef StackData
_ = Map String () -> Map String ()
forall a. a -> a
id

    assignments :: Map String Token
assignments = (Map String Token
 -> (Map String Token -> Map String Token) -> Map String Token)
-> Map String Token
-> [Map String Token -> Map String Token]
-> Map String Token
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl (((Map String Token -> Map String Token)
 -> Map String Token -> Map String Token)
-> Map String Token
-> (Map String Token -> Map String Token)
-> Map String Token
forall a b c. (a -> b -> c) -> b -> a -> c
flip (Map String Token -> Map String Token)
-> Map String Token -> Map String Token
forall a b. (a -> b) -> a -> b
($)) Map String Token
forall k a. Map k a
Map.empty ((StackData -> Map String Token -> Map String Token)
-> [StackData] -> [Map String Token -> Map String Token]
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 =
        String -> Token -> Map String Token -> Map String Token
forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert String
name Token
token
    insertAssignment StackData
_ = Map String Token -> Map String Token
forall a. a -> a
id

    unused :: [(String, Token)]
unused = Map String Token -> [(String, Token)]
forall k a. Map k a -> [(k, a)]
Map.assocs (Map String Token -> [(String, Token)])
-> Map String Token -> [(String, Token)]
forall a b. (a -> b) -> a -> b
$ Map String Token -> Map String () -> Map String Token
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) -> m ()
warnFor (String
name, Token
token) =
        Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn (Token -> Id
getId Token
token) Integer
2034 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
            String
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" appears unused. Verify use (or export if used externally)."

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

prop_checkUnassignedReferences1 :: Bool
prop_checkUnassignedReferences1 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"echo $foo"
prop_checkUnassignedReferences2 :: Bool
prop_checkUnassignedReferences2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"foo=hello; echo $foo"
prop_checkUnassignedReferences3 :: Bool
prop_checkUnassignedReferences3 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"MY_VALUE=3; echo $MYVALUE"
prop_checkUnassignedReferences4 :: Bool
prop_checkUnassignedReferences4 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"RANDOM2=foo; echo $RANDOM"
prop_checkUnassignedReferences5 :: Bool
prop_checkUnassignedReferences5 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"foo=..; echo ${foo-bar}"
prop_checkUnassignedReferences7 :: Bool
prop_checkUnassignedReferences7 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"getopts ':h' foo; echo $foo"
prop_checkUnassignedReferences8 :: Bool
prop_checkUnassignedReferences8 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"let 'foo = 1'; echo $foo"
prop_checkUnassignedReferences9 :: Bool
prop_checkUnassignedReferences9 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"echo ${foo-bar}"
prop_checkUnassignedReferences10 :: Bool
prop_checkUnassignedReferences10= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"echo ${foo:?}"
prop_checkUnassignedReferences11 :: Bool
prop_checkUnassignedReferences11= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"declare -A foo; echo \"${foo[@]}\""
prop_checkUnassignedReferences12 :: Bool
prop_checkUnassignedReferences12= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"typeset -a foo; echo \"${foo[@]}\""
prop_checkUnassignedReferences13 :: Bool
prop_checkUnassignedReferences13= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"f() { local foo; echo $foo; }"
prop_checkUnassignedReferences14 :: Bool
prop_checkUnassignedReferences14= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"foo=; echo $foo"
prop_checkUnassignedReferences15 :: Bool
prop_checkUnassignedReferences15= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"f() { true; }; export -f f"
prop_checkUnassignedReferences16 :: Bool
prop_checkUnassignedReferences16= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"USERS=foo; echo $USER"
prop_checkUnassignedReferences18 :: Bool
prop_checkUnassignedReferences18= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"FOOBAR=42; export FOOBAR="
prop_checkUnassignedReferences19 :: Bool
prop_checkUnassignedReferences19= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"readonly foo=bar; echo $foo"
prop_checkUnassignedReferences20 :: Bool
prop_checkUnassignedReferences20= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"printf -v foo bar; echo $foo"
prop_checkUnassignedReferences21 :: Bool
prop_checkUnassignedReferences21= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"echo ${#foo}"
prop_checkUnassignedReferences22 :: Bool
prop_checkUnassignedReferences22= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"echo ${!os*}"
prop_checkUnassignedReferences23 :: Bool
prop_checkUnassignedReferences23= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"declare -a foo; foo[bar]=42;"
prop_checkUnassignedReferences24 :: Bool
prop_checkUnassignedReferences24= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"declare -A foo; foo[bar]=42;"
prop_checkUnassignedReferences25 :: Bool
prop_checkUnassignedReferences25= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"declare -A foo=(); foo[bar]=42;"
prop_checkUnassignedReferences26 :: Bool
prop_checkUnassignedReferences26= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
": ${foo:=bar}"
prop_checkUnassignedReferences28 :: Bool
prop_checkUnassignedReferences28= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"#!/bin/ksh\necho \"${.sh.version}\"\n"
prop_checkUnassignedReferences29 :: Bool
prop_checkUnassignedReferences29= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"f() { local -A foo; echo \"${foo[@]}\"; }"
prop_checkUnassignedReferences34 :: Bool
prop_checkUnassignedReferences34= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"declare -A foo; (( foo[bar] ))"
prop_checkUnassignedReferences35 :: Bool
prop_checkUnassignedReferences35= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences String
"echo ${arr[foo-bar]:?fail}"
prop_checkUnassignedReferences36 :: Bool
prop_checkUnassignedReferences36= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
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 (Bool -> Parameters -> Token -> [TokenComment]
forall p. Bool -> Parameters -> p -> [TokenComment]
checkUnassignedReferences' Bool
True) String
"echo $VAR"

checkUnassignedReferences :: Parameters -> p -> [TokenComment]
checkUnassignedReferences = Bool -> Parameters -> p -> [TokenComment]
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) = State (Map String Token, Map String ()) [()]
-> (Map String Token, Map String ())
-> (Map String Token, Map String ())
forall s a. State s a -> s -> s
execState ((StackData -> StateT (Map String Token, Map String ()) Identity ())
-> [StackData] -> State (Map String Token, Map String ()) [()]
forall (t :: * -> *) (m :: * -> *) a b.
(Traversable t, Monad m) =>
(a -> m b) -> t a -> m (t b)
mapM StackData -> StateT (Map String Token, Map String ()) Identity ()
forall (m :: * -> *).
MonadState (Map String Token, Map String ()) m =>
StackData -> m ()
tally ([StackData] -> State (Map String Token, Map String ()) [()])
-> [StackData] -> State (Map String Token, Map String ()) [()]
forall a b. (a -> b) -> a -> b
$ Parameters -> [StackData]
variableFlow Parameters
params) (Map String Token
forall k a. Map k a
Map.empty, Map String ()
forall k a. Map k a
Map.empty)
    defaultAssigned :: Map String ()
defaultAssigned = [(String, ())] -> Map String ()
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList ([(String, ())] -> Map String ())
-> [(String, ())] -> Map String ()
forall a b. (a -> b) -> a -> b
$ (String -> (String, ())) -> [String] -> [(String, ())]
forall a b. (a -> b) -> [a] -> [b]
map (\String
a -> (String
a, ())) ([String] -> [(String, ())]) -> [String] -> [(String, ())]
forall a b. (a -> b) -> a -> b
$ (String -> Bool) -> [String] -> [String]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool) -> (String -> Bool) -> String -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null) [String]
internalVariables

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

    unassigned :: [(String, Token)]
unassigned = Map String Token -> [(String, Token)]
forall k a. Map k a -> [(k, a)]
Map.toList (Map String Token -> [(String, Token)])
-> Map String Token -> [(String, Token)]
forall a b. (a -> b) -> a -> b
$ Map String Token -> Map String () -> Map String Token
forall k a b. Ord k => Map k a -> Map k b -> Map k a
Map.difference (Map String Token -> Map String () -> Map String Token
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 = (String -> Bool) -> [String] -> [String]
forall a. (a -> Bool) -> [a] -> [a]
filter String -> Bool
isVariableName ([String] -> [String]) -> [String] -> [String]
forall a b. (a -> b) -> a -> b
$ Map String () -> [String]
forall k a. Map k a -> [k]
Map.keys Map String ()
writeMap

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

    isLocal :: String -> Bool
isLocal = (Char -> Bool) -> String -> Bool
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
        m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn (Token -> Id
getId Token
place) Integer
2153 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
            String
"Possible misspelling: " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
var String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" may not be assigned, but " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
match String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" is."

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

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

    warnings :: [TokenComment]
warnings = Writer [TokenComment] [()] -> [TokenComment]
forall w a. Writer w a -> w
execWriter (Writer [TokenComment] [()] -> [TokenComment])
-> ([WriterT [TokenComment] Identity ()]
    -> Writer [TokenComment] [()])
-> [WriterT [TokenComment] Identity ()]
-> [TokenComment]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [WriterT [TokenComment] Identity ()] -> Writer [TokenComment] [()]
forall (t :: * -> *) (m :: * -> *) a.
(Traversable t, Monad m) =>
t (m a) -> m (t a)
sequence ([WriterT [TokenComment] Identity ()] -> [TokenComment])
-> [WriterT [TokenComment] Identity ()] -> [TokenComment]
forall a b. (a -> b) -> a -> b
$ ((String, Token) -> Maybe (WriterT [TokenComment] Identity ()))
-> [(String, Token)] -> [WriterT [TokenComment] Identity ()]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe ((String -> Token -> Maybe (WriterT [TokenComment] Identity ()))
-> (String, Token) -> Maybe (WriterT [TokenComment] Identity ())
forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry String -> Token -> Maybe (WriterT [TokenComment] Identity ())
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.
    isInArray :: String -> Token -> Bool
isInArray String
var Token
t = (Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
isArray ([Token] -> Bool) -> [Token] -> Bool
forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) Token
t
      where
        isArray :: Token -> Bool
isArray T_Array {} = Bool
True
        isArray b :: Token
b@(T_DollarBraced Id
_ Bool
_ Token
_) | String
var String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= String -> String
getBracedReference (Token -> String
bracedString Token
b) = Bool
True
        isArray 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 = [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
v
        rest :: String
rest = (Char -> Bool) -> String -> String
forall a. (a -> Bool) -> [a] -> [a]
dropWhile Char -> Bool
isVariableChar (String -> String) -> String -> String
forall a b. (a -> b) -> a -> b
$ (Char -> Bool) -> String -> String
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (Char -> String -> Bool
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 String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= String
candidate Bool -> Bool -> Bool
&& (Char -> Char) -> String -> String
forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
toLower String
var String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== (Char -> Char) -> String -> String
forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
toLower String
candidate
        then Int
1
        else String -> String -> Int
forall a. Eq a => [a] -> [a] -> Int
dist String
var String
candidate


prop_checkGlobsAsOptions1 :: Bool
prop_checkGlobsAsOptions1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkGlobsAsOptions String
"rm *.txt"
prop_checkGlobsAsOptions2 :: Bool
prop_checkGlobsAsOptions2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkGlobsAsOptions String
"ls ??.*"
prop_checkGlobsAsOptions3 :: Bool
prop_checkGlobsAsOptions3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkGlobsAsOptions String
"rm -- *.txt"
prop_checkGlobsAsOptions4 :: Bool
prop_checkGlobsAsOptions4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkGlobsAsOptions String
"*.txt"
checkGlobsAsOptions :: p -> Token -> m ()
checkGlobsAsOptions p
_ (T_SimpleCommand Id
_ [Token]
_ [Token]
args) =
    (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
check ([Token] -> m ()) -> [Token] -> m ()
forall a b. (a -> b) -> a -> b
$ (Token -> Bool) -> [Token] -> [Token]
forall a. (a -> Bool) -> [a] -> [a]
takeWhile (Bool -> Bool
not (Bool -> Bool) -> (Token -> Bool) -> Token -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> Bool
isEndOfArgs) (Int -> [Token] -> [Token]
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 String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"*" Bool -> Bool -> Bool
|| String
s String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"?" =
        Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
info Id
id Integer
2035 String
"Use ./*glob* or -- *glob* so names with dashes won't become options."
    check Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

    isEndOfArgs :: Token -> Bool
isEndOfArgs Token
t =
        case [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> [String] -> String
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
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkWhileReadPitfalls1 :: Bool
prop_checkWhileReadPitfalls1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkWhileReadPitfalls String
"while read foo; do ssh $foo uptime; done < file"
prop_checkWhileReadPitfalls2 :: Bool
prop_checkWhileReadPitfalls2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkWhileReadPitfalls String
"while read -u 3 foo; do ssh $foo uptime; done 3< file"
prop_checkWhileReadPitfalls3 :: Bool
prop_checkWhileReadPitfalls3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkWhileReadPitfalls String
"while true; do ssh host uptime; done"
prop_checkWhileReadPitfalls4 :: Bool
prop_checkWhileReadPitfalls4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkWhileReadPitfalls String
"while read foo; do ssh $foo hostname < /dev/null; done"
prop_checkWhileReadPitfalls5 :: Bool
prop_checkWhileReadPitfalls5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkWhileReadPitfalls String
"while read foo; do echo ls | ssh $foo; done"
prop_checkWhileReadPitfalls6 :: Bool
prop_checkWhileReadPitfalls6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkWhileReadPitfalls String
"while read foo <&3; do ssh $foo; done 3< foo"
prop_checkWhileReadPitfalls7 :: Bool
prop_checkWhileReadPitfalls7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkWhileReadPitfalls String
"while read foo; do if true; then ssh $foo uptime; fi; done < file"
prop_checkWhileReadPitfalls8 :: Bool
prop_checkWhileReadPitfalls8 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkWhileReadPitfalls String
"while read foo; do ssh -n $foo uptime; done < file"

checkWhileReadPitfalls :: p -> Token -> m ()
checkWhileReadPitfalls p
_ (T_WhileExpression Id
id [Token
command] [Token]
contents)
        | Token -> Bool
isStdinReadCommand Token
command =
    (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
checkMuncher [Token]
contents
  where
    munchers :: [String]
munchers = [ String
"ssh", String
"ffmpeg", String
"mplayer", String
"HandBrakeCLI" ]
    preventionFlags :: [String]
preventionFlags = [String
"n", String
"noconsolecontrols" ]

    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 [String] -> String
forall a. [a] -> a
head ([String]
plaintext [String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++ [String
""]) String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"read"
            Bool -> Bool -> Bool
&& (String
"-u" String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [String]
plaintext)
            Bool -> Bool -> Bool
&& (Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (Bool -> Bool
not (Bool -> Bool) -> (Token -> Bool) -> Token -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> Bool
stdinRedirect) [Token]
redirs
    isStdinReadCommand Token
_ = Bool
False

    checkMuncher :: Token -> m ()
checkMuncher (T_Pipeline Id
_ [Token]
_ (T_Redirecting Id
_ [Token]
redirs Token
cmd:[Token]
_)) | Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ (Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
stdinRedirect [Token]
redirs =
        case Token
cmd of
            (T_IfExpression Id
_ [([Token], [Token])]
thens [Token]
elses) ->
              (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
checkMuncher ([Token] -> m ()) -> ([[Token]] -> [Token]) -> [[Token]] -> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [[Token]] -> [Token]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([[Token]] -> m ()) -> [[Token]] -> m ()
forall a b. (a -> b) -> a -> b
$ (([Token], [Token]) -> [Token])
-> [([Token], [Token])] -> [[Token]]
forall a b. (a -> b) -> [a] -> [b]
map ([Token], [Token]) -> [Token]
forall a b. (a, b) -> a
fst [([Token], [Token])]
thens [[Token]] -> [[Token]] -> [[Token]]
forall a. [a] -> [a] -> [a]
++ (([Token], [Token]) -> [Token])
-> [([Token], [Token])] -> [[Token]]
forall a b. (a -> b) -> [a] -> [b]
map ([Token], [Token]) -> [Token]
forall a b. (a, b) -> b
snd [([Token], [Token])]
thens [[Token]] -> [[Token]] -> [[Token]]
forall a. [a] -> [a] -> [a]
++ [[Token]
elses]

            Token
_ -> Maybe (m ()) -> m ()
forall (m :: * -> *). Monad m => Maybe (m ()) -> m ()
potentially (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
                String
name <- Token -> Maybe String
getCommandBasename Token
cmd
                Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String
name String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
munchers

                -- Sloppily check if the command has a flag to prevent eating stdin.
                let flags :: [(Token, String)]
flags = Token -> [(Token, String)]
getAllFlags Token
cmd
                Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> (Bool -> Bool) -> Bool -> Maybe ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ (String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
preventionFlags) ([String] -> Bool) -> [String] -> Bool
forall a b. (a -> b) -> a -> b
$ ((Token, String) -> String) -> [(Token, String)] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (Token, String) -> String
forall a b. (a, b) -> b
snd [(Token, String)]
flags
                m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ do
                    Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
info Id
id Integer
2095 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
                        String
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" may swallow stdin, preventing this loop from working properly."
                    Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn (Token -> Id
getId Token
cmd) Integer
2095 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
                        String
"Add < /dev/null to prevent " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" from swallowing stdin."
    checkMuncher Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

    stdinRedirect :: Token -> Bool
stdinRedirect (T_FdRedirect Id
_ String
fd Token
_)
        | String
fd String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"" Bool -> Bool -> Bool
|| String
fd String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"0" = Bool
True
    stdinRedirect Token
_ = Bool
False
checkWhileReadPitfalls p
_ Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkPrefixAssign1 :: Bool
prop_checkPrefixAssign1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkPrefixAssignmentReference String
"var=foo echo $var"
prop_checkPrefixAssign2 :: Bool
prop_checkPrefixAssign2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
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) =
    [Token] -> m ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
[Token] -> f ()
check [Token]
path
  where
    name :: String
name = String -> String
getBracedReference (String -> String) -> String -> String
forall a b. (a -> b) -> a -> b
$ Token -> String
bracedString Token
t
    path :: [Token]
path = Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) Token
t
    idPath :: [Id]
idPath = (Token -> Id) -> [Token] -> [Id]
forall a b. (a -> b) -> [a] -> [b]
map Token -> Id
getId [Token]
path

    check :: [Token] -> m ()
check [] = () -> m ()
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]
_) -> (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
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 String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
name Bool -> Bool -> Bool
&& (Id
aId Id -> [Id] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [Id]
idPath) = do
        Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
aId Integer
2097 String
"This assignment is only seen by the forked process."
        Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2098 String
"This expansion will not see the mentioned assignment."
    checkVar Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

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

prop_checkCharRangeGlob1 :: Bool
prop_checkCharRangeGlob1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCharRangeGlob String
"ls *[:digit:].jpg"
prop_checkCharRangeGlob2 :: Bool
prop_checkCharRangeGlob2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCharRangeGlob String
"ls *[[:digit:]].jpg"
prop_checkCharRangeGlob3 :: Bool
prop_checkCharRangeGlob3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCharRangeGlob String
"ls [10-15]"
prop_checkCharRangeGlob4 :: Bool
prop_checkCharRangeGlob4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCharRangeGlob String
"ls [a-zA-Z]"
prop_checkCharRangeGlob5 :: Bool
prop_checkCharRangeGlob5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCharRangeGlob String
"tr -d [a-zA-Z]" -- tr has 2060
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 (Map Id Token -> String -> Token -> Bool
isParamTo (Parameters -> Map Id Token
parentMap Parameters
p) String
"tr" Token
t) =
    if String
":" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
contents
        Bool -> Bool -> Bool
&& String
":" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isSuffixOf` String
contents
        Bool -> Bool -> Bool
&& String
contents String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= String
":"
    then Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2101 String
"Named class needs outer [], e.g. [[:digit:]]."
    else
        Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Char
'[' Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` String
contents Bool -> Bool -> Bool
&& Bool
hasDupes) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
            Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
info Id
id Integer
2102 String
"Ranges can only match single chars (mentioned due to duplicates)."
  where
    isCharClass :: String -> Bool
isCharClass String
str = String
"[" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
str Bool -> Bool -> Bool
&& String
"]" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isSuffixOf` String
str
    contents :: String
contents = Int -> String -> String
forall a. Int -> [a] -> [a]
drop Int
1 (String -> String) -> (String -> String) -> String -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> String -> String
forall a. Int -> [a] -> [a]
take (String -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
str Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1) (String -> String) -> String -> String
forall a b. (a -> b) -> a -> b
$ String
str
    hasDupes :: Bool
hasDupes = (Int -> Bool) -> [Int] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>Int
1) ([Int] -> Bool) -> (String -> [Int]) -> String -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String -> Int) -> [String] -> [Int]
forall a b. (a -> b) -> [a] -> [b]
map String -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length ([String] -> [Int]) -> (String -> [String]) -> String -> [Int]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> [String]
forall a. Eq a => [a] -> [[a]]
group (String -> [String]) -> (String -> String) -> String -> [String]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> String
forall a. Ord a => [a] -> [a]
sort (String -> String) -> (String -> String) -> String -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> Bool) -> String -> String
forall a. (a -> Bool) -> [a] -> [a]
filter (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
'-') (String -> Bool) -> String -> Bool
forall a b. (a -> b) -> a -> b
$ String
contents
checkCharRangeGlob Parameters
_ Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()



prop_checkCdAndBack1 :: Bool
prop_checkCdAndBack1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
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 -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
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 -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCdAndBack String
"while [[ $PWD != / ]]; do cd ..; done"
prop_checkCdAndBack4 :: Bool
prop_checkCdAndBack4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCdAndBack String
"cd $tmp; foo; cd -"
prop_checkCdAndBack5 :: Bool
prop_checkCdAndBack5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCdAndBack String
"cd ..; foo; cd .."
prop_checkCdAndBack6 :: Bool
prop_checkCdAndBack6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
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 -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
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 -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
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 =
    Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Parameters -> Bool
hasSetE Parameters
params) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$ ([Token] -> f ()) -> [[Token]] -> f ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ [Token] -> f ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
[Token] -> f ()
doList ([[Token]] -> f ()) -> [[Token]] -> f ()
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 String -> [String] -> Bool
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" = Token -> Maybe Token
forall (m :: * -> *) a. Monad m => a -> m a
return Token
x
    getCandidate Token
_ = Maybe 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 Id -> Maybe Id
forall (m :: * -> *) a. Monad m => a -> m a
return (Id -> Maybe Id) -> Id -> Maybe Id
forall a b. (a -> b) -> a -> b
$ Token -> Id
getId Token
b
                else [Token] -> Maybe Id
findCdPair (Token
bToken -> [Token] -> [Token]
forall a. a -> [a] -> [a]
:[Token]
rest)
            [Token]
_ -> Maybe Id
forall a. Maybe a
Nothing

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

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


prop_checkFunctionDeclarations1 :: Bool
prop_checkFunctionDeclarations1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkFunctionDeclarations String
"#!/bin/ksh\nfunction foo() { command foo --lol \"$@\"; }"
prop_checkFunctionDeclarations2 :: Bool
prop_checkFunctionDeclarations2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkFunctionDeclarations String
"#!/bin/dash\nfunction foo { lol; }"
prop_checkFunctionDeclarations3 :: Bool
prop_checkFunctionDeclarations3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
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 -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
        Shell
Ksh ->
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Bool
hasKeyword Bool -> Bool -> Bool
&& Bool
hasParens) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
                Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
id Integer
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
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Bool
hasKeyword Bool -> Bool -> Bool
&& Bool
hasParens) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
                Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2112 String
"'function' keyword is non-standard. Delete it."
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Bool
hasKeyword Bool -> Bool -> Bool
&& Bool -> Bool
not Bool
hasParens) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
                Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2113 String
"'function' keyword is non-standard. Use 'foo()' instead of 'function foo'."
checkFunctionDeclarations Parameters
_ Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()



prop_checkStderrPipe1 :: Bool
prop_checkStderrPipe1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkStderrPipe String
"#!/bin/ksh\nfoo |& bar"
prop_checkStderrPipe2 :: Bool
prop_checkStderrPipe2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
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 -> Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
match
        Shell
_ -> m () -> Token -> m ()
forall a b. a -> b -> a
const (m () -> Token -> m ()) -> m () -> Token -> m ()
forall a b. (a -> b) -> a -> b
$ () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    match :: Token -> m ()
match (T_Pipe Id
id String
"|&") =
        Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
id Integer
2118 String
"Ksh does not support |&. Use 2>&1 |."
    match Token
_ = () -> m ()
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"
checkUnpassedInFunctions :: Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions Parameters
params Token
root =
    WriterT [TokenComment] Identity () -> [TokenComment]
forall w a. Writer w a -> w
execWriter (WriterT [TokenComment] Identity () -> [TokenComment])
-> WriterT [TokenComment] Identity () -> [TokenComment]
forall a b. (a -> b) -> a -> b
$ ([(String, Bool, Token)] -> WriterT [TokenComment] Identity ())
-> [[(String, Bool, Token)]] -> WriterT [TokenComment] Identity ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ [(String, Bool, Token)] -> WriterT [TokenComment] Identity ()
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 = [(String, Token)] -> Map String Token
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList ([(String, Token)] -> Map String Token)
-> [(String, Token)] -> Map String Token
forall a b. (a -> b) -> a -> b
$
        (Token -> (String, Token)) -> [Token] -> [(String, Token)]
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 = Writer [Token] Token -> [Token]
forall w a. Writer w a -> w
execWriter (Writer [Token] Token -> [Token])
-> Writer [Token] Token -> [Token]
forall a b. (a -> b) -> a -> b
$ (Token -> WriterT [Token] Identity ())
-> Token -> Writer [Token] Token
forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> Token -> m Token
doAnalysis ([Token] -> WriterT [Token] Identity ()
forall w (m :: * -> *). MonadWriter w m => w -> m ()
tell ([Token] -> WriterT [Token] Identity ())
-> (Token -> [Token]) -> Token -> WriterT [Token] Identity ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Maybe Token -> [Token]
forall a. Maybe a -> [a]
maybeToList (Maybe Token -> [Token])
-> (Token -> Maybe Token) -> Token -> [Token]
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) =
        let flow :: [StackData]
flow = Parameters -> Token -> [StackData]
getVariableFlow Parameters
params Token
body
        in
          if (StackData -> Bool) -> [StackData] -> Bool
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 ((StackData -> Bool) -> [StackData] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any StackData -> Bool
isPositionalAssignment [StackData]
flow)
            then Token -> Maybe Token
forall (m :: * -> *) a. Monad m => a -> m a
return Token
t
            else Maybe Token
forall a. Maybe a
Nothing
    findFunction Token
_ = Maybe 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 = Bool -> Maybe Bool -> Bool
forall a. a -> Maybe a -> a
fromMaybe Bool
False (Maybe Bool -> Bool) -> Maybe Bool -> Bool
forall a b. (a -> b) -> a -> b
$ do
        Token
function <- (Token -> Bool) -> [Token] -> Maybe Token
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) ([Token] -> Maybe Token) -> [Token] -> Maybe Token
forall a b. (a -> b) -> a -> b
$
                Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) Token
child
        Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> Maybe Bool) -> Bool -> Maybe Bool
forall a b. (a -> b) -> a -> b
$ Token -> Id
getId Token
parent Id -> Id -> Bool
forall a. Eq a => a -> a -> Bool
== Token -> Id
getId Token
function

    referenceList :: [(String, Bool, Token)]
    referenceList :: [(String, Bool, Token)]
referenceList = Writer [(String, Bool, Token)] Token -> [(String, Bool, Token)]
forall w a. Writer w a -> w
execWriter (Writer [(String, Bool, Token)] Token -> [(String, Bool, Token)])
-> Writer [(String, Bool, Token)] Token -> [(String, Bool, Token)]
forall a b. (a -> b) -> a -> b
$
        (Token -> WriterT [(String, Bool, Token)] Identity ())
-> Token -> Writer [(String, Bool, Token)] Token
forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> Token -> m Token
doAnalysis (WriterT [(String, Bool, Token)] Identity ()
-> Maybe (WriterT [(String, Bool, Token)] Identity ())
-> WriterT [(String, Bool, Token)] Identity ()
forall a. a -> Maybe a -> a
fromMaybe (() -> WriterT [(String, Bool, Token)] Identity ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()) (Maybe (WriterT [(String, Bool, Token)] Identity ())
 -> WriterT [(String, Bool, Token)] Identity ())
-> (Token -> Maybe (WriterT [(String, Bool, Token)] Identity ()))
-> Token
-> WriterT [(String, Bool, Token)] Identity ()
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
        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String -> Map String Token -> Bool
forall k a. Ord k => k -> Map k a -> Bool
Map.member String
str Map String Token
functionMap
        WriterT [(String, Bool, Token)] Identity ()
-> Maybe (WriterT [(String, Bool, Token)] Identity ())
forall (m :: * -> *) a. Monad m => a -> m a
return (WriterT [(String, Bool, Token)] Identity ()
 -> Maybe (WriterT [(String, Bool, Token)] Identity ()))
-> WriterT [(String, Bool, Token)] Identity ()
-> Maybe (WriterT [(String, Bool, Token)] Identity ())
forall a b. (a -> b) -> a -> b
$ [(String, Bool, Token)]
-> WriterT [(String, Bool, Token)] Identity ()
forall w (m :: * -> *). MonadWriter w m => w -> m ()
tell [(String
str, [Token] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Token]
args, Token
t)]
    checkCommand Token
_ = Maybe (WriterT [(String, Bool, Token)] Identity ())
forall a. Maybe a
Nothing

    isPositional :: String -> Bool
isPositional String
str = String
str String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"*" Bool -> Bool -> Bool
|| String
str String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"@"
        Bool -> Bool -> Bool
|| ((Char -> Bool) -> String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isDigit String
str Bool -> Bool -> Bool
&& String
str String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= String
"0" Bool -> Bool -> Bool
&& String
str String -> String -> Bool
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 = Map String [(String, Bool, Token)] -> [[(String, Bool, Token)]]
forall k a. Map k a -> [a]
Map.elems (Map String [(String, Bool, Token)] -> [[(String, Bool, Token)]])
-> Map String [(String, Bool, Token)] -> [[(String, Bool, Token)]]
forall a b. (a -> b) -> a -> b
$ ((String, Bool, Token)
 -> Map String [(String, Bool, Token)]
 -> Map String [(String, Bool, Token)])
-> Map String [(String, Bool, Token)]
-> [(String, Bool, Token)]
-> Map String [(String, Bool, Token)]
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr (String, Bool, Token)
-> Map String [(String, Bool, Token)]
-> Map String [(String, Bool, Token)]
forall k b c.
Ord k =>
(k, b, c) -> Map k [(k, b, c)] -> Map k [(k, b, c)]
updateWith Map String [(String, Bool, Token)]
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
_) = ([(k, b, c)] -> [(k, b, c)] -> [(k, b, c)])
-> k -> [(k, b, c)] -> Map k [(k, b, c)] -> Map k [(k, b, c)]
forall k a. Ord k => (a -> a -> a) -> k -> a -> Map k a -> Map k a
Map.insertWith [(k, b, c)] -> [(k, b, c)] -> [(k, b, c)]
forall a. [a] -> [a] -> [a]
(++) k
name [(k, b, c)
x]

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

    suggestParams :: (String, b, Token) -> m ()
suggestParams (String
name, b
_, Token
thing) =
        Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
info (Token -> Id
getId Token
thing) Integer
2119 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
            String
"Use " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" \"$@\" if function's $1 should mean script's $1."
    warnForDeclaration :: Token -> String -> m ()
warnForDeclaration Token
func String
name =
        Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn (Token -> Id
getId Token
func) Integer
2120 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
            String
name String -> String -> String
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, Maybe Token -> Token
forall a. HasCallStack => Maybe a -> a
fromJust (Maybe Token -> Token) -> Maybe Token -> Token
forall a b. (a -> b) -> a -> b
$ String -> Map String Token -> Maybe Token
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup String
name Map String Token
functionMap)


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

prop_checkTildeInPath1 :: Bool
prop_checkTildeInPath1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkTildeInPath String
"PATH=\"$PATH:~/bin\""
prop_checkTildeInPath2 :: Bool
prop_checkTildeInPath2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkTildeInPath String
"PATH='~foo/bin'"
prop_checkTildeInPath3 :: Bool
prop_checkTildeInPath3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkTildeInPath String
"PATH=~/bin"
checkTildeInPath :: p -> Token -> m ()
checkTildeInPath p
_ (T_SimpleCommand Id
_ [Token]
vars [Token]
_) =
    (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
checkVar [Token]
vars
  where
    checkVar :: Token -> f ()
checkVar (T_Assignment Id
id AssignmentMode
Assign String
"PATH" [] (T_NormalWord Id
_ [Token]
parts)) =
        Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ((Token -> Bool) -> [Token] -> Bool
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) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
            Id -> Integer -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2147 String
"Literal tilde in PATH works poorly across programs."
    checkVar Token
_ = () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

    hasTilde :: Token -> Bool
hasTilde Token
t = Bool -> Maybe Bool -> Bool
forall a. a -> Maybe a -> a
fromMaybe Bool
False ((Char -> String -> Bool)
-> Maybe Char -> Maybe String -> Maybe Bool
forall (m :: * -> *) a1 a2 r.
Monad m =>
(a1 -> a2 -> r) -> m a1 -> m a2 -> m r
liftM2 Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
elem (Char -> Maybe Char
forall (m :: * -> *) a. Monad m => a -> m a
return Char
'~') ((Token -> Maybe String) -> Token -> Maybe String
getLiteralStringExt (Maybe String -> Token -> Maybe String
forall a b. a -> b -> a
const (Maybe String -> Token -> Maybe String)
-> Maybe String -> Token -> Maybe String
forall a b. (a -> b) -> a -> b
$ String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return String
"") Token
t))
    isQuoted :: Token -> Bool
isQuoted T_DoubleQuoted {} = Bool
True
    isQuoted T_SingleQuoted {} = Bool
True
    isQuoted Token
_ = Bool
False
checkTildeInPath p
_ Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

prop_checkUnsupported3 :: Bool
prop_checkUnsupported3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
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 -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
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 -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnsupported String
"#!/bin/bash\necho \"${ ls; }\""
checkUnsupported :: Parameters -> Token -> f ()
checkUnsupported Parameters
params Token
t =
    Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Bool -> Bool
not ([Shell] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Shell]
support) Bool -> Bool -> Bool
&& (Parameters -> Shell
shellType Parameters
params Shell -> [Shell] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [Shell]
support)) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
        String -> f ()
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 = Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err (Token -> Id
getId Token
t) Integer
2127 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
        String
"To use " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
s String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
", specify #!/usr/bin/env " String -> String -> String
forall a. [a] -> [a] -> [a]
++
            ((Char -> Char) -> String -> String
forall a b. (a -> b) -> [a] -> [b]
map Char -> Char
toLower (String -> String) -> ([Shell] -> String) -> [Shell] -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate String
" or " ([String] -> String) -> ([Shell] -> [String]) -> [Shell] -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Shell -> String) -> [Shell] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map Shell -> String
forall a. Show a => a -> String
show ([Shell] -> String) -> [Shell] -> String
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 -> [CaseType] -> (String, [Shell])
forall (t :: * -> *). Foldable t => t CaseType -> (String, [Shell])
forCase (((CaseType, [Token], [Token]) -> CaseType)
-> [(CaseType, [Token], [Token])] -> [CaseType]
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 CaseType -> t CaseType -> Bool
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 CaseType -> t CaseType -> Bool
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 -> a) -> [a] -> [[a]]
groupWith a -> a
f = (a -> a -> Bool) -> [a] -> [[a]]
forall a. (a -> a -> Bool) -> [a] -> [[a]]
groupBy (a -> a -> Bool
forall a. Eq a => a -> a -> Bool
(==) (a -> a -> Bool) -> (a -> a) -> a -> a -> Bool
forall b c a. (b -> b -> c) -> (a -> b) -> a -> a -> c
`on` a -> a
f)

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


prop_checkSuspiciousIFS1 :: Bool
prop_checkSuspiciousIFS1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSuspiciousIFS String
"IFS=\"\\n\""
prop_checkSuspiciousIFS2 :: Bool
prop_checkSuspiciousIFS2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSuspiciousIFS String
"IFS=$'\\t'"
checkSuspiciousIFS :: Parameters -> Token -> m ()
checkSuspiciousIFS Parameters
params (T_Assignment Id
id AssignmentMode
Assign String
"IFS" [] Token
value) =
    Maybe (m ()) -> m ()
forall (m :: * -> *). Monad m => Maybe (m ()) -> m ()
potentially (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
        String
str <- Token -> Maybe String
getLiteralString Token
value
        m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ String -> m ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
String -> f ()
check String
str
  where
    n :: String
n = if Parameters -> Shell
shellType Parameters
params Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
== Shell
Sh then String
"'<literal linefeed here>'" else String
"$'\\n'"
    t :: String
t = if Parameters -> Shell
shellType Parameters
params Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
== Shell
Sh then String
"\"$(printf '\\t')\"" else String
"$'\\t'"
    check :: String -> m ()
check String
value =
        case String
value of
            String
"\\n" -> String -> m ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
String -> f ()
suggest String
n
            String
"/n" -> String -> m ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
String -> f ()
suggest String
n
            String
"\\t" -> String -> m ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
String -> f ()
suggest String
t
            String
"/t" -> String -> m ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
String -> f ()
suggest String
t
            String
_ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    suggest :: String -> m ()
suggest String
r = Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2141 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
"Did you mean IFS=" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
r String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" ?"
checkSuspiciousIFS Parameters
_ Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkGrepQ1 :: Bool
prop_checkGrepQ1= (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkShouldUseGrepQ String
"[[ $(foo | grep bar) ]]"
prop_checkGrepQ2 :: Bool
prop_checkGrepQ2= (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkShouldUseGrepQ String
"[ -z $(fgrep lol) ]"
prop_checkGrepQ3 :: Bool
prop_checkGrepQ3= (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkShouldUseGrepQ String
"[ -n \"$(foo | zgrep lol)\" ]"
prop_checkGrepQ4 :: Bool
prop_checkGrepQ4= (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkShouldUseGrepQ String
"[ -z $(grep bar | cmd) ]"
prop_checkGrepQ5 :: Bool
prop_checkGrepQ5= (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkShouldUseGrepQ String
"rm $(ls | grep file)"
prop_checkGrepQ6 :: Bool
prop_checkGrepQ6= (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkShouldUseGrepQ String
"[[ -n $(pgrep foo) ]]"
checkShouldUseGrepQ :: p -> Token -> m ()
checkShouldUseGrepQ p
params Token
t =
    Maybe (m ()) -> m ()
forall (m :: * -> *). Monad m => Maybe (m ()) -> m ()
potentially (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ case Token
t of
        TC_Nullary Id
id ConditionType
_ Token
token -> Id -> Bool -> Token -> Maybe (m ())
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 -> Id -> Bool -> Token -> Maybe (m ())
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 -> Id -> Bool -> Token -> Maybe (m ())
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Bool -> Token -> Maybe (m ())
check Id
id Bool
False Token
token
        Token
_ -> String -> Maybe (m ())
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
"! "
        m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ()))
-> (String -> m ()) -> String -> Maybe (m ())
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
style Id
id Integer
2143 (String -> Maybe (m ())) -> String -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$
            String
"Use " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
flip String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" -q instead of " String -> String -> String
forall a. [a] -> [a] -> [a]
++
                String
"comparing output with [ " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
op String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" .. ]."

    getFinalGrep :: Token -> Maybe String
getFinalGrep Token
t = do
        [Token]
cmds <- Token -> Maybe [Token]
forall (m :: * -> *). MonadFail m => Token -> m [Token]
getPipeline Token
t
        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> ([Token] -> Bool) -> [Token] -> Maybe ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not (Bool -> Bool) -> ([Token] -> Bool) -> [Token] -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Token] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null ([Token] -> Maybe ()) -> [Token] -> Maybe ()
forall a b. (a -> b) -> a -> b
$ [Token]
cmds
        String
name <- Token -> Maybe String
getCommandBasename (Token -> Maybe String) -> Token -> Maybe String
forall a b. (a -> b) -> a -> b
$ [Token] -> Token
forall a. [a] -> a
last [Token]
cmds
        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> (String -> Bool) -> String -> Maybe ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Bool
isGrep (String -> Maybe ()) -> String -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String
name
        String -> Maybe String
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 -> [Token] -> m [Token]
forall (m :: * -> *) a. Monad m => a -> m a
return [Token]
cmds
            Token
_ -> String -> m [Token]
forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
"unknown"
    isGrep :: String -> Bool
isGrep = (String -> [String] -> Bool
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 -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting String
"[ -e *.mp3 ]"
prop_checkTestArgumentSplitting2 :: Bool
prop_checkTestArgumentSplitting2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting String
"[[ $a == *b* ]]"
prop_checkTestArgumentSplitting3 :: Bool
prop_checkTestArgumentSplitting3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting String
"[[ *.png == '' ]]"
prop_checkTestArgumentSplitting4 :: Bool
prop_checkTestArgumentSplitting4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting String
"[[ foo == f{o,oo,ooo} ]]"
prop_checkTestArgumentSplitting5 :: Bool
prop_checkTestArgumentSplitting5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting String
"[[ $@ ]]"
prop_checkTestArgumentSplitting6 :: Bool
prop_checkTestArgumentSplitting6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting String
"[ -e $@ ]"
prop_checkTestArgumentSplitting7 :: Bool
prop_checkTestArgumentSplitting7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting String
"[ $@ == $@ ]"
prop_checkTestArgumentSplitting8 :: Bool
prop_checkTestArgumentSplitting8 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting String
"[[ $@ = $@ ]]"
prop_checkTestArgumentSplitting9 :: Bool
prop_checkTestArgumentSplitting9 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting String
"[[ foo =~ bar{1,2} ]]"
prop_checkTestArgumentSplitting10 :: Bool
prop_checkTestArgumentSplitting10 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting String
"[ \"$@\" ]"
prop_checkTestArgumentSplitting11 :: Bool
prop_checkTestArgumentSplitting11 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting String
"[[ \"$@\" ]]"
prop_checkTestArgumentSplitting12 :: Bool
prop_checkTestArgumentSplitting12 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting String
"[ *.png ]"
prop_checkTestArgumentSplitting13 :: Bool
prop_checkTestArgumentSplitting13 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting String
"[ \"$@\" == \"\" ]"
prop_checkTestArgumentSplitting14 :: Bool
prop_checkTestArgumentSplitting14 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting String
"[[ \"$@\" == \"\" ]]"
prop_checkTestArgumentSplitting15 :: Bool
prop_checkTestArgumentSplitting15 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting String
"[[ \"$*\" == \"\" ]]"
prop_checkTestArgumentSplitting16 :: Bool
prop_checkTestArgumentSplitting16 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting String
"[[ -v foo[123] ]]"
prop_checkTestArgumentSplitting17 :: Bool
prop_checkTestArgumentSplitting17 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting String
"#!/bin/ksh\n[ -e foo* ]"
prop_checkTestArgumentSplitting18 :: Bool
prop_checkTestArgumentSplitting18 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting String
"#!/bin/ksh\n[ -d foo* ]"
checkTestArgumentSplitting :: Parameters -> Token -> Writer [TokenComment] ()
checkTestArgumentSplitting :: Parameters -> Token -> WriterT [TokenComment] Identity ()
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 String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"-v"
            then
                Bool
-> WriterT [TokenComment] Identity ()
-> WriterT [TokenComment] Identity ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (ConditionType
typ ConditionType -> ConditionType -> Bool
forall a. Eq a => a -> a -> Bool
== ConditionType
SingleBracket) (WriterT [TokenComment] Identity ()
 -> WriterT [TokenComment] Identity ())
-> WriterT [TokenComment] Identity ()
-> WriterT [TokenComment] Identity ()
forall a b. (a -> b) -> a -> b
$
                    Id -> Integer -> String -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err (Token -> Id
getId Token
token) Integer
2208 (String -> WriterT [TokenComment] Identity ())
-> String -> WriterT [TokenComment] Identity ()
forall a b. (a -> b) -> a -> b
$
                      String
"Use [[ ]] or quote arguments to -v to avoid glob expansion."
            else
                if (ConditionType
typ ConditionType -> ConditionType -> Bool
forall a. Eq a => a -> a -> Bool
== ConditionType
SingleBracket Bool -> Bool -> Bool
&& Parameters -> Shell
shellType Parameters
params Shell -> Shell -> Bool
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.
                    Bool
-> WriterT [TokenComment] Identity ()
-> WriterT [TokenComment] Identity ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
op String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Char
'-'Char -> String -> String
forall a. a -> [a] -> [a]
:Char
cChar -> String -> String
forall a. a -> [a] -> [a]
:[] | Char
c <- String
"bcdfgkprsuwxLhNOGRS" ]) (WriterT [TokenComment] Identity ()
 -> WriterT [TokenComment] Identity ())
-> WriterT [TokenComment] Identity ()
-> WriterT [TokenComment] Identity ()
forall a b. (a -> b) -> a -> b
$
                        Id -> Integer -> String -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn (Token -> Id
getId Token
token) Integer
2245 (String -> WriterT [TokenComment] Identity ())
-> String -> WriterT [TokenComment] Identity ()
forall a b. (a -> b) -> a -> b
$
                            String
op String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" only applies to the first expansion of this glob. Use a loop to check any/all."
                else
                    Id -> Integer -> String -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err (Token -> Id
getId Token
token) Integer
2144 (String -> WriterT [TokenComment] Identity ())
-> String -> WriterT [TokenComment] Identity ()
forall a b. (a -> b) -> a -> b
$
                       String
op String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" doesn't work with globs. Use a for loop."

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

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

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

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

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

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


prop_checkMaskedReturns1 :: Bool
prop_checkMaskedReturns1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkMaskedReturns String
"f() { local a=$(false); }"
prop_checkMaskedReturns2 :: Bool
prop_checkMaskedReturns2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkMaskedReturns String
"declare a=$(false)"
prop_checkMaskedReturns3 :: Bool
prop_checkMaskedReturns3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkMaskedReturns String
"declare a=\"`false`\""
prop_checkMaskedReturns4 :: Bool
prop_checkMaskedReturns4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkMaskedReturns String
"declare a; a=$(false)"
prop_checkMaskedReturns5 :: Bool
prop_checkMaskedReturns5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkMaskedReturns String
"f() { local -r a=$(false); }"
checkMaskedReturns :: p -> Token -> m ()
checkMaskedReturns p
_ t :: Token
t@(T_SimpleCommand Id
id [Token]
_ (Token
cmd:[Token]
rest)) = Maybe (m ()) -> m ()
forall (m :: * -> *). Monad m => Maybe (m ()) -> m ()
potentially (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
    String
name <- Token -> Maybe String
getCommandName Token
t
    Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String
name String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"declare", String
"export"]
        Bool -> Bool -> Bool
|| String
name String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"local" Bool -> Bool -> Bool
&& String
"r" String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` ((Token, String) -> String) -> [(Token, String)] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (Token, String) -> String
forall a b. (a, b) -> b
snd (Token -> [(Token, String)]
getAllFlags Token
t)
    m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
checkArgs [Token]
rest
  where
    checkArgs :: Token -> m ()
checkArgs (T_Assignment Id
id AssignmentMode
_ String
_ [Token]
_ Token
word) | (Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
hasReturn ([Token] -> Bool) -> [Token] -> Bool
forall a b. (a -> b) -> a -> b
$ Token -> [Token]
getWordParts Token
word =
        Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2155 String
"Declare and assign separately to avoid masking return values."
    checkArgs Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

    hasReturn :: Token -> Bool
hasReturn Token
t = case Token
t of
        T_Backticked {} -> Bool
True
        T_DollarExpansion {} -> Bool
True
        Token
_ -> Bool
False
checkMaskedReturns p
_ Token
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkReadWithoutR1 :: Bool
prop_checkReadWithoutR1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkReadWithoutR String
"read -a foo"
prop_checkReadWithoutR2 :: Bool
prop_checkReadWithoutR2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkReadWithoutR String
"read -ar foo"
checkReadWithoutR :: p -> Token -> f ()
checkReadWithoutR p
_ t :: Token
t@T_SimpleCommand {} | Token
t Token -> String -> Bool
`isUnqualifiedCommand` String
"read" =
    Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (String
"r" String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` ((Token, String) -> String) -> [(Token, String)] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (Token, String) -> String
forall a b. (a, b) -> b
snd (Token -> [(Token, String)]
getAllFlags Token
t)) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
        Id -> Integer -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
info (Token -> Id
getId (Token -> Id) -> Token -> Id
forall a b. (a -> b) -> a -> b
$ Token -> Token
getCommandTokenOrThis Token
t) Integer
2162 String
"read without -r will mangle backslashes."
checkReadWithoutR p
_ Token
_ = () -> f ()
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 Writer [TokenComment] Token -> [TokenComment]
forall w a. Writer w a -> w
execWriter (Writer [TokenComment] Token -> [TokenComment])
-> Writer [TokenComment] Token -> [TokenComment]
forall a b. (a -> b) -> a -> b
$ (Token -> WriterT [TokenComment] Identity ())
-> Token -> Writer [TokenComment] Token
forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> Token -> m Token
doAnalysis Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
checkElement Token
root
  where
    checkElement :: Token -> f ()
checkElement t :: Token
t@T_SimpleCommand {} = do
        let name :: String
name = Token -> String
getName Token
t
        Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when(String
name String -> [String] -> Bool
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 String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"pushd", String
"popd"] Bool -> Bool -> Bool
&& (String
"n" String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` ((Token, String) -> String) -> [(Token, String)] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (Token, String) -> String
forall a b. (a, b) -> b
snd (Token -> [(Token, String)]
getAllFlags Token
t)))
            Bool -> Bool -> Bool
&& Bool -> Bool
not ([Token] -> Bool
isCondition ([Token] -> Bool) -> [Token] -> Bool
forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) Token
t)) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
                Id -> Integer -> String -> Fix -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> Fix -> m ()
warnWithFix (Token -> Id
getId Token
t) Integer
2164
                    (String
"Use '" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" ... || exit' or '" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" ... || return' in case " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" fails.")
                    ([Replacement] -> Fix
fixWith [Id -> Parameters -> Integer -> String -> Replacement
replaceEnd (Token -> Id
getId Token
t) Parameters
params Integer
0 String
" || exit"])
    checkElement Token
_ = () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    getName :: Token -> String
getName Token
t = String -> Maybe String -> String
forall a. a -> Maybe a -> a
fromMaybe String
"" (Maybe String -> String) -> Maybe String -> 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 -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
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 -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
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 -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkLoopVariableReassignment String
"for i in *; do for j in *.bar; do true; done; done"
checkLoopVariableReassignment :: Parameters -> Token -> m ()
checkLoopVariableReassignment Parameters
params Token
token =
    Maybe (m ()) -> m ()
forall (m :: * -> *). Monad m => Maybe (m ()) -> m ()
potentially (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ case Token
token of
        T_ForIn {} -> Maybe (m ())
check
        T_ForArithmetic {} -> Maybe (m ())
check
        Token
_ -> Maybe (m ())
forall a. Maybe a
Nothing
  where
    check :: Maybe (m ())
check = do
        String
str <- Token -> Maybe String
loopVariable Token
token
        Token
next <- [Token] -> Maybe Token
forall a. [a] -> Maybe a
listToMaybe ([Token] -> Maybe Token) -> [Token] -> Maybe Token
forall a b. (a -> b) -> a -> b
$ (Token -> Bool) -> [Token] -> [Token]
forall a. (a -> Bool) -> [a] -> [a]
filter (\Token
x -> Token -> Maybe String
loopVariable Token
x Maybe String -> Maybe String -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Maybe String
forall a. a -> Maybe a
Just String
str) [Token]
path
        m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ do
            Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn (Token -> Id
getId Token
token) Integer
2165 String
"This nested loop overrides the index variable of its parent."
            Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn (Token -> Id
getId Token
next)  Integer
2167 String
"This parent loop has its index variable overridden."
    path :: [Token]
path = Int -> [Token] -> [Token]
forall a. Int -> [a] -> [a]
drop Int
1 ([Token] -> [Token]) -> [Token] -> [Token]
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]
_ -> String -> Maybe String
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]
_ -> String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return String
var
            Token
_ -> String -> Maybe String
forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
"not loop"

prop_checkTrailingBracket1 :: Bool
prop_checkTrailingBracket1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkTrailingBracket String
"if -z n ]]; then true; fi "
prop_checkTrailingBracket2 :: Bool
prop_checkTrailingBracket2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkTrailingBracket String
"if [[ -z n ]]; then true; fi "
prop_checkTrailingBracket3 :: Bool
prop_checkTrailingBracket3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkTrailingBracket String
"a || b ] && thing"
prop_checkTrailingBracket4 :: Bool
prop_checkTrailingBracket4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkTrailingBracket String
"run [ foo ]"
prop_checkTrailingBracket5 :: Bool
prop_checkTrailingBracket5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
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]
_) -> Token -> Token -> m ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
Token -> Token -> f ()
check ([Token] -> Token
forall a. [a] -> a
last [Token]
tokens) Token
token
        Token
_ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    check :: Token -> Token -> m ()
check Token
t Token
command =
        case Token
t of
            T_NormalWord Id
id [T_Literal Id
_ String
str] -> Maybe (m ()) -> m ()
forall (m :: * -> *). Monad m => Maybe (m ()) -> m ()
potentially (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
                Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String
str String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [ String
"]]", String
"]" ]
                let opposite :: String
opposite = String -> String
invert String
str
                    parameters :: [String]
parameters = Token -> [String]
oversimplify Token
command
                Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String
opposite String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [String]
parameters
                m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2171 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
                    String
"Found trailing " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
str String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" outside test. Add missing " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
opposite String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" or quote if intentional."
            Token
_ -> () -> m ()
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 -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkReturnAgainstZero String
"[ $? -eq 0 ]"
prop_checkReturnAgainstZero2 :: Bool
prop_checkReturnAgainstZero2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkReturnAgainstZero String
"[[ \"$?\" -gt 0 ]]"
prop_checkReturnAgainstZero3 :: Bool
prop_checkReturnAgainstZero3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkReturnAgainstZero String
"[[ 0 -ne $? ]]"
prop_checkReturnAgainstZero4 :: Bool
prop_checkReturnAgainstZero4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkReturnAgainstZero String
"[[ $? -eq 4 ]]"
prop_checkReturnAgainstZero5 :: Bool
prop_checkReturnAgainstZero5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkReturnAgainstZero String
"[[ 0 -eq $? ]]"
prop_checkReturnAgainstZero6 :: Bool
prop_checkReturnAgainstZero6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkReturnAgainstZero String
"[[ $R -eq 0 ]]"
prop_checkReturnAgainstZero7 :: Bool
prop_checkReturnAgainstZero7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkReturnAgainstZero String
"(( $? == 0 ))"
prop_checkReturnAgainstZero8 :: Bool
prop_checkReturnAgainstZero8 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkReturnAgainstZero String
"(( $? ))"
prop_checkReturnAgainstZero9 :: Bool
prop_checkReturnAgainstZero9 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkReturnAgainstZero String
"(( ! $? ))"
checkReturnAgainstZero :: p -> Token -> m ()
checkReturnAgainstZero p
_ Token
token =
    case Token
token of
        TC_Binary Id
id ConditionType
_ String
_ Token
lhs Token
rhs -> Token -> Token -> m ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
Token -> Token -> f ()
check Token
lhs Token
rhs
        TA_Binary Id
id String
_ Token
lhs Token
rhs -> Token -> Token -> m ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
Token -> Token -> f ()
check Token
lhs Token
rhs
        TA_Unary Id
id String
_ Token
exp ->
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isExitCode Token
exp) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ Id -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Id -> m ()
message (Token -> Id
getId Token
exp)
        TA_Sequence Id
_ [Token
exp] ->
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isExitCode Token
exp) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ Id -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Id -> m ()
message (Token -> Id
getId Token
exp)
        Token
_ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    check :: Token -> Token -> m ()
check Token
lhs Token
rhs =
        if Token -> Bool
isZero Token
rhs Bool -> Bool -> Bool
&& Token -> Bool
isExitCode Token
lhs
        then Id -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Id -> m ()
message (Token -> Id
getId Token
lhs)
        else Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isZero Token
lhs Bool -> Bool -> Bool
&& Token -> Bool
isExitCode Token
rhs) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ Id -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Id -> m ()
message (Token -> Id
getId Token
rhs)
    isZero :: Token -> Bool
isZero Token
t = Token -> Maybe String
getLiteralString Token
t Maybe String -> Maybe String -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Maybe String
forall a. a -> Maybe a
Just String
"0"
    isExitCode :: Token -> Bool
isExitCode Token
t =
        case Token -> [Token]
getWordParts Token
t of
            [exp :: Token
exp@T_DollarBraced {}] -> Token -> String
bracedString Token
exp String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"?"
            [Token]
_ -> Bool
False
    message :: Id -> m ()
message Id
id = Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
style Id
id Integer
2181 String
"Check exit code directly with e.g. 'if mycmd;', not indirectly with $?."

prop_checkRedirectedNowhere1 :: Bool
prop_checkRedirectedNowhere1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectedNowhere String
"> file"
prop_checkRedirectedNowhere2 :: Bool
prop_checkRedirectedNowhere2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectedNowhere String
"> file | grep foo"
prop_checkRedirectedNowhere3 :: Bool
prop_checkRedirectedNowhere3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectedNowhere String
"grep foo | > bar"
prop_checkRedirectedNowhere4 :: Bool
prop_checkRedirectedNowhere4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectedNowhere String
"grep foo > bar"
prop_checkRedirectedNowhere5 :: Bool
prop_checkRedirectedNowhere5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectedNowhere String
"foo | grep bar > baz"
prop_checkRedirectedNowhere6 :: Bool
prop_checkRedirectedNowhere6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectedNowhere String
"var=$(value) 2> /dev/null"
prop_checkRedirectedNowhere7 :: Bool
prop_checkRedirectedNowhere7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkRedirectedNowhere String
"var=$(< file)"
prop_checkRedirectedNowhere8 :: Bool
prop_checkRedirectedNowhere8 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
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] -> Maybe (m ()) -> m ()
forall (m :: * -> *). Monad m => Maybe (m ()) -> m ()
potentially (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
            Token
redir <- Token -> Maybe Token
getDanglingRedirect Token
single
            Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> (Bool -> Bool) -> Bool -> Maybe ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ Token -> Bool
isInExpansion Token
token
            m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn (Token -> Id
getId Token
redir) Integer
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 -> [Token] -> (Token -> m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ [Token]
list ((Token -> m ()) -> m ()) -> (Token -> m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ \Token
x -> Maybe (m ()) -> m ()
forall (m :: * -> *). Monad m => Maybe (m ()) -> m ()
potentially (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
            Token
redir <- Token -> Maybe Token
getDanglingRedirect Token
x
            m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err (Token -> Id
getId Token
redir) Integer
2189 String
"You can't have | between this redirection and the command it should apply to."

        Token
_ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    isInExpansion :: Token -> Bool
isInExpansion Token
t =
        case Int -> [Token] -> [Token]
forall a. Int -> [a] -> [a]
drop Int
1 ([Token] -> [Token]) -> [Token] -> [Token]
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
_ [] []) -> Token -> Maybe Token
forall (m :: * -> *) a. Monad m => a -> m a
return Token
first
            Token
_ -> Maybe 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
verifyTree 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]=\"\" )"
checkArrayAssignmentIndices :: Parameters -> Token -> [TokenComment]
checkArrayAssignmentIndices Parameters
params Token
root =
    (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> Parameters -> Token -> [TokenComment]
forall w t.
Monoid w =>
(t -> Token -> WriterT w Identity ()) -> t -> Token -> w
runNodeAnalysis Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
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 String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
assocs in
                    (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (Bool -> Token -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Bool -> Token -> m ()
checkElement Bool
isAssoc) [Token]
list
            Token
_ -> () -> m ()
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
"") ->
                Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2192 String
"This array element has no value. Remove spaces after = or use \"\" for empty string."
            T_IndexedElement {} ->
                () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

            T_NormalWord Id
_ [Token]
parts ->
                let literalEquals :: [m ()]
literalEquals = do
                    Token
part <- [Token]
parts
                    (Id
id, String
str) <- case Token
part of
                        T_Literal Id
id String
str -> [(Id
id,String
str)]
                        Token
_ -> []
                    Bool -> [()]
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> [()]) -> Bool -> [()]
forall a b. (a -> b) -> a -> b
$ Char
'=' Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
str
                    m () -> [m ()]
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> [m ()]) -> m () -> [m ()]
forall a b. (a -> b) -> a -> b
$ Id -> Integer -> String -> Fix -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> Fix -> m ()
warnWithFix Id
id Integer
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
surroundWidth Id
id Parameters
params String
"\"")
                in
                    if [m ()] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [m ()]
literalEquals Bool -> Bool -> Bool
&& Bool
isAssociative
                    then Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn (Token -> Id
getId Token
t) Integer
2190 String
"Elements in associative arrays need index, e.g. array=( [index]=value ) ."
                    else [m ()] -> m ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ [m ()]
literalEquals

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

prop_checkUnmatchableCases1 :: Bool
prop_checkUnmatchableCases1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnmatchableCases String
"case foo in bar) true; esac"
prop_checkUnmatchableCases2 :: Bool
prop_checkUnmatchableCases2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnmatchableCases String
"case foo-$bar in ??|*) true; esac"
prop_checkUnmatchableCases3 :: Bool
prop_checkUnmatchableCases3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnmatchableCases String
"case foo in foo) true; esac"
prop_checkUnmatchableCases4 :: Bool
prop_checkUnmatchableCases4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
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 -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
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 -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnmatchableCases String
"case $f in ?*) true;; *) false;; esac"
prop_checkUnmatchableCases7 :: Bool
prop_checkUnmatchableCases7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
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 -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
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 -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
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  = ((CaseType, [Token], [Token]) -> [Token])
-> [(CaseType, [Token], [Token])] -> [Token]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (CaseType, [Token], [Token]) -> [Token]
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 = ((CaseType, [Token], [Token]) -> [Token])
-> [(CaseType, [Token], [Token])] -> [Token]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (CaseType, [Token], [Token]) -> [Token]
forall a b c. (a, b, c) -> b
snd3 ([(CaseType, [Token], [Token])] -> [Token])
-> [(CaseType, [Token], [Token])] -> [Token]
forall a b. (a -> b) -> a -> b
$ ((CaseType, [Token], [Token]) -> Bool)
-> [(CaseType, [Token], [Token])] -> [(CaseType, [Token], [Token])]
forall a. (a -> Bool) -> [a] -> [a]
filter (\(CaseType, [Token], [Token])
x -> (CaseType, [Token], [Token]) -> CaseType
forall a b c. (a, b, c) -> a
fst3 (CaseType, [Token], [Token])
x CaseType -> CaseType -> Bool
forall a. Eq a => a -> a -> Bool
== CaseType
CaseBreak) [(CaseType, [Token], [Token])]
list

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

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

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

        Token
_ -> () -> m ()
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 -> m ()
check [PseudoGlob]
target Token
candidate = Maybe (m ()) -> m ()
forall (m :: * -> *). Monad m => Maybe (m ()) -> m ()
potentially (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
        [PseudoGlob]
candidateGlob <- Token -> Maybe [PseudoGlob]
wordToPseudoGlob Token
candidate
        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> (Bool -> Bool) -> Bool -> Maybe ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ [PseudoGlob] -> [PseudoGlob] -> Bool
pseudoGlobsCanOverlap [PseudoGlob]
target [PseudoGlob]
candidateGlob
        m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn (Token -> Id
getId Token
candidate) Integer
2195
                    String
"This pattern will never match the case statement's word. Double check them."

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

        valids :: [(Token, [PseudoGlob])]
valids = ((Token, Maybe [PseudoGlob]) -> [(Token, [PseudoGlob])])
-> t (Token, Maybe [PseudoGlob]) -> [(Token, [PseudoGlob])]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (Token, Maybe [PseudoGlob]) -> [(Token, [PseudoGlob])]
forall a b. (a, Maybe b) -> [(a, b)]
f t (Token, Maybe [PseudoGlob])
rest
        f :: (a, Maybe b) -> [(a, b)]
f (a
x, Just b
y) = [(a
x,b
y)]
        f (a, Maybe b)
_ = []
    checkDoms ((Token, Maybe [PseudoGlob]), t (Token, Maybe [PseudoGlob]))
_ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()


prop_checkSubshellAsTest1 :: Bool
prop_checkSubshellAsTest1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkSubshellAsTest String
"( -e file )"
prop_checkSubshellAsTest2 :: Bool
prop_checkSubshellAsTest2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkSubshellAsTest String
"( 1 -gt 2 )"
prop_checkSubshellAsTest3 :: Bool
prop_checkSubshellAsTest3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkSubshellAsTest String
"( grep -c foo bar )"
prop_checkSubshellAsTest4 :: Bool
prop_checkSubshellAsTest4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkSubshellAsTest String
"[ 1 -gt 2 ]"
prop_checkSubshellAsTest5 :: Bool
prop_checkSubshellAsTest5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkSubshellAsTest String
"( -e file && -x file )"
prop_checkSubshellAsTest6 :: Bool
prop_checkSubshellAsTest6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkSubshellAsTest String
"( -e file || -x file && -t 1 )"
prop_checkSubshellAsTest7 :: Bool
prop_checkSubshellAsTest7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkSubshellAsTest String
"( ! -d file )"
checkSubshellAsTest :: p -> Token -> m ()
checkSubshellAsTest p
_ Token
t =
    case Token
t of
        T_Subshell Id
id [Token
w] -> Id -> Token -> m ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
Id -> Token -> f ()
check Id
id Token
w
        Token
_ -> () -> m ()
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]
_))]) ->
            Id -> Token -> Token -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Token -> Token -> m ()
checkParams Id
id Token
first Token
second
        Token
_ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()


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


prop_checkSplittingInArrays1 :: Bool
prop_checkSplittingInArrays1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSplittingInArrays String
"a=( $var )"
prop_checkSplittingInArrays2 :: Bool
prop_checkSplittingInArrays2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSplittingInArrays String
"a=( $(cmd) )"
prop_checkSplittingInArrays3 :: Bool
prop_checkSplittingInArrays3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSplittingInArrays String
"a=( \"$var\" )"
prop_checkSplittingInArrays4 :: Bool
prop_checkSplittingInArrays4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSplittingInArrays String
"a=( \"$(cmd)\" )"
prop_checkSplittingInArrays5 :: Bool
prop_checkSplittingInArrays5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSplittingInArrays String
"a=( $! $$ $# )"
prop_checkSplittingInArrays6 :: Bool
prop_checkSplittingInArrays6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSplittingInArrays String
"a=( ${#arr[@]} )"
prop_checkSplittingInArrays7 :: Bool
prop_checkSplittingInArrays7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSplittingInArrays String
"a=( foo{1,2} )"
prop_checkSplittingInArrays8 :: Bool
prop_checkSplittingInArrays8 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
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 -> (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
check [Token]
elements
        Token
_ -> () -> m ()
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 -> (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
checkPart [Token]
parts
        Token
_ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    checkPart :: Token -> m ()
checkPart Token
part = case Token
part of
        T_DollarExpansion Id
id [Token]
_ -> Id -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Id -> m ()
forCommand Id
id
        T_DollarBraceCommandExpansion Id
id [Token]
_ -> Id -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Id -> m ()
forCommand Id
id
        T_Backticked Id
id [Token]
_ -> Id -> m ()
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
&& Bool -> Bool
not (String -> String
getBracedReference (Token -> String
bracedString Token
part) String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
variablesWithoutSpaces)
            -> Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2206 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
                if Parameters -> Shell
shellType Parameters
params Shell -> Shell -> Bool
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
_ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

    forCommand :: Id -> m ()
forCommand Id
id =
        Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2207 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
            if Parameters -> Shell
shellType Parameters
params Shell -> Shell -> Bool
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 -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkRedirectionToNumber String
"( 1 > 2 )"
prop_checkRedirectionToNumber2 :: Bool
prop_checkRedirectionToNumber2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkRedirectionToNumber String
"foo 1>2"
prop_checkRedirectionToNumber3 :: Bool
prop_checkRedirectionToNumber3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkRedirectionToNumber String
"echo foo > '2'"
prop_checkRedirectionToNumber4 :: Bool
prop_checkRedirectionToNumber4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkRedirectionToNumber String
"foo 1>&2"
checkRedirectionToNumber :: p -> Token -> m ()
checkRedirectionToNumber p
_ Token
t = case Token
t of
    T_IoFile Id
id Token
_ Token
word -> Maybe (m ()) -> m ()
forall (m :: * -> *). Monad m => Maybe (m ()) -> m ()
potentially (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
        String
file <- Token -> Maybe String
getUnquotedLiteral Token
word
        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ (Char -> Bool) -> String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isDigit String
file
        m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id Integer
2210 String
"This is a file redirection. Was it supposed to be a comparison or fd operation?"
    Token
_ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

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


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


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

prop_checkPipeToNowhere1 :: Bool
prop_checkPipeToNowhere1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkPipeToNowhere String
"foo | echo bar"
prop_checkPipeToNowhere2 :: Bool
prop_checkPipeToNowhere2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkPipeToNowhere String
"basename < file.txt"
prop_checkPipeToNowhere3 :: Bool
prop_checkPipeToNowhere3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkPipeToNowhere String
"printf 'Lol' <<< str"
prop_checkPipeToNowhere4 :: Bool
prop_checkPipeToNowhere4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkPipeToNowhere String
"printf 'Lol' << eof\nlol\neof\n"
prop_checkPipeToNowhere5 :: Bool
prop_checkPipeToNowhere5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
checkPipeToNowhere String
"echo foo | xargs du"
prop_checkPipeToNowhere6 :: Bool
prop_checkPipeToNowhere6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
checkPipeToNowhere String
"ls | echo $(cat)"
prop_checkPipeToNowhere7 :: Bool
prop_checkPipeToNowhere7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
checkPipeToNowhere String
"echo foo | var=$(cat) ls"
prop_checkPipeToNowhere8 :: Bool
prop_checkPipeToNowhere8 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkPipeToNowhere String
"foo | true"
prop_checkPipeToNowhere9 :: Bool
prop_checkPipeToNowhere9 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
checkPipeToNowhere String
"mv -i f . < /dev/stdin"
checkPipeToNowhere :: Parameters -> Token -> WriterT [TokenComment] Identity ()
checkPipeToNowhere :: Parameters -> Token -> WriterT [TokenComment] Identity ()
checkPipeToNowhere Parameters
_ Token
t =
    case Token
t of
        T_Pipeline Id
_ [Token]
_ (Token
first:[Token]
rest) -> (Token -> WriterT [TokenComment] Identity ())
-> [Token] -> WriterT [TokenComment] Identity ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
checkPipe [Token]
rest
        T_Redirecting Id
_ [Token]
redirects Token
cmd -> Bool
-> WriterT [TokenComment] Identity ()
-> WriterT [TokenComment] Identity ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ((Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
redirectsStdin [Token]
redirects) (WriterT [TokenComment] Identity ()
 -> WriterT [TokenComment] Identity ())
-> WriterT [TokenComment] Identity ()
-> WriterT [TokenComment] Identity ()
forall a b. (a -> b) -> a -> b
$ Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
checkRedir Token
cmd
        Token
_ -> () -> WriterT [TokenComment] Identity ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    checkPipe :: Token -> m ()
checkPipe Token
redir = Maybe (m ()) -> m ()
forall (m :: * -> *). Monad m => Maybe (m ()) -> m ()
potentially (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
        Token
cmd <- Token -> Maybe Token
getCommand Token
redir
        String
name <- Token -> Maybe String
getCommandBasename Token
cmd
        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String
name String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
nonReadingCommands
        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> (Bool -> Bool) -> Bool -> Maybe ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ Token -> Bool
hasAdditionalConsumers Token
cmd
        -- Confusing echo for cat is so common that it's worth a special case
        let suggestion :: String
suggestion =
                if String
name String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"echo"
                then String
"Did you want 'cat' instead?"
                else String
"Wrong command or missing xargs?"
        m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn (Token -> Id
getId Token
cmd) Integer
2216 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
            String
"Piping to '" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"', a command that doesn't read stdin. " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
suggestion

    checkRedir :: Token -> m ()
checkRedir Token
cmd = Maybe (m ()) -> m ()
forall (m :: * -> *). Monad m => Maybe (m ()) -> m ()
potentially (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
        String
name <- Token -> Maybe String
getCommandBasename Token
cmd
        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String
name String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
nonReadingCommands
        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> (Bool -> Bool) -> Bool -> Maybe ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ Token -> Bool
hasAdditionalConsumers Token
cmd
        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> (Bool -> Bool) -> Bool -> Maybe ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String
name String -> [String] -> Bool
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 String -> String -> Bool
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?"
        m () -> Maybe (m ())
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn (Token -> Id
getId Token
cmd) Integer
2217 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
            String
"Redirecting to '" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"', a command that doesn't read stdin. " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
suggestion

    -- Could any words in a SimpleCommand consume stdin (e.g. echo "$(cat)")?
    hasAdditionalConsumers :: Token -> Bool
hasAdditionalConsumers Token
t = Bool -> Maybe Bool -> Bool
forall a. a -> Maybe a -> a
fromMaybe Bool
True (Maybe Bool -> Bool) -> Maybe Bool -> Bool
forall a b. (a -> b) -> a -> b
$ do
        (Token -> Maybe ()) -> Token -> Maybe Token
forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> Token -> m Token
doAnalysis (Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> (Token -> Bool) -> Token -> Maybe ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not (Bool -> Bool) -> (Token -> Bool) -> Token -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> Bool
mayConsume) Token
t
        Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
False

    mayConsume :: Token -> Bool
mayConsume Token
t =
        case Token
t of
            T_ProcSub {} -> Bool
True
            T_Backticked {} -> Bool
True
            T_DollarExpansion {} -> Bool
True
            Token
_ -> Bool
False

    redirectsStdin :: Token -> Bool
redirectsStdin Token
t =
        case Token
t of
            T_FdRedirect Id
_ String
_ (T_IoFile Id
_ T_Less {} Token
_) -> Bool
True
            T_FdRedirect Id
_ String
_ T_HereDoc {} -> Bool
True
            T_FdRedirect Id
_ String
_ T_HereString {} -> Bool
True
            Token
_ -> Bool
False

prop_checkUseBeforeDefinition1 :: Bool
prop_checkUseBeforeDefinition1 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall t. t -> Token -> [TokenComment]
checkUseBeforeDefinition String
"f; f() { true; }"
prop_checkUseBeforeDefinition2 :: Bool
prop_checkUseBeforeDefinition2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall t. t -> Token -> [TokenComment]
checkUseBeforeDefinition String
"f() { true; }; f"
prop_checkUseBeforeDefinition3 :: Bool
prop_checkUseBeforeDefinition3 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
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 Parameters -> Token -> [TokenComment]
forall t. t -> Token -> [TokenComment]
checkUseBeforeDefinition String
"mycmd || mycmd() { f; }"
checkUseBeforeDefinition :: p -> Token -> [TokenComment]
checkUseBeforeDefinition p
_ Token
t =
    WriterT [TokenComment] Identity () -> [TokenComment]
forall w a. Writer w a -> w
execWriter (WriterT [TokenComment] Identity () -> [TokenComment])
-> WriterT [TokenComment] Identity () -> [TokenComment]
forall a b. (a -> b) -> a -> b
$ StateT (Map String Token) (WriterT [TokenComment] Identity) ()
-> Map String Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *) s a. Monad m => StateT s m a -> s -> m a
evalStateT ((Token
 -> StateT (Map String Token) (WriterT [TokenComment] Identity) ())
-> [Token]
-> StateT (Map String Token) (WriterT [TokenComment] Identity) ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token
-> StateT (Map String Token) (WriterT [TokenComment] Identity) ()
forall (m :: * -> *).
(MonadState (Map String Token) m, MonadWriter [TokenComment] m) =>
Token -> m ()
examine ([Token]
 -> StateT (Map String Token) (WriterT [TokenComment] Identity) ())
-> [Token]
-> StateT (Map String Token) (WriterT [TokenComment] Identity) ()
forall a b. (a -> b) -> a -> b
$ [Token]
revCommands) Map String Token
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
_)] ->
            (Map String Token -> Map String Token) -> m ()
forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify ((Map String Token -> Map String Token) -> m ())
-> (Map String Token -> Map String Token) -> m ()
forall a b. (a -> b) -> a -> b
$ String -> Token -> Map String Token -> Map String Token
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 <- m (Map String Token)
forall s (m :: * -> *). MonadState s m => m s
get
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Map String Token -> Bool
forall k a. Map k a -> Bool
Map.null Map String Token
m) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
                (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (Map String Token -> Token -> m ()
forall (m :: * -> *) a.
MonadWriter [TokenComment] m =>
Map String a -> Token -> m ()
checkUsage Map String Token
m) ([Token] -> m ()) -> [Token] -> m ()
forall a b. (a -> b) -> a -> b
$ (Token -> [Token]) -> [Token] -> [Token]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Token -> [Token]
recursiveSequences [Token]
cmds
        Token
_ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

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

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

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

prop_checkSubshelledTests1 :: Bool
prop_checkSubshelledTests1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSubshelledTests String
"a && ( [ b ] || ! [ c ] )"
prop_checkSubshelledTests2 :: Bool
prop_checkSubshelledTests2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSubshelledTests String
"( [ a ] )"
prop_checkSubshelledTests3 :: Bool
prop_checkSubshelledTests3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSubshelledTests String
"( [ a ] && [ b ] || test c )"
prop_checkSubshelledTests4 :: Bool
prop_checkSubshelledTests4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSubshelledTests String
"( [ a ] && { [ b ] && [ c ]; } )"
checkSubshelledTests :: Parameters -> Token -> m ()
checkSubshelledTests Parameters
params Token
t =
    case Token
t of
        T_Subshell Id
id [Token]
list | (Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Token -> Bool
isTestStructure [Token]
list ->
            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) ->
                    Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
style Id
id Integer
2233 String
"Remove superfluous (..) around condition."

                -- Special case for ([ x ])
                ()
_ | [Token] -> Bool
isSingleTest [Token]
list ->
                    Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
style Id
id Integer
2234 String
"Remove superfluous (..) around test command."

                -- General case for ([ x ] || [ y ] && etc)
                ()
_ -> Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
style Id
id Integer
2235 String
"Use { ..; } instead of (..) to avoid subshell overhead."
        Token
_ -> () -> m ()
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

    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 -> (Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Token -> Bool
isTestStructure [Token]
ts
                    T_Subshell   Id
_ [Token]
ts -> (Token -> Bool) -> [Token] -> Bool
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 (Token -> Bool) -> [Token] -> [Token]
forall a. (a -> Bool) -> [a] -> [a]
dropWhile Token -> Bool
skippable (Int -> [Token] -> [Token]
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

    -- 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 -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkInvertedStringTest String
"[ ! -z $var ]"
prop_checkInvertedStringTest2 :: Bool
prop_checkInvertedStringTest2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkInvertedStringTest String
"! [[ -n $var ]]"
prop_checkInvertedStringTest3 :: Bool
prop_checkInvertedStringTest3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkInvertedStringTest String
"! [ -x $var ]"
prop_checkInvertedStringTest4 :: Bool
prop_checkInvertedStringTest4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkInvertedStringTest String
"[[ ! -w $var ]]"
prop_checkInvertedStringTest5 :: Bool
prop_checkInvertedStringTest5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
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" -> Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
style (Token -> Id
getId Token
t) Integer
2236 String
"Use -z instead of ! -n."
                String
"-z" -> Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
style (Token -> Id
getId Token
t) Integer
2236 String
"Use -n instead of ! -z."
                String
_ -> () -> m ()
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" -> Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
style (Token -> Id
getId Token
t) Integer
2237 String
"Use [ -z .. ] instead of ! [ -n .. ]."
                String
"-z" -> Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
style (Token -> Id
getId Token
t) Integer
2237 String
"Use [ -n .. ] instead of ! [ -z .. ]."
                String
_ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
        Token
_ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

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

prop_checkNullaryExpansionTest1 :: Bool
prop_checkNullaryExpansionTest1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNullaryExpansionTest String
"[[ $(a) ]]"
prop_checkNullaryExpansionTest2 :: Bool
prop_checkNullaryExpansionTest2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNullaryExpansionTest String
"[[ $a ]]"
prop_checkNullaryExpansionTest3 :: Bool
prop_checkNullaryExpansionTest3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNullaryExpansionTest String
"[[ $a=1 ]]"
prop_checkNullaryExpansionTest4 :: Bool
prop_checkNullaryExpansionTest4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNullaryExpansionTest String
"[[ -n $(a) ]]"
prop_checkNullaryExpansionTest5 :: Bool
prop_checkNullaryExpansionTest5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkNullaryExpansionTest String
"[[ \"$a$b\" ]]"
prop_checkNullaryExpansionTest6 :: Bool
prop_checkNullaryExpansionTest6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
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 ->
                    Id -> Integer -> String -> Fix -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> Fix -> m ()
styleWithFix Id
id Integer
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 | (Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all (Bool -> Bool
not (Bool -> Bool) -> (Token -> Bool) -> Token -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> Bool
isConstant) [Token]
x ->
                    Id -> Integer -> String -> Fix -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> Fix -> m ()
styleWithFix Id
id Integer
2244 String
"Prefer explicit -n to check non-empty string (or use =/-ne to check boolean/integer)." Fix
fix
                [Token]
_ -> () -> m ()
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 -> Integer -> String -> Replacement
replaceStart Id
id Parameters
params Integer
0 String
"-n "]
        Token
_ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()


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

prop_checkDefaultCase1 :: Bool
prop_checkDefaultCase1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkDefaultCase String
"case $1 in a) true ;; esac"
prop_checkDefaultCase2 :: Bool
prop_checkDefaultCase2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkDefaultCase String
"case $1 in ?*?) true ;; *? ) true ;; esac"
prop_checkDefaultCase3 :: Bool
prop_checkDefaultCase3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
checkDefaultCase String
"case $1 in x|*) true ;; esac"
prop_checkDefaultCase4 :: Bool
prop_checkDefaultCase4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *) p.
MonadWriter [TokenComment] f =>
p -> Token -> f ()
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 ->
            Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (((CaseType, [Token], [Token]) -> Bool)
-> [(CaseType, [Token], [Token])] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (CaseType, [Token], [Token]) -> Bool
forall (t :: * -> *) a c. Foldable t => (a, t Token, c) -> Bool
canMatchAny [(CaseType, [Token], [Token])]
list) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
                Id -> Integer -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
info Id
id Integer
2249 String
"Consider adding a default *) case, even if it just exits with error."
        Token
_ -> () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    canMatchAny :: (a, t Token, c) -> Bool
canMatchAny (a
_, t Token
list, c
_) = (Token -> Bool) -> t Token -> Bool
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 = Bool -> Maybe Bool -> Bool
forall a. a -> Maybe a -> a
fromMaybe Bool
False (Maybe Bool -> Bool) -> Maybe Bool -> Bool
forall a b. (a -> b) -> a -> b
$ do
        [PseudoGlob]
pg <- Token -> Maybe [PseudoGlob]
wordToExactPseudoGlob Token
pat
        Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> Maybe Bool) -> Bool -> Maybe Bool
forall a b. (a -> b) -> a -> b
$ [PseudoGlob] -> [PseudoGlob] -> Bool
pseudoGlobIsSuperSetof [PseudoGlob]
pg [PseudoGlob
PGMany]

prop_checkUselessBang1 :: Bool
prop_checkUselessBang1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUselessBang String
"set -e; ! true; rest"
prop_checkUselessBang2 :: Bool
prop_checkUselessBang2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUselessBang String
"! true; rest"
prop_checkUselessBang3 :: Bool
prop_checkUselessBang3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUselessBang String
"set -e; while true; do ! true; done"
prop_checkUselessBang4 :: Bool
prop_checkUselessBang4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUselessBang String
"set -e; if ! true; then true; fi"
prop_checkUselessBang5 :: Bool
prop_checkUselessBang5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUselessBang String
"set -e; ( ! true )"
prop_checkUselessBang6 :: Bool
prop_checkUselessBang6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUselessBang String
"set -e; { ! true; }"
prop_checkUselessBang7 :: Bool
prop_checkUselessBang7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUselessBang String
"set -e; x() { ! [ x ]; }"
prop_checkUselessBang8 :: Bool
prop_checkUselessBang8 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUselessBang String
"set -e; if { ! true; }; then true; fi"
prop_checkUselessBang9 :: Bool
prop_checkUselessBang9 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
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 = Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Parameters -> Bool
hasSetE Parameters
params) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$ (Token -> f ()) -> [Token] -> f ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> f ()
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 (Bool -> Bool) -> Bool -> Bool
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) ->
                TokenComment -> m ()
forall a (m :: * -> *). (NFData a, MonadWriter [a] m) => a -> m ()
addComment (TokenComment -> m ()) -> TokenComment -> m ()
forall a b. (a -> b) -> a -> b
$ Severity -> Id -> Integer -> String -> Fix -> TokenComment
makeCommentWithFix Severity
InfoC Id
id Integer
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 -> Integer -> String -> Replacement
replaceStart Id
id Parameters
params Integer
1 String
"", Id -> Parameters -> Integer -> String -> Replacement
replaceEnd (Token -> Id
getId Token
cmd) Parameters
params Integer
0 String
" && exit 1"])
            Token
_ -> () -> m ()
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 -> [Token] -> [Token]
forall a. [a] -> [a]
dropLast [Token]
list
            T_BraceGroup Id
_ [Token]
list -> if Token -> Bool
isFunctionBody Token
t then [Token] -> [Token]
forall a. [a] -> [a]
dropLast [Token]
list else [Token]
list
            T_Subshell Id
_ [Token]
list -> [Token] -> [Token]
forall a. [a] -> [a]
dropLast [Token]
list
            T_WhileExpression Id
_ [Token]
conds [Token]
cmds -> [Token] -> [Token]
forall a. [a] -> [a]
dropLast [Token]
conds [Token] -> [Token] -> [Token]
forall a. [a] -> [a] -> [a]
++ [Token]
cmds
            T_UntilExpression Id
_ [Token]
conds [Token]
cmds -> [Token] -> [Token]
forall a. [a] -> [a]
dropLast [Token]
conds [Token] -> [Token] -> [Token]
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 ->
                (([Token], [Token]) -> [Token]) -> [([Token], [Token])] -> [Token]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap ([Token] -> [Token]
forall a. [a] -> [a]
dropLast ([Token] -> [Token])
-> (([Token], [Token]) -> [Token]) -> ([Token], [Token]) -> [Token]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ([Token], [Token]) -> [Token]
forall a b. (a, b) -> a
fst) [([Token], [Token])]
conds [Token] -> [Token] -> [Token]
forall a. [a] -> [a] -> [a]
++ (([Token], [Token]) -> [Token]) -> [([Token], [Token])] -> [Token]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap ([Token], [Token]) -> [Token]
forall a b. (a, b) -> b
snd [([Token], [Token])]
conds [Token] -> [Token] -> [Token]
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 a -> [a] -> [a]
forall a. a -> [a] -> [a]
: [a] -> [a]
dropLast [a]
rest
            [a]
_ -> []

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