{-
    Copyright 2012-2024 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 #-}
{-# LANGUAGE PatternGuards #-}
module ShellCheck.Analytics (checker, optionalChecks, ShellCheck.Analytics.runTests) where

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

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

-- Checks that are run on the AST root
treeChecks :: [Parameters -> Token -> [TokenComment]]
treeChecks :: [Parameters -> Token -> [TokenComment]]
treeChecks = [
    [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]
forall {p}. Parameters -> p -> [TokenComment]
checkQuotesInLiterals
    ,Parameters -> Token -> [TokenComment]
forall {t}. t -> Token -> [TokenComment]
checkShebangParameters
    ,Parameters -> 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
    ,Parameters -> Token -> [TokenComment]
checkAliasUsedInSameParsingUnit
    ,Parameters -> Token -> [TokenComment]
forall {p}. Parameters -> p -> [TokenComment]
checkArrayValueUsedAsIndex
    ]

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

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


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

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

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

nodeChecks :: [Parameters -> Token -> Writer [TokenComment] ()]
nodeChecks :: [Parameters -> Token -> WriterT [TokenComment] Identity ()]
nodeChecks = [
    Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoc
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForInQuoted
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkDoubleBracketOperators
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkLiteralBreakingTest
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkArithmeticBadOctal
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkComparisonAgainstGlob
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCaseAgainstGlob
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCommarrays
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOrNeq
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkEchoWc
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantIfs
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipedAssignment
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkAssignAteCommand
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoeVar
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkQuotedCondRegex
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkForInCat
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkFindExec
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkValidCondOps
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobbedRegex
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTestRedirects
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkBadParameterSubstitution
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTildeInQuotes
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkLonelyDotDash
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExec
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExpansion
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkDollarBrackets
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSshHereDoc
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobsAsOptions
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
checkWhileReadPitfalls
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOverridingPath
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkArrayAsString
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnsupported
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkMultipleAppends
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSuspiciousIFS
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTildeInPath
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkReadWithoutR
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkLoopVariableReassignment
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTrailingBracket
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSubshellAsTest
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSplittingInArrays
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkRedirectionToNumber
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobAsCommand
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkFlagAsCommand
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkEmptyCondition
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
checkPipeToNowhere
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkForLoopGlobVariables
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSubshelledTests
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkInvertedStringTest
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
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
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkTranslatedStringVariable
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkModifiedArithmeticInRedirection
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
checkBlatantRecursion
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkBadTestAndOr
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkAssignToSelf
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkEqualsInCommand
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSecondArgIsComparison
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkComparisonWithLeadingX
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCommandWithTrailingSymbol
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnquotedParameterExpansionPattern
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkBatsTestDoesNotUseNegation
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkCommandIsUnreachable
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
checkSpacefulnessCfg
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkOverwrittenExitCode
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnnecessaryArithmeticExpansionIndex
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkUnnecessaryParens
    ,Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkPlusEqualsNumber
    ]

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


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

optionalTreeChecks :: [(CheckDescription, (Parameters -> Token -> [TokenComment]))]
optionalTreeChecks :: [(CheckDescription, Parameters -> Token -> [TokenComment])]
optionalTreeChecks = [
    (CheckDescription
newCheckDescription {
        cdName = "quote-safe-variables",
        cdDescription = "Suggest quoting variables without metacharacters",
        cdPositive = "var=hello; echo $var",
        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 ()
checkVerboseSpacefulnessCfg])

    ,(CheckDescription
newCheckDescription {
        cdName = "avoid-nullary-conditions",
        cdDescription = "Suggest explicitly using -n in `[ $var ]`",
        cdPositive = "[ \"$var\" ]",
        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 = "add-default-case",
        cdDescription = "Suggest adding a default case in `case` statements",
        cdPositive = "case $? in 0) echo 'Success';; esac",
        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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkDefaultCase])

    ,(CheckDescription
newCheckDescription {
        cdName = "require-variable-braces",
        cdDescription = "Suggest putting braces around all variable references",
        cdPositive = "var=hello; echo $var",
        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 = "check-unassigned-uppercase",
        cdDescription = "Warn when uppercase variables are unassigned",
        cdPositive = "echo $VAR",
        cdNegative = "VAR=hello; echo $VAR"
    }, Bool -> Parameters -> Token -> [TokenComment]
forall {p}. Bool -> Parameters -> p -> [TokenComment]
checkUnassignedReferences' Bool
True)

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

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

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

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

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

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

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

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

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

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

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

verifyCodes :: (Parameters -> Token -> Writer [TokenComment] ()) -> [Code] -> String -> Bool
verifyCodes :: (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> [Code] -> String -> Bool
verifyCodes Parameters -> Token -> WriterT [TokenComment] Identity ()
f [Code]
l String
s = Maybe [Code]
codes Maybe [Code] -> Maybe [Code] -> Bool
forall a. Eq a => a -> a -> Bool
== [Code] -> Maybe [Code]
forall a. a -> Maybe a
Just [Code]
l
  where
    treeCheck :: Parameters -> Token -> [TokenComment]
treeCheck = (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
    comments :: Maybe [TokenComment]
comments = (Parameters -> Token -> [TokenComment])
-> String -> Maybe [TokenComment]
runAndGetComments Parameters -> Token -> [TokenComment]
treeCheck String
s
    codes :: Maybe [Code]
codes = (TokenComment -> Code) -> [TokenComment] -> [Code]
forall a b. (a -> b) -> [a] -> [b]
map (Comment -> Code
cCode (Comment -> Code)
-> (TokenComment -> Comment) -> TokenComment -> Code
forall b c a. (b -> c) -> (a -> b) -> a -> c
. TokenComment -> Comment
tcComment) ([TokenComment] -> [Code]) -> Maybe [TokenComment] -> Maybe [Code]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Maybe [TokenComment]
comments

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

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

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

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

-- Checks whether the current parent path is part of a condition
isCondition :: NonEmpty Token -> Bool
isCondition (Token
x NE.:| [Token]
xs) = (Token -> (Token -> Bool) -> Token -> Bool)
-> (Token -> Bool) -> [Token] -> Token -> Bool
forall a b. (a -> b -> b) -> b -> [a] -> b
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr Token -> (Token -> Bool) -> Token -> Bool
go (Bool -> Token -> Bool
forall a b. a -> b -> a
const Bool
False) [Token]
xs Token
x
  where
    go :: Token -> (Token -> Bool) -> Token -> Bool
go Token
_ Token -> Bool
_ T_BatsTest{} = Bool
True -- count anything in a @test as conditional
    go Token
parent Token -> Bool
go_rest Token
child =
        Token -> Id
getId Token
child Id -> [Id] -> Bool
forall a. Eq a => a -> [a] -> 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
go_rest Token
parent
    getConditionChildren :: Token -> [Token]
getConditionChildren Token
t =
        case Token
t of
            T_AndIf Id
_ Token
left Token
right -> [Token
left]
            T_OrIf Id
id Token
left Token
right -> [Token
left]
            T_IfExpression Id
id [([Token], [Token])]
conditions [Token]
elses -> (([Token], [Token]) -> [Token]) -> [([Token], [Token])] -> [Token]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (Int -> [Token] -> [Token]
forall a. Int -> [a] -> [a]
take Int
1 ([Token] -> [Token])
-> (([Token], [Token]) -> [Token]) -> ([Token], [Token]) -> [Token]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Token] -> [Token]
forall a. [a] -> [a]
reverse ([Token] -> [Token])
-> (([Token], [Token]) -> [Token]) -> ([Token], [Token]) -> [Token]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ([Token], [Token]) -> [Token]
forall a b. (a, b) -> a
fst) [([Token], [Token])]
conditions
            T_WhileExpression Id
id [Token]
c [Token]
l -> Int -> [Token] -> [Token]
forall a. Int -> [a] -> [a]
take Int
1 ([Token] -> [Token]) -> ([Token] -> [Token]) -> [Token] -> [Token]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Token] -> [Token]
forall a. [a] -> [a]
reverse ([Token] -> [Token]) -> [Token] -> [Token]
forall a b. (a -> b) -> a -> b
$ [Token]
c
            T_UntilExpression Id
id [Token]
c [Token]
l -> Int -> [Token] -> [Token]
forall a. Int -> [a] -> [a]
take Int
1 ([Token] -> [Token]) -> ([Token] -> [Token]) -> [Token] -> [Token]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Token] -> [Token]
forall a. [a] -> [a]
reverse ([Token] -> [Token]) -> [Token] -> [Token]
forall a b. (a -> b) -> a -> b
$ [Token]
c
            Token
_ -> []

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

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

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

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

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

prop_checkEchoWc3 :: Bool
prop_checkEchoWc3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkEchoWc String
"n=$(echo $foo | wc -c)"
checkEchoWc :: p -> Token -> f ()
checkEchoWc p
_ (T_Pipeline Id
id [Token]
_ [Token
a, Token
b]) =
    Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when ([String]
acmd [String] -> [String] -> Bool
forall a. Eq a => a -> a -> Bool
== [String
"echo", String
"${VAR}"]) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
        case [String]
bcmd of
            [String
"wc", String
"-c"] -> f ()
countMsg
            [String
"wc", String
"-m"] -> f ()
countMsg
            [String]
_ -> () -> f ()
forall a. a -> f a
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 -> Code -> String -> f ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style Id
id Code
2000 String
"See if you can use ${#variable} instead."
checkEchoWc p
_ Token
_ = () -> f ()
forall a. a -> f a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipedAssignment String
"A=ls | grep foo"
prop_checkPipedAssignment2 :: Bool
prop_checkPipedAssignment2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipedAssignment String
"A=foo cmd | grep foo"
prop_checkPipedAssignment3 :: Bool
prop_checkPipedAssignment3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipedAssignment String
"A=foo"
checkPipedAssignment :: p -> Token -> m ()
checkPipedAssignment p
_ (T_Pipeline Id
_ [Token]
_ (T_Redirecting Id
_ [Token]
_ (T_SimpleCommand Id
id (Token
_:[Token]
_) []):Token
_:[Token]
_)) =
    Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2036 String
"If you wanted to assign the output of the pipeline, use a=$(b | c) ."
checkPipedAssignment p
_ Token
_ = () -> m ()
forall a. a -> m a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkAssignAteCommand String
"A=ls -l"
prop_checkAssignAteCommand2 :: Bool
prop_checkAssignAteCommand2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkAssignAteCommand String
"A=ls --sort=$foo"
prop_checkAssignAteCommand3 :: Bool
prop_checkAssignAteCommand3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkAssignAteCommand String
"A=cat foo | grep bar"
prop_checkAssignAteCommand4 :: Bool
prop_checkAssignAteCommand4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkAssignAteCommand String
"A=foo ls -l"
prop_checkAssignAteCommand5 :: Bool
prop_checkAssignAteCommand5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkAssignAteCommand String
"PAGER=cat grep bar"
prop_checkAssignAteCommand6 :: Bool
prop_checkAssignAteCommand6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkAssignAteCommand String
"PAGER=\"cat\" grep bar"
prop_checkAssignAteCommand7 :: Bool
prop_checkAssignAteCommand7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkAssignAteCommand String
"here=pwd"
checkAssignAteCommand :: p -> Token -> m ()
checkAssignAteCommand p
_ (T_SimpleCommand Id
id [T_Assignment Id
_ AssignmentMode
_ String
_ [Token]
_ Token
assignmentTerm] [Token]
list) =
    -- Check if first word is intended as an argument (flag or glob).
    if [Token] -> Bool
firstWordIsArg [Token]
list
    then
        Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2037 String
"To assign the output of a command, use var=$(cmd) ."
    else
        -- Check if it's a known, unquoted command name.
        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 -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2209 String
"Use var=$(command) to assign output (or quote to assign string)."
  where
    isCommonCommand :: Maybe String -> Bool
isCommonCommand (Just String
s) = String
s String -> [String] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
commonCommands
    isCommonCommand Maybe String
_ = Bool
False
    firstWordIsArg :: [Token] -> Bool
firstWordIsArg (Token
head:[Token]
_) = Token -> Bool
isGlob Token
head Bool -> Bool -> Bool
|| Token -> Bool
isUnquotedFlag Token
head
    firstWordIsArg [] = Bool
False

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

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

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

    getLiterals :: Token -> Maybe String
getLiterals (T_Literal Id
_ String
s) = String -> Maybe String
forall a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return String
s
    getLiterals (T_Glob Id
_ String
s) = String -> Maybe String
forall a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return String
s
    getLiterals Token
_ = Maybe String
forall a. Maybe a
Nothing
checkWrongArithmeticAssignment Parameters
_ Token
_ = () -> m ()
forall a. a -> m a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoc String
"cat foo | grep bar"
prop_checkUuoc2 :: Bool
prop_checkUuoc2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoc String
"cat * | grep bar"
prop_checkUuoc3 :: Bool
prop_checkUuoc3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoc String
"cat \"$var\" | grep bar"
prop_checkUuoc3b :: Bool
prop_checkUuoc3b = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoc String
"cat $var | grep bar"
prop_checkUuoc3c :: Bool
prop_checkUuoc3c = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoc String
"cat \"${!var}\" | grep bar"
prop_checkUuoc4 :: Bool
prop_checkUuoc4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoc String
"cat $var"
prop_checkUuoc5 :: Bool
prop_checkUuoc5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoc String
"cat \"$@\""
prop_checkUuoc6 :: Bool
prop_checkUuoc6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoc String
"cat -n | grep bar"
checkUuoc :: p -> Token -> m ()
checkUuoc p
_ (T_Pipeline Id
_ [Token]
_ (T_Redirecting Id
_ [Token]
_ Token
cmd:Token
_:[Token]
_)) =
    String -> (Token -> [Token] -> m ()) -> Token -> m ()
forall {m :: * -> *}.
Monad m =>
String -> (Token -> [Token] -> m ()) -> Token -> m ()
checkCommand String
"cat" (([Token] -> m ()) -> Token -> [Token] -> m ()
forall a b. a -> b -> a
const [Token] -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
[Token] -> m ()
f) Token
cmd
  where
    f :: [Token] -> m ()
f [Token
word] | Bool -> Bool
not (Token -> Bool
mayBecomeMultipleArgs Token
word Bool -> Bool -> Bool
|| Token -> Bool
isOption Token
word) =
        Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style (Token -> Id
getId Token
word) Code
2002 String
"Useless cat. Consider 'cmd < file | ..' or 'cmd file | ..' instead."
    f [Token]
_ = () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    isOption :: Token -> Bool
isOption Token
word = String
"-" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` Token -> String
onlyLiteralString Token
word
checkUuoc p
_ Token
_ = () -> m ()
forall a. a -> m a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"ls | grep -v mp3"
prop_checkPipePitfalls4 :: Bool
prop_checkPipePitfalls4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"find . -print0 | xargs -0 foo"
prop_checkPipePitfalls5 :: Bool
prop_checkPipePitfalls5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"ls -N | foo"
prop_checkPipePitfalls6 :: Bool
prop_checkPipePitfalls6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"find . | xargs foo"
prop_checkPipePitfalls7 :: Bool
prop_checkPipePitfalls7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"find . -printf '%s\\n' | xargs foo"
prop_checkPipePitfalls8 :: Bool
prop_checkPipePitfalls8 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"foo | grep bar | wc -l"
prop_checkPipePitfalls9 :: Bool
prop_checkPipePitfalls9 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"foo | grep -o bar | wc -l"
prop_checkPipePitfalls10 :: Bool
prop_checkPipePitfalls10 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"foo | grep -o bar | wc"
prop_checkPipePitfalls11 :: Bool
prop_checkPipePitfalls11 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"foo | grep bar | wc"
prop_checkPipePitfalls12 :: Bool
prop_checkPipePitfalls12 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"foo | grep -o bar | wc -c"
prop_checkPipePitfalls13 :: Bool
prop_checkPipePitfalls13 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"foo | grep bar | wc -c"
prop_checkPipePitfalls14 :: Bool
prop_checkPipePitfalls14 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"foo | grep -o bar | wc -cmwL"
prop_checkPipePitfalls15 :: Bool
prop_checkPipePitfalls15 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"foo | grep bar | wc -cmwL"
prop_checkPipePitfalls16 :: Bool
prop_checkPipePitfalls16 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"foo | grep -r bar | wc -l"
prop_checkPipePitfalls17 :: Bool
prop_checkPipePitfalls17 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"foo | grep -l bar | wc -l"
prop_checkPipePitfalls18 :: Bool
prop_checkPipePitfalls18 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"foo | grep -L bar | wc -l"
prop_checkPipePitfalls19 :: Bool
prop_checkPipePitfalls19 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"foo | grep -A2 bar | wc -l"
prop_checkPipePitfalls20 :: Bool
prop_checkPipePitfalls20 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"foo | grep -B999 bar | wc -l"
prop_checkPipePitfalls21 :: Bool
prop_checkPipePitfalls21 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"foo | grep --after-context 999 bar | wc -l"
prop_checkPipePitfalls22 :: Bool
prop_checkPipePitfalls22 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"foo | grep -B 1 --after-context 999 bar | wc -l"
prop_checkPipePitfalls23 :: Bool
prop_checkPipePitfalls23 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPipePitfalls String
"ps -o pid,args -p $(pgrep java) | grep -F net.shellcheck.Test"
checkPipePitfalls :: p -> Token -> m ()
checkPipePitfalls p
_ (T_Pipeline Id
id [Token]
_ [Token]
commands) = do
    [String] -> ([Token] -> m ()) -> m Bool
forall {m :: * -> *} {b}.
Monad m =>
[String] -> ([Token] -> m b) -> m Bool
for [String
"find", String
"xargs"] (([Token] -> m ()) -> m Bool) -> ([Token] -> m ()) -> m Bool
forall a b. (a -> b) -> a -> b
$
        \(Token
find:Token
xargs:[Token]
_) ->
          let args :: [String]
args = Token -> [String]
oversimplify Token
xargs [String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++ Token -> [String]
oversimplify Token
find
          in
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless ((([String] -> Bool) -> Bool) -> [[String] -> Bool] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (([String] -> Bool) -> [String] -> Bool
forall a b. (a -> b) -> a -> b
$ [String]
args) [
                Char -> [String] -> Bool
forall {t :: * -> *}. Foldable t => Char -> t String -> Bool
hasShortParameter Char
'0',
                String -> [String] -> Bool
forall {t :: * -> *}. Foldable t => String -> t String -> Bool
hasParameter String
"null",
                String -> [String] -> Bool
forall {t :: * -> *}. Foldable t => String -> t String -> Bool
hasParameter String
"print0",
                String -> [String] -> Bool
forall {t :: * -> *}. Foldable t => String -> t String -> Bool
hasParameter String
"printf"
              ]) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
find) Code
2038
                      String
"Use 'find .. -print0 | xargs -0 ..' or 'find .. -exec .. +' to allow non-alphanumeric filenames."

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

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

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

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


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

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


prop_checkForInQuoted :: Bool
prop_checkForInQuoted = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForInQuoted String
"for f in \"$(ls)\"; do echo foo; done"
prop_checkForInQuoted2 :: Bool
prop_checkForInQuoted2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForInQuoted String
"for f in \"$@\"; do echo foo; done"
prop_checkForInQuoted2a :: Bool
prop_checkForInQuoted2a = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForInQuoted String
"for f in *.mp3; do echo foo; done"
prop_checkForInQuoted2b :: Bool
prop_checkForInQuoted2b = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForInQuoted String
"for f in \"*.mp3\"; do echo foo; done"
prop_checkForInQuoted3 :: Bool
prop_checkForInQuoted3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForInQuoted String
"for f in 'find /'; do true; done"
prop_checkForInQuoted4 :: Bool
prop_checkForInQuoted4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForInQuoted String
"for f in 1,2,3; do true; done"
prop_checkForInQuoted4a :: Bool
prop_checkForInQuoted4a = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForInQuoted String
"for f in foo{1,2,3}; do true; done"
prop_checkForInQuoted5 :: Bool
prop_checkForInQuoted5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForInQuoted String
"for f in ls; do true; done"
prop_checkForInQuoted6 :: Bool
prop_checkForInQuoted6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForInQuoted String
"for f in \"${!arr}\"; do true; done"
prop_checkForInQuoted7 :: Bool
prop_checkForInQuoted7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForInQuoted String
"for f in ls, grep, mv; do true; done"
prop_checkForInQuoted8 :: Bool
prop_checkForInQuoted8 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForInQuoted String
"for f in 'ls', 'grep', 'mv'; do true; done"
prop_checkForInQuoted9 :: Bool
prop_checkForInQuoted9 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForInQuoted String
"for f in 'ls,' 'grep,' 'mv'; do true; done"
checkForInQuoted :: Parameters -> Token -> m ()
checkForInQuoted Parameters
_ (T_ForIn Id
_ String
f [T_NormalWord Id
_ [word :: Token
word@(T_DoubleQuoted Id
id [Token]
list)]] [Token]
_)
    | (Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
willSplit [Token]
list Bool -> Bool -> Bool
&& Bool -> Bool
not (Token -> Bool
mayBecomeMultipleArgs Token
word)
            Bool -> Bool -> Bool
|| Bool -> (String -> Bool) -> Maybe String -> Bool
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
False String -> Bool
forall {t :: * -> *}. Foldable t => t Char -> Bool
wouldHaveBeenGlob (Token -> Maybe String
getLiteralString Token
word) =
        Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2066 String
"Since you double quoted this, it will not word split, and the loop will only run once."
checkForInQuoted Parameters
_ (T_ForIn Id
_ String
f [T_NormalWord Id
_ [T_SingleQuoted Id
id String
_]] [Token]
_) =
    Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2041 String
"This is a literal string. To run as a command, use $(..) instead of '..' . "
checkForInQuoted Parameters
_ (T_ForIn Id
_ String
_ [Token
single] [Token]
_)
    | Bool -> (String -> Bool) -> Maybe String -> Bool
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
False (Char
',' Char -> String -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem`) (Maybe String -> Bool) -> Maybe String -> Bool
forall a b. (a -> b) -> a -> b
$ Token -> Maybe String
getUnquotedLiteral Token
single =
        Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
single) Code
2042 String
"Use spaces, not commas, to separate loop elements."
    | Bool -> Bool
