{-
    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 options :: 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 "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 (\c :: 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 spec :: AnalysisSpec
spec list :: [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 (\f :: Parameters -> Token -> [TokenComment]
f -> Parameters -> Token -> [TokenComment]
f Parameters
params Token
root) [Parameters -> Token -> [TokenComment]]
list

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

checkList :: t (t -> [b]) -> t -> [b]
checkList l :: t (t -> [b])
l t :: t
t = ((t -> [b]) -> [b]) -> t (t -> [b]) -> [b]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (\f :: 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 f :: t -> Token -> WriterT w Identity ()
f p :: t
p t :: 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 checkList :: 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
        (\p :: t
p t :: 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_ ((\ f :: 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
. (\ f :: 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 (desc :: CheckDescription
desc, check :: 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 = "quote-safe-variables",
        cdDescription :: String
cdDescription = "Suggest quoting variables without metacharacters",
        cdPositive :: String
cdPositive = "var=hello; echo $var",
        cdNegative :: String
cdNegative = "var=hello; echo \"$var\""
    }, Parameters -> Token -> [TokenComment]
checkVerboseSpacefulness)

    ,(CheckDescription
newCheckDescription {
        cdName :: String
cdName = "avoid-nullary-conditions",
        cdDescription :: String
cdDescription = "Suggest explicitly using -n in `[ $var ]`",
        cdPositive :: String
cdPositive = "[ \"$var\" ]",
        cdNegative :: String
cdNegative = "[ -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 = "add-default-case",
        cdDescription :: String
cdDescription = "Suggest adding a default case in `case` statements",
        cdPositive :: String
cdPositive = "case $? in 0) echo 'Success';; esac",
        cdNegative :: String
cdNegative = "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 = "require-variable-braces",
        cdDescription :: String
cdDescription = "Suggest putting braces around all variable references",
        cdPositive :: String
cdPositive = "var=hello; echo $var",
        cdNegative :: String
cdNegative = "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 = "check-unassigned-uppercase",
        cdDescription :: String
cdDescription = "Warn when uppercase variables are unassigned",
        cdPositive :: String
cdPositive = "echo $VAR",
        cdNegative :: String
cdNegative = "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 (desc :: CheckDescription
desc, check :: b
check) = (CheckDescription -> String
cdName CheckDescription
desc, b
check)

wouldHaveBeenGlob :: t Char -> Bool
wouldHaveBeenGlob s :: t Char
s = '*' 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 f :: Parameters -> Token -> WriterT [TokenComment] Identity ()
f s :: 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 f :: Parameters -> Token -> WriterT [TokenComment] Identity ()
f s :: 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 f :: Parameters -> Token -> [TokenComment]
f s :: 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 f :: Parameters -> Token -> [TokenComment]
f s :: 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 str :: String
str f :: Token -> [Token] -> f ()
f t :: Token
t@(T_SimpleCommand id :: Id
id _ (cmd :: Token
cmd:rest :: [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 _ _ _ = () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

checkUnqualifiedCommand :: String -> (Token -> [Token] -> f ()) -> Token -> f ()
checkUnqualifiedCommand str :: String
str f :: Token -> [Token] -> f ()
f t :: Token
t@(T_SimpleCommand id :: Id
id _ (cmd :: Token
cmd:rest :: [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 _ _ _ = () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()


checkNode :: (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Maybe Bool
checkNode f :: 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 f :: Parameters -> Token -> [TokenComment]
f s :: 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 :: [a]
b
    = [Int] -> Int
forall a. [a] -> a
last (if Int
lab Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== 0 then [Int]
mainDiag
            else if Int
lab Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> 0 then [[Int]]
lowers [[Int]] -> Int -> [Int]
forall a. [a] -> Int -> a
!! (Int
lab Int -> Int -> Int
forall a. Num a => a -> a -> a
- 1)
                 else{- < 0 -}   [[Int]]
uppers [[Int]] -> Int -> [Int]
forall a. [a] -> Int -> a
!! (-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) (-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 :: [[a]]
diags = []
          eachDiag a :: [a]
a (bch :: a
bch:bs :: [a]
bs) (lastDiag :: [a]
lastDiag:diags :: [[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]
b diagAbove :: [a]
diagAbove diagBelow :: [a]
diagBelow = [a]
thisdiag
              where doDiag :: [a] -> [a] -> a -> [a] -> [a] -> [a]
doDiag [] b :: [a]
b nw :: a
nw n :: [a]
n w :: [a]
w = []
                    doDiag a :: [a]
a [] nw :: a
nw n :: [a]
n w :: [a]
w = []
                    doDiag (ach :: a
ach:as :: [a]
as) (bch :: a
bch:bs :: [a]
bs) nw :: a
nw n :: [a]
n w :: [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 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 = 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 x :: a
x y :: a
y z :: 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 params :: 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 [_] = Bool
False
isCondition (child :: Token
child:parent :: Token
parent:rest :: [Token]
rest) =
    case Token
child of
        T_BatsTest {} -> Bool
True -- count anything in a @test as conditional
        _ -> 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 t :: Token
t =
        case Token
t of
            T_AndIf _ left :: Token
left right :: Token
right -> [Token
left]
            T_OrIf id :: Id
id left :: Token
left right :: Token
right -> [Token
left]
            T_IfExpression id :: Id
id conditions :: [([Token], [Token])]
conditions elses :: [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 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
id c :: [Token]
c l :: [Token]
l -> Int -> [Token] -> [Token]
forall a. Int -> [a] -> [a]
take 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
id c :: [Token]
c l :: [Token]
l -> Int -> [Token] -> [Token]
forall a. Int -> [a] -> [a]
take 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
            _ -> []

-- helpers to build replacements
replaceStart :: Id -> Parameters -> Integer -> String -> Replacement
replaceStart id :: Id
id params :: Parameters
params n :: Integer
n r :: String
r =
    let tp :: Map Id (Position, Position)
tp = Parameters -> Map Id (Position, Position)
tokenPositions Parameters
params
        (start :: Position
start, _) = 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
id params :: Parameters
params n :: Integer
n r :: String
r =
    let tp :: Map Id (Position, Position)
tp = Parameters -> Map Id (Position, Position)
tokenPositions Parameters
params
        (_, end :: 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
id params :: Parameters
params s :: String
s = [Replacement] -> Fix
fixWith [Id -> Parameters -> Integer -> String -> Replacement
replaceStart Id
id Parameters
params 0 String
s, Id -> Parameters -> Integer -> String -> Replacement
replaceEnd Id
id Parameters
params 0 String
s]
fixWith :: [Replacement] -> Fix
fixWith fixes :: [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 "n=$(echo $foo | wc -c)"
checkEchoWc :: p -> Token -> f ()
checkEchoWc _ (T_Pipeline id :: Id
id _ [a :: Token
a, b :: 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
== ["echo", "${VAR}"]) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
        case [String]
bcmd of
            ["wc", "-c"] -> f ()
countMsg
            ["wc", "-m"] -> f ()
countMsg
            _ -> () -> 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 2000 "See if you can use ${#variable} instead."
checkEchoWc _ _ = () -> 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 "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 "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 "A=foo"
checkPipedAssignment :: p -> Token -> m ()
checkPipedAssignment _ (T_Pipeline _ _ (T_Redirecting _ _ (T_SimpleCommand id :: Id
id (_:_) []):_:_)) =
    Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id 2036 "If you wanted to assign the output of the pipeline, use a=$(b | c) ."
checkPipedAssignment _ _ = () -> 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 "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 "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 "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 "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 "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 "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 "here=pwd"
checkAssignAteCommand :: p -> Token -> m ()
checkAssignAteCommand _ (T_SimpleCommand id :: Id
id (T_Assignment _ _ _ _ assignmentTerm :: Token
assignmentTerm:[]) list :: [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 2037 "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 2209 "Use var=$(command) to assign output (or quote to assign string)."
  where
    isCommonCommand :: Maybe String -> Bool
isCommonCommand (Just s :: String
s) = String
s String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
commonCommands
    isCommonCommand _ = Bool
False
    firstWordIsArg :: [Token] -> Bool
firstWordIsArg list :: [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
!!! 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 _ _ = () -> 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 "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 "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 "foo + opts"
checkArithmeticOpCommand :: p -> Token -> m ()
checkArithmeticOpCommand _ (T_SimpleCommand id :: Id
id [T_Assignment {}] (firstWord :: Token
firstWord:_)) =
    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 op :: 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` ["+", "-", "*", "/"]) (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) 2099 (String -> f ()) -> String -> f ()
forall a b. (a -> b) -> a -> b
$
                "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]
++ " 2))"
checkArithmeticOpCommand _ _ = () -> 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 "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 "n=2; i=n*2"
checkWrongArithmeticAssignment :: Parameters -> Token -> m ()
checkWrongArithmeticAssignment params :: Parameters
params (T_SimpleCommand id :: Id
id (T_Assignment _ _ _ _ val :: 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
!!! 0
    String
op <- [String]
match [String] -> Int -> Maybe String
forall a. [a] -> Int -> Maybe a
!!! 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) 2100 (String -> Maybe (m ())) -> String -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$
        "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]
++ " 2))"
  where
    regex :: Regex
regex = String -> Regex
mkRegex "^([_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 (_, _, name :: String
name, _)) =
        String -> () -> Map String () -> Map String ()
forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert String
name ()
    insertRef _ = Map String () -> Map String ()
forall a. a -> a
Prelude.id

    getNormalString :: Token -> Maybe String
getNormalString (T_NormalWord _ words :: [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 (\x :: [String]
x y :: 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 _ = Maybe String
forall a. Maybe a
Nothing

    getLiterals :: Token -> Maybe String
getLiterals (T_Literal _ s :: String
s) = String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return String
s
    getLiterals (T_Glob _ s :: String
s) = String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return String
s
    getLiterals _ = Maybe String
forall a. Maybe a
Nothing
checkWrongArithmeticAssignment _ _ = () -> 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 "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 "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 "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 "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 "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 "cat -n | grep bar"
checkUuoc :: p -> Token -> f ()
checkUuoc _ (T_Pipeline _ _ (T_Redirecting _ _ cmd :: Token
cmd:_:_)) =
    String -> (Token -> [Token] -> f ()) -> Token -> f ()
forall (f :: * -> *).
Monad f =>
String -> (Token -> [Token] -> f ()) -> Token -> f ()
checkCommand "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 [word :: 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) 2002 "Useless cat. Consider 'cmd < file | ..' or 'cmd file | ..' instead."
    f _ = () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    isOption :: Token -> Bool
isOption word :: Token
word = "-" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` Token -> String
onlyLiteralString Token
word
checkUuoc _ _ = () -> 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 "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 "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 "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 "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 "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 "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 "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 "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 "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 "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 "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 "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 "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 "foo | grep -r bar | wc -l"
checkPipePitfalls :: p -> Token -> m ()
checkPipePitfalls _ (T_Pipeline id :: Id
id _ commands :: [Token]
commands) = do
    [String] -> ([Token] -> m ()) -> m Bool
forall (m :: * -> *) b.
Monad m =>
[String] -> ([Token] -> m b) -> m Bool
for ["find", "xargs"] (([Token] -> m ()) -> m Bool) -> ([Token] -> m ()) -> m Bool
forall a b. (a -> b) -> a -> b
$
        \(find :: Token
find:xargs :: Token
xargs:_) ->
          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 '0',
                String -> [String] -> Bool
forall (t :: * -> *). Foldable t => String -> t String -> Bool
hasParameter "null",
                String -> [String] -> Bool
forall (t :: * -> *). Foldable t => String -> t String -> Bool
hasParameter "print0",
                String -> [String] -> Bool
forall (t :: * -> *). Foldable t => String -> t String -> Bool
hasParameter "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) 2038
                      "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' ["ps", "grep"] ((Id -> m ()) -> m Bool) -> (Id -> m ()) -> m Bool
forall a b. (a -> b) -> a -> b
$
        \x :: Id
x -> Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
info Id
x 2009 "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 ["grep", "wc"] (([Token] -> m ()) -> m Bool) -> ([Token] -> m ()) -> m Bool
forall a b. (a -> b) -> a -> b
$
        \(grep :: Token
grep:wc :: Token
wc:_) ->
            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` ["o", "only-matching", "r", "R", "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` ["m", "chars", "w", "words", "c", "bytes", "L", "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) 2126 "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' ["ls", "grep"] ((Id -> m ()) -> m Bool) -> (Id -> m ()) -> m Bool
forall a b. (a -> b) -> a -> b
$
            \x :: Id
x -> Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
x 2010 "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' ["ls", "xargs"] ((Id -> m ()) -> m Bool) -> (Id -> m ()) -> m Bool
forall a b. (a -> b) -> a -> b
$
            \x :: Id
x -> Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
x 2011 "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 ["ls", "?"] (([Token] -> m ()) -> m Bool) -> ([Token] -> m ()) -> m Bool
forall a b. (a -> b) -> a -> b
$
            \(ls :: Token
ls:_) -> Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Char -> [String] -> Bool
forall (t :: * -> *). Foldable t => Char -> t String -> Bool
hasShortParameter '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) 2012 "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 l :: [String]
l f :: [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) -> (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
. (\ n :: 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' l :: [String]
l f :: 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 func :: Id -> m ()
func (x :: Token
x:_) = Id -> m ()
func (Token -> Id
getId (Token -> Id) -> Token -> Id
forall a b. (a -> b) -> a -> b
$ Token -> Token
getCommandTokenOrThis Token
x)
    first _ _ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    hasShortParameter :: Char -> t String -> Bool
hasShortParameter char :: Char
char = (String -> Bool) -> t String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (\x :: String
x -> "-" 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 =
        (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
== '-'))
checkPipePitfalls _ _ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

indexOfSublists :: [String] -> [String] -> [t]
indexOfSublists sub :: [String]
sub = t -> [String] -> [t]
forall t. Num t => t -> [String] -> [t]
f 0
  where
    f :: t -> [String] -> [t]
f _ [] = []
    f n :: t
n a :: [String]
a@(r :: String
r:rest :: [String]
rest) =
        let others :: [t]
others = t -> [String] -> [t]
f (t
nt -> t -> t
forall a. Num a => a -> a -> a
+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 ("?":r1 :: [String]
r1) (_:r2 :: [String]
r2) = [String] -> [String] -> Bool
match [String]
r1 [String]
r2
    match (x1 :: String
x1:r1 :: [String]
r1) (x2 :: String
x2:r2 :: [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 [] _ = Bool
True
    match _ _ = Bool
False


prop_checkShebangParameters1 :: Bool
prop_checkShebangParameters1 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall t. t -> Token -> [TokenComment]
checkShebangParameters "#!/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 "#! /bin/sh  -l "
checkShebangParameters :: t -> Token -> [TokenComment]
checkShebangParameters p :: t
p (T_Annotation _ _ t :: Token
t) = t -> Token -> [TokenComment]
checkShebangParameters t
p Token
t
checkShebangParameters _ (T_Script _ (T_Literal id :: Id
id sb :: String
sb) _) =
    [Severity -> Id -> Integer -> String -> TokenComment
makeComment Severity
ErrorC Id
id 2096 "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
> 2]

prop_checkShebang1 :: Bool
prop_checkShebang1 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkShebang "#!/usr/bin/env bash -x\necho cow"
prop_checkShebang2 :: Bool
prop_checkShebang2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkShebang "#! /bin/sh  -l "
prop_checkShebang3 :: Bool
prop_checkShebang3 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkShebang "ls -l"
prop_checkShebang4 :: Bool
prop_checkShebang4 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkShebang "#shellcheck shell=sh\nfoo"
prop_checkShebang5 :: Bool
prop_checkShebang5 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkShebang "#!/usr/bin/env ash"
prop_checkShebang6 :: Bool
prop_checkShebang6 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkShebang "#!/usr/bin/env ash\n# shellcheck shell=dash\n"
prop_checkShebang7 :: Bool
prop_checkShebang7 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkShebang "#!/usr/bin/env ash\n# shellcheck shell=sh\n"
prop_checkShebang8 :: Bool
prop_checkShebang8 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkShebang "#!bin/sh\ntrue"
prop_checkShebang9 :: Bool
prop_checkShebang9 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkShebang "# shellcheck shell=sh\ntrue"
prop_checkShebang10 :: Bool
prop_checkShebang10= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkShebang "#!foo\n# shellcheck shell=sh ignore=SC2239\ntrue"
prop_checkShebang11 :: Bool
prop_checkShebang11= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkShebang "#!/bin/sh/\ntrue"
prop_checkShebang12 :: Bool
prop_checkShebang12= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkShebang "#!/bin/sh/ -xe\ntrue"
checkShebang :: Parameters -> Token -> [TokenComment]
checkShebang params :: Parameters
params (T_Annotation _ list :: [Annotation]
list t :: 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 _) = Bool
True
    isOverride _ = Bool
False
checkShebang params :: Parameters
params (T_Script _ (T_Literal id :: Id
id sb :: String
sb) _) = 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
== "") (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 2148 "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
== "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 2187 "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 -> 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 2239 "Ensure the shebang uses an absolute path to the interpreter."
        case String -> [String]
words String
sb of
            first :: String
first:_ ->
                Bool
-> WriterT [TokenComment] Identity ()
-> WriterT [TokenComment] Identity ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ("/" 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 2246 "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 "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 "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 "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 "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 "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 "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 "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 "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 "for f in \"${!arr}\"; do true; done"
checkForInQuoted :: p -> Token -> f ()
checkForInQuoted _ (T_ForIn _ f :: String
f [T_NormalWord _ [word :: Token
word@(T_DoubleQuoted id :: Id
id list :: [Token]
list)]] _) =
    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 (\x :: 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 2066 "Since you double quoted this, it will not word split, and the loop will only run once."
checkForInQuoted _ (T_ForIn _ f :: String
f [T_NormalWord _ [T_SingleQuoted id :: Id
id _]] _) =
    Id -> Integer -> String -> f ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id 2041 "This is a literal string. To run as a command, use $(..) instead of '..' . "
checkForInQuoted _ (T_ForIn _ f :: String
f [T_NormalWord _ [T_Literal id :: Id
id s :: String
s]] _) =
    if ',' 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 -> 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 2042 "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 2043 "This loop will only ever run once for a constant value. Did you perhaps mean to loop over dir/*, $var or $(cmd)?"
checkForInQuoted _ _ = () -> 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 "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 "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 "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 "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 "for f in $(cat foo | grep bar | wc -l); do stuff; done"
checkForInCat :: p -> Token -> m ()
checkForInCat _ (T_ForIn _ f :: String
f [T_NormalWord _ w :: [Token]
w] _) = (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
id [T_Pipeline _ _ r :: [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 2013 "To read lines rather than words, pipe/redirect to a 'while read' loop."
    checkF (T_Backticked id :: Id
id cmds :: [Token]
cmds) = Token -> m ()
checkF (Id -> [Token] -> Token
T_DollarExpansion Id
id [Token]
cmds)
    checkF _ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    isLineBased :: Token -> Bool
isLineBased cmd :: Token
cmd = (String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Token
cmd Token -> String -> Bool
`isCommand`)
                        ["grep", "fgrep", "egrep", "sed", "cat", "awk", "cut", "sort"]
checkForInCat _ _ = () -> 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 "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 "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 "for f in `find / -name '*.mp3'`; do mplayer \"$f\"; done"
checkForInLs :: p -> Token -> m ()
checkForInLs _ = Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
try
  where
   try :: Token -> m ()
try (T_ForIn _ f :: String
f [T_NormalWord _ [T_DollarExpansion id :: Id
id [x :: Token
x]]] _) =
        Id -> String -> Token -> m ()
forall (m :: * -> *) p.
MonadWriter [TokenComment] m =>
Id -> p -> Token -> m ()
check Id
id String
f Token
x
   try (T_ForIn _ f :: String
f [T_NormalWord _ [T_Backticked id :: Id
id [x :: Token
x]]] _) =
        Id -> String -> Token -> m ()
forall (m :: * -> *) p.
MonadWriter [TokenComment] m =>
Id -> p -> Token -> m ()
check Id
id String
f Token
x
   try _ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
   check :: Id -> p -> Token -> m ()
check id :: Id
id f :: p
f x :: Token
x =
    case Token -> [String]
oversimplify Token
x of
      ("ls":n :: [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 -> 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 2045 "Iterating over ls output is fragile. Use globs."
      ("find":_) -> Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id 2044 "For loops over find output are fragile. Use find -exec or a while read loop."
      _ -> () -> 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 "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 "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 "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 "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 "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 "find / -type d -execdir rm *.jpg \\;"
checkFindExec :: p -> Token -> m ()
checkFindExec _ cmd :: Token
cmd@(T_SimpleCommand _ _ t :: [Token]
t@(h :: Token
h:r :: [Token]
r)) | Token
cmd Token -> String -> Bool
`isCommand` "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 2067 "Missing ';' or + terminating -exec. You can't use |/||/&&, and ';' has to be a separate, quoted argument."

  where
    broken :: [Token] -> Bool -> m Bool
broken [] v :: Bool
v = Bool -> m Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
v
    broken (w :: Token
w:r :: [Token]
r) v :: 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 "-exec" -> [Token] -> Bool -> m Bool
broken [Token]
r Bool
True
            Just "-execdir" -> [Token] -> Bool -> m Bool
broken [Token]
r Bool
True
            Just "+" -> [Token] -> Bool -> m Bool
broken [Token]
r Bool
False
            Just ";" -> [Token] -> Bool -> m Bool
broken [Token]
r Bool
False
            _ -> [Token] -> Bool -> m Bool
broken [Token]
r Bool
v

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

    warnFor :: Token -> f ()
warnFor x :: 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) 2014 "This will expand once before find runs, not per file found."

    fromWord :: Token -> [Token]
fromWord (T_NormalWord _ l :: [Token]
l) = [Token]
l
    fromWord _ = []
checkFindExec _ _ = () -> 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 "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 "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 "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 "[ $(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 "[ ! $(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 "[[ $(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 "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 "$(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 "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 "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 "echo foo `# inline comment`"
checkUnquotedExpansions :: Parameters -> Token -> f ()
checkUnquotedExpansions params :: Parameters
params =
    Token -> f ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
check
  where
    check :: Token -> f ()
check t :: Token
t@(T_DollarExpansion _ c :: [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 _ c :: [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 _ c :: [Token]
c) = Token -> [Token] -> f ()
forall (f :: * -> *) (t :: * -> *) a.
(Foldable t, MonadWriter [TokenComment] f) =>
Token -> t a -> f ()
examine Token
t [Token]
c
    check _ = () -> 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 t :: Token
t contents :: 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) 2046 "Quote this to prevent word splitting."

    shouldBeSplit :: Token -> Bool
shouldBeSplit t :: 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 "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 "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 "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 "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 "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 "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 "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 "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 "while read -r line; do _=\"$fname\"; done <\"$fname\""
checkRedirectToSame :: Parameters -> Token -> m ()
checkRedirectToSame params :: Parameters
params s :: Token
s@(T_Pipeline _ _ list :: [Token]
list) =
    (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (\l :: Token
l -> ((Token -> m Token) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (\x :: 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 x :: Id
x = Severity -> Id -> Integer -> String -> TokenComment
makeComment Severity
InfoC Id
x 2094
                "Make sure not to read and write the same file in the same pipeline."
    checkOccurrences :: Token -> Token -> f ()
checkOccurrences t :: Token
t@(T_NormalWord exceptId :: Id
exceptId x :: [Token]
x) u :: Token
u@(T_NormalWord newId :: Id
newId y :: [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 _ _ = () -> 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 (\t :: Token
t ->
        case Token
t of
            T_Redirecting _ ls :: [Token]
ls _ -> (Token -> [Token]) -> [Token] -> [Token]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Token -> [Token]
getRedirs [Token]
ls
            _ -> [])
    getRedirs :: Token -> [Token]
getRedirs (T_FdRedirect _ _ (T_IoFile _ op :: Token
op file :: Token
file)) =
            case Token
op of T_Greater _ -> [Token
file]
                       T_Less _    -> [Token
file]
                       T_DGREAT _  -> [Token
file]
                       _ -> []
    getRedirs _ = []
    special :: Token -> Bool
special x :: Token
x = "/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 t :: Token
t =
        case Int -> [Token] -> [Token]
forall a. Int -> [a] -> [a]
drop 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 _ op :: Token
op _:_ ->
                case Token
op of
                    T_Greater _  -> Bool
True
                    T_DGREAT _ -> Bool
True
                    _ -> Bool
False
            _ -> Bool
False
    isHarmlessCommand :: Token -> Bool
isHarmlessCommand arg :: 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` ["echo", "printf", "sponge"]
    containsAssignment :: Token -> Bool
containsAssignment arg :: 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 _ _ = () -> 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 "[[ ! -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 "[[ ! -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 "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 "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 "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 "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 "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 "if true; then foo && bar || baz; fi"
checkShorthandIf :: Parameters -> Token -> m ()
checkShorthandIf params :: Parameters
params x :: Token
x@(T_AndIf id :: Id
id _ (T_OrIf _ _ (T_Pipeline _ _ t :: [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 2015 "Note that A && B || C is not if-then-else. C may run when A is true."
  where
    isOk :: [Token] -> Bool
isOk [t :: 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` ["echo", "exit", "return", "printf"])
    isOk _ = 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 _ _ = () -> 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 "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 "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 "[[ $* = 'a b' ]]"
checkDollarStar :: Parameters -> Token -> f ()
checkDollarStar p :: Parameters
p t :: Token
t@(T_NormalWord _ [b :: Token
b@(T_DollarBraced id :: Id
id _ _)])
      | Token -> String
bracedString Token
b String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "*"  =
    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 2048 "Use \"$@\" (with quotes) to prevent whitespace problems."
checkDollarStar _ _ = () -> 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 "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 "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 "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 "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 "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 "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 "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 "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 "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 "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 "echo ${@+\"$@\"}"
checkUnquotedDollarAt :: Parameters -> Token -> m ()
checkUnquotedDollarAt p :: Parameters
p word :: Token
word@(T_NormalWord _ parts :: [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 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
$ \x :: 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) 2068
                "Double quote array expansions to avoid re-splitting elements."
checkUnquotedDollarAt _ _ = () -> 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 "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 "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 "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 "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 "echo \"${arr[@]}\""
checkConcatenatedDollarAt :: Parameters -> Token -> f ()
checkConcatenatedDollarAt p :: 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 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 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 t :: Token
t = Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err (Token -> Id
getId Token
t) 2145 "Argument mixes string and array. Use * or separate argument."
checkConcatenatedDollarAt _ _ = () -> 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 "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 "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 "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 "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 "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 "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 "a=( $@ )"
checkArrayAsString :: p -> Token -> m ()
checkArrayAsString _ (T_Assignment id :: Id
id _ _ _ word :: 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) 2124
        "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) 2125
          "Brace expansions and globs are literal in assignments. Quote it or use an array."
checkArrayAsString _ _ = () -> 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 "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 "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 "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 "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 "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 "echo $PIPESTATUS"
prop_checkArrayWithoutIndex7 :: Bool
prop_checkArrayWithoutIndex7 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkArrayWithoutIndex "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 "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 "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 "read -ra arr <<< 'foo bar'; echo \"$arr\""
checkArrayWithoutIndex :: Parameters -> p -> [TokenComment]
checkArrayWithoutIndex params :: Parameters
params _ =
    (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 (\x :: String
x -> (String
x,())) [String]
arrayVariables
    readF :: p -> Token -> p -> m [TokenComment]
readF _ (T_DollarBraced id :: Id
id _ token :: Token
token) _ = 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 2128
                    "Expanding an array without an index only gives the first element."
    readF _ _ _ = [TokenComment] -> m [TokenComment]
forall (m :: * -> *) a. Monad m => a -> m a
return []

    writeF :: p -> Token -> String -> DataType -> m [TokenComment]
writeF _ (T_Assignment id :: Id
id mode :: AssignmentMode
mode name :: String
name [] _) _ (DataString _) = 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
                Assign -> [Severity -> Id -> Integer -> String -> TokenComment
makeComment Severity
WarningC Id
id 2178 "Variable was used as an array but is now assigned a string."]
                Append -> [Severity -> Id -> Integer -> String -> TokenComment
makeComment Severity
WarningC Id
id 2179 "Use array+=(\"item\") to append items to an array."]

    writeF _ t :: Token
t name :: String
name (DataArray _) = 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 _ expr :: Token
expr name :: String
name _ = 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 expr :: Token
expr =
        case Token
expr of
            T_Assignment _ _ _ (_:_) _ -> Bool
True
            _ -> 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 "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 "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 "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 "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 "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 "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 "{ cmd > file; } 2>&1"
checkStderrRedirect :: Parameters -> Token -> f ()
checkStderrRedirect params :: Parameters
params redir :: Token
redir@(T_Redirecting _ [
    T_FdRedirect id :: Id
id "2" (T_IoDuplicate _ (T_GREATAND _) "1"),
    T_FdRedirect _ _ (T_IoFile _ op :: Token
op _)
    ] _) = case Token
op of
            T_Greater _ -> f ()
error
            T_DGREAT _ -> f ()
error
            _ -> () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    usesOutput :: Token -> Bool
usesOutput t :: Token
t =
        case Token
t of
            (T_Pipeline _ _ list :: [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
> 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
            _ -> 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 2069 "To redirect stdout+stderr, 2>&1 must be last (or use '{ cmd > file; } 2>&1' to clarify)."

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

lt :: a -> a
lt x :: a
x = String -> a -> a
forall a. String -> a -> a
trace ("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 t :: a
t = String -> a -> a
forall a. String -> a -> a
trace ("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 "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 "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 "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 "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 "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 "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 "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 "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 "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 "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 "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 "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 "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 "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 "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 "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 "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 "[ -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 "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 "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 "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 "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 "echo '```'"

checkSingleQuotedVariables :: Parameters -> Token -> f ()
checkSingleQuotedVariables params :: Parameters
params t :: Token
t@(T_SingleQuoted id :: Id
id s :: 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 "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 2016
        "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 "" (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
== "find" then Token -> String
getFindCommand Token
cmd else if String
name String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "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 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` [
                "trap"
                ,"sh"
                ,"bash"
                ,"ksh"
                ,"zsh"
                ,"ssh"
                ,"eval"
                ,"xprop"
                ,"alias"
                ,"sudo" -- covering "sudo sh" and such
                ,"docker" -- like above
                ,"dpkg-query"
                ,"jq"  -- could also check that user provides --arg
                ,"rename"
                ,"unset"
                ,"git filter-branch"
                ]
            Bool -> Bool -> Bool
|| "awk" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isSuffixOf` String
commandName
            Bool -> Bool -> Bool
|| "perl" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
commandName

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

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

    getFindCommand :: Token -> String
getFindCommand (T_SimpleCommand _ _ words :: [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 (\x :: 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 "-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 "-execdir") [Maybe String]
list
        in
          case [Maybe String]
cmd of
            (flag :: Maybe String
flag:cmd :: Maybe String
cmd:rest :: [Maybe String]
rest) -> String -> Maybe String -> String
forall a. a -> Maybe a -> a
fromMaybe "find" Maybe String
cmd
            _ -> "find"
    getFindCommand (T_Redirecting _ _ cmd :: Token
cmd) = Token -> String
getFindCommand Token
cmd
    getFindCommand _ = "find"
    getGitCommand :: Token -> String
getGitCommand (T_SimpleCommand _ _ words :: [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 "git":Just "filter-branch":_ -> "git filter-branch"
            _ -> "git"
    getGitCommand (T_Redirecting _ _ cmd :: Token
cmd) = Token -> String
getGitCommand Token
cmd
    getGitCommand _ = "git"
checkSingleQuotedVariables _ _ = () -> 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 "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 "[ -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 "[[ -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 "[ -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 "[ -n \"$@\" ]"
checkUnquotedN :: p -> Token -> m ()
checkUnquotedN _ (TC_Unary _ SingleBracket "-n" (T_NormalWord id :: Id
id [t :: Token
t])) | Token -> Bool
willSplit Token
t =
       Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
id 2070 "-n doesn't work with unquoted arguments. Quote or use [[ ]]."
checkUnquotedN _ _ = () -> 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 "[[ $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 "[[ 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 "[[ $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 "[[ $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 "[[ $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 "[[ 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 "[[ 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 "[ 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 "[ 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 "[ $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 "[ 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 "[ $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 "[[ 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 "[ $foo '>' $bar ]"
checkNumberComparisons :: Parameters -> Token -> m ()
checkNumberComparisons params :: Parameters
params (TC_Binary id :: Id
id typ :: ConditionType
typ op :: String
op lhs :: Token
lhs rhs :: 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 2071 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
            String
op String -> String -> String
forall a. [a] -> [a] -> [a]
++ " 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]
++ " 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 2071 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
op String -> String -> String
forall a. [a] -> [a] -> [a]
++ " is not a valid operator. " String -> String -> String
forall a. [a] -> [a] -> [a]
++
              "Use " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> String
eqv String
op String -> String -> String
forall a. [a] -> [a] -> [a]
++ " ."
      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 2122 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
op String -> String -> String
forall a. [a] -> [a] -> [a]
++ " is not a valid operator. " String -> String -> String
forall a. [a] -> [a] -> [a]
++
                "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]
++ " 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` ["<", ">"]) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
            case Parameters -> Shell
shellType Parameters
params of
                Sh -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()  -- These are unsupported and will be caught by bashism checks.
                Dash -> Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
id 2073 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ "Escape \\" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
op String -> String -> String
forall a. [a] -> [a] -> [a]
++ " to prevent it redirecting."
                _ -> Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
id 2073 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ "Escape \\" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
op String -> String -> String
forall a. [a] -> [a] -> [a]
++ " 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` ["-lt", "-gt", "-le", "-ge", "-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 ["<", "\\<", ">", "\\>"]
      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 ["<=", "\\<=", ">=", "\\>="]

      checkDecimals :: Token -> f ()
checkDecimals hs :: 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) 2072 String
decimalError
      decimalError :: String
decimalError = "Decimals are not supported. " String -> String -> String
forall a. [a] -> [a] -> [a]
++
        "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 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 t :: 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 "") 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 x :: 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` "+-. "

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

      isNum :: Token -> Bool
isNum t :: Token
t =
        case Token -> [String]
oversimplify Token
t of
            [v :: String
v] -> (Char -> Bool) -> String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isDigit String
v
            _ -> Bool
False
      isFraction :: Token -> Bool
isFraction t :: Token
t =
        case Token -> [String]
oversimplify Token
t of
            [v :: 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
            _ -> Bool
False

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

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

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

      floatRegex :: Regex
floatRegex = String -> Regex
mkRegex "^[-+]?[0-9]+\\.[0-9]+$"
checkNumberComparisons _ _ = () -> 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 "[ test =~ foo ]"
checkSingleBracketOperators :: Parameters -> Token -> f ()
checkSingleBracketOperators params :: Parameters
params (TC_Binary id :: Id
id SingleBracket "=~" lhs :: Token
lhs rhs :: 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 2074 (String -> f ()) -> String -> f ()
forall a b. (a -> b) -> a -> b
$ "Can't use =~ in [ ]. Use [[..]] instead."
checkSingleBracketOperators _ _ = () -> 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 "[[ 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 "[[ foo < bar ]]"
checkDoubleBracketOperators :: p -> Token -> m ()
checkDoubleBracketOperators _ x :: Token
x@(TC_Binary id :: Id
id typ :: ConditionType
typ op :: String
op lhs :: Token
lhs rhs :: 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` ["\\<", "\\>"] =
        Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
id 2075 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ "Escaping " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
op String -> String -> String
forall a. [a] -> [a] -> [a]
++" is required in [..], but invalid in [[..]]"
checkDoubleBracketOperators _ _ = () -> 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 "[ 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 "[[ 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 "[[ 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 "[ 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 "[ -z 3 -o a = b ]"
checkConditionalAndOrs :: p -> Token -> m ()
checkConditionalAndOrs _ t :: Token
t =
    case Token
t of
        (TC_And id :: Id
id SingleBracket "&&" _ _) ->
            Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
id 2107 "Instead of [ a && b ], use [ a ] && [ b ]."
        (TC_And id :: Id
id DoubleBracket "-a" _ _) ->
            Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
id 2108 "In [[..]], use && instead of -a."
        (TC_Or id :: Id
id SingleBracket "||" _ _) ->
            Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
id 2109 "Instead of [ a || b ], use [ a ] || [ b ]."
        (TC_Or id :: Id
id DoubleBracket "-o" _ _) ->
            Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
id 2110 "In [[..]], use || instead of -o."

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

        _ -> () -> 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 "[[ $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 "[[ $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 "[[ $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 "[[ $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 "[[ $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 "[[ $foo =~ 'cow|bar' ]]"
checkQuotedCondRegex :: p -> Token -> f ()
checkQuotedCondRegex _ (TC_Binary _ _ "=~" _ rhs :: Token
rhs) =
    case Token
rhs of
        T_NormalWord id :: Id
id [T_DoubleQuoted _ _] -> Token -> f ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
error Token
rhs
        T_NormalWord id :: Id
id [T_SingleQuoted _ _] -> Token -> f ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
error Token
rhs
        _ -> () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    error :: Token -> f ()
error t :: 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) 2076
                "Don't quote right-hand side of =~, it'll match literally rather than as a regex."
    re :: Regex
re = String -> Regex
mkRegex "[][*.+()|]"
    hasMetachars :: String -> Bool
hasMetachars s :: String
s = String
s String -> Regex -> Bool
`matches` Regex
re
    isConstantNonRe :: Token -> Bool
isConstantNonRe t :: 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 _ _ = () -> 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 "[[ $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 "[[ $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 "[[ $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 "[[ $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 "[[ $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 "[[ $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 "[[ $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 "[[ $foo =~ x\\* ]]"
checkGlobbedRegex :: p -> Token -> f ()
checkGlobbedRegex _ (TC_Binary _ DoubleBracket "=~" _ rhs :: 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) 2049 "=~ is for regex, but this looks like a glob. Use = instead."
checkGlobbedRegex _ _ = () -> 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 "[[ 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 "[ 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 "[[ 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 "[[ $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 "[[ $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 "[[ $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 "[[ 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 "[ 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 "[[ ~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 "[[ *.png == [a-z] ]]"
checkConstantIfs :: p -> Token -> m ()
checkConstantIfs _ (TC_Binary id :: Id
id typ :: ConditionType
typ op :: String
op lhs :: Token
lhs rhs :: 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 2050 "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` [ "-lt", "-gt", "-le", "-ge", "-eq", "-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` [ "-nt", "-ot", "-ef"]

    checkUnmatchable :: Id -> String -> Token -> Token -> f ()
checkUnmatchable id :: Id
id op :: String
op lhs :: Token
lhs rhs :: 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` ["=", "==", "!="] 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 2193 "The arguments to this comparison can never be equal. Make sure your syntax is correct."
checkConstantIfs _ _ = () -> 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 "[[ 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 "[ $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 "[ $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 "[ \"$(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 "[ -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 "[ -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 "[ -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 "[ $(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 "[ -z foo ]"
checkLiteralBreakingTest :: p -> Token -> m ()
checkLiteralBreakingTest _ t :: 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 _ _ w :: Token
w@(T_NormalWord _ l :: [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 "Argument to implicit -n is always true due to literal strings."
            (TC_Unary _ _ op :: String
op w :: Token
w@(T_NormalWord _ l :: [Token]
l)) ->
                case String
op of
                    "-n" -> Token -> String -> Maybe (m ())
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Token -> String -> Maybe (m ())
tautologyWarning Token
w "Argument to -n is always true due to literal strings."
                    "-z" -> Token -> String -> Maybe (m ())
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Token -> String -> Maybe (m ())
tautologyWarning Token
w "Argument to -z is always false due to literal strings."
                    _ -> String -> Maybe (m ())
forall (m :: * -> *) a. MonadFail m => String -> m a
fail "not relevant"
            _ -> String -> Maybe (m ())
forall (m :: * -> *) a. MonadFail m => String -> m a
fail "not my problem"
  where
    hasEquals :: Token -> Bool
hasEquals = (String -> Bool) -> Token -> Bool
matchToken ('=' 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 m :: String -> Bool
m t :: 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 list :: [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) 2077 "You need spaces around the comparison operator."
    tautologyWarning :: Token -> String -> Maybe (m ())
tautologyWarning t :: Token
t s :: 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) 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 "[[ '$(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 "[ \"-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 "[[ 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 "[[ ! 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 "[[ 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 "[ 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 "[ false ]"
checkConstantNullary :: p -> Token -> m ()
checkConstantNullary _ (TC_Nullary _ _ t :: Token
t) | Token -> Bool
isConstant Token
t =
    case 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 -> Maybe String
getLiteralString Token
t of
        "false" -> Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err (Token -> Id
getId Token
t) 2158 "[ false ] is true. Remove the brackets."
        "0" -> Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err (Token -> Id
getId Token
t) 2159 "[ 0 ] is true. Use 'false' instead."
        "true" -> Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
style (Token -> Id
getId Token
t) 2160 "Instead of '[ true ]', just use 'true'."
        "1" -> Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
style (Token -> Id
getId Token
t) 2161 "Instead of '[ 1 ]', use 'true'."
        _ -> Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err (Token -> Id
getId Token
t) 2078 "This expression is constant. Did you forget a $ somewhere?"
  where
    string :: String
string = 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 -> Maybe String
getLiteralString Token
t

checkConstantNullary _ _ = () -> 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 "((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 "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 "declare -A foo; foo[1.2]=bar"
checkForDecimals :: Parameters -> Token -> m ()
checkForDecimals params :: Parameters
params t :: Token
t@(TA_Expansion id :: Id
id _) = 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
!!! 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 -> 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 2079 "(( )) doesn't support decimals. Use bc or awk."
checkForDecimals _ _ = () -> 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 "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 "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 "echo $((c/10*10))"
checkDivBeforeMult :: Parameters -> Token -> m ()
checkDivBeforeMult params :: Parameters
params (TA_Binary _ "*" (TA_Binary id :: Id
id "/" _ x :: Token
x) y :: 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 2017 "Increase precision by replacing a/b*c with a*c/b."
checkDivBeforeMult _ _ = () -> 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 "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 "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 "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 "(( ! $? ))"
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 "(($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 "(( 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 "(( 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 "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 "(( 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 "(( 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 "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 "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 "(( $$ ))"
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 "(( $! ))"
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 "(( ${!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 "(( ${x+1} + ${x=42} ))"
checkArithmeticDeref :: Parameters -> Token -> m ()
checkArithmeticDeref params :: Parameters
params t :: Token
t@(TA_Expansion _ [b :: Token
b@(T_DollarBraced id :: Id
id _ _)]) =
    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 s :: 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
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 t :: 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
            _ -> 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 2004 "$/${} is unnecessary on arithmetic variables."
    noWarning :: m ()
noWarning = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
checkArithmeticDeref _ _ = () -> 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 "(( 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 "(( 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 "(( 1 ^ 0777 ))"
checkArithmeticBadOctal :: p -> Token -> m ()
checkArithmeticBadOctal _ t :: Token
t@(TA_Expansion id :: Id
id _) = 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 2080 "Numbers with leading 0 are considered octal."
  where
    octalRE :: Regex
octalRE = String -> Regex
mkRegex "^0[0-7]*[8-9]"
checkArithmeticBadOctal _ _ = () -> 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 "[[ $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 "[[ $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 "[ $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 "[ $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 "[[ $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 "[ $f != /* ]"
checkComparisonAgainstGlob :: Parameters -> Token -> m ()
checkComparisonAgainstGlob _ (TC_Binary _ DoubleBracket op :: String
op _ (T_NormalWord id :: Id
id [T_DollarBraced _ _ _]))
    | String
op String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` ["=", "==", "!="] =
        Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id 2053 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ "Quote the right-hand side of " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
op String -> String -> String
forall a. [a] -> [a] -> [a]
++ " in [[ ]] to prevent glob matching."
checkComparisonAgainstGlob params :: Parameters
params (TC_Binary _ SingleBracket op :: String
op _ word :: Token
word)
        | String
op String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` ["=", "==", "!="] 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) 2081 String
msg
  where
    msg :: String
msg = if Parameters -> Bool
isBashLike Parameters
params
            then "[ .. ] can't match globs. Use [[ .. ]] or case statement."
            else "[ .. ] can't match globs. Use a case statement."

checkComparisonAgainstGlob _ _ = () -> 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 "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 "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 "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 "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 "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 "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 "a=(1,2)"
checkCommarrays :: p -> Token -> f ()
checkCommarrays _ (T_Array id :: Id
id l :: [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 2054 "Use spaces, not commas, to separate array elements."
  where
    literal :: Token -> String
literal (T_IndexedElement _ _ l :: Token
l) = Token -> String
literal Token
l
    literal (T_NormalWord _ l :: [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 _ str :: String
str) = String
str
    literal _ = ""

    isCommaSeparated :: String -> Bool
isCommaSeparated = Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
elem ','
checkCommarrays _ _ = () -> 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 "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 "(( 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 "[ \"$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 "[ 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 "[[ $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 "[ $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 "[ $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 "[[ $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 _ (TC_Or id :: Id
id typ :: ConditionType
typ op :: String
op (TC_Binary _ _ op1 :: String
op1 lhs1 :: Token
lhs1 rhs1 :: Token
rhs1 ) (TC_Binary _ _ op2 :: String
op2 lhs2 :: Token
lhs2 rhs2 :: 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
== "-ne" Bool -> Bool -> Bool
|| String
op1 String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "!=")) 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 2055 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ "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 "-a" else "&&") String -> String -> String
forall a. [a] -> [a] -> [a]
++ " here, otherwise it's always true."

-- For arithmetic context "or"
checkOrNeq _ (TA_Binary id :: Id
id "||" (TA_Binary _ "!=" word1 :: Token
word1 _) (TA_Binary _ "!=" word2 :: Token
word2 _))
    | 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 2056 "You probably wanted && here, otherwise it's always true."

-- For command level "or": [ x != y ] || [ x != z ]
checkOrNeq _ (T_OrIf id :: Id
id lhs :: Token
lhs rhs :: 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
    (lhs1 :: Token
lhs1, op1 :: String
op1, rhs1 :: Token
rhs1) <- Token -> Maybe (Token, String, Token)
forall (m :: * -> *).
MonadFail m =>
Token -> m (Token, String, Token)
getExpr Token
lhs
    (lhs2 :: Token
lhs2, op2 :: String
op2, rhs2 :: 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` ["-ne", "!="]
    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 2252 "You probably wanted && here, otherwise it's always true."
  where
    getExpr :: Token -> m (Token, String, Token)
getExpr x :: Token
x =
        case Token
x of
            T_OrIf _ lhs :: Token
lhs _ -> Token -> m (Token, String, Token)
getExpr Token
lhs -- Fetches x and y in `T_OrIf x (T_OrIf y z)`
            T_Pipeline _ _ [x :: Token
x] -> Token -> m (Token, String, Token)
getExpr Token
x
            T_Redirecting _ _ c :: Token
c -> Token -> m (Token, String, Token)
getExpr Token
c
            T_Condition _ _ c :: Token
c -> Token -> m (Token, String, Token)
getExpr Token
c
            TC_Binary _ _ op :: String
op lhs :: Token
lhs rhs :: Token
rhs -> (Token, String, Token) -> m (Token, String, Token)
forall (m :: * -> *) a. Monad m => a -> m a
return (Token
lhs, String
op, Token
rhs)
            _ -> String -> m (Token, String, Token)
forall (m :: * -> *) a. MonadFail m => String -> m a
fail ""

checkOrNeq _ _ = () -> 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 "[[ 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 "[ -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 "[ 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 "[ 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 "[[ ! -v foo ]]"
checkValidCondOps :: p -> Token -> m ()
checkValidCondOps _ (TC_Binary id :: Id
id _ s :: String
s _ _)
    | 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 2057 "Unknown binary operator."
checkValidCondOps _ (TC_Unary id :: Id
id _ s :: String
s _)
    | 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 2058 "Unknown unary operator."
checkValidCondOps _ _ = () -> 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 "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 "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 "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 "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 "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 "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 "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 "#!/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 "foo $(echo $(<file))"
checkUuoeVar :: p -> Token -> f ()
checkUuoeVar _ p :: Token
p =
    case Token
p of
        T_Backticked id :: Id
id [cmd :: Token
cmd] -> Id -> Token -> f ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
Id -> Token -> f ()
check Id
id Token
cmd
        T_DollarExpansion id :: Id
id [cmd :: Token
cmd] -> Id -> Token -> f ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
Id -> Token -> f ()
check Id
id Token
cmd
        _ -> () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    couldBeOptimized :: Token -> Bool
couldBeOptimized f :: Token
f = case Token
f of
        T_Glob {} -> Bool
False
        T_Extglob {} -> Bool
False
        T_BraceExpansion {} -> Bool
False
        T_NormalWord _ l :: [Token]
l -> (Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Token -> Bool
couldBeOptimized [Token]
l
        T_DoubleQuoted _ l :: [Token]
l -> (Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Token -> Bool
couldBeOptimized [Token]
l
        _ -> Bool
True

    check :: Id -> Token -> f ()
check id :: Id
id (T_Pipeline _ _ [T_Redirecting _ _ c :: Token
c]) = Id -> Token -> f ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
Id -> Token -> f ()
warnForEcho Id
id Token
c
    check _ _ = () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    isCovered :: Token -> t a -> Bool
isCovered first :: Token
first rest :: 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
id = String -> (Token -> [Token] -> f ()) -> Token -> f ()
forall (f :: * -> *).
Monad f =>
String -> (Token -> [Token] -> f ()) -> Token -> f ()
checkUnqualifiedCommand "echo" ((Token -> [Token] -> f ()) -> Token -> f ())
-> (Token -> [Token] -> f ()) -> Token -> f ()
forall a b. (a -> b) -> a -> b
$ \_ vars :: [Token]
vars ->
        case [Token]
vars of
          (first :: Token
first:rest :: [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 -> 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 2116
                    "Useless echo? Instead of 'cmd $(echo foo)', just use 'cmd foo'."
          _ -> () -> 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 "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 "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 "/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 "test 1 -eq 2 2> file"
checkTestRedirects :: p -> Token -> m ()
checkTestRedirects _ (T_Redirecting id :: Id
id redirs :: [Token]
redirs cmd :: Token
cmd) | Token
cmd Token -> String -> Bool
`isCommand` "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 t :: 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) 2065 "This is interpreted as a shell file redirection, not a comparison."
    suspicious :: Token -> Bool
suspicious t :: 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 _ fd :: String
fd (T_IoFile _ op :: Token
op _) -> String
fd String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= "2" Bool -> Bool -> Bool
&& Token -> Bool
isComparison Token
op
            _ -> Bool
False
    isComparison :: Token -> Bool
isComparison t :: Token
t =
        case Token
t of
            T_Greater _ -> Bool
True
            T_Less _ -> Bool
True
            _ -> Bool
False
checkTestRedirects _ _ = () -> 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 "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 "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 "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 "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 "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 "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 "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 "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 "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 "PS1='\\[\\e\\]'"
checkPS1Assignments :: p -> Token -> f ()
checkPS1Assignments _ (T_Assignment _ _ "PS1" _ word :: Token
word) = Token -> f ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
warnFor Token
word
  where
    warnFor :: Token -> f ()
warnFor word :: 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) 2025 "Make sure all escape sequences are enclosed in \\[..\\] to prevent line wrapping issues"
    containsUnescaped :: String -> Bool
containsUnescaped s :: String
s =
        let unenclosed :: String
unenclosed = Regex -> String -> String -> String
subRegex Regex
enclosedRegex String
s "" 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 "\\\\\\[.*\\\\\\]" -- FIXME: shouldn't be eager
    escapeRegex :: Regex
escapeRegex = String -> Regex
mkRegex "\\\\x1[Bb]|\\\\e|\x1B|\\\\033"
checkPS1Assignments _ _ = () -> 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 "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 "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 "echo `#inlined comment` foo"
checkBackticks :: Parameters -> Token -> m ()
checkBackticks params :: Parameters
params (T_Backticked id :: Id
id list :: [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 2006  "Use $(...) notation instead of legacy backticked `...`."
            ([Replacement] -> Fix
fixWith [Id -> Parameters -> Integer -> String -> Replacement
replaceStart Id
id Parameters
params 1 "$(", Id -> Parameters -> Integer -> String -> Replacement
replaceEnd Id
id Parameters
params 1 ")"])
checkBackticks _ _ = () -> 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 "${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 "${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 "${$#}"
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 "${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 "${bar}"
checkIndirectExpansion :: p -> Token -> f ()
checkIndirectExpansion _ (T_DollarBraced i :: Id
i _ (T_NormalWord _ contents :: [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 2082 "To expand via indirection, use arrays, ${!name} or (for sh only) eval."
  where
    isIndirection :: [Token] -> Bool
isIndirection vars :: [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 t :: Token
t =
        case Token
t of T_DollarExpansion _ _ ->  Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
True
                  T_Backticked _ _ ->       Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
True
                  T_DollarBraced _ _ _ ->     Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
True
                  T_DollarArithmetic _ _ -> Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
True
                  T_Literal _ s :: 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
                  _ -> Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
False

checkIndirectExpansion _ _ = () -> 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 "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 "'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 "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 "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 "\"$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 "\"$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 "${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 "  '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 "[[ $x =~ \"foo\"(\"bar\"|\"baz\") ]]"
checkInexplicablyUnquoted :: Parameters -> Token -> m ()
checkInexplicablyUnquoted params :: Parameters
params (T_NormalWord id :: Id
id tokens :: [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 _ _:T_Literal id :: Id
id str :: String
str:_)
        | 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 2026 "This word is outside of quotes. Did you intend to 'nest '\"'single quotes'\"' instead'? "

    check (T_DoubleQuoted _ a :: [Token]
a:trapped :: Token
trapped:T_DoubleQuoted _ b :: [Token]
b:_) =
        case Token
trapped of
            T_DollarExpansion id :: Id
id _ -> Id -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Id -> m ()
warnAboutExpansion Id
id
            T_DollarBraced id :: Id
id _ _ -> Id -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Id -> m ()
warnAboutExpansion Id
id
            T_Literal id :: Id
id s :: 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
            _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

    check _ = () -> 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 t :: [Token]
t =
        case [Token]
t of
            (T_Redirecting {} : _) -> Bool
False
            (a :: Token
a:(TC_Binary _ _ "=~" lhs :: Token
lhs rhs :: Token
rhs):rest :: [Token]
rest) -> Token -> Id
getId Token
a Id -> Id -> Bool
forall a. Eq a => a -> a -> Bool
== Token -> Id
getId Token
rhs
            _:rest :: [Token]
rest -> [Token] -> Bool
isRegex [Token]
rest
            _ -> 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 x :: [Token]
x = case [Token]
x of
        [T_DollarExpansion _ _] -> Bool
True
        [T_DollarBraced _ _ _] -> Bool
True
        [T_Backticked _ _] -> Bool
True
        _ -> Bool
False

    warnAboutExpansion :: Id -> m ()
warnAboutExpansion id :: Id
id =
        Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id 2027 "The surrounding quotes actually unquote this. Remove or escape them."
    warnAboutLiteral :: Id -> m ()
warnAboutLiteral id :: Id
id =
        Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id 2140 "Word is of the form \"A\"B\"C\" (B indicated). Did you mean \"ABC\" or \"A\\\"B\\\"C\"?"
checkInexplicablyUnquoted _ _ = () -> 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 "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 "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 "~/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 "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 "awk '$0 ~ /foo/'"
checkTildeInQuotes :: p -> Token -> m ()
checkTildeInQuotes _ = Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
check
  where
    verify :: Id -> String -> m ()
verify id :: Id
id ('~':'/':_) = Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id 2088 "Tilde does not expand in quotes. Use $HOME."
    verify _ _ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    check :: Token -> m ()
check (T_NormalWord _ (T_SingleQuoted id :: Id
id str :: String
str:_)) =
        Id -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> String -> m ()
verify Id
id String
str
    check (T_NormalWord _ (T_DoubleQuoted _ (T_Literal id :: Id
id str :: String
str:_):_)) =
        Id -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> String -> m ()
verify Id
id String
str
    check _ = () -> 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 "./ 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 "./file"
checkLonelyDotDash :: p -> Token -> m ()
checkLonelyDotDash _ t :: Token
t@(T_Redirecting id :: Id
id _ _)
    | Token -> String -> Bool
isUnqualifiedCommand Token
t "./" =
        Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
id 2083 "Don't add spaces after the slash in './file'."
checkLonelyDotDash _ _ = () -> 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 "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 "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 "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 "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 "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 "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 "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 "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 "for file in rc.d/*; do exec \"$file\"; done"
checkSpuriousExec :: p -> Token -> m ()
checkSpuriousExec _ = Token -> m ()
doLists
  where
    doLists :: Token -> m ()
doLists (T_Script _ _ cmds :: [Token]
cmds) = [Token] -> Bool -> m ()
doList [Token]
cmds Bool
False
    doLists (T_BraceGroup _ cmds :: [Token]
cmds) = [Token] -> Bool -> m ()
doList [Token]
cmds Bool
False
    doLists (T_WhileExpression _ _ cmds :: [Token]
cmds) = [Token] -> Bool -> m ()
doList [Token]
cmds Bool
True
    doLists (T_UntilExpression _ _ cmds :: [Token]
cmds) = [Token] -> Bool -> m ()
doList [Token]
cmds Bool
True
    doLists (T_ForIn _ _ _ cmds :: [Token]
cmds) = [Token] -> Bool -> m ()
doList [Token]
cmds Bool
True
    doLists (T_ForArithmetic _ _ _ _ cmds :: [Token]
cmds) = [Token] -> Bool -> m ()
doList [Token]
cmds Bool
True
    doLists (T_IfExpression _ thens :: [([Token], [Token])]
thens elses :: [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_ (\(_, l :: [Token]
l) -> [Token] -> Bool -> m ()
doList [Token]
l Bool
False) [([Token], [Token])]
thens
        [Token] -> Bool -> m ()
doList [Token]
elses Bool
False
    doLists _ = () -> 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 _ _ [cmd :: Token
cmd]) =
        Token -> (String -> Bool) -> Bool
isCommandMatch Token
cmd (String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` ["echo", "exit"])
    cleanup _ = 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@(current :: Token
current:following :: Token
following:_) 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' (current :: Token
current:tail :: [Token]
tail) True = do
        Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
commentIfExec Token
current
        [Token] -> Bool -> m ()
doList [Token]
tail Bool
True
    doList' _ _ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

    commentIfExec :: Token -> m ()
commentIfExec (T_Pipeline id :: Id
id _ list :: [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 1 [Token]
list
    commentIfExec (T_Redirecting _ _ f :: Token
f@(
      T_SimpleCommand id :: Id
id _ (cmd :: Token
cmd:arg :: Token
arg:_))) =
        Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token
f Token -> String -> Bool
`isUnqualifiedCommand` "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 2093
            "Remove \"exec \" if script should continue after this command."
    commentIfExec _ = () -> 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 "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 "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 "$(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 "$((i++))"
checkSpuriousExpansion :: p -> Token -> m ()
checkSpuriousExpansion _ (T_SimpleCommand _ _ [T_NormalWord _ [word :: Token
word]]) = Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
check Token
word
  where
    check :: Token -> m ()
check word :: Token
word = case Token
word of
        T_DollarExpansion id :: Id
id _ ->
            Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id 2091 "Remove surrounding $() to avoid executing output."
        T_Backticked id :: Id
id _ ->
            Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id 2092 "Remove backticks to avoid executing output."
        T_DollarArithmetic id :: Id
id _ ->
            Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
id 2084 "Remove '$' or use '_=$((expr))' to avoid executing output."
        T_DoubleQuoted id :: Id
id [subword :: Token
subword] -> Token -> m ()
check Token
subword
        _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
checkSpuriousExpansion _ _ = () -> 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 "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 "echo $((1+2))"
checkDollarBrackets :: p -> Token -> m ()
checkDollarBrackets _ (T_DollarBracket id :: Id
id _) =
    Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
style Id
id 2007 "Use $((..)) instead of deprecated $[..]"
checkDollarBrackets _ _ = () -> 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 "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 "ssh host << 'foo'\necho $PATH\nfoo"
checkSshHereDoc :: p -> Token -> m ()
checkSshHereDoc _ (T_Redirecting _ redirs :: [Token]
redirs cmd :: Token
cmd)
        | Token
cmd Token -> String -> Bool
`isCommand` "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 "[`$]"
    checkHereDoc :: Token -> m ()
checkHereDoc (T_FdRedirect _ _ (T_HereDoc id :: Id
id _ Unquoted token :: String
token tokens :: [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 2087 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ "Quote '" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
token String -> String -> String
forall a. [a] -> [a] -> [a]
++ "' to make here document expansions happen on the server side rather than on the client."
    checkHereDoc _ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
checkSshHereDoc _ _ = () -> 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 "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 "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 "( A=foo; ); rm $A"
prop_subshellAssignmentCheck4 :: Bool
prop_subshellAssignmentCheck4 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
subshellAssignmentCheck "( A=foo; rm $A; )"
prop_subshellAssignmentCheck5 :: Bool
prop_subshellAssignmentCheck5 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
subshellAssignmentCheck "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 "( 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 "( 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 "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 "n=3 & echo $((n++))"
prop_subshellAssignmentCheck9 :: Bool
prop_subshellAssignmentCheck9 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree    Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
subshellAssignmentCheck "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 "(( n <<= 3 )) & (( n |= 4 )) &"
prop_subshellAssignmentCheck11 :: Bool
prop_subshellAssignmentCheck11 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
subshellAssignmentCheck "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 "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 "#!/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 "#!/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 "#!/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 "(set -e); echo $@"
prop_subshellAssignmentCheck17 :: Bool
prop_subshellAssignmentCheck17 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
subshellAssignmentCheck "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 "( 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 "#!/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 "@test 'foo' { a=1; }\n@test 'bar' { echo $a; }\n"
subshellAssignmentCheck :: Parameters -> p -> [TokenComment]
subshellAssignmentCheck params :: Parameters
params t :: 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 [("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 [] _ _ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
findSubshelled (Assignment x :: (Token, Token, String, DataType)
x@(_, _, str :: String
str, _):rest :: [StackData]
rest) ((reason :: String
reason,scope :: [(Token, Token, String, DataType)]
scope):lol :: [(String, [(Token, Token, String, DataType)])]
lol) deadVars :: 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 (_, readToken :: Token
readToken, str :: String
str):rest :: [StackData]
rest) scopes :: [(String, [(Token, Token, String, DataType)])]
scopes deadVars :: 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
        Alive -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
        Dead writeToken :: Token
writeToken reason :: String
reason -> do
                    Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
info (Token -> Id
getId Token
writeToken) 2030 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ "Modification of " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
str String -> String -> String
forall a. [a] -> [a] -> [a]
++ " is local (to subshell caused by "String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
reason String -> String -> String
forall a. [a] -> [a] -> [a]
++")."
                    Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
info (Token -> Id
getId Token
readToken) 2031 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
str String -> String -> String
forall a. [a] -> [a] -> [a]
++ " 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 str :: String
str =
        String
str String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` ["@", "*", "IFS"]

findSubshelled (StackScope (SubshellScope reason :: String
reason):rest :: [StackData]
rest) scopes :: [(String, [(Token, Token, String, DataType)])]
scopes deadVars :: 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 (StackScopeEnd:rest :: [StackData]
rest) ((reason :: String
reason, scope :: [(Token, Token, String, DataType)]
scope):oldScopes :: [(String, [(Token, Token, String, DataType)])]
oldScopes) deadVars :: 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 (\m :: Map String VariableState
m (_, token :: Token
token, var :: String
var, _) ->
            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 readFunc :: Token -> Token -> String -> State t [v]
readFunc writeFunc :: Token -> Token -> String -> DataType -> State t [v]
writeFunc empty :: t
empty flow :: [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 (\list :: [v]
list x :: 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 (base :: Token
base, token :: Token
token, name :: String
name)) =
        Token -> Token -> String -> State t [v]
readFunc Token
base Token
token String
name
    doFlow (Assignment (base :: Token
base, token :: Token
token, name :: String
name, values :: DataType
values)) =
        Token -> Token -> String -> DataType -> State t [v]
writeFunc Token
base Token
token String
name DataType
values
    doFlow _ = [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 "a='cow moo'; echo $a"
prop_checkSpacefulness2 :: Bool
prop_checkSpacefulness2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness "a='cow moo'; [[ $a ]]"
prop_checkSpacefulness3 :: Bool
prop_checkSpacefulness3 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness "a='cow*.mp3'; echo \"$a\""
prop_checkSpacefulness4 :: Bool
prop_checkSpacefulness4 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkSpacefulness "for f in *.mp3; do echo $f; done"
prop_checkSpacefulness4a :: Bool
prop_checkSpacefulness4a= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness "foo=3; foo=$(echo $foo)"
prop_checkSpacefulness5 :: Bool
prop_checkSpacefulness5 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkSpacefulness "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 "a=foo$(lol); echo $a"
prop_checkSpacefulness7 :: Bool
prop_checkSpacefulness7 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkSpacefulness "a=foo\\ bar; rm $a"
prop_checkSpacefulness8 :: Bool
prop_checkSpacefulness8 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness "a=foo\\ bar; a=foo; rm $a"
prop_checkSpacefulness10 :: Bool
prop_checkSpacefulness10= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkSpacefulness "rm $1"
prop_checkSpacefulness11 :: Bool
prop_checkSpacefulness11= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkSpacefulness "rm ${10//foo/bar}"
prop_checkSpacefulness12 :: Bool
prop_checkSpacefulness12= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness "(( $1 + 3 ))"
prop_checkSpacefulness13 :: Bool
prop_checkSpacefulness13= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness "if [[ $2 -gt 14 ]]; then true; fi"
prop_checkSpacefulness14 :: Bool
prop_checkSpacefulness14= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness "foo=$3 env"
prop_checkSpacefulness15 :: Bool
prop_checkSpacefulness15= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness "local foo=$1"
prop_checkSpacefulness16 :: Bool
prop_checkSpacefulness16= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness "declare foo=$1"
prop_checkSpacefulness17 :: Bool
prop_checkSpacefulness17= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkSpacefulness "echo foo=$1"
prop_checkSpacefulness18 :: Bool
prop_checkSpacefulness18= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness "$1 --flags"
prop_checkSpacefulness19 :: Bool
prop_checkSpacefulness19= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkSpacefulness "echo $PWD"
prop_checkSpacefulness20 :: Bool
prop_checkSpacefulness20= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness "n+='foo bar'"
prop_checkSpacefulness21 :: Bool
prop_checkSpacefulness21= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness "select foo in $bar; do true; done"
prop_checkSpacefulness22 :: Bool
prop_checkSpacefulness22= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness "echo $\"$1\""
prop_checkSpacefulness23 :: Bool
prop_checkSpacefulness23= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness "a=(1); echo ${a[@]}"
prop_checkSpacefulness24 :: Bool
prop_checkSpacefulness24= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkSpacefulness "a='a    b'; cat <<< $a"
prop_checkSpacefulness25 :: Bool
prop_checkSpacefulness25= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkSpacefulness "a='s/[0-9]//g'; sed $a"
prop_checkSpacefulness26 :: Bool
prop_checkSpacefulness26= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkSpacefulness "a='foo bar'; echo {1,2,$a}"
prop_checkSpacefulness27 :: Bool
prop_checkSpacefulness27= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness "echo ${a:+'foo'}"
prop_checkSpacefulness28 :: Bool
prop_checkSpacefulness28= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness "exec {n}>&1; echo $n"
prop_checkSpacefulness29 :: Bool
prop_checkSpacefulness29= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness "n=$(stuff); exec {n}>&-;"
prop_checkSpacefulness30 :: Bool
prop_checkSpacefulness30= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkSpacefulness "file='foo bar'; echo foo > $file;"
prop_checkSpacefulness31 :: Bool
prop_checkSpacefulness31= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness "echo \"`echo \\\"$1\\\"`\""
prop_checkSpacefulness32 :: Bool
prop_checkSpacefulness32= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness "var=$1; [ -v var ]"
prop_checkSpacefulness33 :: Bool
prop_checkSpacefulness33= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkSpacefulness "for file; do echo $file; done"
prop_checkSpacefulness34 :: Bool
prop_checkSpacefulness34= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkSpacefulness "declare foo$n=$1"
prop_checkSpacefulness35 :: Bool
prop_checkSpacefulness35= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness "echo ${1+\"$1\"}"
prop_checkSpacefulness36 :: Bool
prop_checkSpacefulness36= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness "arg=$#; echo $arg"
prop_checkSpacefulness37 :: Bool
prop_checkSpacefulness37= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkSpacefulness "@test 'status' {\n [ $status -eq 0 ]\n}"
prop_checkSpacefulness37v :: Bool
prop_checkSpacefulness37v = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkVerboseSpacefulness "@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 params :: 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 x :: a
x = [a] -> m ()
forall w (m :: * -> *). MonadWriter w m => w -> m ()
tell [a
x]
    onFind :: Bool -> Token -> p -> f ()
onFind spaces :: Bool
spaces token :: Token
token _ =
        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) 2223
                         "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) 2086
                         "Double quote to prevent globbing and word splitting."
                         (Parameters -> Token -> Fix
addDoubleQuotesAround Parameters
params Token
token)

    isDefaultAssignment :: Map Id Token -> Token -> Bool
isDefaultAssignment parents :: Map Id Token
parents token :: 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) ["=", ":="]
            Bool -> Bool -> Bool
&& Map Id Token -> String -> Token -> Bool
isParamTo Map Id Token
parents ":" Token
token


prop_checkSpacefulness4v :: Bool
prop_checkSpacefulness4v= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkVerboseSpacefulness "foo=3; foo=$(echo $foo)"
prop_checkSpacefulness8v :: Bool
prop_checkSpacefulness8v= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkVerboseSpacefulness "a=foo\\ bar; a=foo; rm $a"
prop_checkSpacefulness28v :: Bool
prop_checkSpacefulness28v = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkVerboseSpacefulness "exec {n}>&1; echo $n"
prop_checkSpacefulness36v :: Bool
prop_checkSpacefulness36v = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkVerboseSpacefulness "arg=$#; echo $arg"
checkVerboseSpacefulness :: Parameters -> Token -> [TokenComment]
checkVerboseSpacefulness params :: 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 spaces :: Bool
spaces token :: Token
token name :: 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) 2248
                    "Prefer double quoting even when variables don't contain special characters."
                    (Parameters -> Token -> Fix
addDoubleQuotesAround Parameters
params Token
token)]

addDoubleQuotesAround :: Parameters -> Token -> Fix
addDoubleQuotesAround params :: Parameters
params token :: Token
token = (Id -> Parameters -> String -> Fix
surroundWidth (Token -> Id
getId Token
token) Parameters
params "\"")
checkSpacefulness'
    :: (Bool -> Token -> String -> Writer [TokenComment] ()) ->
            Parameters -> Token -> [TokenComment]
checkSpacefulness' :: (Bool -> Token -> String -> WriterT [TokenComment] Identity ())
-> Parameters -> Token -> [TokenComment]
checkSpacefulness' onFind :: Bool -> Token -> String -> WriterT [TokenComment] Identity ()
onFind params :: Parameters
params t :: 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 name :: 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 name :: k
name bool :: 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 _ token :: Token
token name :: 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 x :: a
x = [a] -> m ()
forall w (m :: * -> *). MonadWriter w m => w -> m ()
tell [a
x]

    writeF :: p -> p -> String -> DataType -> m [a]
writeF _ _ name :: String
name (DataString 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 _ _ name :: String
name (DataString 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 _ _ name :: String
name (DataString (SourceFrom vals :: [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 (\x :: 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 _ _ _ _ = [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 t :: Token
t =
        case Token
t of
            (T_DollarBraced _ _ _ ) -> Bool
True
            _ -> Bool
False

    isSpacefulWord :: (String -> Bool) -> [Token] -> Bool
    isSpacefulWord :: (String -> Bool) -> [Token] -> Bool
isSpacefulWord f :: 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 spacefulF :: String -> Bool
spacefulF x :: Token
x =
        case Token
x of
          T_DollarExpansion _ _ -> Bool
True
          T_Backticked _ _ -> Bool
True
          T_Glob _ _         -> Bool
True
          T_Extglob {}       -> Bool
True
          T_Literal _ s :: 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 _ s :: 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 _ _ _ -> 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 _ w :: [Token]
w   -> (String -> Bool) -> [Token] -> Bool
isSpacefulWord String -> Bool
spacefulF [Token]
w
          T_DoubleQuoted _ w :: [Token]
w -> (String -> Bool) -> [Token] -> Bool
isSpacefulWord String -> Bool
spacefulF [Token]
w
          _ -> Bool
False
      where
        globspace :: String
globspace = "*?[] \t\n"
        containsAny :: t a -> t a -> Bool
containsAny s :: 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 "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 "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 "#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 "echo $* $1"
checkVariableBraces :: Parameters -> Token -> f ()
checkVariableBraces params :: Parameters
params t :: Token
t =
    case Token
t of
        T_DollarBraced id :: Id
id False _ ->
            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 2250
                    "Prefer putting braces around variable references even when not strictly required."
                    (Token -> Fix
fixFor Token
t)

        _ -> () -> 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
token = [Replacement] -> Fix
fixWith [Id -> Parameters -> Integer -> String -> Replacement
replaceStart (Token -> Id
getId Token
token) Parameters
params 1 "${"
                           ,Id -> Parameters -> Integer -> String -> Replacement
replaceEnd (Token -> Id
getId Token
token) Parameters
params 0 "}"]

prop_checkQuotesInLiterals1 :: Bool
prop_checkQuotesInLiterals1 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkQuotesInLiterals "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 "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 "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 "param=('--foo='); app \"${param[@]}\""
prop_checkQuotesInLiterals4 :: Bool
prop_checkQuotesInLiterals4 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkQuotesInLiterals "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 "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 "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 "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 "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 "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 "param=\"/foo/'bar baz'/etc\"; rm ${#param}"
checkQuotesInLiterals :: Parameters -> p -> [TokenComment]
checkQuotesInLiterals params :: Parameters
params t :: 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 name :: 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 name :: k
name ref :: 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 "\"|([/= ]|^)'|'( |$)|\\\\ "
    containsQuotes :: String -> Bool
containsQuotes s :: String
s = String
s String -> Regex -> Bool
`matches` Regex
quoteRegex

    writeF :: p -> p -> String -> DataType -> StateT (Map String Id) Identity [a]
writeF _ _ name :: String
name (DataString (SourceFrom values :: [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
            Nothing -> String -> StateT (Map String Id) Identity ()
deleteQuotes String
name
            Just x :: 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 _ _ _ _ = [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 :: Map String Id
map (T_DollarBraced id :: Id
id _ t :: 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 quoteMap :: Map String Id
quoteMap (T_DoubleQuoted id :: Id
id tokens :: [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 quoteMap :: Map String Id
quoteMap (T_NormalWord id :: Id
id tokens :: [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 _ t :: 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 t :: Token
t =
        case Token
t of
            T_DollarBraced id :: Id
id _ _ -> "#" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` Token -> String
bracedString Token
t
            _ -> Bool
False

    readF :: p -> Token -> k -> m [TokenComment]
readF _ expr :: Token
expr name :: 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 "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) 2089 (String -> TokenComment) -> String -> TokenComment
forall a b. (a -> b) -> a -> b
$
                      "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) 2090
                      "Quotes/backslashes in this variable will not be respected."
                ]
              else [])
    suggestion :: String
suggestion =
        if Shell -> Bool
supportsArrays (Parameters -> Shell
shellType Parameters
params)
        then "Use an array."
        else "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 "foo() { :; }; sudo foo"
prop_checkFunctionsUsedExternally2 :: Bool
prop_checkFunctionsUsedExternally2 =
  (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall t. t -> Token -> [TokenComment]
checkFunctionsUsedExternally "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 "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 "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 "f() { :; }; echo f"
prop_checkFunctionsUsedExternally4 :: Bool
prop_checkFunctionsUsedExternally4 =
  (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall t. t -> Token -> [TokenComment]
checkFunctionsUsedExternally "foo() { :; }; sudo \"foo\""
prop_checkFunctionsUsedExternally5 :: Bool
prop_checkFunctionsUsedExternally5 =
  (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall t. t -> Token -> [TokenComment]
checkFunctionsUsedExternally "foo() { :; }; ssh host foo"
prop_checkFunctionsUsedExternally6 :: Bool
prop_checkFunctionsUsedExternally6 =
  (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall t. t -> Token -> [TokenComment]
checkFunctionsUsedExternally "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 "install() { :; }; sudo apt-get install foo"
checkFunctionsUsedExternally :: p -> Token -> [TokenComment]
checkFunctionsUsedExternally params :: p
params t :: 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 _ t :: Token
t@(T_SimpleCommand _ _ (cmd :: Token
cmd:args :: [Token]
args)) =
        case Token -> Maybe String
getCommandBasename Token
t of
            Just name :: String
name -> do
                let argStrings :: [(String, Token)]
argStrings = (Token -> (String, Token)) -> [Token] -> [(String, Token)]
forall a b. (a -> b) -> [a] -> [b]
map (\x :: Token
x -> (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 -> 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
            _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    checkCommand _ _ = () -> 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 name :: String
name argAndString :: [(String, b)]
argAndString =
        case String
name of
            "chroot" -> [(String, b)]
firstNonFlag
            "screen" -> [(String, b)]
firstNonFlag
            "sudo" -> [(String, b)]
firstNonFlag
            "xargs" -> [(String, b)]
firstNonFlag
            "tmux" -> [(String, b)]
firstNonFlag
            "ssh" -> Int -> [(String, b)] -> [(String, b)]
forall a. Int -> [a] -> [a]
take 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 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
            "find" -> Int -> [(String, b)] -> [(String, b)]
forall a. Int -> [a] -> [a]
take 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 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 (\x :: (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
            _ -> []
      where
        firstNonFlag :: [(String, b)]
firstNonFlag = Int -> [(String, b)] -> [(String, b)]
forall a. Int -> [a] -> [a]
take 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 = ["-exec", "-execdir", "-ok"]
        dropFlags :: [(String, b)] -> [(String, b)]
dropFlags = ((String, b) -> Bool) -> [(String, b)] -> [(String, b)]
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (\x :: (String, b)
x -> "-" 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 f :: Token -> StateT [a] Identity ()
f t :: 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
id _ _ name :: String
name _) = ([(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
id _ (_:args :: [Token]
args))
        | Token
t Token -> String -> Bool
`isUnqualifiedCommand` "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 _ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    getAlias :: Token -> f ()
getAlias arg :: Token
arg =
        let string :: String
string = Token -> String
onlyLiteralString Token
arg
        in Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ('=' 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
/= '=') String
string, Token -> Id
getId Token
arg)(String, Id) -> [(String, Id)] -> [(String, Id)]
forall a. a -> [a] -> [a]
:)

    checkArg :: String -> (a, Token) -> m ()
checkArg cmd :: String
cmd (_, arg :: 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) 2033
              "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 2032 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
              "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]
++ "."

prop_checkUnused0 :: Bool
prop_checkUnused0 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments "var=foo; echo $var"
prop_checkUnused1 :: Bool
prop_checkUnused1 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments "var=foo; echo $bar"
prop_checkUnused2 :: Bool
prop_checkUnused2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments "var=foo; export var;"
prop_checkUnused3 :: Bool
prop_checkUnused3 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments "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 "local i=0"
prop_checkUnused5 :: Bool
prop_checkUnused5 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments "read lol; echo $lol"
prop_checkUnused6 :: Bool
prop_checkUnused6 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments "var=4; (( var++ ))"
prop_checkUnused7 :: Bool
prop_checkUnused7 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments "var=2; $((var))"
prop_checkUnused8 :: Bool
prop_checkUnused8 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments "var=2; var=3;"
prop_checkUnused9 :: Bool
prop_checkUnused9 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments "read ''"
prop_checkUnused10 :: Bool
prop_checkUnused10= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments "read -p 'test: '"
prop_checkUnused11 :: Bool
prop_checkUnused11= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments "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 "read foo; echo ${!foo}"
prop_checkUnused13 :: Bool
prop_checkUnused13= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments "x=(1); (( x[0] ))"
prop_checkUnused14 :: Bool
prop_checkUnused14= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments "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 "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 "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 "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 "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 "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 "a=1; PS1='$a'"
prop_checkUnused21 :: Bool
prop_checkUnused21= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments "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 "a=1; [ -v a ]"
prop_checkUnused23 :: Bool
prop_checkUnused23= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments "a=1; [ -R a ]"
prop_checkUnused24 :: Bool
prop_checkUnused24= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments "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 "readarray foo; echo ${foo[@]}"
prop_checkUnused26 :: Bool
prop_checkUnused26= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments "declare -F foo"
prop_checkUnused27 :: Bool
prop_checkUnused27= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments "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 "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 "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 "let a=1"
prop_checkUnused31 :: Bool
prop_checkUnused31= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments "let 'a=1'"
prop_checkUnused32 :: Bool
prop_checkUnused32= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments "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 "a=foo; [[ foo =~ ^{$a}$ ]]"
prop_checkUnused34 :: Bool
prop_checkUnused34= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments "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 "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 "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 "fd=2; exec {fd}>&-"
prop_checkUnused38 :: Bool
prop_checkUnused38= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments "(( a=42 ))"
prop_checkUnused39 :: Bool
prop_checkUnused39= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments "declare -x -f foo"
prop_checkUnused40 :: Bool
prop_checkUnused40= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments "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 "@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 "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 "DEFINE_string foo '' ''"
prop_checkUnused44 :: Bool
prop_checkUnused44= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments "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 "readonly foo=bar"
prop_checkUnused46 :: Bool
prop_checkUnused46= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnusedAssignments "readonly foo=(bar)"
checkUnusedAssignments :: Parameters -> p -> [TokenComment]
checkUnusedAssignments params :: Parameters
params t :: 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 (base :: Token
base, token :: Token
token, name :: 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 _ = 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, name :: String
name, _)) | 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 _ = 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 (name :: String
name, token :: Token
token) =
        Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn (Token -> Id
getId Token
token) 2034 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
            String
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ " 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 "echo $foo"
prop_checkUnassignedReferences2 :: Bool
prop_checkUnassignedReferences2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences "foo=hello; echo $foo"
prop_checkUnassignedReferences3 :: Bool
prop_checkUnassignedReferences3 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences "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 "RANDOM2=foo; echo $RANDOM"
prop_checkUnassignedReferences5 :: Bool
prop_checkUnassignedReferences5 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences "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 "foo=..; echo ${foo-bar}"
prop_checkUnassignedReferences7 :: Bool
prop_checkUnassignedReferences7 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences "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 "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 "echo ${foo-bar}"
prop_checkUnassignedReferences10 :: Bool
prop_checkUnassignedReferences10= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences "echo ${foo:?}"
prop_checkUnassignedReferences11 :: Bool
prop_checkUnassignedReferences11= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences "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 "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 "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 "foo=; echo $foo"
prop_checkUnassignedReferences15 :: Bool
prop_checkUnassignedReferences15= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences "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 "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 "USERS=foo; echo $USER"
prop_checkUnassignedReferences18 :: Bool
prop_checkUnassignedReferences18= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences "FOOBAR=42; export FOOBAR="
prop_checkUnassignedReferences19 :: Bool
prop_checkUnassignedReferences19= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences "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 "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 "echo ${#foo}"
prop_checkUnassignedReferences22 :: Bool
prop_checkUnassignedReferences22= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences "echo ${!os*}"
prop_checkUnassignedReferences23 :: Bool
prop_checkUnassignedReferences23= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences "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 "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 "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 "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 ": ${foo:=bar}"
prop_checkUnassignedReferences28 :: Bool
prop_checkUnassignedReferences28= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall p. Parameters -> p -> [TokenComment]
checkUnassignedReferences "#!/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 "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 "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 "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 "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 "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 "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 "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 "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 "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) "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' includeGlobals :: Bool
includeGlobals params :: Parameters
params t :: p
t = [TokenComment]
warnings
  where
    (readMap :: Map String Token
readMap, writeMap :: 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 (\a :: 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 (_, _, name :: String
name, _))  =
        ((Map String Token, Map String ())
 -> (Map String Token, Map String ()))
-> m ()
forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify (\(read :: Map String Token
read, written :: 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 (_, place :: Token
place, name :: String
name)) =
        ((Map String Token, Map String ())
 -> (Map String Token, Map String ()))
-> m ()
forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify (\(read :: Map String Token
read, written :: 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 _ = () -> 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 var :: String
var = do
        (match :: String
match, score :: 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 (\x :: 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 var :: p
var match :: t a
match score :: 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
> 3 Bool -> Bool -> Bool
&& a
score a -> a -> Bool
forall a. Ord a => a -> a -> Bool
<= 1
                Bool -> Bool -> Bool
|| Int
l Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> 7 Bool -> Bool -> Bool
&& a
score a -> a -> Bool
forall a. Ord a => a -> a -> Bool
<= 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 var :: String
var place :: 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) 2153 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
            "Possible misspelling: " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
var String -> String -> String
forall a. [a] -> [a] -> [a]
++ " may not be assigned, but " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
match String -> String -> String
forall a. [a] -> [a] -> [a]
++ " is."

    warningForLocals :: String -> Token -> m (m ())
warningForLocals var :: String
var place :: 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) 2154 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
            String
var String -> String -> String
forall a. [a] -> [a] -> [a]
++ " is referenced but not assigned" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
optionalTip String -> String -> String
forall a. [a] -> [a] -> [a]
++ "."
      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 " (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
forall a. [a] -> [a] -> [a]
++ ")\" )"
            else String -> Maybe String -> String
forall a. a -> Maybe a -> a
fromMaybe "" (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
$ " (did you mean '" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
match String -> String -> String
forall a. [a] -> [a] -> [a]
++ "'?)"

    warningFor :: String -> Token -> Maybe (m ())
warningFor var :: String
var place :: 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 var :: String
var t :: 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 _ _ _) | String
var String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= String -> String
getBracedReference (Token -> String
bracedString Token
b) = Bool
True
        isArray _ = Bool
False

    isGuarded :: Token -> Bool
isGuarded (T_DollarBraced _ _ v :: 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
name
    isGuarded _ = Bool
False
    --  :? or :- with optional array index and colon
    guardRegex :: Regex
guardRegex = String -> Regex
mkRegex "^(\\[.*\\])?:?[-?]"

    match :: String -> String -> Int
match var :: String
var candidate :: 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 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 "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 "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 "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 "*.txt"
checkGlobsAsOptions :: p -> Token -> m ()
checkGlobsAsOptions _ (T_SimpleCommand _ _ args :: [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 1 [Token]
args)
  where
    check :: Token -> m ()
check v :: Token
v@(T_NormalWord _ (T_Glob id :: Id
id s :: String
s:_)) | String
s String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "*" Bool -> Bool -> Bool
|| String
s String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "?" =
        Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
info Id
id 2035 "Use ./*glob* or -- *glob* so names with dashes won't become options."
    check _ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

    isEndOfArgs :: Token -> Bool
isEndOfArgs t :: 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
            "--" -> Bool
True
            ":::" -> Bool
True
            "::::" -> Bool
True
            _ -> Bool
False

checkGlobsAsOptions _ _ = () -> 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 "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 "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 "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 "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 "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 "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 "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 "while read foo; do ssh -n $foo uptime; done < file"

checkWhileReadPitfalls :: p -> Token -> m ()
checkWhileReadPitfalls _ (T_WhileExpression id :: Id
id [command :: Token
command] contents :: [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 = [ "ssh", "ffmpeg", "mplayer", "HandBrakeCLI" ]
    preventionFlags :: [String]
preventionFlags = ["n", "noconsolecontrols" ]

    isStdinReadCommand :: Token -> Bool
isStdinReadCommand (T_Pipeline _ _ [T_Redirecting id :: Id
id redirs :: [Token]
redirs cmd :: 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 -> Bool
forall a. Eq a => a -> a -> Bool
== "read"
            Bool -> Bool -> Bool
&& ("-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 _ = Bool
False

    checkMuncher :: Token -> m ()
checkMuncher (T_Pipeline _ _ (T_Redirecting _ redirs :: [Token]
redirs cmd :: Token
cmd:_)) | 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 _ thens :: [([Token], [Token])]
thens elses :: [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]

            _ -> 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 2095 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
                        String
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ " 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) 2095 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
                        "Add < /dev/null to prevent " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ " from swallowing stdin."
    checkMuncher _ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

    stdinRedirect :: Token -> Bool
stdinRedirect (T_FdRedirect _ fd :: String
fd _)
        | String
fd String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "" Bool -> Bool -> Bool
|| String
fd String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "0" = Bool
True
    stdinRedirect _ = Bool
False
checkWhileReadPitfalls _ _ = () -> 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 "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 "var=$(echo $var) cmd"
checkPrefixAssignmentReference :: Parameters -> Token -> m ()
checkPrefixAssignmentReference params :: Parameters
params t :: Token
t@(T_DollarBraced id :: Id
id _ value :: 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 (t :: Token
t:rest :: [Token]
rest) =
        case Token
t of
            T_SimpleCommand _ vars :: [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
            _ -> [Token] -> m ()
check [Token]
rest
    checkVar :: Token -> m ()
checkVar (T_Assignment aId :: Id
aId mode :: AssignmentMode
mode aName :: String
aName [] value :: 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 2097 "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 2098 "This expansion will not see the mentioned assignment."
    checkVar _ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

checkPrefixAssignmentReference _ _ = () -> 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 "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 "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 "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 "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 "tr -d [a-zA-Z]" -- tr has 2060
checkCharRangeGlob :: Parameters -> Token -> m ()
checkCharRangeGlob p :: Parameters
p t :: Token
t@(T_Glob id :: Id
id str :: 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) "tr" Token
t) =
    if ":" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
contents
        Bool -> Bool -> Bool
&& ":" 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
/= ":"
    then Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id 2101 "Named class needs outer [], e.g. [[:digit:]]."
    else
        Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ('[' 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 2102 "Ranges can only match single chars (mentioned due to duplicates)."
  where
    isCharClass :: String -> Bool
isCharClass str :: String
str = "[" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
str Bool -> Bool -> Bool
&& "]" 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 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
- 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
>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
/= '-') (String -> Bool) -> String -> Bool
forall a b. (a -> b) -> a -> b
$ String
contents
checkCharRangeGlob _ _ = () -> 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 "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 "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 "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 "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 "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 "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 "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 "cd tmp\nfoo\n# shellcheck disable=SC2103\ncd ..\n"
checkCdAndBack :: Parameters -> Token -> f ()
checkCdAndBack params :: Parameters
params t :: 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 t :: Token
t =
        case Token -> [String]
oversimplify Token
t of
            [_, p :: String
p] -> String
p String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` ["..", "-"]
            _ -> Bool
False

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

    findCdPair :: [Token] -> Maybe Id
findCdPair list :: [Token]
list =
        case [Token]
list of
            (a :: Token
a:b :: Token
b:rest :: [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)
            _ -> Maybe Id
forall a. Maybe a
Nothing

    doList :: [Token] -> m ()
doList list :: [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 2103 "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 "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 "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 "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 "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 "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 "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 "#!/bin/ksh\nwhile true; do true | { break; }; done"
checkLoopKeywordScope :: Parameters -> Token -> m ()
checkLoopKeywordScope params :: Parameters
params t :: 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 ["continue", "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 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) 2104 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ "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]
++ "."
        else Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err (Token -> Id
getId Token
t) 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]
++ " 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 str :: String
str:_ -> Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn (Token -> Id
getId Token
t) 2106 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
            "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]
++ "."
        _ -> () -> 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 t :: Token
t = case Parameters -> Token -> Scope
leadType Parameters
params Token
t of
        NoneScope -> Maybe String
forall a. Maybe a
Nothing
        SubshellScope str :: String
str -> String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return String
str
    relevant :: Token -> Bool
relevant t :: 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 _ _ = () -> 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 "#!/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 "#!/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 "foo() { echo bar; }"
checkFunctionDeclarations :: Parameters -> Token -> m ()
checkFunctionDeclarations params :: Parameters
params
        (T_Function id :: Id
id (FunctionKeyword hasKeyword :: Bool
hasKeyword) (FunctionParentheses hasParens :: Bool
hasParens) _ _) =
    case Parameters -> Shell
shellType Parameters
params of
        Bash -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
        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 2111 "ksh does not allow 'function' keyword and '()' at the same time."
        Dash -> m ()
forSh
        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 2112 "'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 2113 "'function' keyword is non-standard. Use 'foo()' instead of 'function foo'."
checkFunctionDeclarations _ _ = () -> 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 "#!/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 "#!/bin/bash\nfoo |& bar"
checkStderrPipe :: Parameters -> Token -> m ()
checkStderrPipe params :: Parameters
params =
    case Parameters -> Shell
shellType Parameters
params of
        Ksh -> Token -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Token -> m ()
match
        _ -> 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
id "|&") =
        Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err Id
id 2118 "Ksh does not support |&. Use 2>&1 |."
    match _ = () -> 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 "foo() { echo $1; }; foo"
prop_checkUnpassedInFunctions2 :: Bool
prop_checkUnpassedInFunctions2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions "foo() { echo $1; };"
prop_checkUnpassedInFunctions3 :: Bool
prop_checkUnpassedInFunctions3 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions "foo() { echo $lol; }; foo"
prop_checkUnpassedInFunctions4 :: Bool
prop_checkUnpassedInFunctions4 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions "foo() { echo $0; }; foo"
prop_checkUnpassedInFunctions5 :: Bool
prop_checkUnpassedInFunctions5 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions "foo() { echo $1; }; foo 'lol'; foo"
prop_checkUnpassedInFunctions6 :: Bool
prop_checkUnpassedInFunctions6 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions "foo() { set -- *; echo $1; }; foo"
prop_checkUnpassedInFunctions7 :: Bool
prop_checkUnpassedInFunctions7 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions "foo() { echo $1; }; foo; foo;"
prop_checkUnpassedInFunctions8 :: Bool
prop_checkUnpassedInFunctions8 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions "foo() { echo $((1)); }; foo;"
prop_checkUnpassedInFunctions9 :: Bool
prop_checkUnpassedInFunctions9 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions "foo() { echo $(($b)); }; foo;"
prop_checkUnpassedInFunctions10 :: Bool
prop_checkUnpassedInFunctions10= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions "foo() { echo $!; }; foo;"
prop_checkUnpassedInFunctions11 :: Bool
prop_checkUnpassedInFunctions11= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions "foo() { bar() { echo $1; }; bar baz; }; foo;"
prop_checkUnpassedInFunctions12 :: Bool
prop_checkUnpassedInFunctions12= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions "foo() { echo ${!var*}; }; foo;"
prop_checkUnpassedInFunctions13 :: Bool
prop_checkUnpassedInFunctions13= (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions "# shellcheck disable=SC2120\nfoo() { echo $1; }\nfoo\n"
checkUnpassedInFunctions :: Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions params :: Parameters
params root :: 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 _ _ _ name :: String
name _) -> (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
id _ _ name :: String
name body :: 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 _ = Maybe Token
forall a. Maybe a
Nothing

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

    isDirectChildOf :: Token -> Token -> Bool
isDirectChildOf child :: Token
child parent :: 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 (\x :: Token
x -> case Token
x of
            T_Function {} -> Bool
True
            T_Script {} -> Bool
True  -- for sourced files
            _ -> 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 _ _ (cmd :: Token
cmd:args :: [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 _ = Maybe (WriterT [(String, Bool, Token)] Identity ())
forall a. Maybe a
Nothing

    isPositional :: String -> Bool
isPositional str :: String
str = String
str String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "*" Bool -> Bool -> Bool
|| String
str String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "@"
        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
/= "0" Bool -> Bool -> Bool
&& String
str String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= "")

    isArgumentless :: (a, b, c) -> b
isArgumentless (_, b :: b
b, _) = 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@(name :: k
name, _, _) = ([(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 group :: [(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 (name :: String
name, func :: 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 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 (name :: String
name, _, thing :: Token
thing) =
        Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
info (Token -> Id
getId Token
thing) 2119 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
            "Use " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ " \"$@\" if function's $1 should mean script's $1."
    warnForDeclaration :: Token -> String -> m ()
warnForDeclaration func :: Token
func name :: String
name =
        Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn (Token -> Id
getId Token
func) 2120 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
            String
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ " references arguments, but none are ever passed."

    getFunction :: [(String, b, c)] -> (String, Token)
getFunction ((name :: String
name, _, _):_) =
        (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 "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 "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 "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 "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 "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 "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 "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 "PATH=$PATH:/stuff"
checkOverridingPath :: p -> Token -> m ()
checkOverridingPath _ (T_SimpleCommand _ vars :: [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
id Assign "PATH" [] word :: 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) ["/bin", "/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 -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
string Bool -> Bool -> Bool
&& ':' 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 -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` String
string Bool -> Bool -> Bool
&& '/' 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 _ = () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    notify :: Id -> m ()
notify id :: Id
id = Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id 2123 "PATH is the shell search path. Use another name."
checkOverridingPath _ _ = () -> 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 "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 "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 "PATH=~/bin"
checkTildeInPath :: p -> Token -> m ()
checkTildeInPath _ (T_SimpleCommand _ vars :: [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
id Assign "PATH" [] (T_NormalWord _ parts :: [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 (\x :: 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 2147 "Literal tilde in PATH works poorly across programs."
    checkVar _ = () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

    hasTilde :: Token -> Bool
hasTilde t :: 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 '~') ((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 "") Token
t))
    isQuoted :: Token -> Bool
isQuoted T_DoubleQuoted {} = Bool
True
    isQuoted T_SingleQuoted {} = Bool
True
    isQuoted _ = Bool
False
checkTildeInPath _ _ = () -> 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 "#!/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 "#!/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 "#!/bin/bash\necho \"${ ls; }\""
checkUnsupported :: Parameters -> Token -> f ()
checkUnsupported params :: Parameters
params t :: 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
    (name :: String
name, support :: [Shell]
support) = Token -> (String, [Shell])
shellSupport Token
t
    report :: String -> m ()
report s :: String
s = Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
err (Token -> Id
getId Token
t) 2127 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
        "To use " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
s String -> String -> String
forall a. [a] -> [a] -> [a]
++ ", 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 " 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 t :: Token
t =
  case Token
t of
    T_CaseExpression _ _ list :: [(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 (\(a :: CaseType
a,_,_) -> CaseType
a) [(CaseType, [Token], [Token])]
list)
    T_DollarBraceCommandExpansion {} -> ("${ ..; } command expansion", [Shell
Ksh])
    _ -> ("", [])
  where
    forCase :: t CaseType -> (String, [Shell])
forCase seps :: t CaseType
seps | CaseType
CaseContinue CaseType -> t CaseType -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` t CaseType
seps = ("cases with ;;&", [Shell
Bash])
    forCase seps :: t CaseType
seps | CaseType
CaseFallThrough CaseType -> t CaseType -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` t CaseType
seps = ("cases with ;&", [Shell
Bash, Shell
Ksh])
    forCase _ = ("", [])


groupWith :: (a -> a) -> [a] -> [[a]]
groupWith f :: 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 "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 "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 "foo < file; bar < file; baz < file;"
checkMultipleAppends :: p -> Token -> m ()
checkMultipleAppends params :: p
params t :: 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 list :: [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 (f :: Maybe (a, Id)
f:_:_:_) | 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) 2129
            "Consider using { cmd1; cmd2; } >> file instead of individual redirects."
    checkGroup _ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    getTarget :: Token -> Maybe (Token, Id)
getTarget (T_Annotation _ _ t :: Token
t) = Token -> Maybe (Token, Id)
getTarget Token
t
    getTarget (T_Pipeline _ _ args :: [Token]
args@(_:_)) = Token -> Maybe (Token, Id)
getTarget ([Token] -> Token
forall a. [a] -> a
last [Token]
args)
    getTarget (T_Redirecting id :: Id
id list :: [Token]
list _) = 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
!!! 0
        (Token, Id) -> Maybe (Token, Id)
forall (m :: * -> *) a. Monad m => a -> m a
return (Token
file, Id
id)
    getTarget _ = Maybe (Token, Id)
forall a. Maybe a
Nothing
    getAppend :: Token -> Maybe Token
getAppend (T_FdRedirect _ _ (T_IoFile _ T_DGREAT {} f :: Token
f)) = Token -> Maybe Token
forall (m :: * -> *) a. Monad m => a -> m a
return Token
f
    getAppend _ = 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 "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 "IFS=$'\\t'"
checkSuspiciousIFS :: Parameters -> Token -> m ()
checkSuspiciousIFS params :: Parameters
params (T_Assignment id :: Id
id Assign "IFS" [] value :: 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 "'<literal linefeed here>'" else "$'\\n'"
    t :: String
t = if Parameters -> Shell
shellType Parameters
params Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
== Shell
Sh then "\"$(printf '\\t')\"" else "$'\\t'"
    check :: String -> m ()
check value :: String
value =
        case String
value of
            "\\n" -> String -> m ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
String -> f ()
suggest String
n
            "/n" -> String -> m ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
String -> f ()
suggest String
n
            "\\t" -> String -> m ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
String -> f ()
suggest String
t
            "/t" -> String -> m ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
String -> f ()
suggest String
t
            _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    suggest :: String -> m ()
suggest r :: String
r = Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id 2141 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ "Did you mean IFS=" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
r String -> String -> String
forall a. [a] -> [a] -> [a]
++ " ?"
checkSuspiciousIFS _ _ = () -> 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 "[[ $(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 "[ -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 "[ -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 "[ -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 "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 "[[ -n $(pgrep foo) ]]"
checkShouldUseGrepQ :: p -> Token -> m ()
checkShouldUseGrepQ params :: p
params t :: 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
id _ token :: 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
id _ "-n" token :: 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
id _ "-z" token :: Token
token -> Id -> Bool -> Token -> Maybe (m ())
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Bool -> Token -> Maybe (m ())
check Id
id Bool
False Token
token
        _ -> String -> Maybe (m ())
forall (m :: * -> *) a. MonadFail m => String -> m a
fail "not check"
  where
    check :: Id -> Bool -> Token -> Maybe (m ())
check id :: Id
id bool :: Bool
bool token :: Token
token = do
        String
name <- Token -> Maybe String
getFinalGrep Token
token
        let op :: String
op = if Bool
bool then "-n" else "-z"
        let flip :: String
flip = if Bool
bool then "" else "! "
        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 2143 (String -> Maybe (m ())) -> String -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$
            "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]
++ " -q instead of " String -> String -> String
forall a. [a] -> [a] -> [a]
++
                "comparing output with [ " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
op String -> String -> String
forall a. [a] -> [a] -> [a]
++ " .. ]."

    getFinalGrep :: Token -> Maybe String
getFinalGrep t :: 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 t :: Token
t =
        case Token
t of
            T_NormalWord _ [x :: Token
x] -> Token -> m [Token]
getPipeline Token
x
            T_DoubleQuoted _ [x :: Token
x] -> Token -> m [Token]
getPipeline Token
x
            T_DollarExpansion _ [x :: Token
x] -> Token -> m [Token]
getPipeline Token
x
            T_Pipeline _ _ cmds :: [Token]
cmds -> [Token] -> m [Token]
forall (m :: * -> *) a. Monad m => a -> m a
return [Token]
cmds
            _ -> String -> m [Token]
forall (m :: * -> *) a. MonadFail m => String -> m a
fail "unknown"
    isGrep :: String -> Bool
isGrep = (String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` ["grep", "egrep", "fgrep", "zgrep"])

prop_checkTestArgumentSplitting1 :: Bool
prop_checkTestArgumentSplitting1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting "[ -e *.mp3 ]"
prop_checkTestArgumentSplitting2 :: Bool
prop_checkTestArgumentSplitting2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting "[[ $a == *b* ]]"
prop_checkTestArgumentSplitting3 :: Bool
prop_checkTestArgumentSplitting3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting "[[ *.png == '' ]]"
prop_checkTestArgumentSplitting4 :: Bool
prop_checkTestArgumentSplitting4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting "[[ foo == f{o,oo,ooo} ]]"
prop_checkTestArgumentSplitting5 :: Bool
prop_checkTestArgumentSplitting5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting "[[ $@ ]]"
prop_checkTestArgumentSplitting6 :: Bool
prop_checkTestArgumentSplitting6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting "[ -e $@ ]"
prop_checkTestArgumentSplitting7 :: Bool
prop_checkTestArgumentSplitting7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting "[ $@ == $@ ]"
prop_checkTestArgumentSplitting8 :: Bool
prop_checkTestArgumentSplitting8 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting "[[ $@ = $@ ]]"
prop_checkTestArgumentSplitting9 :: Bool
prop_checkTestArgumentSplitting9 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting "[[ foo =~ bar{1,2} ]]"
prop_checkTestArgumentSplitting10 :: Bool
prop_checkTestArgumentSplitting10 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting "[ \"$@\" ]"
prop_checkTestArgumentSplitting11 :: Bool
prop_checkTestArgumentSplitting11 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting "[[ \"$@\" ]]"
prop_checkTestArgumentSplitting12 :: Bool
prop_checkTestArgumentSplitting12 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting "[ *.png ]"
prop_checkTestArgumentSplitting13 :: Bool
prop_checkTestArgumentSplitting13 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting "[ \"$@\" == \"\" ]"
prop_checkTestArgumentSplitting14 :: Bool
prop_checkTestArgumentSplitting14 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting "[[ \"$@\" == \"\" ]]"
prop_checkTestArgumentSplitting15 :: Bool
prop_checkTestArgumentSplitting15 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting "[[ \"$*\" == \"\" ]]"
prop_checkTestArgumentSplitting16 :: Bool
prop_checkTestArgumentSplitting16 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting "[[ -v foo[123] ]]"
prop_checkTestArgumentSplitting17 :: Bool
prop_checkTestArgumentSplitting17 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting "#!/bin/ksh\n[ -e foo* ]"
prop_checkTestArgumentSplitting18 :: Bool
prop_checkTestArgumentSplitting18 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting "#!/bin/ksh\n[ -d foo* ]"
checkTestArgumentSplitting :: Parameters -> Token -> Writer [TokenComment] ()
checkTestArgumentSplitting :: Parameters -> Token -> WriterT [TokenComment] Identity ()
checkTestArgumentSplitting params :: Parameters
params t :: Token
t =
    case Token
t of
        (TC_Unary _ typ :: ConditionType
typ op :: String
op token :: Token
token) | Token -> Bool
isGlob Token
token ->
            if String
op String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "-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) 2208 (String -> WriterT [TokenComment] Identity ())
-> String -> WriterT [TokenComment] Identity ()
forall a b. (a -> b) -> a -> b
$
                      "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 -> String -> String
forall a. a -> [a] -> [a]
:Char
cChar -> String -> String
forall a. a -> [a] -> [a]
:[] | Char
c <- "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) 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]
++ " 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) 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]
++ " doesn't work with globs. Use a for loop."

        (TC_Nullary _ typ :: ConditionType
typ token :: 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 _ typ :: ConditionType
typ op :: String
op token :: Token
token) -> ConditionType -> Token -> WriterT [TokenComment] Identity ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
ConditionType -> Token -> f ()
checkAll ConditionType
typ Token
token

        (TC_Binary _ typ :: ConditionType
typ op :: String
op lhs :: Token
lhs rhs :: Token
rhs) ->
            if String
op String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` ["=", "==", "!=", "=~"]
            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]
        _ -> () -> WriterT [TokenComment] Identity ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    checkAll :: ConditionType -> Token -> m ()
checkAll typ :: ConditionType
typ token :: 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 typ :: ConditionType
typ token :: 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) 2198 "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) 2199 "Arrays implicitly concatenate in [[ ]]. Use a loop (or explicit * instead of @)."

    checkBraces :: ConditionType -> Token -> f ()
checkBraces typ :: ConditionType
typ token :: 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) 2200 "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) 2201 "Brace expansion doesn't happen in [[ ]]. Use a loop."

    checkGlobs :: ConditionType -> Token -> f ()
checkGlobs typ :: ConditionType
typ token :: 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) 2202 "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) 2203 "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 "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 "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 "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 "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 "f() { local -r a=$(false); }"
checkMaskedReturns :: p -> Token -> m ()
checkMaskedReturns _ t :: Token
t@(T_SimpleCommand id :: Id
id _ (cmd :: Token
cmd:rest :: [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` ["declare", "export"]
        Bool -> Bool -> Bool
|| String
name String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "local" Bool -> Bool -> Bool
&& "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
id _ _ _ word :: 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 2155 "Declare and assign separately to avoid masking return values."
    checkArgs _ = () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

    hasReturn :: Token -> Bool
hasReturn t :: Token
t = case Token
t of
        T_Backticked {} -> Bool
True
        T_DollarExpansion {} -> Bool
True
        _ -> Bool
False
checkMaskedReturns _ _ = () -> 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 "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 "read -ar foo"
checkReadWithoutR :: p -> Token -> f ()
checkReadWithoutR _ t :: Token
t@T_SimpleCommand {} | Token
t Token -> String -> Bool
`isUnqualifiedCommand` "read" =
    Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless ("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) 2162 "read without -r will mangle backslashes."
checkReadWithoutR _ _ = () -> 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 "cd ~/src; rm -r foo"
prop_checkUncheckedCd2 :: Bool
prop_checkUncheckedCd2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd "cd ~/src || exit; rm -r foo"
prop_checkUncheckedCd3 :: Bool
prop_checkUncheckedCd3 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd "set -e; cd ~/src; rm -r foo"
prop_checkUncheckedCd4 :: Bool
prop_checkUncheckedCd4 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd "if cd foo; then rm foo; fi"
prop_checkUncheckedCd5 :: Bool
prop_checkUncheckedCd5 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd "if true; then cd foo; fi"
prop_checkUncheckedCd6 :: Bool
prop_checkUncheckedCd6 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd "cd .."
prop_checkUncheckedCd7 :: Bool
prop_checkUncheckedCd7 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd "#!/bin/bash -e\ncd foo\nrm bar"
prop_checkUncheckedCd8 :: Bool
prop_checkUncheckedCd8 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd "set -o errexit; cd foo; rm bar"
prop_checkUncheckedCd9 :: Bool
prop_checkUncheckedCd9 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd "builtin cd ~/src; rm -r foo"
prop_checkUncheckedPushd1 :: Bool
prop_checkUncheckedPushd1 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd "pushd ~/src; rm -r foo"
prop_checkUncheckedPushd2 :: Bool
prop_checkUncheckedPushd2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd "pushd ~/src || exit; rm -r foo"
prop_checkUncheckedPushd3 :: Bool
prop_checkUncheckedPushd3 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd "set -e; pushd ~/src; rm -r foo"
prop_checkUncheckedPushd4 :: Bool
prop_checkUncheckedPushd4 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd "if pushd foo; then rm foo; fi"
prop_checkUncheckedPushd5 :: Bool
prop_checkUncheckedPushd5 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd "if true; then pushd foo; fi"
prop_checkUncheckedPushd6 :: Bool
prop_checkUncheckedPushd6 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd "pushd .."
prop_checkUncheckedPushd7 :: Bool
prop_checkUncheckedPushd7 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd "#!/bin/bash -e\npushd foo\nrm bar"
prop_checkUncheckedPushd8 :: Bool
prop_checkUncheckedPushd8 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd "set -o errexit; pushd foo; rm bar"
prop_checkUncheckedPushd9 :: Bool
prop_checkUncheckedPushd9 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd "pushd -n foo"
prop_checkUncheckedPopd1 :: Bool
prop_checkUncheckedPopd1 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd "popd; rm -r foo"
prop_checkUncheckedPopd2 :: Bool
prop_checkUncheckedPopd2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd "popd || exit; rm -r foo"
prop_checkUncheckedPopd3 :: Bool
prop_checkUncheckedPopd3 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd "set -e; popd; rm -r foo"
prop_checkUncheckedPopd4 :: Bool
prop_checkUncheckedPopd4 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd "if popd; then rm foo; fi"
prop_checkUncheckedPopd5 :: Bool
prop_checkUncheckedPopd5 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd "if true; then popd; fi"
prop_checkUncheckedPopd6 :: Bool
prop_checkUncheckedPopd6 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd "popd"
prop_checkUncheckedPopd7 :: Bool
prop_checkUncheckedPopd7 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd "#!/bin/bash -e\npopd\nrm bar"
prop_checkUncheckedPopd8 :: Bool
prop_checkUncheckedPopd8 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd "set -o errexit; popd; rm bar"
prop_checkUncheckedPopd9 :: Bool
prop_checkUncheckedPopd9 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd "popd -n foo"
prop_checkUncheckedPopd10 :: Bool
prop_checkUncheckedPopd10 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd "cd ../.."
prop_checkUncheckedPopd11 :: Bool
prop_checkUncheckedPopd11 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd "cd ../.././.."
prop_checkUncheckedPopd12 :: Bool
prop_checkUncheckedPopd12 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd "cd /"
prop_checkUncheckedPopd13 :: Bool
prop_checkUncheckedPopd13 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd "cd ../../.../.."

checkUncheckedCdPushdPopd :: Parameters -> Token -> [TokenComment]
checkUncheckedCdPushdPopd params :: Parameters
params root :: 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` ["cd", "pushd", "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` ["pushd", "popd"] Bool -> Bool -> Bool
&& ("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) 2164
                    ("Use '" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ " ... || exit' or '" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ " ... || return' in case " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ " fails.")
                    ([Replacement] -> Fix
fixWith [Id -> Parameters -> Integer -> String -> Replacement
replaceEnd (Token -> Id
getId Token
t) Parameters
params 0 " || exit"])
    checkElement _ = () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    getName :: Token -> String
getName t :: Token
t = 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 -> Maybe String
getCommandName Token
t
    isSafeDir :: Token -> Bool
isSafeDir t :: Token
t = case Token -> [String]
oversimplify Token
t of
          [_, str :: String
str] -> String
str String -> Regex -> Bool
`matches` Regex
regex
          _ -> Bool
False
    regex :: Regex
regex = String -> Regex
mkRegex "^/*((\\.|\\.\\.)/+)*(\\.|\\.\\.)?$"

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 "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 "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 "for i in *; do for j in *.bar; do true; done; done"
checkLoopVariableReassignment :: Parameters -> Token -> m ()
checkLoopVariableReassignment params :: Parameters
params token :: 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
        _ -> 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 (\x :: 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) 2165 "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)  2167 "This parent loop has its index variable overridden."
    path :: [Token]
path = Int -> [Token] -> [Token]
forall a. Int -> [a] -> [a]
drop 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 t :: Token
t =
        case Token
t of
            T_ForIn _ s :: String
s _ _ -> String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return String
s
            T_ForArithmetic _
                (TA_Sequence _
                    [TA_Assignment _ "="
                        (TA_Variable _ var :: String
var _ ) _])
                            _ _ _ -> String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return String
var
            _ -> String -> Maybe String
forall (m :: * -> *) a. MonadFail m => String -> m a
fail "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 "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 "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 "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 "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 "run bar ']'"
checkTrailingBracket :: p -> Token -> m ()
checkTrailingBracket _ token :: Token
token =
    case Token
token of
        T_SimpleCommand _ _ tokens :: [Token]
tokens@(_:_) -> Token -> Token -> m ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
Token -> Token -> f ()
check ([Token] -> Token
forall a. [a] -> a
last [Token]
tokens) Token
token
        _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    check :: Token -> Token -> m ()
check t :: Token
t command :: Token
command =
        case Token
t of
            T_NormalWord id :: Id
id [T_Literal _ str :: 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` [ "]]", "]" ]
                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 2171 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
                    "Found trailing " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
str String -> String -> String
forall a. [a] -> [a] -> [a]
++ " outside test. Add missing " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
opposite String -> String -> String
forall a. [a] -> [a] -> [a]
++ " or quote if intentional."
            _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    invert :: String -> String
invert s :: String
s =
        case String
s of
            "]]" -> "[["
            "]" -> "["
            x :: 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 "[ $? -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 "[[ \"$?\" -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 "[[ 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 "[[ $? -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 "[[ 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 "[[ $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 "(( $? == 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 "(( $? ))"
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 "(( ! $? ))"
checkReturnAgainstZero :: p -> Token -> m ()
checkReturnAgainstZero _ token :: Token
token =
    case Token
token of
        TC_Binary id :: Id
id _ _ lhs :: Token
lhs rhs :: Token
rhs -> Token -> Token -> m ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
Token -> Token -> f ()
check Token
lhs Token
rhs
        TA_Binary id :: Id
id _ lhs :: Token
lhs rhs :: Token
rhs -> Token -> Token -> m ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
Token -> Token -> f ()
check Token
lhs Token
rhs
        TA_Unary id :: Id
id _ exp :: 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 _ [exp :: 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)
        _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    check :: Token -> Token -> m ()
check lhs :: Token
lhs rhs :: 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 t :: 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 "0"
    isExitCode :: Token -> Bool
isExitCode t :: 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
== "?"
            _ -> Bool
False
    message :: Id -> m ()
message id :: Id
id = Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
style Id
id 2181 "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 "> 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 "> 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 "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 "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 "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 "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 "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 "var=`< file`"
checkRedirectedNowhere :: Parameters -> Token -> m ()
checkRedirectedNowhere params :: Parameters
params token :: Token
token =
    case Token
token of
        T_Pipeline _ _ [single :: 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) 2188 "This redirection doesn't have a command. Move to its command (or use 'true' as no-op)."

        T_Pipeline _ _ list :: [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
$ \x :: 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) 2189 "You can't have | between this redirection and the command it should apply to."

        _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    isInExpansion :: Token -> Bool
isInExpansion t :: Token
t =
        case Int -> [Token] -> [Token]
forall a. Int -> [a] -> [a]
drop 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 _ [_] : _ -> Bool
True
            T_Backticked _ [_] : _ -> Bool
True
            t :: Token
t@T_Annotation {} : _ -> Token -> Bool
isInExpansion Token
t
            _ -> Bool
False
    getDanglingRedirect :: Token -> Maybe Token
getDanglingRedirect token :: Token
token =
        case Token
token of
            T_Redirecting _ (first :: Token
first:_) (T_SimpleCommand _ [] []) -> Token -> Maybe Token
forall (m :: * -> *) a. Monad m => a -> m a
return Token
first
            _ -> Maybe Token
forall a. Maybe a
Nothing


prop_checkArrayAssignmentIndices1 :: Bool
prop_checkArrayAssignmentIndices1 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkArrayAssignmentIndices "declare -A foo; foo=(bar)"
prop_checkArrayAssignmentIndices2 :: Bool
prop_checkArrayAssignmentIndices2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkArrayAssignmentIndices "declare -a foo; foo=(bar)"
prop_checkArrayAssignmentIndices3 :: Bool
prop_checkArrayAssignmentIndices3 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkArrayAssignmentIndices "declare -A foo; foo=([i]=bar)"
prop_checkArrayAssignmentIndices4 :: Bool
prop_checkArrayAssignmentIndices4 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkArrayAssignmentIndices "typeset -A foo; foo+=(bar)"
prop_checkArrayAssignmentIndices5 :: Bool
prop_checkArrayAssignmentIndices5 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkArrayAssignmentIndices "arr=( [foo]= bar )"
prop_checkArrayAssignmentIndices6 :: Bool
prop_checkArrayAssignmentIndices6 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkArrayAssignmentIndices "arr=( [foo] = bar )"
prop_checkArrayAssignmentIndices7 :: Bool
prop_checkArrayAssignmentIndices7 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkArrayAssignmentIndices "arr=( var=value )"
prop_checkArrayAssignmentIndices8 :: Bool
prop_checkArrayAssignmentIndices8 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkArrayAssignmentIndices "arr=( [foo]=bar )"
prop_checkArrayAssignmentIndices9 :: Bool
prop_checkArrayAssignmentIndices9 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkArrayAssignmentIndices "arr=( [foo]=\"\" )"
checkArrayAssignmentIndices :: Parameters -> Token -> [TokenComment]
checkArrayAssignmentIndices params :: Parameters
params root :: 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 _ t :: Token
t =
        case Token
t of
            T_Assignment _ _ name :: String
name [] (T_Array _ list :: [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
            _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

    checkElement :: Bool -> Token -> m ()
checkElement isAssociative :: Bool
isAssociative t :: Token
t =
        case Token
t of
            T_IndexedElement _ _ (T_Literal id :: Id
id "") ->
                Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id 2192 "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 _ parts :: [Token]
parts ->
                let literalEquals :: [m ()]
literalEquals = do
                    Token
part <- [Token]
parts
                    (id :: Id
id, str :: String
str) <- case Token
part of
                        T_Literal id :: Id
id str :: String
str -> [(Id
id,String
str)]
                        _ -> []
                    Bool -> [()]
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> [()]) -> Bool -> [()]
forall a b. (a -> b) -> a -> b
$ '=' 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 2191 "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 "\"")
                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) 2190 "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

            _ -> () -> 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 "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 "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 "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 "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 "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 "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 "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 "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 "case $f in x) true;;& x) false;; esac"
checkUnmatchableCases :: Parameters -> Token -> m ()
checkUnmatchableCases params :: Parameters
params t :: Token
t =
    case Token
t of
        T_CaseExpression _ word :: Token
word list :: [(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 (\x :: (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) 2194
                        "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 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

        _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    fst3 :: (a, b, c) -> a
fst3 (x :: a
x,_,_) = a
x
    snd3 :: (a, b, c) -> b
snd3 (_,x :: b
x,_) = b
x
    tp :: Map Id (Position, Position)
tp = Parameters -> Map Id (Position, Position)
tokenPositions Parameters
params
    check :: [PseudoGlob] -> Token -> m ()
check target :: [PseudoGlob]
target candidate :: 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) 2195
                    "This pattern will never match the case statement's word. Double check them."

    tupMap :: (a -> b) -> [a] -> [(a, b)]
tupMap f :: a -> b
f l :: [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 ((glob :: Token
glob, Just x :: [PseudoGlob]
x), rest :: t (Token, Maybe [PseudoGlob])
rest) =
        case ((Token, [PseudoGlob]) -> Bool)
-> [(Token, [PseudoGlob])] -> [(Token, [PseudoGlob])]
forall a. (a -> Bool) -> [a] -> [a]
filter (\(_, p :: [PseudoGlob]
p) -> [PseudoGlob]
x [PseudoGlob] -> [PseudoGlob] -> Bool
`pseudoGlobIsSuperSetof` [PseudoGlob]
p) [(Token, [PseudoGlob])]
valids of
            ((first :: Token
first,_):_) -> do
                Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn (Token -> Id
getId Token
glob) 2221 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ "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) 2222 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ "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)
            _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
      where
        patternContext :: Id -> String
        patternContext :: Id -> String
patternContext id :: 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 l :: Integer
l -> " 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
<> "."
              _      -> "."

        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 (x :: a
x, Just y :: b
y) = [(a
x,b
y)]
        f _ = []
    checkDoms _ = () -> 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 "( -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 "( 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 "( 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 "[ 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 "( -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 "( -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 "( ! -d file )"
checkSubshellAsTest :: p -> Token -> m ()
checkSubshellAsTest _ t :: Token
t =
    case Token
t of
        T_Subshell id :: Id
id [w :: Token
w] -> Id -> Token -> m ()
forall (f :: * -> *).
MonadWriter [TokenComment] f =>
Id -> Token -> f ()
check Id
id Token
w
        _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    check :: Id -> Token -> m ()
check id :: Id
id t :: Token
t = case Token
t of
        (T_Banged _ w :: Token
w) -> Id -> Token -> m ()
check Id
id Token
w
        (T_AndIf _ w :: Token
w _) -> Id -> Token -> m ()
check Id
id Token
w
        (T_OrIf _ w :: Token
w _) -> Id -> Token -> m ()
check Id
id Token
w
        (T_Pipeline _ _ [T_Redirecting _ _ (T_SimpleCommand _ [] (first :: Token
first:second :: Token
second:_))]) ->
            Id -> Token -> Token -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Token -> Token -> m ()
checkParams Id
id Token
first Token
second
        _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()


    checkParams :: Id -> Token -> Token -> m ()
checkParams id :: Id
id first :: Token
first second :: 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 2204 "(..) 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 2205 "(..) 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 "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 "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 "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 "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 "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 "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 "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 "a=( * )"
checkSplittingInArrays :: Parameters -> Token -> m ()
checkSplittingInArrays params :: Parameters
params t :: Token
t =
    case Token
t of
        T_Array _ elements :: [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
        _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    check :: Token -> m ()
check word :: Token
word = case Token
word of
        T_NormalWord _ parts :: [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
        _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    checkPart :: Token -> m ()
checkPart part :: Token
part = case Token
part of
        T_DollarExpansion id :: Id
id _ -> Id -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Id -> m ()
forCommand Id
id
        T_DollarBraceCommandExpansion id :: Id
id _ -> Id -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Id -> m ()
forCommand Id
id
        T_Backticked id :: Id
id _ -> Id -> m ()
forall (m :: * -> *). MonadWriter [TokenComment] m => Id -> m ()
forCommand Id
id
        T_DollarBraced id :: Id
id _ str :: 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 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 "Quote to prevent word splitting/globbing, or split robustly with read -A or while read."
                else "Quote to prevent word splitting/globbing, or split robustly with mapfile or read -a."
        _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

    forCommand :: Id -> m ()
forCommand id :: Id
id =
        Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
warn Id
id 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 "Prefer read -A or while read to split command output (or quote to avoid splitting)."
            else "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 "( 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 "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 "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 "foo 1>&2"
checkRedirectionToNumber :: p -> Token -> m ()
checkRedirectionToNumber _ t :: Token
t = case Token
t of
    T_IoFile id :: Id
id _ word :: 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 2210 "This is a file redirection. Was it supposed to be a comparison or fd operation?"
    _ -> () -> 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 "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 "$(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 "echo foo*"
checkGlobAsCommand :: p -> Token -> f ()
checkGlobAsCommand _ t :: Token
t = case Token
t of
    T_SimpleCommand _ _ (first :: Token
first:_) ->
        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) 2211 "This is a glob used as a command name. Was it supposed to be in ${..}, array, or is it missing quoting?"
    _ -> () -> 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 "-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 "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 "'--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 "var=cmd --arg"  -- Handled by SC2037
checkFlagAsCommand :: p -> Token -> f ()
checkFlagAsCommand _ t :: Token
t = case Token
t of
    T_SimpleCommand _ [] (first :: Token
first:_) ->
        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) 2215 "This flag is used as a command name. Bad line break or missing [ .. ]?"
    _ -> () -> 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 "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 "[ foo -o bar ]"
checkEmptyCondition :: p -> Token -> m ()
checkEmptyCondition _ t :: Token
t = case Token
t of
    TC_Empty id :: Id
id _ -> Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
style Id
id 2212 "Use 'false' instead of empty [/[[ conditionals."
    _ -> () -> 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 "foo | echo bar"
prop_checkPipeToNowhere2 :: Bool
prop_checkPipeToNowhere2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkPipeToNowhere "basename < file.txt"
prop_checkPipeToNowhere3 :: Bool
prop_checkPipeToNowhere3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkPipeToNowhere "printf 'Lol' <<< str"
prop_checkPipeToNowhere4 :: Bool
prop_checkPipeToNowhere4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkPipeToNowhere "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 "echo foo | xargs du"
prop_checkPipeToNowhere6 :: Bool
prop_checkPipeToNowhere6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
checkPipeToNowhere "ls | echo $(cat)"
prop_checkPipeToNowhere7 :: Bool
prop_checkPipeToNowhere7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
checkPipeToNowhere "echo foo | var=$(cat) ls"
prop_checkPipeToNowhere8 :: Bool
prop_checkPipeToNowhere8 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
checkPipeToNowhere "foo | true"
prop_checkPipeToNowhere9 :: Bool
prop_checkPipeToNowhere9 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
checkPipeToNowhere "mv -i f . < /dev/stdin"
checkPipeToNowhere :: Parameters -> Token -> WriterT [TokenComment] Identity ()
checkPipeToNowhere :: Parameters -> Token -> WriterT [TokenComment] Identity ()
checkPipeToNowhere _ t :: Token
t =
    case Token
t of
        T_Pipeline _ _ (first :: Token
first:rest :: [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 _ redirects :: [Token]
redirects cmd :: 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
        _ -> () -> WriterT [TokenComment] Identity ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    checkPipe :: Token -> m ()
checkPipe redir :: 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
== "echo"
                then "Did you want 'cat' instead?"
                else "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) 2216 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
            "Piping to '" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ "', a command that doesn't read stdin. " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
suggestion

    checkRedir :: Token -> m ()
checkRedir cmd :: 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` ["cp", "mv", "rm"] Bool -> Bool -> Bool
&& Token
cmd Token -> String -> Bool
`hasFlag` "i"
        let suggestion :: String
suggestion =
                if String
name String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== "echo"
                then "Did you want 'cat' instead?"
                else "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) 2217 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$
            "Redirecting to '" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ "', 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 t :: 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 t :: Token
t =
        case Token
t of
            T_ProcSub {} -> Bool
True
            T_Backticked {} -> Bool
True
            T_DollarExpansion {} -> Bool
True
            _ -> Bool
False

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

prop_checkUseBeforeDefinition1 :: Bool
prop_checkUseBeforeDefinition1 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
forall t. t -> Token -> [TokenComment]
checkUseBeforeDefinition "f; f() { true; }"
prop_checkUseBeforeDefinition2 :: Bool
prop_checkUseBeforeDefinition2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall t. t -> Token -> [TokenComment]
checkUseBeforeDefinition "f() { true; }; f"
prop_checkUseBeforeDefinition3 :: Bool
prop_checkUseBeforeDefinition3 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
forall t. t -> Token -> [TokenComment]
checkUseBeforeDefinition "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 "mycmd || mycmd() { f; }"
checkUseBeforeDefinition :: p -> Token -> [TokenComment]
checkUseBeforeDefinition _ t :: 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 t :: Token
t = case Token
t of
        T_Pipeline _ _ [T_Redirecting _ _ (T_Function _ _ _ name :: String
name _)] ->
            (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 _ _ w :: Token
w -> Token -> m ()
examine Token
w
        T_Pipeline _ _ cmds :: [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
        _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()

    checkUsage :: Map String a -> Token -> m ()
checkUsage map :: Map String a
map cmd :: 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) 2218
                "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 x :: 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 "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 "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 "for i in $var; do true; done"
checkForLoopGlobVariables :: p -> Token -> m ()
checkForLoopGlobVariables _ t :: Token
t =
    case Token
t of
        T_ForIn _ _ words :: [Token]
words _ -> (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
        _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    check :: Token -> f ()
check (T_NormalWord _ parts :: [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 t :: Token
t = Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
info (Token -> Id
getId Token
t) 2231
        "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 "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 "( [ 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 "( [ 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 "( [ a ] && { [ b ] && [ c ]; } )"
checkSubshelledTests :: Parameters -> Token -> m ()
checkSubshelledTests params :: Parameters
params t :: Token
t =
    case Token
t of
        T_Subshell id :: Id
id list :: [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 2233 "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 2234 "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 2235 "Use { ..; } instead of (..) to avoid subshell overhead."
        _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where

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

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

    isTestCommand :: Token -> Bool
isTestCommand t :: Token
t =
        case Token
t of
            T_Pipeline _ [] [T_Redirecting _ _ cmd :: Token
cmd] ->
                case Token
cmd of
                    T_Condition {} -> Bool
True
                    _ -> Token
cmd Token -> String -> Bool
`isCommand` "test"
            _ -> 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 chain :: [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 1 [Token]
chain) of
            T_IfExpression {}    : _ -> Bool
True
            T_WhileExpression {} : _ -> Bool
True
            T_UntilExpression {} : _ -> Bool
True
            _ -> Bool
False

    -- Skip any parent of a T_Subshell until we reach something interesting
    skippable :: Token -> Bool
skippable t :: Token
t =
        case Token
t of
            T_Redirecting _ [] _ -> Bool
True
            T_Pipeline _ [] _ -> Bool
True
            T_Annotation {} -> Bool
True
            _ -> 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 "[ ! -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 "! [[ -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 "! [ -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 "[[ ! -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 "[ -z $var ]"
checkInvertedStringTest :: p -> Token -> m ()
checkInvertedStringTest _ t :: Token
t =
    case Token
t of
        TC_Unary _ _ "!" (TC_Unary _ _ op :: String
op _) ->
            case String
op of
                "-n" -> Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
style (Token -> Id
getId Token
t) 2236 "Use -z instead of ! -n."
                "-z" -> Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
style (Token -> Id
getId Token
t) 2236 "Use -n instead of ! -z."
                _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
        T_Banged _ (T_Pipeline _ _
          [T_Redirecting _ _ (T_Condition _ _ (TC_Unary _ _ op :: String
op _))]) ->
            case String
op of
                "-n" -> Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
style (Token -> Id
getId Token
t) 2237 "Use [ -z .. ] instead of ! [ -n .. ]."
                "-z" -> Id -> Integer -> String -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> m ()
style (Token -> Id
getId Token
t) 2237 "Use [ -n .. ] instead of ! [ -z .. ]."
                _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
        _ -> () -> 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 "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 "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 "ls > myfile"
checkRedirectionToCommand :: p -> Token -> f ()
checkRedirectionToCommand _ t :: Token
t =
    case Token
t of
        T_IoFile _ _ (T_NormalWord id :: Id
id [T_Literal _ str :: 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
== "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 2238 "Redirecting to/from command name instead of file. Did you want pipes/xargs (or quote to ignore)?"
        _ -> () -> 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 "[[ $(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 "[[ $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 "[[ $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 "[[ -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 "[[ \"$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 "[[ `x` ]]"
checkNullaryExpansionTest :: Parameters -> Token -> m ()
checkNullaryExpansionTest params :: Parameters
params t :: Token
t =
    case Token
t of
        TC_Nullary _ _ word :: Token
word ->
            case Token -> [Token]
getWordParts Token
word of
                [t :: 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 2243 "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
                x :: [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 2244 "Prefer explicit -n to check non-empty string (or use =/-ne to check boolean/integer)." Fix
fix
                _ -> () -> 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 0 "-n "]
        _ -> () -> 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 "$\"(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 "$\"{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 "\"$(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 "$\"..\""
checkDollarQuoteParen :: Parameters -> Token -> m ()
checkDollarQuoteParen params :: Parameters
params t :: Token
t =
    case Token
t of
        T_DollarDoubleQuoted id :: Id
id ((T_Literal _ (c :: Char
c:_)):_) | Char
c Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` "({" ->
            Id -> Integer -> String -> Fix -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Integer -> String -> Fix -> m ()
warnWithFix Id
id 2247 "Flip leading $ and \" if this should be a quoted substitution." (Id -> Fix
fix Id
id)
        _ -> () -> m ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    fix :: Id -> Fix
fix id :: Id
id = [Replacement] -> Fix
fixWith [Id -> Parameters -> Integer -> String -> Replacement
replaceStart Id
id Parameters
params 2 "\"$"]

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 "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 "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 "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 "case $1 in **) true ;; esac"
checkDefaultCase :: p -> Token -> f ()
checkDefaultCase _ t :: Token
t =
    case Token
t of
        T_CaseExpression id :: Id
id _ list :: [(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 2249 "Consider adding a default *) case, even if it just exits with error."
        _ -> () -> f ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    canMatchAny :: (a, t Token, c) -> Bool
canMatchAny (_, list :: t Token
list, _) = (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' pat :: 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 "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 "! 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 "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 "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 "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 "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 "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 "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 "set -e; while ! true; do true; done"
checkUselessBang :: Parameters -> Token -> f ()
checkUselessBang params :: Parameters
params t :: 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 t :: Token
t =
        case Token
t of
            T_Banged id :: Id
id cmd :: 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 2251
                        "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 1 "", Id -> Parameters -> Integer -> String -> Replacement
replaceEnd (Token -> Id
getId Token
cmd) Parameters
params 0 " && exit 1"])
            _ -> () -> 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 t :: Token
t =
        case Token
t of
            T_Script _ _ list :: [Token]
list -> [Token] -> [Token]
forall a. [a] -> [a]
dropLast [Token]
list
            T_BraceGroup _ list :: [Token]
list -> if Token -> Bool
isFunctionBody Token
t then [Token] -> [Token]
forall a. [a] -> [a]
dropLast [Token]
list else [Token]
list
            T_Subshell _ list :: [Token]
list -> [Token] -> [Token]
forall a. [a] -> [a]
dropLast [Token]
list
            T_WhileExpression _ conds :: [Token]
conds cmds :: [Token]
cmds -> [Token] -> [Token]
forall a. [a] -> [a]
dropLast [Token]
conds [Token] -> [Token] -> [Token]
forall a. [a] -> [a] -> [a]
++ [Token]
cmds
            T_UntilExpression _ conds :: [Token]
conds cmds :: [Token]
cmds -> [Token] -> [Token]
forall a. [a] -> [a]
dropLast [Token]
conds [Token] -> [Token] -> [Token]
forall a. [a] -> [a] -> [a]
++ [Token]
cmds
            T_ForIn _ _ _ list :: [Token]
list -> [Token]
list
            T_ForArithmetic _ _ _ _ list :: [Token]
list -> [Token]
list
            T_Annotation _ _ t :: Token
t -> Token -> [Token]
getNonReturningCommands Token
t
            T_IfExpression _ conds :: [([Token], [Token])]
conds elses :: [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
            _ -> []

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

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

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