not (Token -> Bool
willSplit Token
single Bool -> Bool -> Bool
|| Token -> Bool
mayBecomeMultipleArgs Token
single) =
        Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
single) Code
2043 String
"This loop will only ever run once. Bad quoting or missing glob/expansion?"
checkForInQuoted Parameters
params (T_ForIn Id
_ String
_ [Token]
multiple [Token]
_) =
    [Token] -> (Token -> m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ [Token]
multiple ((Token -> m ()) -> m ()) -> (Token -> m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ \Token
arg -> Maybe (m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
        Token
suffix <- Token -> Maybe Token
getTrailingUnquotedLiteral Token
arg
        String
string <- Token -> Maybe String
getLiteralString Token
suffix
        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String
"," String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isSuffixOf` String
string
        m () -> Maybe (m ())
forall a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$
            Id -> Code -> String -> Fix -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Id -> Code -> String -> Fix -> m ()
warnWithFix (Token -> Id
getId Token
arg) Code
2258
                String
"The trailing comma is part of the value, not a separator. Delete or quote it."
                ([Replacement] -> Fix
fixWith [Id -> Parameters -> Code -> String -> Replacement
replaceEnd (Token -> Id
getId Token
suffix) Parameters
params Code
1 String
""])
checkForInQuoted Parameters
_ Token
_ = () -> m ()
forall a. a -> m a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkForInCat String
"for f in $(cat foo); do stuff; done"
prop_checkForInCat1a :: Bool
prop_checkForInCat1a = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkForInCat String
"for f in `cat foo`; do stuff; done"
prop_checkForInCat2 :: Bool
prop_checkForInCat2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkForInCat String
"for f in $(cat foo | grep lol); do stuff; done"
prop_checkForInCat2a :: Bool
prop_checkForInCat2a = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkForInCat String
"for f in `cat foo | grep lol`; do stuff; done"
prop_checkForInCat3 :: Bool
prop_checkForInCat3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkForInCat String
"for f in $(cat foo | grep bar | wc -l); do stuff; done"
checkForInCat :: p -> Token -> m ()
checkForInCat p
_ (T_ForIn Id
_ String
f [T_NormalWord Id
_ [Token]
w] [Token]
_) = (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkF [Token]
w
  where
    checkF :: Token -> m ()
checkF (T_DollarExpansion Id
id [T_Pipeline Id
_ [Token]
_ [Token]
r])
        | (Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Token -> Bool
isLineBased [Token]
r =
            Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info Id
id Code
2013 String
"To read lines rather than words, pipe/redirect to a 'while read' loop."
    checkF (T_Backticked Id
id [Token]
cmds) = Token -> m ()
checkF (Id -> [Token] -> Token
T_DollarExpansion Id
id [Token]
cmds)
    checkF Token
_ = () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    isLineBased :: Token -> Bool
isLineBased Token
cmd = (String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Token
cmd Token -> String -> Bool
`isCommand`)
                        [String
"grep", String
"fgrep", String
"egrep", String
"sed", String
"cat", String
"awk", String
"cut", String
"sort"]
checkForInCat p
_ Token
_ = () -> m ()
forall a. a -> m a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkForInLs String
"for f in $(ls *.mp3); do mplayer \"$f\"; done"
prop_checkForInLs2 :: Bool
prop_checkForInLs2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkForInLs String
"for f in `ls *.mp3`; do mplayer \"$f\"; done"
prop_checkForInLs3 :: Bool
prop_checkForInLs3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkForInLs String
"for f in `find / -name '*.mp3'`; do mplayer \"$f\"; done"
checkForInLs :: p -> Token -> m ()
checkForInLs p
_ = Token -> m ()
forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
try
  where
   try :: Token -> m ()
try (T_ForIn Id
_ String
f [T_NormalWord Id
_ [T_DollarExpansion Id
id [Token
x]]] [Token]
_) =
        Id -> String -> Token -> m ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
Id -> p -> Token -> m ()
check Id
id String
f Token
x
   try (T_ForIn Id
_ String
f [T_NormalWord Id
_ [T_Backticked Id
id [Token
x]]] [Token]
_) =
        Id -> String -> Token -> m ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
Id -> p -> Token -> m ()
check Id
id String
f Token
x
   try Token
_ = () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
   check :: Id -> p -> Token -> m ()
check Id
id p
f Token
x =
    case Token -> [String]
oversimplify Token
x of
      (String
"ls":[String]
n) ->
        let warntype :: Id -> Code -> String -> m ()
warntype = if (String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (String
"-" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf`) [String]
n then Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn else Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err in
          Id -> Code -> String -> m ()
warntype Id
id Code
2045 String
"Iterating over ls output is fragile. Use globs."
      (String
"find":[String]
_) -> Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2044 String
"For loops over find output are fragile. Use find -exec or a while read loop."
      [String]
_ -> () -> m ()
forall a. a -> m a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkFindExec String
"find / -name '*.php' -exec rm {};"
prop_checkFindExec2 :: Bool
prop_checkFindExec2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkFindExec String
"find / -exec touch {} && ls {} \\;"
prop_checkFindExec3 :: Bool
prop_checkFindExec3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkFindExec String
"find / -execdir cat {} | grep lol +"
prop_checkFindExec4 :: Bool
prop_checkFindExec4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkFindExec String
"find / -name '*.php' -exec foo {} +"
prop_checkFindExec5 :: Bool
prop_checkFindExec5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkFindExec String
"find / -execdir bash -c 'a && b' \\;"
prop_checkFindExec6 :: Bool
prop_checkFindExec6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkFindExec String
"find / -type d -execdir rm *.jpg \\;"
checkFindExec :: p -> Token -> m ()
checkFindExec p
_ cmd :: Token
cmd@(T_SimpleCommand Id
_ [Token]
_ t :: [Token]
t@(Token
h:[Token]
r)) | Token
cmd Token -> String -> Bool
`isCommand` String
"find" = do
    Bool
c <- [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. HasCallStack => [a] -> a
last [Token]
t in
            Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
wordId Code
2067 String
"Missing ';' or + terminating -exec. You can't use |/||/&&, and ';' has to be a separate, quoted argument."

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

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

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

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


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

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


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

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


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


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

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


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

prop_checkConcatenatedDollarAt1 :: Bool
prop_checkConcatenatedDollarAt1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkConcatenatedDollarAt String
"echo \"foo$@\""
prop_checkConcatenatedDollarAt2 :: Bool
prop_checkConcatenatedDollarAt2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkConcatenatedDollarAt String
"echo ${arr[@]}lol"
prop_checkConcatenatedDollarAt3 :: Bool
prop_checkConcatenatedDollarAt3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkConcatenatedDollarAt String
"echo $a$@"
prop_checkConcatenatedDollarAt4 :: Bool
prop_checkConcatenatedDollarAt4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkConcatenatedDollarAt String
"echo $@"
prop_checkConcatenatedDollarAt5 :: Bool
prop_checkConcatenatedDollarAt5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkConcatenatedDollarAt String
"echo \"${arr[@]}\""
checkConcatenatedDollarAt :: Parameters -> Token -> m ()
checkConcatenatedDollarAt Parameters
p word :: Token
word@T_NormalWord {}
    | Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ Shell -> Map Id Token -> Token -> Bool
isQuoteFree (Parameters -> Shell
shellType Parameters
p) (Parameters -> Map Id Token
parentMap Parameters
p) Token
word
    Bool -> Bool -> Bool
|| [Token] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null (Int -> [Token] -> [Token]
forall a. Int -> [a] -> [a]
drop Int
1 [Token]
parts) =
        (Token -> m ()) -> Maybe 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 ()
for Maybe Token
array
  where
    parts :: [Token]
parts = Token -> [Token]
getWordParts Token
word
    array :: Maybe Token
array = (Token -> Bool) -> [Token] -> Maybe Token
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find Token -> Bool
isArrayExpansion [Token]
parts
    for :: Token -> m ()
for Token
t = Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
t) Code
2145 String
"Argument mixes string and array. Use * or separate argument."
checkConcatenatedDollarAt Parameters
_ Token
_ = () -> m ()
forall a. a -> m a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkArrayAsString String
"a=$@"
prop_checkArrayAsString2 :: Bool
prop_checkArrayAsString2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkArrayAsString String
"a=\"${arr[@]}\""
prop_checkArrayAsString3 :: Bool
prop_checkArrayAsString3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkArrayAsString String
"a=*.png"
prop_checkArrayAsString4 :: Bool
prop_checkArrayAsString4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkArrayAsString String
"a={1..10}"
prop_checkArrayAsString5 :: Bool
prop_checkArrayAsString5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkArrayAsString String
"a='*.gif'"
prop_checkArrayAsString6 :: Bool
prop_checkArrayAsString6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkArrayAsString String
"a=$*"
prop_checkArrayAsString7 :: Bool
prop_checkArrayAsString7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkArrayAsString String
"a=( $@ )"
checkArrayAsString :: p -> Token -> m ()
checkArrayAsString p
_ (T_Assignment Id
id AssignmentMode
_ String
_ [Token]
_ Token
word) =
    if Token -> Bool
willConcatInAssignment Token
word
    then
      Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
word) Code
2124
        String
"Assigning an array to a string! Assign as array, or use * instead of @ to concatenate."
    else
      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 -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
word) Code
2125
          String
"Brace expansions and globs are literal in assignments. Quote it or use an array."
checkArrayAsString p
_ Token
_ = () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()

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

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

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

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

prop_checkStderrRedirect :: Bool
prop_checkStderrRedirect = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkStderrRedirect String
"test 2>&1 > cow"
prop_checkStderrRedirect2 :: Bool
prop_checkStderrRedirect2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkStderrRedirect String
"test > cow 2>&1"
prop_checkStderrRedirect3 :: Bool
prop_checkStderrRedirect3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkStderrRedirect String
"test 2>&1 > file | grep stderr"
prop_checkStderrRedirect4 :: Bool
prop_checkStderrRedirect4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkStderrRedirect String
"errors=$(test 2>&1 > file)"
prop_checkStderrRedirect5 :: Bool
prop_checkStderrRedirect5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkStderrRedirect String
"read < <(test 2>&1 > file)"
prop_checkStderrRedirect6 :: Bool
prop_checkStderrRedirect6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkStderrRedirect String
"foo | bar 2>&1 > /dev/null"
prop_checkStderrRedirect7 :: Bool
prop_checkStderrRedirect7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkStderrRedirect String
"{ cmd > file; } 2>&1"
checkStderrRedirect :: Parameters -> Token -> f ()
checkStderrRedirect Parameters
params redir :: Token
redir@(T_Redirecting Id
_ [
    T_FdRedirect Id
id String
"2" (T_IoDuplicate Id
_ (T_GREATAND Id
_) String
"1"),
    T_FdRedirect Id
_ String
_ (T_IoFile Id
_ Token
op Token
_)
    ] Token
_) = case Token
op of
            T_Greater Id
_ -> f ()
error
            T_DGREAT Id
_ -> f ()
error
            Token
_ -> () -> f ()
forall a. a -> f a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    usesOutput :: Token -> Bool
usesOutput Token
t =
        case Token
t of
            (T_Pipeline Id
_ [Token]
_ [Token]
list) -> [Token] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Token]
list Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
1 Bool -> Bool -> Bool
&& Bool -> Bool
not (Map Id Token -> Token -> Token -> Bool
isParentOf (Parameters -> Map Id Token
parentMap Parameters
params) ([Token] -> Token
forall a. HasCallStack => [a] -> a
last [Token]
list) Token
redir)
            T_ProcSub {} -> Bool
True
            T_DollarExpansion {} -> Bool
True
            T_Backticked {} -> Bool
True
            Token
_ -> Bool
False
    isCaptured :: Bool
isCaptured = (Token -> Bool) -> NonEmpty Token -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
usesOutput (NonEmpty Token -> Bool) -> NonEmpty Token -> Bool
forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> NonEmpty 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 -> Code -> String -> f ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2069 String
"To redirect stdout+stderr, 2>&1 must be last (or use '{ cmd > file; } 2>&1' to clarify)."

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

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


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


checkSingleQuotedVariables :: Parameters -> Token -> f ()
checkSingleQuotedVariables Parameters
params t :: Token
t@(T_SingleQuoted Id
id String
s) =
    Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
s String -> Regex -> Bool
`matches` Regex
re) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
        if String
"sed" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
commandName
        then Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (String
s String -> Regex -> Bool
`matches` Regex
sedContra) f ()
showMessage
        else Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless Bool
isProbablyOk f ()
showMessage
  where
    parents :: Map Id Token
parents = Parameters -> Map Id Token
parentMap Parameters
params
    showMessage :: f ()
showMessage = Id -> Code -> String -> f ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info Id
id Code
2016
        String
"Expressions don't expand in single quotes, use double quotes for that."
    commandName :: String
commandName = String -> Maybe String -> String
forall a. a -> Maybe a -> a
fromMaybe String
"" (Maybe String -> String) -> Maybe String -> String
forall a b. (a -> b) -> a -> b
$ do
        Token
cmd <- Map Id Token -> Token -> Maybe Token
getClosestCommand Map Id Token
parents Token
t
        String
name <- Token -> Maybe String
getCommandBasename Token
cmd
        String -> Maybe String
forall a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return (String -> Maybe String) -> String -> Maybe String
forall a b. (a -> b) -> a -> b
$ if String
name String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"find" then Token -> String
getFindCommand Token
cmd else if String
name String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"git" then Token -> String
getGitCommand Token
cmd else if String
name String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"mumps" then Token -> String
getMumpsCommand 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 -> NonEmpty Token -> [Token]
forall a. Int -> NonEmpty a -> [a]
NE.take Int
3 (NonEmpty Token -> [Token]) -> NonEmpty Token -> [Token]
forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> NonEmpty Token
getPath Map Id Token
parents Token
t)
            Bool -> Bool -> Bool
|| String
commandName String -> [String] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [
                String
"trap"
                ,String
"sh"
                ,String
"bash"
                ,String
"ksh"
                ,String
"zsh"
                ,String
"ssh"
                ,String
"eval"
                ,String
"xprop"
                ,String
"alias"
                ,String
"sudo" -- covering "sudo sh" and such
                ,String
"docker" -- like above
                ,String
"podman"
                ,String
"dpkg-query"
                ,String
"jq"  -- could also check that user provides --arg
                ,String
"rename"
                ,String
"rg"
                ,String
"unset"
                ,String
"git filter-branch"
                ,String
"mumps -run %XCMD"
                ,String
"mumps -run LOOP%XCMD"
                ]
            Bool -> Bool -> Bool
|| String
"awk" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isSuffixOf` String
commandName
            Bool -> Bool -> Bool
|| String
"perl" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
commandName

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

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

    getFindCommand :: Token -> String
getFindCommand (T_SimpleCommand Id
_ [Token]
_ [Token]
words) =
        let list :: [Maybe String]
list = (Token -> Maybe String) -> [Token] -> [Maybe String]
forall a b. (a -> b) -> [a] -> [b]
map Token -> Maybe String
getLiteralString [Token]
words
            cmd :: [Maybe String]
cmd  = (Maybe String -> Bool) -> [Maybe String] -> [Maybe String]
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (\Maybe String
x -> Maybe String
x Maybe String -> Maybe String -> Bool
forall a. Eq a => a -> a -> Bool
/= String -> Maybe String
forall a. a -> Maybe a
Just String
"-exec" Bool -> Bool -> Bool
&& Maybe String
x Maybe String -> Maybe String -> Bool
forall a. Eq a => a -> a -> Bool
/= String -> Maybe String
forall a. a -> Maybe a
Just String
"-execdir") [Maybe String]
list
        in
          case [Maybe String]
cmd of
            (Maybe String
flag:Maybe String
cmd:[Maybe String]
rest) -> String -> Maybe String -> String
forall a. a -> Maybe a -> a
fromMaybe String
"find" Maybe String
cmd
            [Maybe String]
_ -> String
"find"
    getFindCommand (T_Redirecting Id
_ [Token]
_ Token
cmd) = Token -> String
getFindCommand Token
cmd
    getFindCommand Token
_ = String
"find"
    getGitCommand :: Token -> String
getGitCommand (T_SimpleCommand Id
_ [Token]
_ [Token]
words) =
        case (Token -> Maybe String) -> [Token] -> [Maybe String]
forall a b. (a -> b) -> [a] -> [b]
map Token -> Maybe String
getLiteralString [Token]
words of
            Just String
"git":Just String
"filter-branch":[Maybe String]
_ -> String
"git filter-branch"
            [Maybe String]
_ -> String
"git"
    getGitCommand (T_Redirecting Id
_ [Token]
_ Token
cmd) = Token -> String
getGitCommand Token
cmd
    getGitCommand Token
_ = String
"git"
    getMumpsCommand :: Token -> String
getMumpsCommand (T_SimpleCommand Id
_ [Token]
_ [Token]
words) =
        case (Token -> Maybe String) -> [Token] -> [Maybe String]
forall a b. (a -> b) -> [a] -> [b]
map Token -> Maybe String
getLiteralString [Token]
words of
            Just String
"mumps":Just String
"-run":Just String
"%XCMD":[Maybe String]
_ -> String
"mumps -run %XCMD"
            Just String
"mumps":Just String
"-run":Just String
"LOOP%XCMD":[Maybe String]
_ -> String
"mumps -run LOOP%XCMD"
            [Maybe String]
_ -> String
"mumps"
    getMumpsCommand (T_Redirecting Id
_ [Token]
_ Token
cmd) = Token -> String
getMumpsCommand Token
cmd
    getMumpsCommand Token
_ = String
"mumps"
checkSingleQuotedVariables Parameters
_ Token
_ = () -> f ()
forall a. a -> f a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUnquotedN String
"if [ -n $foo ]; then echo cow; fi"
prop_checkUnquotedN2 :: Bool
prop_checkUnquotedN2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUnquotedN String
"[ -n $cow ]"
prop_checkUnquotedN3 :: Bool
prop_checkUnquotedN3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUnquotedN String
"[[ -n $foo ]] && echo cow"
prop_checkUnquotedN4 :: Bool
prop_checkUnquotedN4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUnquotedN String
"[ -n $cow -o -t 1 ]"
prop_checkUnquotedN5 :: Bool
prop_checkUnquotedN5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUnquotedN String
"[ -n \"$@\" ]"
checkUnquotedN :: p -> Token -> f ()
checkUnquotedN p
_ (TC_Unary Id
_ ConditionType
SingleBracket String
"-n" Token
t) | Token -> Bool
willSplit Token
t =
    Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless ((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
t) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$ -- There's SC2198 for these
       Id -> Code -> String -> f ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
t) Code
2070 String
"-n doesn't work with unquoted arguments. Quote or use [[ ]]."
checkUnquotedN p
_ Token
_ = () -> f ()
forall a. a -> f a
forall (m :: * -> *) a. Monad m => a -> m a
return ()

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

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

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

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

    Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
op String -> [String] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
arithmeticBinaryTestOps) (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]
        (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 ()
checkString [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 a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
elem [String
"<", String
"\\<", String
">", String
"\\>"]
      isLeGe :: String -> Bool
isLeGe = (String -> [String] -> Bool) -> [String] -> String -> Bool
forall a b c. (a -> b -> c) -> b -> a -> c
flip String -> [String] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
elem [String
"<=", String
"\\<=", String
">=", String
"\\>="]

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

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

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

      isNonNum :: Token -> Bool
isNonNum Token
t = 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 -> Bool) -> String -> Bool
forall a b. (a -> b) -> a -> b
$ Token -> String
onlyLiteralString Token
t
      numChar :: Char -> Bool
numChar Char
x = Char -> Bool
isDigit Char
x Bool -> Bool -> Bool
|| Char
x Char -> String -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
"+-. "

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

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

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

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

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

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

prop_checkSingleBracketOperators1 :: Bool
prop_checkSingleBracketOperators1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSingleBracketOperators String
"[ test =~ foo ]"
checkSingleBracketOperators :: Parameters -> Token -> m ()
checkSingleBracketOperators Parameters
params (TC_Binary Id
id ConditionType
SingleBracket String
"=~" Token
lhs Token
rhs)
    | Parameters -> Shell
shellType Parameters
params Shell -> [Shell] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Shell
Bash, Shell
Ksh] =
        Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2074 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
"Can't use =~ in [ ]. Use [[..]] instead."
checkSingleBracketOperators Parameters
_ Token
_ = () -> m ()
forall a. a -> m a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkDoubleBracketOperators String
"[[ 3 \\< 4 ]]"
prop_checkDoubleBracketOperators3 :: Bool
prop_checkDoubleBracketOperators3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkDoubleBracketOperators String
"[[ foo < bar ]]"
checkDoubleBracketOperators :: p -> Token -> m ()
checkDoubleBracketOperators p
_ x :: Token
x@(TC_Binary Id
id ConditionType
typ String
op Token
lhs Token
rhs)
    | ConditionType
typ ConditionType -> ConditionType -> Bool
forall a. Eq a => a -> a -> Bool
== ConditionType
DoubleBracket Bool -> Bool -> Bool
&& String
op String -> [String] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"\\<", String
"\\>"] =
        Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2075 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
"Escaping " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
op String -> String -> String
forall a. [a] -> [a] -> [a]
++String
" is required in [..], but invalid in [[..]]"
checkDoubleBracketOperators p
_ Token
_ = () -> m ()
forall a. a -> m a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConditionalAndOrs String
"[ foo && bar ]"
prop_checkConditionalAndOrs2 :: Bool
prop_checkConditionalAndOrs2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConditionalAndOrs String
"[[ foo -o bar ]]"
prop_checkConditionalAndOrs3 :: Bool
prop_checkConditionalAndOrs3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConditionalAndOrs String
"[[ foo || bar ]]"
prop_checkConditionalAndOrs4 :: Bool
prop_checkConditionalAndOrs4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConditionalAndOrs String
"[ foo -a bar ]"
prop_checkConditionalAndOrs5 :: Bool
prop_checkConditionalAndOrs5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConditionalAndOrs String
"[ -z 3 -o a = b ]"
checkConditionalAndOrs :: p -> Token -> m ()
checkConditionalAndOrs p
_ Token
t =
    case Token
t of
        (TC_And Id
id ConditionType
SingleBracket String
"&&" Token
_ Token
_) ->
            Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2107 String
"Instead of [ a && b ], use [ a ] && [ b ]."
        (TC_And Id
id ConditionType
DoubleBracket String
"-a" Token
_ Token
_) ->
            Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2108 String
"In [[..]], use && instead of -a."
        (TC_Or Id
id ConditionType
SingleBracket String
"||" Token
_ Token
_) ->
            Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2109 String
"Instead of [ a || b ], use [ a ] || [ b ]."
        (TC_Or Id
id ConditionType
DoubleBracket String
"-o" Token
_ Token
_) ->
            Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2110 String
"In [[..]], use || instead of -o."

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

        Token
_ -> () -> m ()
forall a. a -> m a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkQuotedCondRegex String
"[[ $foo =~ \"bar.*\" ]]"
prop_checkQuotedCondRegex2 :: Bool
prop_checkQuotedCondRegex2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkQuotedCondRegex String
"[[ $foo =~ '(cow|bar)' ]]"
prop_checkQuotedCondRegex3 :: Bool
prop_checkQuotedCondRegex3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkQuotedCondRegex String
"[[ $foo =~ $foo ]]"
prop_checkQuotedCondRegex4 :: Bool
prop_checkQuotedCondRegex4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkQuotedCondRegex String
"[[ $foo =~ \"bar\" ]]"
prop_checkQuotedCondRegex5 :: Bool
prop_checkQuotedCondRegex5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkQuotedCondRegex String
"[[ $foo =~ 'cow bar' ]]"
prop_checkQuotedCondRegex6 :: Bool
prop_checkQuotedCondRegex6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkQuotedCondRegex String
"[[ $foo =~ 'cow|bar' ]]"
checkQuotedCondRegex :: p -> Token -> f ()
checkQuotedCondRegex p
_ (TC_Binary Id
_ ConditionType
_ String
"=~" Token
_ Token
rhs) =
    case Token
rhs of
        T_NormalWord Id
id [T_DoubleQuoted Id
_ [Token]
_] -> Token -> f ()
forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
error Token
rhs
        T_NormalWord Id
id [T_SingleQuoted Id
_ String
_] -> Token -> f ()
forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
error Token
rhs
        Token
_ -> () -> f ()
forall a. a -> f a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    error :: Token -> f ()
error Token
t =
        Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Token -> Bool
isConstantNonRe Token
t) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
            Id -> Code -> String -> f ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
t) Code
2076
                String
"Remove quotes from right-hand side of =~ to match as a regex rather than literally."
    re :: Regex
re = String -> Regex
mkRegex String
"[][*.+()|]"
    hasMetachars :: String -> Bool
hasMetachars String
s = String
s String -> Regex -> Bool
`matches` Regex
re
    isConstantNonRe :: Token -> Bool
isConstantNonRe Token
t = 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 a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> Maybe Bool) -> (Bool -> Bool) -> Bool -> Maybe Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not (Bool -> Maybe Bool) -> Bool -> Maybe Bool
forall a b. (a -> b) -> a -> b
$ String -> Bool
hasMetachars String
s
checkQuotedCondRegex p
_ Token
_ = () -> f ()
forall a. a -> f a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobbedRegex String
"[[ $foo =~ *foo* ]]"
prop_checkGlobbedRegex2 :: Bool
prop_checkGlobbedRegex2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobbedRegex String
"[[ $foo =~ f* ]]"
prop_checkGlobbedRegex3 :: Bool
prop_checkGlobbedRegex3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobbedRegex String
"[[ $foo =~ $foo ]]"
prop_checkGlobbedRegex4 :: Bool
prop_checkGlobbedRegex4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobbedRegex String
"[[ $foo =~ ^c.* ]]"
prop_checkGlobbedRegex5 :: Bool
prop_checkGlobbedRegex5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobbedRegex String
"[[ $foo =~ \\* ]]"
prop_checkGlobbedRegex6 :: Bool
prop_checkGlobbedRegex6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobbedRegex String
"[[ $foo =~ (o*) ]]"
prop_checkGlobbedRegex7 :: Bool
prop_checkGlobbedRegex7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobbedRegex String
"[[ $foo =~ \\*foo ]]"
prop_checkGlobbedRegex8 :: Bool
prop_checkGlobbedRegex8 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobbedRegex String
"[[ $foo =~ x\\* ]]"
checkGlobbedRegex :: p -> Token -> m ()
checkGlobbedRegex p
_ (TC_Binary Id
_ ConditionType
DoubleBracket String
"=~" Token
_ Token
rhs)
    | String -> Bool
isConfusedGlobRegex String
s =
        Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
rhs) Code
2049 String
"=~ is for regex, but this looks like a glob. Use = instead."
    where s :: String
s = [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
checkGlobbedRegex p
_ Token
_ = () -> m ()
forall a. a -> m a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantIfs String
"[[ foo != bar ]]"
prop_checkConstantIfs2a :: Bool
prop_checkConstantIfs2a = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantIfs String
"[ n -le 4 ]"
prop_checkConstantIfs2b :: Bool
prop_checkConstantIfs2b = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantIfs String
"[[ n -le 4 ]]"
prop_checkConstantIfs3 :: Bool
prop_checkConstantIfs3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantIfs String
"[[ $n -le 4 && n != 2 ]]"
prop_checkConstantIfs4 :: Bool
prop_checkConstantIfs4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantIfs String
"[[ $n -le 3 ]]"
prop_checkConstantIfs5 :: Bool
prop_checkConstantIfs5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantIfs String
"[[ $n -le $n ]]"
prop_checkConstantIfs6 :: Bool
prop_checkConstantIfs6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantIfs String
"[[ a -ot b ]]"
prop_checkConstantIfs7 :: Bool
prop_checkConstantIfs7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantIfs String
"[ a -nt b ]"
prop_checkConstantIfs8 :: Bool
prop_checkConstantIfs8 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantIfs String
"[[ ~foo == '~foo' ]]"
prop_checkConstantIfs9 :: Bool
prop_checkConstantIfs9 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantIfs String
"[[ *.png == [a-z] ]]"
prop_checkConstantIfs10 :: Bool
prop_checkConstantIfs10 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantIfs String
"[[ ~me == ~+ ]]"
prop_checkConstantIfs11 :: Bool
prop_checkConstantIfs11 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantIfs String
"[[ ~ == ~+ ]]"
prop_checkConstantIfs12 :: Bool
prop_checkConstantIfs12 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkConstantIfs String
"[[ '~' == x ]]"
checkConstantIfs :: p -> Token -> m ()
checkConstantIfs p
_ (TC_Binary Id
id ConditionType
typ String
op Token
lhs Token
rhs) | Bool -> Bool
not Bool
isDynamic =
    if Token -> Bool
isConstant Token
lhs Bool -> Bool -> Bool
&& Token -> Bool
isConstant Token
rhs
        then  Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2050 String
"This expression is constant. Did you forget the $ on a variable?"
        else 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 a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
arithmeticBinaryTestOps
            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 a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [ String
"-nt", String
"-ot", String
"-ef"]

    checkUnmatchable :: Id -> String -> Token -> Token -> f ()
checkUnmatchable Id
id String
op Token
lhs Token
rhs =
        Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
op String -> [String] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"=", String
"==", String
"!="] Bool -> Bool -> Bool
&& Bool -> Bool
not (Token -> Token -> Bool
wordsCanBeEqual Token
lhs Token
rhs)) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
            Id -> Code -> String -> f ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2193 String
"The arguments to this comparison can never be equal. Make sure your syntax is correct."
checkConstantIfs p
_ Token
_ = () -> m ()
forall a. a -> m a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkLiteralBreakingTest String
"[[ a==$foo ]]"
prop_checkLiteralBreakingTest2 :: Bool
prop_checkLiteralBreakingTest2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkLiteralBreakingTest String
"[ $foo=3 ]"
prop_checkLiteralBreakingTest3 :: Bool
prop_checkLiteralBreakingTest3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkLiteralBreakingTest String
"[ $foo!=3 ]"
prop_checkLiteralBreakingTest4 :: Bool
prop_checkLiteralBreakingTest4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkLiteralBreakingTest String
"[ \"$(ls) \" ]"
prop_checkLiteralBreakingTest5 :: Bool
prop_checkLiteralBreakingTest5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkLiteralBreakingTest String
"[ -n \"$(true) \" ]"
prop_checkLiteralBreakingTest6 :: Bool
prop_checkLiteralBreakingTest6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkLiteralBreakingTest String
"[ -z $(true)z ]"
prop_checkLiteralBreakingTest7 :: Bool
prop_checkLiteralBreakingTest7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkLiteralBreakingTest String
"[ -z $(true) ]"
prop_checkLiteralBreakingTest8 :: Bool
prop_checkLiteralBreakingTest8 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkLiteralBreakingTest String
"[ $(true)$(true) ]"
prop_checkLiteralBreakingTest10 :: Bool
prop_checkLiteralBreakingTest10 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkLiteralBreakingTest String
"[ -z foo ]"
checkLiteralBreakingTest :: p -> Token -> m ()
checkLiteralBreakingTest p
_ Token
t = Maybe (m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$
        case Token
t of
            (TC_Nullary Id
_ ConditionType
_ w :: Token
w@(T_NormalWord Id
_ [Token]
l)) -> do
                Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> (Bool -> Bool) -> Bool -> Maybe ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ Token -> Bool
isConstant Token
w -- Covered by SC2078
                [Token] -> Maybe (m ())
forall {t :: * -> *} {m :: * -> *}.
(Foldable t, MonadWriter [TokenComment] m) =>
t Token -> Maybe (m ())
comparisonWarning [Token]
l Maybe (m ()) -> Maybe (m ()) -> Maybe (m ())
forall a. Maybe a -> Maybe a -> Maybe a
forall (m :: * -> *) a. MonadPlus m => m a -> m a -> m a
`mplus` Token -> String -> Maybe (m ())
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Token -> String -> Maybe (m ())
tautologyWarning Token
w String
"Argument to implicit -n is always true due to literal strings."
            (TC_Unary Id
_ ConditionType
_ String
op w :: Token
w@(T_NormalWord Id
_ [Token]
l)) ->
                case String
op of
                    String
"-n" -> Token -> String -> Maybe (m ())
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Token -> String -> Maybe (m ())
tautologyWarning Token
w String
"Argument to -n is always true due to literal strings."
                    String
"-z" -> Token -> String -> Maybe (m ())
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Token -> String -> Maybe (m ())
tautologyWarning Token
w String
"Argument to -z is always false due to literal strings."
                    String
_ -> String -> Maybe (m ())
forall a. String -> Maybe a
forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
"not relevant"
            Token
_ -> String -> Maybe (m ())
forall a. String -> Maybe a
forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
"not my problem"
  where
    hasEquals :: Token -> Bool
hasEquals = (String -> Bool) -> Token -> Bool
matchToken (Char
'=' Char -> String -> Bool
forall a. Eq a => a -> [a] -> 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 a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null)
    matchToken :: (String -> Bool) -> Token -> Bool
matchToken String -> Bool
m Token
t = Bool -> (String -> Bool) -> Maybe String -> Bool
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
False String -> Bool
m (Token -> Maybe String
getLiteralString Token
t)

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

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

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

prop_checkForDecimals1 :: Bool
prop_checkForDecimals1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForDecimals String
"((3.14*c))"
prop_checkForDecimals2 :: Bool
prop_checkForDecimals2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForDecimals String
"foo[1.2]=bar"
prop_checkForDecimals3 :: Bool
prop_checkForDecimals3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkForDecimals String
"declare -A foo; foo[1.2]=bar"
checkForDecimals :: Parameters -> Token -> m ()
checkForDecimals Parameters
params t :: Token
t@(TA_Expansion Id
id [Token]
_) = Maybe (m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (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)
    Char
first:String
rest <- 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
$ Char -> Bool
isDigit Char
first Bool -> Bool -> Bool
&& Char
'.' Char -> String -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
rest
    m () -> Maybe (m ())
forall a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2079 String
"(( )) doesn't support decimals. Use bc or awk."
checkForDecimals Parameters
_ Token
_ = () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()

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

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

    normalWarning :: f ()
normalWarning = Id -> Code -> String -> f ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style Id
id Code
2004 String
"$/${} is unnecessary on arithmetic variables."
    noWarning :: f ()
noWarning = () -> f ()
forall a. a -> f a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
checkArithmeticDeref Parameters
_ Token
_ = () -> f ()
forall a. a -> f a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkArithmeticBadOctal String
"(( 0192 ))"
prop_checkArithmeticBadOctal2 :: Bool
prop_checkArithmeticBadOctal2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkArithmeticBadOctal String
"(( 0x192 ))"
prop_checkArithmeticBadOctal3 :: Bool
prop_checkArithmeticBadOctal3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkArithmeticBadOctal String
"(( 1 ^ 0777 ))"
checkArithmeticBadOctal :: p -> Token -> m ()
checkArithmeticBadOctal p
_ t :: Token
t@(TA_Expansion Id
id [Token]
_) = Maybe (m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (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 a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2080 String
"Numbers with leading 0 are considered octal."
  where
    octalRE :: Regex
octalRE = String -> Regex
mkRegex String
"^0[0-7]*[8-9]"
checkArithmeticBadOctal p
_ Token
_ = () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()

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

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

prop_checkCaseAgainstGlob1 :: Bool
prop_checkCaseAgainstGlob1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCaseAgainstGlob String
"case foo in lol$n) foo;; esac"
prop_checkCaseAgainstGlob2 :: Bool
prop_checkCaseAgainstGlob2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCaseAgainstGlob String
"case foo in $(foo)) foo;; esac"
prop_checkCaseAgainstGlob3 :: Bool
prop_checkCaseAgainstGlob3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCaseAgainstGlob String
"case foo in *$bar*) foo;; esac"
checkCaseAgainstGlob :: p -> Token -> m ()
checkCaseAgainstGlob p
_ Token
t =
    case Token
t of
        (T_CaseExpression Id
_ Token
_ [(CaseType, [Token], [Token])]
cases) -> ((CaseType, [Token], [Token]) -> m ())
-> [(CaseType, [Token], [Token])] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (CaseType, [Token], [Token]) -> m ()
forall {t :: * -> *} {m :: * -> *} {a} {c}.
(Foldable t, MonadWriter [TokenComment] m) =>
(a, t Token, c) -> m ()
check [(CaseType, [Token], [Token])]
cases
        Token
_ -> () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    check :: (a, t Token, c) -> m ()
check (a
_, t Token
list, c
_) = (Token -> m ()) -> t 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' t Token
list
    check' :: Token -> m ()
check' expr :: Token
expr@(T_NormalWord Id
_ [Token]
list)
        -- If it's already a glob, assume that's what the user wanted
        | Bool -> Bool
not (Token -> Bool
isGlob Token
expr) Bool -> Bool -> Bool
&& (Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
isQuoteableExpansion [Token]
list =
            Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
expr) Code
2254 String
"Quote expansions in case patterns to match literally rather than as a glob."
    check' Token
_ = () -> m ()
forall a. a -> m a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCommarrays String
"a=(1, 2)"
prop_checkCommarrays2 :: Bool
prop_checkCommarrays2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCommarrays String
"a+=(1,2,3)"
prop_checkCommarrays3 :: Bool
prop_checkCommarrays3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCommarrays String
"cow=(1 \"foo,bar\" 3)"
prop_checkCommarrays4 :: Bool
prop_checkCommarrays4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCommarrays String
"cow=('one,' 'two')"
prop_checkCommarrays5 :: Bool
prop_checkCommarrays5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCommarrays String
"a=([a]=b, [c]=d)"
prop_checkCommarrays6 :: Bool
prop_checkCommarrays6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCommarrays String
"a=([a]=b,[c]=d,[e]=f)"
prop_checkCommarrays7 :: Bool
prop_checkCommarrays7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkCommarrays String
"a=(1,2)"
checkCommarrays :: p -> Token -> f ()
checkCommarrays p
_ (T_Array Id
id [Token]
l) =
    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 -> Code -> String -> f ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2054 String
"Use spaces, not commas, to separate array elements."
  where
    literal :: Token -> String
literal (T_IndexedElement Id
_ [Token]
_ Token
l) = Token -> String
literal Token
l
    literal (T_NormalWord Id
_ [Token]
l) = (Token -> String) -> [Token] -> String
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Token -> String
literal [Token]
l
    literal (T_Literal Id
_ String
str) = String
str
    literal Token
_ = String
""

    isCommaSeparated :: String -> Bool
isCommaSeparated = Char -> String -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
elem Char
','
checkCommarrays p
_ Token
_ = () -> f ()
forall a. a -> f a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOrNeq String
"if [[ $lol -ne cow || $lol -ne foo ]]; then echo foo; fi"
prop_checkOrNeq2 :: Bool
prop_checkOrNeq2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOrNeq String
"(( a!=lol || a!=foo ))"
prop_checkOrNeq3 :: Bool
prop_checkOrNeq3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOrNeq String
"[ \"$a\" != lol || \"$a\" != foo ]"
prop_checkOrNeq4 :: Bool
prop_checkOrNeq4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOrNeq String
"[ a != $cow || b != $foo ]"
prop_checkOrNeq5 :: Bool
prop_checkOrNeq5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOrNeq String
"[[ $a != /home || $a != */public_html/* ]]"
prop_checkOrNeq6 :: Bool
prop_checkOrNeq6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOrNeq String
"[ $a != a ] || [ $a != b ]"
prop_checkOrNeq7 :: Bool
prop_checkOrNeq7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOrNeq String
"[ $a != a ] || [ $a != b ] || true"
prop_checkOrNeq8 :: Bool
prop_checkOrNeq8 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOrNeq String
"[[ $a != x || $a != x ]]"
prop_checkOrNeq9 :: Bool
prop_checkOrNeq9 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOrNeq String
"[ 0 -ne $FOO ] || [ 0 -ne $BAR ]"
-- This only catches the most idiomatic cases. Fixme?

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

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

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

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


checkOrNeq p
_ Token
_ = () -> m ()
forall a. a -> m a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkValidCondOps String
"[[ a -xz b ]]"
prop_checkValidCondOps2 :: Bool
prop_checkValidCondOps2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkValidCondOps String
"[ -M a ]"
prop_checkValidCondOps2a :: Bool
prop_checkValidCondOps2a = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkValidCondOps String
"[ 3 \\> 2 ]"
prop_checkValidCondOps3 :: Bool
prop_checkValidCondOps3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkValidCondOps String
"[ 1 = 2 -a 3 -ge 4 ]"
prop_checkValidCondOps4 :: Bool
prop_checkValidCondOps4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkValidCondOps String
"[[ ! -v foo ]]"
checkValidCondOps :: p -> Token -> m ()
checkValidCondOps p
_ (TC_Binary Id
id ConditionType
_ String
s Token
_ Token
_)
    | String
s String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [String]
binaryTestOps =
        Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2057 String
"Unknown binary operator."
checkValidCondOps p
_ (TC_Unary Id
id ConditionType
_ String
s Token
_)
    | String
s String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem`  [String]
unaryTestOps =
        Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2058 String
"Unknown unary operator."
checkValidCondOps p
_ Token
_ = () -> m ()
forall a. a -> m a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoeVar String
"for f in $(echo $tmp); do echo lol; done"
prop_checkUuoeVar2 :: Bool
prop_checkUuoeVar2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoeVar String
"date +`echo \"$format\"`"
prop_checkUuoeVar3 :: Bool
prop_checkUuoeVar3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoeVar String
"foo \"$(echo -e '\r')\""
prop_checkUuoeVar4 :: Bool
prop_checkUuoeVar4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoeVar String
"echo $tmp"
prop_checkUuoeVar5 :: Bool
prop_checkUuoeVar5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoeVar String
"foo \"$(echo \"$(date) value:\" $value)\""
prop_checkUuoeVar6 :: Bool
prop_checkUuoeVar6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoeVar String
"foo \"$(echo files: *.png)\""
prop_checkUuoeVar7 :: Bool
prop_checkUuoeVar7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoeVar String
"foo $(echo $(bar))" -- covered by 2005
prop_checkUuoeVar8 :: Bool
prop_checkUuoeVar8 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoeVar String
"#!/bin/sh\nz=$(echo)"
prop_checkUuoeVar9 :: Bool
prop_checkUuoeVar9 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkUuoeVar String
"foo $(echo $(<file))"
checkUuoeVar :: p -> Token -> m ()
checkUuoeVar p
_ Token
p =
    case Token
p of
        T_Backticked Id
id [Token
cmd] -> Id -> Token -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Token -> m ()
check Id
id Token
cmd
        T_DollarExpansion Id
id [Token
cmd] -> Id -> Token -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Token -> m ()
check Id
id Token
cmd
        Token
_ -> () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    couldBeOptimized :: Token -> Bool
couldBeOptimized Token
f = case Token
f of
        T_Glob {} -> Bool
False
        T_Extglob {} -> Bool
False
        T_BraceExpansion {} -> Bool
False
        T_NormalWord Id
_ [Token]
l -> (Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Token -> Bool
couldBeOptimized [Token]
l
        T_DoubleQuoted Id
_ [Token]
l -> (Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Token -> Bool
couldBeOptimized [Token]
l
        Token
_ -> Bool
True

    check :: Id -> Token -> m ()
check Id
id (T_Pipeline Id
_ [Token]
_ [T_Redirecting Id
_ [Token]
_ Token
c]) = Id -> Token -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Token -> m ()
warnForEcho Id
id Token
c
    check Id
_ Token
_ = () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    isCovered :: Token -> t a -> Bool
isCovered Token
first t a
rest = t a -> Bool
forall a. 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 -> m ()
warnForEcho Id
id = String -> (Token -> [Token] -> m ()) -> Token -> m ()
forall {m :: * -> *}.
Monad m =>
String -> (Token -> [Token] -> m ()) -> Token -> m ()
checkUnqualifiedCommand String
"echo" ((Token -> [Token] -> m ()) -> Token -> m ())
-> (Token -> [Token] -> m ()) -> Token -> m ()
forall a b. (a -> b) -> a -> b
$ \Token
_ [Token]
vars ->
        case [Token]
vars of
          (Token
first:[Token]
rest) ->
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Token -> [Token] -> Bool
forall {t :: * -> *} {a}. Foldable t => Token -> t a -> Bool
isCovered Token
first [Token]
rest Bool -> Bool -> Bool
|| String
"-" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` Token -> String
onlyLiteralString Token
first) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
                Bool -> m () -> m ()
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) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style Id
id Code
2116
                    String
"Useless echo? Instead of 'cmd $(echo foo)', just use 'cmd foo'."
          [Token]
_ -> () -> m ()
forall a. a -> m a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTestRedirects String
"test 3 > 1"
prop_checkTestRedirects2 :: Bool
prop_checkTestRedirects2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTestRedirects String
"test 3 \\> 1"
prop_checkTestRedirects3 :: Bool
prop_checkTestRedirects3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTestRedirects String
"/usr/bin/test $var > $foo"
prop_checkTestRedirects4 :: Bool
prop_checkTestRedirects4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTestRedirects String
"test 1 -eq 2 2> file"
checkTestRedirects :: p -> Token -> m ()
checkTestRedirects p
_ (T_Redirecting Id
id [Token]
redirs Token
cmd) | Token
cmd Token -> String -> Bool
`isCommand` String
"test" =
    (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
check [Token]
redirs
  where
    check :: Token -> f ()
check Token
t =
        Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
suspicious Token
t) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
            Id -> Code -> String -> f ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
t) Code
2065 String
"This is interpreted as a shell file redirection, not a comparison."
    suspicious :: Token -> Bool
suspicious Token
t = -- Ignore redirections of stderr because these are valid for squashing e.g. int errors,
        case Token
t of  -- and >> and similar redirections because these are probably not comparisons.
            T_FdRedirect Id
_ String
fd (T_IoFile Id
_ Token
op Token
_) -> String
fd String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= String
"2" Bool -> Bool -> Bool
&& Token -> Bool
isComparison Token
op
            Token
_ -> Bool
False
    isComparison :: Token -> Bool
isComparison Token
t =
        case Token
t of
            T_Greater Id
_ -> Bool
True
            T_Less Id
_ -> Bool
True
            Token
_ -> Bool
False
checkTestRedirects p
_ Token
_ = () -> m ()
forall a. a -> m a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPS1Assignments String
"PS1='\\033[1;35m\\$ '"
prop_checkPS11a :: Bool
prop_checkPS11a = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPS1Assignments String
"export PS1='\\033[1;35m\\$ '"
prop_checkPSf2 :: Bool
prop_checkPSf2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPS1Assignments String
"PS1='\\h \\e[0m\\$ '"
prop_checkPS13 :: Bool
prop_checkPS13 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPS1Assignments String
"PS1=$'\\x1b[c '"
prop_checkPS14 :: Bool
prop_checkPS14 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPS1Assignments String
"PS1=$'\\e[3m; '"
prop_checkPS14a :: Bool
prop_checkPS14a = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPS1Assignments String
"export PS1=$'\\e[3m; '"
prop_checkPS15 :: Bool
prop_checkPS15 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPS1Assignments String
"PS1='\\[\\033[1;35m\\]\\$ '"
prop_checkPS16 :: Bool
prop_checkPS16 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPS1Assignments String
"PS1='\\[\\e1m\\e[1m\\]\\$ '"
prop_checkPS17 :: Bool
prop_checkPS17 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPS1Assignments String
"PS1='e033x1B'"
prop_checkPS18 :: Bool
prop_checkPS18 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkPS1Assignments String
"PS1='\\[\\e\\]'"
checkPS1Assignments :: p -> Token -> f ()
checkPS1Assignments p
_ (T_Assignment Id
_ AssignmentMode
_ String
"PS1" [Token]
_ Token
word) = Token -> f ()
forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
warnFor Token
word
  where
    warnFor :: Token -> f ()
warnFor Token
word =
        let contents :: String
contents = [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
word in
            Bool -> f () -> f ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String -> Bool
containsUnescaped String
contents) (f () -> f ()) -> f () -> f ()
forall a b. (a -> b) -> a -> b
$
                Id -> Code -> String -> f ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
info (Token -> Id
getId Token
word) Code
2025 String
"Make sure all escape sequences are enclosed in \\[..\\] to prevent line wrapping issues"
    containsUnescaped :: String -> Bool
containsUnescaped String
s =
        let unenclosed :: String
unenclosed = Regex -> String -> String -> String
subRegex Regex
enclosedRegex String
s String
"" in
           Maybe [String] -> Bool
forall a. Maybe a -> Bool
isJust (Maybe [String] -> Bool) -> Maybe [String] -> Bool
forall a b. (a -> b) -> a -> b
$ Regex -> String -> Maybe [String]
matchRegex Regex
escapeRegex String
unenclosed
    enclosedRegex :: Regex
enclosedRegex = String -> Regex
mkRegex String
"\\\\\\[.*\\\\\\]" -- FIXME: shouldn't be eager
    escapeRegex :: Regex
escapeRegex = String -> Regex
mkRegex String
"\\\\x1[Bb]|\\\\e|\x1B|\\\\033"
checkPS1Assignments p
_ Token
_ = () -> f ()
forall a. a -> f a
forall (m :: * -> *) a. Monad m => a -> m a
return ()

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


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

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

  where

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

    isIndirectionPart :: Token -> Maybe Bool
isIndirectionPart Token
t =
        case Token
t of T_DollarExpansion {} ->  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 Id
_ String
s -> if (Char -> Bool) -> String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isVariableChar String
s
                                    then Maybe Bool
forall a. Maybe a
Nothing
                                    else Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
False
                  Token
_ -> Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
False

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

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

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

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

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

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

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

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

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


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

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

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

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

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

    warnAboutExpansion :: Id -> m ()
warnAboutExpansion Id
id =
        Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2027 String
"The surrounding quotes actually unquote this. Remove or escape them."
    warnAboutLiteral :: Id -> m ()
warnAboutLiteral Id
id =
        Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2140 String
"Word is of the form \"A\"B\"C\" (B indicated). Did you mean \"ABC\" or \"A\\\"B\\\"C\"?"
checkInexplicablyUnquoted Parameters
_ Token
_ = () -> m ()
forall a. a -> m a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTildeInQuotes String
"var=\"~/out.txt\""
prop_checkTildeInQuotes2 :: Bool
prop_checkTildeInQuotes2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTildeInQuotes String
"foo > '~/dir'"
prop_checkTildeInQuotes4 :: Bool
prop_checkTildeInQuotes4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTildeInQuotes String
"~/file"
prop_checkTildeInQuotes5 :: Bool
prop_checkTildeInQuotes5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTildeInQuotes String
"echo '/~foo/cow'"
prop_checkTildeInQuotes6 :: Bool
prop_checkTildeInQuotes6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTildeInQuotes String
"awk '$0 ~ /foo/'"
checkTildeInQuotes :: p -> Token -> m ()
checkTildeInQuotes p
_ = Token -> m ()
forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
check
  where
    verify :: Id -> String -> m ()
verify Id
id (Char
'~':Char
'/':String
_) = Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2088 String
"Tilde does not expand in quotes. Use $HOME."
    verify Id
_ String
_ = () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    check :: Token -> m ()
check (T_NormalWord Id
_ (T_SingleQuoted Id
id String
str:[Token]
_)) =
        Id -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> String -> m ()
verify Id
id String
str
    check (T_NormalWord Id
_ (T_DoubleQuoted Id
_ (T_Literal Id
id String
str:[Token]
_):[Token]
_)) =
        Id -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> String -> m ()
verify Id
id String
str
    check Token
_ = () -> m ()
forall a. a -> m a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkLonelyDotDash String
"./ file"
prop_checkLonelyDotDash2 :: Bool
prop_checkLonelyDotDash2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkLonelyDotDash String
"./file"
checkLonelyDotDash :: p -> Token -> m ()
checkLonelyDotDash p
_ t :: Token
t@(T_Redirecting Id
id [Token]
_ Token
_)
    | Token -> String -> Bool
isUnqualifiedCommand Token
t String
"./" =
        Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2083 String
"Don't add spaces after the slash in './file'."
checkLonelyDotDash p
_ Token
_ = () -> m ()
forall a. a -> m a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExec String
"exec foo; true"
prop_checkSpuriousExec2 :: Bool
prop_checkSpuriousExec2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExec String
"if a; then exec b; exec c; fi"
prop_checkSpuriousExec3 :: Bool
prop_checkSpuriousExec3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExec String
"echo cow; exec foo"
prop_checkSpuriousExec4 :: Bool
prop_checkSpuriousExec4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExec String
"if a; then exec b; fi"
prop_checkSpuriousExec5 :: Bool
prop_checkSpuriousExec5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExec String
"exec > file; cmd"
prop_checkSpuriousExec6 :: Bool
prop_checkSpuriousExec6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExec String
"exec foo > file; cmd"
prop_checkSpuriousExec7 :: Bool
prop_checkSpuriousExec7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExec String
"exec file; echo failed; exit 3"
prop_checkSpuriousExec8 :: Bool
prop_checkSpuriousExec8 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExec String
"exec {origout}>&1- >tmp.log 2>&1; bar"
prop_checkSpuriousExec9 :: Bool
prop_checkSpuriousExec9 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExec String
"for file in rc.d/*; do exec \"$file\"; done"
prop_checkSpuriousExec10 :: Bool
prop_checkSpuriousExec10 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExec String
"exec file; r=$?; printf >&2 'failed\n'; return $r"
prop_checkSpuriousExec11 :: Bool
prop_checkSpuriousExec11 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExec String
"exec file; :"
checkSpuriousExec :: p -> Token -> m ()
checkSpuriousExec p
_ = Token -> m ()
doLists
  where
    doLists :: Token -> m ()
doLists (T_Script Id
_ Token
_ [Token]
cmds) = [Token] -> Bool -> m ()
doList [Token]
cmds Bool
False
    doLists (T_BraceGroup Id
_ [Token]
cmds) = [Token] -> Bool -> m ()
doList [Token]
cmds Bool
False
    doLists (T_WhileExpression Id
_ [Token]
_ [Token]
cmds) = [Token] -> Bool -> m ()
doList [Token]
cmds Bool
True
    doLists (T_UntilExpression Id
_ [Token]
_ [Token]
cmds) = [Token] -> Bool -> m ()
doList [Token]
cmds Bool
True
    doLists (T_ForIn Id
_ String
_ [Token]
_ [Token]
cmds) = [Token] -> Bool -> m ()
doList [Token]
cmds Bool
True
    doLists (T_ForArithmetic Id
_ Token
_ Token
_ Token
_ [Token]
cmds) = [Token] -> Bool -> m ()
doList [Token]
cmds Bool
True
    doLists (T_IfExpression Id
_ [([Token], [Token])]
thens [Token]
elses) = do
        (([Token], [Token]) -> m ()) -> [([Token], [Token])] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (\([Token]
_, [Token]
l) -> [Token] -> Bool -> m ()
doList [Token]
l Bool
False) [([Token], [Token])]
thens
        [Token] -> Bool -> m ()
doList [Token]
elses Bool
False
    doLists Token
_ = () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()

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

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

    commentIfExec :: Token -> m ()
commentIfExec (T_Pipeline Id
id [Token]
_ [Token
c]) = Token -> m ()
commentIfExec Token
c
    commentIfExec (T_Redirecting Id
_ [Token]
_ (T_SimpleCommand Id
id [Token]
_ (Token
cmd:Token
additionalArg:[Token]
_))) |
        Token -> Maybe String
getLiteralString Token
cmd Maybe String -> Maybe String -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Maybe String
forall a. a -> Maybe a
Just String
"exec" =
            Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2093 String
"Remove \"exec \" if script should continue after this command."
    commentIfExec Token
_ = () -> m ()
forall a. a -> m a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExpansion String
"if $(true); then true; fi"
prop_checkSpuriousExpansion3 :: Bool
prop_checkSpuriousExpansion3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExpansion String
"$(cmd) --flag1 --flag2"
prop_checkSpuriousExpansion4 :: Bool
prop_checkSpuriousExpansion4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSpuriousExpansion String
"$((i++))"
checkSpuriousExpansion :: p -> Token -> m ()
checkSpuriousExpansion p
_ (T_SimpleCommand Id
_ [Token]
_ [T_NormalWord Id
_ [Token
word]]) = Token -> m ()
forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
check Token
word
  where
    check :: Token -> m ()
check Token
word = case Token
word of
        T_DollarExpansion Id
id [Token]
_ ->
            Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2091 String
"Remove surrounding $() to avoid executing output (or use eval if intentional)."
        T_Backticked Id
id [Token]
_ ->
            Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2092 String
"Remove backticks to avoid executing output (or use eval if intentional)."
        T_DollarArithmetic Id
id Token
_ ->
            Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err Id
id Code
2084 String
"Remove '$' or use '_=$((expr))' to avoid executing output."
        Token
_ -> () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
checkSpuriousExpansion p
_ Token
_ = () -> m ()
forall a. a -> m a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkDollarBrackets String
"echo $[1+2]"
prop_checkDollarBrackets2 :: Bool
prop_checkDollarBrackets2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkDollarBrackets String
"echo $((1+2))"
checkDollarBrackets :: p -> Token -> m ()
checkDollarBrackets p
_ (T_DollarBracket Id
id Token
_) =
    Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style Id
id Code
2007 String
"Use $((..)) instead of deprecated $[..]"
checkDollarBrackets p
_ Token
_ = () -> m ()
forall a. a -> m a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSshHereDoc String
"ssh host << foo\necho $PATH\nfoo"
prop_checkSshHereDoc2 :: Bool
prop_checkSshHereDoc2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSshHereDoc String
"ssh host << 'foo'\necho $PATH\nfoo"
checkSshHereDoc :: p -> Token -> m ()
checkSshHereDoc p
_ (T_Redirecting Id
_ [Token]
redirs Token
cmd)
        | Token
cmd Token -> String -> Bool
`isCommand` String
"ssh" =
    (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkHereDoc [Token]
redirs
  where
    hasVariables :: Regex
hasVariables = String -> Regex
mkRegex String
"[`$]"
    checkHereDoc :: Token -> m ()
checkHereDoc (T_FdRedirect Id
_ String
_ (T_HereDoc Id
id Dashed
_ Quoted
Unquoted String
token [Token]
tokens))
        | Bool -> Bool
not ((Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Token -> Bool
isConstant [Token]
tokens) =
        Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2087 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
"Quote '" String -> String -> String
forall a. [a] -> [a] -> [a]
++ (String -> String
e4m String
token) String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"' to make here document expansions happen on the server side rather than on the client."
    checkHereDoc Token
_ = () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
checkSshHereDoc p
_ Token
_ = () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()

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


findSubshelled :: [StackData]
-> [(String, [(Token, Token, String, DataType)])]
-> Map String VariableState
-> m ()
findSubshelled [] [(String, [(Token, Token, String, DataType)])]
_ Map String VariableState
_ = () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
findSubshelled (Assignment x :: (Token, Token, String, DataType)
x@(Token
_, Token
_, String
str, DataType
data_):[StackData]
rest) scopes :: [(String, [(Token, Token, String, DataType)])]
scopes@((String
reason,[(Token, Token, String, DataType)]
scope):[(String, [(Token, Token, String, DataType)])]
restscope) Map String VariableState
deadVars =
    if DataType -> Bool
isTrueAssignmentSource DataType
data_
    then [StackData]
-> [(String, [(Token, Token, String, DataType)])]
-> Map String VariableState
-> m ()
findSubshelled [StackData]
rest ((String
reason, (Token, Token, String, DataType)
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)])]
restscope) (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
    else [StackData]
-> [(String, [(Token, Token, String, DataType)])]
-> Map String VariableState
-> m ()
findSubshelled [StackData]
rest [(String, [(Token, Token, String, DataType)])]
scopes Map String VariableState
deadVars

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

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

findSubshelled (StackData
StackScopeEnd:[StackData]
rest) ((String
reason, [(Token, Token, String, DataType)]
scope):[(String, [(Token, Token, String, DataType)])]
oldScopes) Map String VariableState
deadVars =
    [StackData]
-> [(String, [(Token, Token, String, DataType)])]
-> Map String VariableState
-> m ()
findSubshelled [StackData]
rest [(String, [(Token, Token, String, DataType)])]
oldScopes (Map String VariableState -> m ())
-> Map String VariableState -> m ()
forall a b. (a -> b) -> a -> b
$
        (Map String VariableState
 -> (Token, Token, String, DataType) -> Map String VariableState)
-> Map String VariableState
-> [(Token, Token, String, DataType)]
-> Map String VariableState
forall b a. (b -> a -> b) -> b -> [a] -> b
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl (\Map String VariableState
m (Token
_, Token
token, String
var, DataType
_) ->
            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 :: forall t v.
(Token -> Token -> String -> State t [v])
-> (Token -> Token -> String -> DataType -> State t [v])
-> t
-> [StackData]
-> [v]
doVariableFlowAnalysis Token -> Token -> String -> State t [v]
readFunc Token -> Token -> String -> DataType -> State t [v]
writeFunc t
empty [StackData]
flow = State t [v] -> t -> [v]
forall s a. State s a -> s -> a
evalState (
    ([v] -> StackData -> State t [v])
-> [v] -> [StackData] -> State t [v]
forall (t :: * -> *) (m :: * -> *) b a.
(Foldable t, Monad m) =>
(b -> a -> m b) -> b -> t a -> m b
foldM (\[v]
list StackData
x -> do { [v]
l <- StackData -> State t [v]
doFlow StackData
x;  [v] -> State t [v]
forall a. a -> StateT t Identity a
forall (m :: * -> *) a. Monad m => a -> m a
return ([v] -> State t [v]) -> [v] -> State t [v]
forall a b. (a -> b) -> a -> b
$ [v]
l [v] -> [v] -> [v]
forall a. [a] -> [a] -> [a]
++ [v]
list; }) [] [StackData]
flow
    ) t
empty
  where
    doFlow :: StackData -> State t [v]
doFlow (Reference (Token
base, Token
token, String
name)) =
        Token -> Token -> String -> State t [v]
readFunc Token
base Token
token String
name
    doFlow (Assignment (Token
base, Token
token, String
name, DataType
values)) =
        Token -> Token -> String -> DataType -> State t [v]
writeFunc Token
base Token
token String
name DataType
values
    doFlow StackData
_ = [v] -> State t [v]
forall a. a -> StateT t Identity a
forall (m :: * -> *) a. Monad m => a -> m a
return []

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

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

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

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

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

  where
    bracedString :: String
bracedString = [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
list
    name :: String
name = String -> String
getBracedReference String
bracedString
    parents :: Map Id Token
parents = Parameters -> Map Id Token
parentMap Parameters
params
    needsQuoting :: Bool
needsQuoting =
              Bool -> Bool
not (Token -> Bool
isArrayExpansion Token
token) -- There's another warning for this
              Bool -> Bool -> Bool
&& Bool -> Bool
not (Token -> Bool
isCountingReference Token
token)
              Bool -> Bool -> Bool
&& Bool -> Bool
not (Shell -> Map Id Token -> Token -> Bool
isQuoteFree (Parameters -> Shell
shellType Parameters
params) Map Id Token
parents Token
token)
              Bool -> Bool -> Bool
&& Bool -> Bool
not (Token -> Bool
isQuotedAlternativeReference Token
token)
              Bool -> Bool -> Bool
&& Bool -> Bool
not (Map Id Token -> Token -> Bool
usedAsCommandName Map Id Token
parents Token
token)

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

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

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

checkSpacefulnessCfg' Bool
_ Parameters
_ Token
_ = () -> WriterT [TokenComment] Identity ()
forall a. a -> WriterT [TokenComment] Identity a
forall (m :: * -> *) a. Monad m => a -> m a
return ()


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

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

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

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

    squashesQuotes :: Token -> Bool
squashesQuotes Token
t =
        case Token
t of
            T_DollarBraced Id
id Bool
_ Token
l -> String
"#" 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
l)
            Token
_ -> Bool
False

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


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

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

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

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

    patternContext :: Id -> String
patternContext Id
id =
        case Position -> Code
posLine (Position -> Code)
-> ((Position, Position) -> Position)
-> (Position, Position)
-> Code
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Position, Position) -> Position
forall a b. (a, b) -> a
fst ((Position, Position) -> Code)
-> Maybe (Position, Position) -> Maybe Code
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 (Parameters -> Map Id (Position, Position)
tokenPositions Parameters
params) of
          Just Code
l -> String
" on line " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Code -> String
forall a. Show a => a -> String
show Code
l String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"."
          Maybe Code
_      -> String
"."

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    isGuarded :: Token -> Bool
isGuarded (T_DollarBraced Id
_ Bool
_ Token
v) =
        String
rest String -> Regex -> Bool
`matches` Regex
guardRegex
      where
        name :: String
name = [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 a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
"#!") String
name
    isGuarded Token
_ = Bool
False
    --  :? or :- with optional array index and colon
    guardRegex :: Regex
guardRegex = String -> Regex
mkRegex String
"^(\\[.*\\])?:?[-?]"

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


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

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

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

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

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

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

        Bool
-> WriterT [TokenComment] Identity ()
-> WriterT [TokenComment] Identity ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless ((Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
stdinRedirect [Token]
redirs) (WriterT [TokenComment] Identity ()
 -> WriterT [TokenComment] Identity ())
-> WriterT [TokenComment] Identity ()
-> WriterT [TokenComment] Identity ()
forall a b. (a -> b) -> a -> b
$ do
            -- Recurse into ifs/loops/groups/etc if this doesn't redirect
            (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 ()
checkMuncher ([Token] -> WriterT [TokenComment] Identity ())
-> [Token] -> WriterT [TokenComment] Identity ()
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
cmd

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

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

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

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


prop_checkPrefixAssign1 :: Bool
prop_checkPrefixAssign1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkPrefixAssignmentReference String
"var=foo echo $var"
prop_checkPrefixAssign2 :: Bool
prop_checkPrefixAssign2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkPrefixAssignmentReference String
"var=$(echo $var) cmd"
checkPrefixAssignmentReference :: Parameters -> Token -> m ()
checkPrefixAssignmentReference Parameters
params t :: Token
t@(T_DollarBraced Id
id Bool
_ Token
value) =
    [Token] -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
[Token] -> m ()
check [Token]
path
  where
    name :: String
name = String -> String
getBracedReference (String -> String) -> String -> String
forall a b. (a -> b) -> a -> b
$ [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
value
    path :: [Token]
path = NonEmpty Token -> [Token]
forall a. NonEmpty a -> [a]
NE.toList (NonEmpty Token -> [Token]) -> NonEmpty Token -> [Token]
forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> NonEmpty 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 a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    check (Token
t:[Token]
rest) =
        case Token
t of
            T_SimpleCommand Id
_ [Token]
vars (Token
_:[Token]
_) -> (Token -> m ()) -> [Token] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ Token -> m ()
forall {m :: * -> *}. MonadWriter [TokenComment] m => Token -> m ()
checkVar [Token]
vars
            Token
_ -> [Token] -> m ()
check [Token]
rest
    checkVar :: Token -> m ()
checkVar (T_Assignment Id
aId AssignmentMode
mode String
aName [] Token
value) |
            String
aName String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
name Bool -> Bool -> Bool
&& (Id
aId Id -> [Id] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [Id]
idPath) = do
        Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
aId Code
2097 String
"This assignment is only seen by the forked process."
        Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2098 String
"This expansion will not see the mentioned assignment."
    checkVar Token
_ = () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()

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

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

    isIgnoredCommand :: Bool
isIgnoredCommand = 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
p) Token
t
        Bool -> Maybe Bool
forall a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> Maybe Bool) -> Bool -> Maybe Bool
forall a b. (a -> b) -> a -> b
$ Token -> (String -> Bool) -> Bool
isCommandMatch Token
cmd (String -> [String] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"tr", String
"read"])

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



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

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

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

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

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


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

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



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

prop_checkUnpassedInFunctions1 :: Bool
prop_checkUnpassedInFunctions1 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions String
"foo() { echo $1; }; foo"
prop_checkUnpassedInFunctions2 :: Bool
prop_checkUnpassedInFunctions2 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions String
"foo() { echo $1; };"
prop_checkUnpassedInFunctions3 :: Bool
prop_checkUnpassedInFunctions3 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions String
"foo() { echo $lol; }; foo"
prop_checkUnpassedInFunctions4 :: Bool
prop_checkUnpassedInFunctions4 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions String
"foo() { echo $0; }; foo"
prop_checkUnpassedInFunctions5 :: Bool
prop_checkUnpassedInFunctions5 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions String
"foo() { echo $1; }; foo 'lol'; foo"
prop_checkUnpassedInFunctions6 :: Bool
prop_checkUnpassedInFunctions6 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions String
"foo() { set -- *; echo $1; }; foo"
prop_checkUnpassedInFunctions7 :: Bool
prop_checkUnpassedInFunctions7 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions String
"foo() { echo $1; }; foo; foo;"
prop_checkUnpassedInFunctions8 :: Bool
prop_checkUnpassedInFunctions8 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions String
"foo() { echo $((1)); }; foo;"
prop_checkUnpassedInFunctions9 :: Bool
prop_checkUnpassedInFunctions9 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions String
"foo() { echo $(($b)); }; foo;"
prop_checkUnpassedInFunctions10 :: Bool
prop_checkUnpassedInFunctions10 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions String
"foo() { echo $!; }; foo;"
prop_checkUnpassedInFunctions11 :: Bool
prop_checkUnpassedInFunctions11 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions String
"foo() { bar() { echo $1; }; bar baz; }; foo;"
prop_checkUnpassedInFunctions12 :: Bool
prop_checkUnpassedInFunctions12 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions String
"foo() { echo ${!var*}; }; foo;"
prop_checkUnpassedInFunctions13 :: Bool
prop_checkUnpassedInFunctions13 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyNotTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions String
"# shellcheck disable=SC2120\nfoo() { echo $1; }\nfoo\n"
prop_checkUnpassedInFunctions14 :: Bool
prop_checkUnpassedInFunctions14 = (Parameters -> Token -> [TokenComment]) -> String -> Bool
verifyTree Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions String
"foo() { echo $#; }; foo"
checkUnpassedInFunctions :: Parameters -> Token -> [TokenComment]
checkUnpassedInFunctions Parameters
params Token
root =
    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
$ Writer [(String, Token)] Token -> [(String, Token)]
forall w a. Writer w a -> w
execWriter (Writer [(String, Token)] Token -> [(String, Token)])
-> Writer [(String, Token)] Token -> [(String, Token)]
forall a b. (a -> b) -> a -> b
$ (Token -> WriterT [(String, Token)] Identity ())
-> Token -> Writer [(String, Token)] Token
forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> Token -> m Token
doAnalysis ([(String, Token)] -> WriterT [(String, Token)] Identity ()
forall w (m :: * -> *). MonadWriter w m => w -> m ()
tell ([(String, Token)] -> WriterT [(String, Token)] Identity ())
-> (Token -> [(String, Token)])
-> Token
-> WriterT [(String, Token)] Identity ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Maybe (String, Token) -> [(String, Token)]
forall a. Maybe a -> [a]
maybeToList (Maybe (String, Token) -> [(String, Token)])
-> (Token -> Maybe (String, Token)) -> Token -> [(String, Token)]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> Maybe (String, Token)
findFunction) Token
root

    findFunction :: Token -> Maybe (String, Token)
findFunction t :: Token
t@(T_Function Id
id FunctionKeyword
_ FunctionParentheses
_ String
name Token
body)
        | (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)
        = (String, Token) -> Maybe (String, Token)
forall a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return (String
name,Token
t)
        where flow :: [StackData]
flow = Parameters -> Token -> [StackData]
getVariableFlow Parameters
params Token
body
    findFunction Token
_ = Maybe (String, Token)
forall a. Maybe a
Nothing

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

    isDirectChildOf :: Token -> Token -> Bool
isDirectChildOf Token
child Token
parent = Bool -> Maybe Bool -> Bool
forall a. a -> Maybe a -> a
fromMaybe Bool
False (Maybe Bool -> Bool) -> Maybe Bool -> Bool
forall a b. (a -> b) -> a -> b
$ do
        Token
function <- (Token -> Bool) -> NonEmpty Token -> Maybe Token
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (\Token
x -> case Token
x of
            T_Function {} -> Bool
True
            T_Script {} -> Bool
True  -- for sourced files
            Token
_ -> Bool
False) (NonEmpty Token -> Maybe Token) -> NonEmpty Token -> Maybe Token
forall a b. (a -> b) -> a -> b
$
                Map Id Token -> Token -> NonEmpty Token
getPath (Parameters -> Map Id Token
parentMap Parameters
params) Token
child
        Bool -> Maybe Bool
forall a. a -> Maybe a
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 (Maybe (WriterT [(String, Bool, Token)] Identity ())
-> WriterT [(String, Bool, Token)] Identity ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (Maybe (WriterT [(String, Bool, Token)] Identity ())
 -> WriterT [(String, Bool, Token)] Identity ())
-> (Token -> Maybe (WriterT [(String, Bool, Token)] Identity ()))
-> Token
-> WriterT [(String, Bool, Token)] Identity ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> Maybe (WriterT [(String, Bool, Token)] Identity ())
checkCommand) Token
root
    checkCommand :: Token -> Maybe (Writer [(String, Bool, Token)] ())
    checkCommand :: Token -> Maybe (WriterT [(String, Bool, Token)] Identity ())
checkCommand t :: Token
t@(T_SimpleCommand Id
_ [Token]
_ (Token
cmd:[Token]
args)) = do
        String
str <- Token -> Maybe String
getLiteralString Token
cmd
        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String -> Map String Token -> Bool
forall k a. Ord k => k -> Map k a -> Bool
Map.member String
str Map String Token
functionMap
        WriterT [(String, Bool, Token)] Identity ()
-> Maybe (WriterT [(String, Bool, Token)] Identity ())
forall a. a -> Maybe a
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 a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Token]
args, Token
t)]
    checkCommand Token
_ = Maybe (WriterT [(String, Bool, Token)] Identity ())
forall a. Maybe a
Nothing

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

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

    warnForGroup :: [(String, Bool, Token)] -> f ()
warnForGroup [(String, Bool, Token)]
group =
        -- Allow ignoring SC2120 on the function to ignore all calls
        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 Bool -> Bool -> Bool
&& Bool -> Bool
not 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
        where (String
name, Token
func) = [(String, Bool, Token)] -> (String, Token)
forall {b} {c}. [(String, b, c)] -> (String, Token)
getFunction [(String, Bool, Token)]
group
              ignoring :: Bool
ignoring = Parameters -> Code -> Token -> Bool
shouldIgnoreCode Parameters
params Code
2120 Token
func

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

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


prop_checkOverridingPath1 :: Bool
prop_checkOverridingPath1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOverridingPath String
"PATH=\"$var/$foo\""
prop_checkOverridingPath2 :: Bool
prop_checkOverridingPath2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOverridingPath String
"PATH=\"mydir\""
prop_checkOverridingPath3 :: Bool
prop_checkOverridingPath3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOverridingPath String
"PATH=/cow/foo"
prop_checkOverridingPath4 :: Bool
prop_checkOverridingPath4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOverridingPath String
"PATH=/cow/foo/bin"
prop_checkOverridingPath5 :: Bool
prop_checkOverridingPath5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOverridingPath String
"PATH='/bin:/sbin'"
prop_checkOverridingPath6 :: Bool
prop_checkOverridingPath6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOverridingPath String
"PATH=\"$var/$foo\" cmd"
prop_checkOverridingPath7 :: Bool
prop_checkOverridingPath7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOverridingPath String
"PATH=$OLDPATH"
prop_checkOverridingPath8 :: Bool
prop_checkOverridingPath8 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkOverridingPath String
"PATH=$PATH:/stuff"
checkOverridingPath :: p -> Token -> m ()
checkOverridingPath p
_ (T_SimpleCommand Id
_ [Token]
vars []) =
    (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 -> m ()
checkVar (T_Assignment Id
id AssignmentMode
Assign String
"PATH" [] Token
word)
        | Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
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 a. Eq a => [a] -> [a] -> Bool
`isInfixOf` String
string) [String
"/bin", String
"/sbin" ] = do
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Char
'/' Char -> String -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
string Bool -> Bool -> Bool
&& Char
':' Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` String
string) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ Id -> m ()
forall {m :: * -> *}. MonadWriter [TokenComment] m => Id -> m ()
notify Id
id
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
isLiteral Token
word Bool -> Bool -> Bool
&& Char
':' Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` String
string Bool -> Bool -> Bool
&& Char
'/' Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` String
string) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ Id -> m ()
forall {m :: * -> *}. MonadWriter [TokenComment] m => Id -> m ()
notify Id
id
        where 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
    checkVar Token
_ = () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    notify :: Id -> m ()
notify Id
id = Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2123 String
"PATH is the shell search path. Use another name."
checkOverridingPath p
_ Token
_ = () -> m ()
forall a. a -> m a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTildeInPath String
"PATH=\"$PATH:~/bin\""
prop_checkTildeInPath2 :: Bool
prop_checkTildeInPath2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTildeInPath String
"PATH='~foo/bin'"
prop_checkTildeInPath3 :: Bool
prop_checkTildeInPath3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkTildeInPath String
"PATH=~/bin"
checkTildeInPath :: p -> Token -> m ()
checkTildeInPath p
_ (T_SimpleCommand Id
_ [Token]
vars [Token]
_) =
    (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 -> m ()
checkVar (T_Assignment Id
id AssignmentMode
Assign String
"PATH" [] (T_NormalWord Id
_ [Token]
parts))
        | (Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (\Token
x -> Token -> Bool
isQuoted Token
x Bool -> Bool -> Bool
&& Token -> Bool
hasTilde Token
x) [Token]
parts =
            Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2147 String
"Literal tilde in PATH works poorly across programs."
    checkVar Token
_ = () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()

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

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

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


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

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


prop_checkSuspiciousIFS1 :: Bool
prop_checkSuspiciousIFS1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSuspiciousIFS String
"IFS=\"\\n\""
prop_checkSuspiciousIFS2 :: Bool
prop_checkSuspiciousIFS2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSuspiciousIFS String
"IFS=$'\\t'"
prop_checkSuspiciousIFS3 :: Bool
prop_checkSuspiciousIFS3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkSuspiciousIFS String
"IFS=' \\t\\n'"
checkSuspiciousIFS :: Parameters -> Token -> m ()
checkSuspiciousIFS Parameters
params (T_Assignment Id
_ AssignmentMode
_ String
"IFS" [] Token
value) =
    (String -> m ()) -> Maybe String -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ String -> m ()
forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
String -> f ()
check (Maybe String -> m ()) -> Maybe String -> m ()
forall a b. (a -> b) -> a -> b
$ Token -> Maybe String
getLiteralString Token
value
  where
    hasDollarSingle :: Bool
hasDollarSingle = Parameters -> Shell
shellType Parameters
params Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
== Shell
Bash Bool -> Bool -> Bool
|| Parameters -> Shell
shellType Parameters
params Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
== Shell
Ksh
    n :: String
n = if Bool
hasDollarSingle then  String
"$'\\n'" else String
"'<literal linefeed here>'"
    t :: String
t = if Bool
hasDollarSingle then  String
"$'\\t'" else String
"\"$(printf '\\t')\""
    check :: String -> m ()
check String
value =
        case String
value of
            String
"\\n" -> String -> m ()
forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
String -> f ()
suggest String
n
            String
"\\t" -> String -> m ()
forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
String -> f ()
suggest String
t
            String
x | Char
'\\' Char -> String -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
x -> String -> m ()
forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
String -> f ()
suggest2 String
"a literal backslash"
            String
x | Char
'n' Char -> String -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
x -> String -> m ()
forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
String -> f ()
suggest2 String
"the literal letter 'n'"
            String
x | Char
't' Char -> String -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
x -> String -> m ()
forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
String -> f ()
suggest2 String
"the literal letter 't'"
            String
_ -> () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
    suggest :: String -> m ()
suggest String
r = Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
value) Code
2141 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
"This backslash is literal. Did you mean IFS=" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
r String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" ?"
    suggest2 :: String -> m ()
suggest2 String
desc = Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
value) Code
2141 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
"This IFS value contains " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
desc String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
". For tabs/linefeeds/escapes, use $'..', literal, or printf."
checkSuspiciousIFS Parameters
_ Token
_ = () -> m ()
forall a. a -> m a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkShouldUseGrepQ String
"[[ $(foo | grep bar) ]]"
prop_checkGrepQ2 :: Bool
prop_checkGrepQ2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkShouldUseGrepQ String
"[ -z $(fgrep lol) ]"
prop_checkGrepQ3 :: Bool
prop_checkGrepQ3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkShouldUseGrepQ String
"[ -n \"$(foo | zgrep lol)\" ]"
prop_checkGrepQ4 :: Bool
prop_checkGrepQ4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkShouldUseGrepQ String
"[ -z $(grep bar | cmd) ]"
prop_checkGrepQ5 :: Bool
prop_checkGrepQ5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkShouldUseGrepQ String
"rm $(ls | grep file)"
prop_checkGrepQ6 :: Bool
prop_checkGrepQ6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkShouldUseGrepQ String
"[[ -n $(pgrep foo) ]]"
checkShouldUseGrepQ :: p -> Token -> m ()
checkShouldUseGrepQ p
params Token
t =
    Maybe (m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ case Token
t of
        TC_Nullary Id
id ConditionType
_ Token
token -> Id -> Bool -> Token -> Maybe (m ())
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Bool -> Token -> Maybe (m ())
check Id
id Bool
True Token
token
        TC_Unary Id
id ConditionType
_ String
"-n" Token
token -> Id -> Bool -> Token -> Maybe (m ())
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Bool -> Token -> Maybe (m ())
check Id
id Bool
True Token
token
        TC_Unary Id
id ConditionType
_ String
"-z" Token
token -> Id -> Bool -> Token -> Maybe (m ())
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Bool -> Token -> Maybe (m ())
check Id
id Bool
False Token
token
        Token
_ -> String -> Maybe (m ())
forall a. String -> Maybe a
forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
"not check"
  where
    check :: Id -> Bool -> Token -> Maybe (m ())
check Id
id Bool
bool Token
token = do
        String
name <- Token -> Maybe String
getFinalGrep Token
token
        let op :: String
op = if Bool
bool then String
"-n" else String
"-z"
        let flip :: String
flip = if Bool
bool then String
"" else String
"! "
        m () -> Maybe (m ())
forall a. a -> Maybe a
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 -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style Id
id Code
2143 (String -> Maybe (m ())) -> String -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$
            String
"Use " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
flip String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
name String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" -q instead of " String -> String -> String
forall a. [a] -> [a] -> [a]
++
                String
"comparing output with [ " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
op String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
" .. ]."

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

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

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

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

        (TC_Binary Id
_ ConditionType
typ String
op Token
lhs Token
rhs) | String
op String -> [String] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
arithmeticBinaryTestOps ->
            if ConditionType
typ ConditionType -> ConditionType -> Bool
forall a. Eq a => a -> a -> Bool
== ConditionType
DoubleBracket
            then
                (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
c -> do
                        ConditionType -> Token -> WriterT [TokenComment] Identity ()
forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
ConditionType -> Token -> f ()
checkArrays ConditionType
typ Token
c
                        ConditionType -> Token -> WriterT [TokenComment] Identity ()
forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
ConditionType -> Token -> f ()
checkBraces ConditionType
typ Token
c) [Token
lhs, 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_ (\Token
c -> do
                        ConditionType -> Token -> WriterT [TokenComment] Identity ()
forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
ConditionType -> Token -> f ()
checkNumericalGlob ConditionType
typ Token
c
                        ConditionType -> Token -> WriterT [TokenComment] Identity ()
forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
ConditionType -> Token -> f ()
checkArrays ConditionType
typ Token
c
                        ConditionType -> Token -> WriterT [TokenComment] Identity ()
forall {f :: * -> *}.
MonadWriter [TokenComment] f =>
ConditionType -> Token -> f ()
checkBraces ConditionType
typ Token
c) [Token
lhs, Token
rhs]

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

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

    checkBraces :: ConditionType -> Token -> f ()
checkBraces ConditionType
typ Token
token =
        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 -> Code -> String -> f ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
token) Code
2200 String
"Brace expansions don't work as operands in [ ]. Use a loop."
            else Id -> Code -> String -> f ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
token) Code
2201 String
"Brace expansion doesn't happen in [[ ]]. Use a loop."

    checkGlobs :: ConditionType -> Token -> f ()
checkGlobs ConditionType
typ Token
token =
        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 -> Code -> String -> f ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
token) Code
2202 String
"Globs don't work as operands in [ ]. Use a loop."
            else Id -> Code -> String -> f ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
err (Token -> Id
getId Token
token) Code
2203 String
"Globs are ignored in [[ ]] except right of =/!=. Use a loop."

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


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

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

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

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

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

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

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

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

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

    isFirstCommandInFunction :: Bool
isFirstCommandInFunction = 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
        let path :: NonEmpty Token
path = Map Id Token -> Token -> NonEmpty Token
getPath (Parameters -> Map Id Token
parentMap Parameters
params) Token
token
        Token
func <- (Token -> Bool) -> NonEmpty Token -> Maybe Token
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find Token -> Bool
isFunction NonEmpty Token
path
        Token
cmd <- Map Id Token -> Token -> Maybe Token
getClosestCommand (Parameters -> Map Id Token
parentMap Parameters
params) Token
token
        Bool -> Maybe Bool
forall a. a -> Maybe a
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
cmd Id -> Id -> Bool
forall a. Eq a => a -> a -> Bool
== Token -> Id
getId (Token -> Token
getFirstCommandInFunction Token
func)

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

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

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


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

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

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


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

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

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

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


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

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

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

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

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

    tupMap :: (t -> b) -> [t] -> [(t, b)]
tupMap t -> b
f [t]
l = (t -> (t, b)) -> [t] -> [(t, b)]
forall a b. (a -> b) -> [a] -> [b]
map (\t
x -> (t
x, t -> b
f t
x)) [t]
l
    checkDoms :: ((Token, Maybe [PseudoGlob]), t (Token, [PseudoGlob])) -> m ()
checkDoms ((Token
glob, Just [PseudoGlob]
x), t (Token, [PseudoGlob])
rest) =
        Maybe (Token, [PseudoGlob])
-> ((Token, [PseudoGlob]) -> m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ (((Token, [PseudoGlob]) -> Bool)
-> t (Token, [PseudoGlob]) -> Maybe (Token, [PseudoGlob])
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (\(Token
_, [PseudoGlob]
p) -> [PseudoGlob]
x [PseudoGlob] -> [PseudoGlob] -> Bool
`pseudoGlobIsSuperSetof` [PseudoGlob]
p) t (Token, [PseudoGlob])
rest) (((Token, [PseudoGlob]) -> m ()) -> m ())
-> ((Token, [PseudoGlob]) -> m ()) -> m ()
forall a b. (a -> b) -> a -> b
$
            \(Token
first,[PseudoGlob]
_) -> do
                Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
glob) Code
2221 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
"This pattern always overrides a later one" String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Id -> String
patternContext (Token -> Id
getId Token
first)
                Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
first) Code
2222 (String -> m ()) -> String -> m ()
forall a b. (a -> b) -> a -> b
$ String
"This pattern never matches because of a previous pattern" String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Id -> String
patternContext (Token -> Id
getId Token
glob)
      where
        patternContext :: Id -> String
        patternContext :: Id -> String
patternContext Id
id =
            case Position -> Code
posLine (Position -> Code)
-> ((Position, Position) -> Position)
-> (Position, Position)
-> Code
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Position, Position) -> Position
forall a b. (a, b) -> a
fst ((Position, Position) -> Code)
-> Maybe (Position, Position) -> Maybe Code
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 Code
l -> String
" on line " String -> String -> String
forall a. Semigroup a => a -> a -> a
<> Code -> String
forall a. Show a => a -> String
show Code
l String -> String -> String
forall a. Semigroup a => a -> a -> a
<> String
"."
              Maybe Code
_      -> String
"."
    checkDoms ((Token, Maybe [PseudoGlob]), t (Token, [PseudoGlob]))
_ = () -> m ()
forall a. a -> m a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSubshellAsTest String
"( -e file )"
prop_checkSubshellAsTest2 :: Bool
prop_checkSubshellAsTest2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSubshellAsTest String
"( 1 -gt 2 )"
prop_checkSubshellAsTest3 :: Bool
prop_checkSubshellAsTest3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSubshellAsTest String
"( grep -c foo bar )"
prop_checkSubshellAsTest4 :: Bool
prop_checkSubshellAsTest4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSubshellAsTest String
"[ 1 -gt 2 ]"
prop_checkSubshellAsTest5 :: Bool
prop_checkSubshellAsTest5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSubshellAsTest String
"( -e file && -x file )"
prop_checkSubshellAsTest6 :: Bool
prop_checkSubshellAsTest6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSubshellAsTest String
"( -e file || -x file && -t 1 )"
prop_checkSubshellAsTest7 :: Bool
prop_checkSubshellAsTest7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkSubshellAsTest String
"( ! -d file )"
checkSubshellAsTest :: p -> Token -> m ()
checkSubshellAsTest p
_ Token
t =
    case Token
t of
        T_Subshell Id
id [Token
w] -> Id -> Token -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Token -> m ()
check Id
id Token
w
        Token
_ -> () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
  where
    check :: Id -> Token -> m ()
check Id
id Token
t = case Token
t of
        (T_Banged Id
_ Token
w) -> Id -> Token -> m ()
check Id
id Token
w
        (T_AndIf Id
_ Token
w Token
_) -> Id -> Token -> m ()
check Id
id Token
w
        (T_OrIf Id
_ Token
w Token
_) -> Id -> Token -> m ()
check Id
id Token
w
        (T_Pipeline Id
_ [Token]
_ [T_Redirecting Id
_ [Token]
_ (T_SimpleCommand Id
_ [] (Token
first:Token
second:[Token]
_))]) ->
            Id -> Token -> Token -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Token -> Token -> m ()
checkParams Id
id Token
first Token
second
        Token
_ -> () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()


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


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

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


prop_checkRedirectionToNumber1 :: Bool
prop_checkRedirectionToNumber1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkRedirectionToNumber String
"( 1 > 2 )"
prop_checkRedirectionToNumber2 :: Bool
prop_checkRedirectionToNumber2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkRedirectionToNumber String
"foo 1>2"
prop_checkRedirectionToNumber3 :: Bool
prop_checkRedirectionToNumber3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkRedirectionToNumber String
"echo foo > '2'"
prop_checkRedirectionToNumber4 :: Bool
prop_checkRedirectionToNumber4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkRedirectionToNumber String
"foo 1>&2"
checkRedirectionToNumber :: p -> Token -> m ()
checkRedirectionToNumber p
_ Token
t = case Token
t of
    T_IoFile Id
id Token
_ Token
word -> Maybe (m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (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 a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2210 String
"This is a file redirection. Was it supposed to be a comparison or fd operation?"
    Token
_ -> () -> m ()
forall a. a -> m a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobAsCommand String
"foo*"
prop_checkGlobAsCommand2 :: Bool
prop_checkGlobAsCommand2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobAsCommand String
"$(var[i])"
prop_checkGlobAsCommand3 :: Bool
prop_checkGlobAsCommand3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkGlobAsCommand String
"echo foo*"
checkGlobAsCommand :: p -> Token -> m ()
checkGlobAsCommand p
_ Token
t = case Token
t of
    T_SimpleCommand Id
_ [Token]
_ (Token
first:[Token]
_)
        | Token -> Bool
isGlob Token
first ->
            Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
first) Code
2211 String
"This is a glob used as a command name. Was it supposed to be in ${..}, array, or is it missing quoting?"
    Token
_ -> () -> m ()
forall a. a -> m a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkFlagAsCommand String
"-e file"
prop_checkFlagAsCommand2 :: Bool
prop_checkFlagAsCommand2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkFlagAsCommand String
"foo\n  --bar=baz"
prop_checkFlagAsCommand3 :: Bool
prop_checkFlagAsCommand3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkFlagAsCommand String
"'--myexec--' args"
prop_checkFlagAsCommand4 :: Bool
prop_checkFlagAsCommand4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkFlagAsCommand String
"var=cmd --arg"  -- Handled by SC2037
checkFlagAsCommand :: p -> Token -> m ()
checkFlagAsCommand p
_ Token
t = case Token
t of
    T_SimpleCommand Id
_ [] (Token
first:[Token]
_)
        | Token -> Bool
isUnquotedFlag Token
first ->
            Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn (Token -> Id
getId Token
first) Code
2215 String
"This flag is used as a command name. Bad line break or missing [ .. ]?"
    Token
_ -> () -> m ()
forall a. a -> m a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkEmptyCondition String
"if [ ]; then ..; fi"
prop_checkEmptyCondition2 :: Bool
prop_checkEmptyCondition2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkEmptyCondition String
"[ foo -o bar ]"
checkEmptyCondition :: p -> Token -> m ()
checkEmptyCondition p
_ Token
t = case Token
t of
    TC_Empty Id
id ConditionType
_ -> Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style Id
id Code
2212 String
"Use 'false' instead of empty [/[[ conditionals."
    Token
_ -> () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()

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

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

        Maybe (m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
            Token
cmd <- Token -> Maybe Token
getCommand Token
stage
            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 a. Eq a => a -> [a] -> 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 -> Maybe ()
forall a b. (a -> b) -> a -> b
$ Bool -> Bool
not Bool
hasConsumers Bool -> Bool -> Bool
&& PipeType
input PipeType -> PipeType -> Bool
forall a. Eq a => a -> a -> Bool
/= PipeType
NoPipe
            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
commandSpecificException String
name Token
cmd

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

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

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

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

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

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

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

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

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

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

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

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

    -- Could any words in a SimpleCommand consume stdin (e.g. echo "$(cat)")?
    hasAdditionalConsumers :: Token -> Bool
hasAdditionalConsumers = (Token -> Bool) -> Token -> Bool
treeContains Token -> Bool
mayConsume
    -- Could any words in a SimpleCommand produce stdout? E.g. >(tee foo)
    hasAdditionalProducers :: Token -> Bool
hasAdditionalProducers = (Token -> Bool) -> Token -> Bool
treeContains Token -> Bool
mayProduce
    treeContains :: (Token -> Bool) -> Token -> Bool
treeContains Token -> Bool
pred Token
t = Maybe Token -> Bool
forall a. Maybe a -> Bool
isNothing (Maybe Token -> Bool) -> Maybe Token -> Bool
forall a b. (a -> b) -> a -> b
$
        (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
pred) Token
t

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

    hasAssignment :: Token -> Bool
hasAssignment Token
t = Maybe Token -> Bool
forall a. Maybe a -> Bool
isNothing (Maybe Token -> Bool) -> Maybe Token -> Bool
forall a b. (a -> b) -> a -> b
$ (Token -> Maybe ()) -> Token -> Maybe Token
forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> Token -> m Token
doAnalysis Token -> Maybe ()
guardNotAssignment Token
t
    guardNotAssignment :: Token -> Maybe ()
guardNotAssignment Token
t =
        case Token
t of
            TA_Assignment {} -> Maybe ()
forall a. Maybe a
Nothing
            TA_Unary Id
_ String
s Token
_ -> 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
"++" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` String
s Bool -> Bool -> Bool
|| String
"--" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` String
s
            T_DollarBraced Id
_ Bool
_ Token
l ->
                let str :: String
str = [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
l
                    modifier :: String
modifier = String -> String
getBracedModifier String
str
                in
                    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
"=" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
modifier Bool -> Bool -> Bool
|| String
":=" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
modifier
            T_DollarBraceCommandExpansion {} -> Maybe ()
forall a. Maybe a
Nothing
            Token
_ -> () -> Maybe ()
forall a. a -> Maybe a
Just ()

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

prop_checkInvertedStringTest1 :: Bool
prop_checkInvertedStringTest1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkInvertedStringTest String
"[ ! -z $var ]"
prop_checkInvertedStringTest2 :: Bool
prop_checkInvertedStringTest2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkInvertedStringTest String
"! [[ -n $var ]]"
prop_checkInvertedStringTest3 :: Bool
prop_checkInvertedStringTest3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkInvertedStringTest String
"! [ -x $var ]"
prop_checkInvertedStringTest4 :: Bool
prop_checkInvertedStringTest4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkInvertedStringTest String
"[[ ! -w $var ]]"
prop_checkInvertedStringTest5 :: Bool
prop_checkInvertedStringTest5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkInvertedStringTest String
"[ -z $var ]"
checkInvertedStringTest :: p -> Token -> m ()
checkInvertedStringTest p
_ Token
t =
    case Token
t of
        TC_Unary Id
_ ConditionType
_ String
"!" (TC_Unary Id
_ ConditionType
_ String
op Token
_) ->
            case String
op of
                String
"-n" -> Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style (Token -> Id
getId Token
t) Code
2236 String
"Use -z instead of ! -n."
                String
"-z" -> Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style (Token -> Id
getId Token
t) Code
2236 String
"Use -n instead of ! -z."
                String
_ -> () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
        T_Banged Id
_ (T_Pipeline Id
_ [Token]
_
          [T_Redirecting Id
_ [Token]
_ (T_Condition Id
_ ConditionType
_ (TC_Unary Id
_ ConditionType
_ String
op Token
_))]) ->
            case String
op of
                String
"-n" -> Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style (Token -> Id
getId Token
t) Code
2237 String
"Use [ -z .. ] instead of ! [ -n .. ]."
                String
"-z" -> Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
style (Token -> Id
getId Token
t) Code
2237 String
"Use [ -n .. ] instead of ! [ -z .. ]."
                String
_ -> () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()
        Token
_ -> () -> m ()
forall a. a -> m a
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 {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkRedirectionToCommand String
"ls > rm"
prop_checkRedirectionToCommand2 :: Bool
prop_checkRedirectionToCommand2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkRedirectionToCommand String
"ls > 'rm'"
prop_checkRedirectionToCommand3 :: Bool
prop_checkRedirectionToCommand3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *} {p}.
MonadWriter [TokenComment] m =>
p -> Token -> m ()
checkRedirectionToCommand String
"ls > myfile"
checkRedirectionToCommand :: p -> Token -> m ()
checkRedirectionToCommand p
_ Token
t =
    case Token
t of
        T_IoFile Id
_ Token
_ (T_NormalWord Id
id [T_Literal Id
_ String
str]) | String
str String -> [String] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
commonCommands
            Bool -> Bool -> Bool
&& String
str String -> String -> Bool
forall a. Eq a => a -> a -> Bool
/= String
"file" -> -- This would be confusing
                Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2238 String
"Redirecting to/from command name instead of file. Did you want pipes/xargs (or quote to ignore)?"
        Token
_ -> () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()

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

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


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

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

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

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

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

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

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

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

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

    followsOnLine :: Token -> Token -> Bool
followsOnLine Token
a Token
b = 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
        (Code
_, Code
end) <- Id -> Maybe (Code, Code)
lineSpan (Token -> Id
getId Token
a)
        (Code
start, Code
_) <- Id -> Maybe (Code, Code)
lineSpan (Token -> Id
getId Token
b)
        Bool -> Maybe Bool
forall a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> Maybe Bool) -> Bool -> Maybe Bool
forall a b. (a -> b) -> a -> b
$ Code
end Code -> Code -> Bool
forall a. Eq a => a -> a -> Bool
== Code
start

    checkUnit :: [Token] -> Writer [TokenComment] ()
    checkUnit :: [Token] -> WriterT [TokenComment] Identity ()
checkUnit [Token]
unit = StateT (Map String Token) (Writer [TokenComment]) ()
-> 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) (Writer [TokenComment]) Token)
-> [Token] -> StateT (Map String Token) (Writer [TokenComment]) ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ ((Token -> StateT (Map String Token) (Writer [TokenComment]) ())
-> Token -> StateT (Map String Token) (Writer [TokenComment]) Token
forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> Token -> m Token
doAnalysis Token -> StateT (Map String Token) (Writer [TokenComment]) ()
findCommands) [Token]
unit) (Map String Token
forall k a. Map k a
Map.empty)

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

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


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


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

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


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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

    mayBeVariableName :: [Token] -> Bool
mayBeVariableName [Token]
l = 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
        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
isQuotes [Token]
l
        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
willBecomeMultipleArgs [Token]
l
        String
str <- (Token -> Maybe String) -> Token -> Maybe String
forall (m :: * -> *).
Monad m =>
(Token -> m String) -> Token -> m String
getLiteralStringExt (\Token
_ -> String -> Maybe String
forall a. a -> Maybe a
Just String
"x") (Id -> [Token] -> Token
T_NormalWord (Int -> Id
Id Int
0) [Token]
l)
        Bool -> Maybe Bool
forall a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> Maybe Bool) -> Bool -> Maybe Bool
forall a b. (a -> b) -> a -> b
$ String -> Bool
isVariableName String
str

    isLeadingNumberVar :: String -> Bool
isLeadingNumberVar String
s =
        case (Char -> Bool) -> String -> String
forall a. (a -> Bool) -> [a] -> [a]
takeWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
'=') String
s of
            lead :: String
lead@(Char
x:String
_) -> Char -> Bool
isDigit Char
x Bool -> Bool -> Bool
&& (Char -> Bool) -> String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isVariableChar String
lead Bool -> Bool -> Bool
&& Bool -> Bool
not ((Char -> Bool) -> String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isDigit String
lead)
            [] -> Bool
False

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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


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

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


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

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

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


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

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

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


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

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

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

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

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

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

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

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

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

            Token
_ -> Maybe (Token, String)
forall a. Maybe a
Nothing

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

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

    isFunction :: Token -> Bool
isFunction Token
cmd = Maybe Id -> Bool
forall a. Maybe a -> Bool
isJust (Maybe Id -> Bool) -> Maybe Id -> Bool
forall a b. (a -> b) -> a -> b
$ do
        String
literalArg <- Token -> Maybe String
getUnquotedLiteral Token
cmd
        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_

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

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


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

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

    findMaskedNodesInList :: [Token] -> WriterT [TokenComment] Identity ()
findMaskedNodesInList = (Token -> Writer [TokenComment] Token)
-> [Token] -> WriterT [TokenComment] Identity ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ ((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 ()
findMaskedNodes)

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

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

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

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

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

    isMaskDeliberate :: Token -> Bool
isMaskDeliberate Token
t = (Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
isOrIf ([Token] -> Bool) -> [Token] -> Bool
forall a b. (a -> b) -> a -> b
$ NonEmpty Token -> [Token]
forall a. NonEmpty a -> [a]
NE.init (NonEmpty Token -> [Token]) -> NonEmpty Token -> [Token]
forall a b. (a -> b) -> a -> b
$ Parameters -> Token -> NonEmpty Token
parents Parameters
params Token
t
      where
        isOrIf :: Token -> Bool
isOrIf (T_OrIf Id
_ Token
_ (T_Pipeline Id
_ [Token]
_ [T_Redirecting Id
_ [Token]
_ Token
cmd]))
            = Token -> Maybe String
getCommandBasename Token
cmd Maybe String -> [Maybe String] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String -> Maybe String
forall a. a -> Maybe a
Just String
"true", String -> Maybe String
forall a. a -> Maybe a
Just String
":"]
        isOrIf Token
_ = Bool
False

    isCheckedElsewhere :: Token -> Bool
isCheckedElsewhere Token
t = (Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Token -> Bool
isDeclaringCommand ([Token] -> Bool) -> [Token] -> Bool
forall a b. (a -> b) -> a -> b
$ NonEmpty Token -> [Token]
forall a. NonEmpty a -> [a]
NE.tail (NonEmpty Token -> [Token]) -> NonEmpty Token -> [Token]
forall a b. (a -> b) -> a -> b
$ Parameters -> Token -> NonEmpty Token
parents Parameters
params Token
t
      where
        isDeclaringCommand :: Token -> Bool
isDeclaringCommand 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
            Token
cmd <- Token -> Maybe Token
getCommand Token
t
            String
basename <- Token -> Maybe String
getCommandBasename Token
cmd
            Bool -> Maybe Bool
forall a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> Maybe Bool) -> Bool -> Maybe Bool
forall a b. (a -> b) -> a -> b
$
                case String
basename of
                    -- local -r x=$(false) is intentionally ignored for SC2155
                    String
"local" | String
"r" String -> [String] -> Bool
forall a. Eq a => a -> [a] -> 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, String)] -> [String]) -> [(Token, String)] -> [String]
forall a b. (a -> b) -> a -> b
$ Token -> [(Token, String)]
getAllFlags Token
cmd) -> Bool
False
                    String
_ -> String
basename String -> [String] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
declaringCommands

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

    isTransparentCommand :: Token -> Bool
isTransparentCommand Token
t = Token -> Maybe String
getCommandBasename Token
t Maybe String -> Maybe String -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Maybe String
forall a. a -> Maybe a
Just String
"time"


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

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

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


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


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

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

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

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

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


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

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


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

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

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


prop_checkPlusEqualsNumber1 :: Bool
prop_checkPlusEqualsNumber1 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkPlusEqualsNumber String
"x+=1"
prop_checkPlusEqualsNumber2 :: Bool
prop_checkPlusEqualsNumber2 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkPlusEqualsNumber String
"x+=42"
prop_checkPlusEqualsNumber3 :: Bool
prop_checkPlusEqualsNumber3 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkPlusEqualsNumber String
"(( x += 1 ))"
prop_checkPlusEqualsNumber4 :: Bool
prop_checkPlusEqualsNumber4 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkPlusEqualsNumber String
"declare -i x=0; x+=1"
prop_checkPlusEqualsNumber5 :: Bool
prop_checkPlusEqualsNumber5 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkPlusEqualsNumber String
"x+='1'"
prop_checkPlusEqualsNumber6 :: Bool
prop_checkPlusEqualsNumber6 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkPlusEqualsNumber String
"n=foo; x+=n"
prop_checkPlusEqualsNumber7 :: Bool
prop_checkPlusEqualsNumber7 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkPlusEqualsNumber String
"n=4; x+=n"
prop_checkPlusEqualsNumber8 :: Bool
prop_checkPlusEqualsNumber8 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verify Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkPlusEqualsNumber String
"n=4; x+=$n"
prop_checkPlusEqualsNumber9 :: Bool
prop_checkPlusEqualsNumber9 = (Parameters -> Token -> WriterT [TokenComment] Identity ())
-> String -> Bool
verifyNot Parameters -> Token -> WriterT [TokenComment] Identity ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Parameters -> Token -> m ()
checkPlusEqualsNumber String
"declare -ia var; var[x]+=1"
checkPlusEqualsNumber :: Parameters -> Token -> m ()
checkPlusEqualsNumber Parameters
params Token
t =
    case Token
t of
        T_Assignment Id
id AssignmentMode
Append String
var [Token]
_ Token
word -> Maybe (m ()) -> m ()
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, Monad m) =>
t (m a) -> m ()
sequence_ (Maybe (m ()) -> m ()) -> Maybe (m ()) -> m ()
forall a b. (a -> b) -> a -> b
$ do
            CFGAnalysis
cfga <- Parameters -> Maybe CFGAnalysis
cfgAnalysis Parameters
params
            ProgramState
state <- CFGAnalysis -> Id -> Maybe ProgramState
CF.getIncomingState CFGAnalysis
cfga Id
id
            Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ ProgramState -> Token -> Bool
isNumber ProgramState
state Token
word
            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
$ 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
$ ProgramState -> String -> Maybe Bool
CF.variableMayBeDeclaredInteger ProgramState
state String
var
            m () -> Maybe (m ())
forall a. a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return (m () -> Maybe (m ())) -> m () -> Maybe (m ())
forall a b. (a -> b) -> a -> b
$ Id -> Code -> String -> m ()
forall {m :: * -> *}.
MonadWriter [TokenComment] m =>
Id -> Code -> String -> m ()
warn Id
id Code
2324 String
"var+=1 will append, not increment. Use (( var += 1 )), declare -i var, or quote number to silence."
        Token
_ -> () -> m ()
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return ()

  where
    isNumber :: ProgramState -> Token -> Bool
isNumber ProgramState
state Token
word =
        let
            unquotedLiteral :: Maybe String
unquotedLiteral = Token -> Maybe String
getUnquotedLiteral Token
word
            isEmpty :: Bool
isEmpty = Maybe String
unquotedLiteral Maybe String -> Maybe String -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Maybe String
forall a. a -> Maybe a
Just String
""
            isUnquotedNumber :: Bool
isUnquotedNumber = Bool -> Bool
not Bool
isEmpty Bool -> Bool -> Bool
&& Bool -> (String -> Bool) -> Maybe String -> Bool
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
False ((Char -> Bool) -> String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isDigit) Maybe String
unquotedLiteral
            isNumericalVariableName :: Bool
isNumericalVariableName = 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
str <- Maybe String
unquotedLiteral
                ProgramState -> String -> Maybe Bool
CF.variableMayBeAssignedInteger ProgramState
state String
str
            isNumericalVariableExpansion :: Bool
isNumericalVariableExpansion =
                case Token
word of
                    T_NormalWord Id
_ [Token
part] -> 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
str <- Token -> Maybe String
getUnmodifiedParameterExpansion Token
part
                        ProgramState -> String -> Maybe Bool
CF.variableMayBeAssignedInteger ProgramState
state String
str
                    Token
_ -> Bool
False
        in
            Bool
isUnquotedNumber Bool -> Bool -> Bool
|| Bool
isNumericalVariableName Bool -> Bool -> Bool
|| Bool
isNumericalVariableExpansion


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