"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "src/ShellCheck/Checks/Commands.hs" between
shellcheck-0.8.0.tar.gz and shellcheck-0.9.0.tar.gz

About: ShellCheck is a static analysis and linting tool for sh/bash scripts (written in Haskell).

Commands.hs  (shellcheck-0.8.0):Commands.hs  (shellcheck-0.9.0)
{- {-
Copyright 2012-2021 Vidar Holen Copyright 2012-2022 Vidar Holen
This file is part of ShellCheck. This file is part of ShellCheck.
https://www.shellcheck.net https://www.shellcheck.net
ShellCheck is free software: you can redistribute it and/or modify ShellCheck is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
ShellCheck is distributed in the hope that it will be useful, ShellCheck is distributed in the hope that it will be useful,
skipping to change at line 30 skipping to change at line 30
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiWayIf #-} {-# LANGUAGE MultiWayIf #-}
-- This module contains checks that examine specific commands by name. -- This module contains checks that examine specific commands by name.
module ShellCheck.Checks.Commands (checker, optionalChecks, ShellCheck.Checks.Co mmands.runTests) where module ShellCheck.Checks.Commands (checker, optionalChecks, ShellCheck.Checks.Co mmands.runTests) where
import ShellCheck.AST import ShellCheck.AST
import ShellCheck.ASTLib import ShellCheck.ASTLib
import ShellCheck.AnalyzerLib import ShellCheck.AnalyzerLib
import ShellCheck.CFG
import qualified ShellCheck.CFGAnalysis as CF
import ShellCheck.Data import ShellCheck.Data
import ShellCheck.Interface import ShellCheck.Interface
import ShellCheck.Parser import ShellCheck.Parser
import ShellCheck.Prelude
import ShellCheck.Regex import ShellCheck.Regex
import Control.Monad import Control.Monad
import Control.Monad.RWS import Control.Monad.RWS
import Data.Char import Data.Char
import Data.Functor.Identity import Data.Functor.Identity
import qualified Data.Graph.Inductive.Graph as G
import Data.List import Data.List
import Data.Maybe import Data.Maybe
import qualified Data.Map.Strict as Map import qualified Data.Map.Strict as M
import qualified Data.Set as S
import Test.QuickCheck.All (forAllProperties) import Test.QuickCheck.All (forAllProperties)
import Test.QuickCheck.Test (quickCheckWithResult, stdArgs, maxSuccess) import Test.QuickCheck.Test (quickCheckWithResult, stdArgs, maxSuccess)
import Debug.Trace -- STRIP
data CommandName = Exactly String | Basename String data CommandName = Exactly String | Basename String
deriving (Eq, Ord) deriving (Eq, Ord)
data CommandCheck = data CommandCheck =
CommandCheck CommandName (Token -> Analysis) CommandCheck CommandName (Token -> Analysis)
verify :: CommandCheck -> String -> Bool verify :: CommandCheck -> String -> Bool
verify f s = producesComments (getChecker [f]) s == Just True verify f s = producesComments (getChecker [f]) s == Just True
verifyNot f s = producesComments (getChecker [f]) s == Just False verifyNot f s = producesComments (getChecker [f]) s == Just False
skipping to change at line 100 skipping to change at line 107
,checkFindRedirections ,checkFindRedirections
,checkReadExpansions ,checkReadExpansions
,checkSudoRedirect ,checkSudoRedirect
,checkSudoArgs ,checkSudoArgs
,checkSourceArgs ,checkSourceArgs
,checkChmodDashr ,checkChmodDashr
,checkXargsDashi ,checkXargsDashi
,checkUnquotedEchoSpaces ,checkUnquotedEchoSpaces
,checkEvalArray ,checkEvalArray
] ]
++ map checkArgComparison declaringCommands ++ map checkArgComparison ("alias" : declaringCommands)
++ map checkMaskedReturns declaringCommands ++ map checkMaskedReturns declaringCommands
++ map checkMultipleDeclaring declaringCommands
++ map checkBackreferencingDeclaration declaringCommands
optionalChecks = map fst optionalCommandChecks optionalChecks = map fst optionalCommandChecks
optionalCommandChecks :: [(CheckDescription, CommandCheck)] optionalCommandChecks :: [(CheckDescription, CommandCheck)]
optionalCommandChecks = [ optionalCommandChecks = [
(newCheckDescription { (newCheckDescription {
cdName = "deprecate-which", cdName = "deprecate-which",
cdDescription = "Suggest 'command -v' instead of 'which'", cdDescription = "Suggest 'command -v' instead of 'which'",
cdPositive = "which javac", cdPositive = "which javac",
cdNegative = "command -v javac" cdNegative = "command -v javac"
}, checkWhich) }, checkWhich)
] ]
optionalCheckMap = Map.fromList $ map (\(desc, check) -> (cdName desc, check)) o ptionalCommandChecks optionalCheckMap = M.fromList $ map (\(desc, check) -> (cdName desc, check)) opt ionalCommandChecks
prop_verifyOptionalExamples = all check optionalCommandChecks prop_verifyOptionalExamples = all check optionalCommandChecks
where where
check (desc, check) = check (desc, check) =
verify check (cdPositive desc) verify check (cdPositive desc)
&& verifyNot check (cdNegative desc) && verifyNot check (cdNegative desc)
-- Run a check against the getopt parser. If it fails, the lists are empty. -- Run a check against the getopt parser. If it fails, the lists are empty.
checkGetOpts str flags args f = checkGetOpts str flags args f =
flags == actualFlags && args == actualArgs flags == actualFlags && args == actualArgs
skipping to change at line 161 skipping to change at line 170
-- Know when to terminate -- Know when to terminate
prop_checkGetOptsT1 = checkGetOpts "-a x -b" ["a", "b"] ["x"] $ getOpts (True, T rue) "ab" [] prop_checkGetOptsT1 = checkGetOpts "-a x -b" ["a", "b"] ["x"] $ getOpts (True, T rue) "ab" []
prop_checkGetOptsT2 = checkGetOpts "-a x -b" ["a"] ["x","-b"] $ getOpts (False, True) "ab" [] prop_checkGetOptsT2 = checkGetOpts "-a x -b" ["a"] ["x","-b"] $ getOpts (False, True) "ab" []
prop_checkGetOptsT3 = checkGetOpts "-a -- -b" ["a"] ["-b"] $ getOpts (True, True ) "ab" [] prop_checkGetOptsT3 = checkGetOpts "-a -- -b" ["a"] ["-b"] $ getOpts (True, True ) "ab" []
prop_checkGetOptsT4 = checkGetOpts "-a -- -b" ["a", "b"] [] $ getOpts (True, Tru e) "a:b" [] prop_checkGetOptsT4 = checkGetOpts "-a -- -b" ["a", "b"] [] $ getOpts (True, Tru e) "a:b" []
prop_checkGenericOptsT1 = checkGetOpts "-x -- -y" ["x"] ["-y"] $ return . getGen ericOpts prop_checkGenericOptsT1 = checkGetOpts "-x -- -y" ["x"] ["-y"] $ return . getGen ericOpts
prop_checkGenericOptsT2 = checkGetOpts "-xy --" ["x", "y"] [] $ return . getGene ricOpts prop_checkGenericOptsT2 = checkGetOpts "-xy --" ["x", "y"] [] $ return . getGene ricOpts
buildCommandMap :: [CommandCheck] -> Map.Map CommandName (Token -> Analysis) buildCommandMap :: [CommandCheck] -> M.Map CommandName (Token -> Analysis)
buildCommandMap = foldl' addCheck Map.empty buildCommandMap = foldl' addCheck M.empty
where where
addCheck map (CommandCheck name function) = addCheck map (CommandCheck name function) =
Map.insertWith composeAnalyzers name function map M.insertWith composeAnalyzers name function map
checkCommand :: Map.Map CommandName (Token -> Analysis) -> Token -> Analysis checkCommand :: M.Map CommandName (Token -> Analysis) -> Token -> Analysis
checkCommand map t@(T_SimpleCommand id cmdPrefix (cmd:rest)) = sequence_ $ do checkCommand map t@(T_SimpleCommand id cmdPrefix (cmd:rest)) = sequence_ $ do
name <- getLiteralString cmd name <- getLiteralString cmd
return $ return $
if '/' `elem` name if '/' `elem` name
then then
Map.findWithDefault nullCheck (Basename $ basename name) map t M.findWithDefault nullCheck (Basename $ basename name) map t
else if name == "builtin" && not (null rest) then else if name == "builtin" && not (null rest) then
let t' = T_SimpleCommand id cmdPrefix rest let t' = T_SimpleCommand id cmdPrefix rest
selectedBuiltin = fromMaybe "" $ getLiteralString . head $ rest selectedBuiltin = fromMaybe "" $ getLiteralString . head $ rest
in Map.findWithDefault nullCheck (Exactly selectedBuiltin) map t' in M.findWithDefault nullCheck (Exactly selectedBuiltin) map t'
else do else do
Map.findWithDefault nullCheck (Exactly name) map t M.findWithDefault nullCheck (Exactly name) map t
Map.findWithDefault nullCheck (Basename name) map t M.findWithDefault nullCheck (Basename name) map t
where where
basename = reverse . takeWhile (/= '/') . reverse basename = reverse . takeWhile (/= '/') . reverse
checkCommand _ _ = return () checkCommand _ _ = return ()
getChecker :: [CommandCheck] -> Checker getChecker :: [CommandCheck] -> Checker
getChecker list = Checker { getChecker list = Checker {
perScript = const $ return (), perScript = const $ return (),
perToken = checkCommand map perToken = checkCommand map
} }
where where
map = buildCommandMap list map = buildCommandMap list
checker :: AnalysisSpec -> Parameters -> Checker checker :: AnalysisSpec -> Parameters -> Checker
checker spec params = getChecker $ commandChecks ++ optionals checker spec params = getChecker $ commandChecks ++ optionals
where where
keys = asOptionalChecks spec keys = asOptionalChecks spec
optionals = optionals =
if "all" `elem` keys if "all" `elem` keys
then map snd optionalCommandChecks then map snd optionalCommandChecks
else mapMaybe (\x -> Map.lookup x optionalCheckMap) keys else mapMaybe (\x -> M.lookup x optionalCheckMap) keys
prop_checkTr1 = verify checkTr "tr [a-f] [A-F]" prop_checkTr1 = verify checkTr "tr [a-f] [A-F]"
prop_checkTr2 = verify checkTr "tr 'a-z' 'A-Z'" prop_checkTr2 = verify checkTr "tr 'a-z' 'A-Z'"
prop_checkTr2a= verify checkTr "tr '[a-z]' '[A-Z]'" prop_checkTr2a = verify checkTr "tr '[a-z]' '[A-Z]'"
prop_checkTr3 = verifyNot checkTr "tr -d '[:lower:]'" prop_checkTr3 = verifyNot checkTr "tr -d '[:lower:]'"
prop_checkTr3a= verifyNot checkTr "tr -d '[:upper:]'" prop_checkTr3a = verifyNot checkTr "tr -d '[:upper:]'"
prop_checkTr3b= verifyNot checkTr "tr -d '|/_[:upper:]'" prop_checkTr3b = verifyNot checkTr "tr -d '|/_[:upper:]'"
prop_checkTr4 = verifyNot checkTr "ls [a-z]" prop_checkTr4 = verifyNot checkTr "ls [a-z]"
prop_checkTr5 = verify checkTr "tr foo bar" prop_checkTr5 = verify checkTr "tr foo bar"
prop_checkTr6 = verify checkTr "tr 'hello' 'world'" prop_checkTr6 = verify checkTr "tr 'hello' 'world'"
prop_checkTr8 = verifyNot checkTr "tr aeiou _____" prop_checkTr8 = verifyNot checkTr "tr aeiou _____"
prop_checkTr9 = verifyNot checkTr "a-z n-za-m" prop_checkTr9 = verifyNot checkTr "a-z n-za-m"
prop_checkTr10= verifyNot checkTr "tr --squeeze-repeats rl lr" prop_checkTr10 = verifyNot checkTr "tr --squeeze-repeats rl lr"
prop_checkTr11= verifyNot checkTr "tr abc '[d*]'" prop_checkTr11 = verifyNot checkTr "tr abc '[d*]'"
prop_checkTr12= verifyNot checkTr "tr '[=e=]' 'e'" prop_checkTr12 = verifyNot checkTr "tr '[=e=]' 'e'"
checkTr = CommandCheck (Basename "tr") (mapM_ f . arguments) checkTr = CommandCheck (Basename "tr") (mapM_ f . arguments)
where where
f w | isGlob w = -- The user will go [ab] -> '[ab]' -> 'ab'. Fixme? f w | isGlob w = -- The user will go [ab] -> '[ab]' -> 'ab'. Fixme?
warn (getId w) 2060 "Quote parameters to tr to prevent glob expansion." warn (getId w) 2060 "Quote parameters to tr to prevent glob expansion."
f word = f word =
case getLiteralString word of case getLiteralString word of
Just "a-z" -> info (getId word) 2018 "Use '[:lower:]' to support accents and foreign alphabets." Just "a-z" -> info (getId word) 2018 "Use '[:lower:]' to support accents and foreign alphabets."
Just "A-Z" -> info (getId word) 2019 "Use '[:upper:]' to support accents and foreign alphabets." Just "A-Z" -> info (getId word) 2019 "Use '[:upper:]' to support accents and foreign alphabets."
Just s -> do -- Eliminate false positives by only looking for dupes in SET2? Just s -> do -- Eliminate false positives by only looking for dupes in SET2?
when (not ("-" `isPrefixOf` s || "[:" `isInfixOf` s) && duplicated s) $ when (not ("-" `isPrefixOf` s || "[:" `isInfixOf` s) && duplicated s) $
skipping to change at line 326 skipping to change at line 335
prop_checkGrepRe1 = verify checkGrepRe "cat foo | grep *.mp3" prop_checkGrepRe1 = verify checkGrepRe "cat foo | grep *.mp3"
prop_checkGrepRe2 = verify checkGrepRe "grep -Ev cow*test *.mp3" prop_checkGrepRe2 = verify checkGrepRe "grep -Ev cow*test *.mp3"
prop_checkGrepRe3 = verify checkGrepRe "grep --regex=*.mp3 file" prop_checkGrepRe3 = verify checkGrepRe "grep --regex=*.mp3 file"
prop_checkGrepRe4 = verifyNot checkGrepRe "grep foo *.mp3" prop_checkGrepRe4 = verifyNot checkGrepRe "grep foo *.mp3"
prop_checkGrepRe5 = verifyNot checkGrepRe "grep-v --regex=moo *" prop_checkGrepRe5 = verifyNot checkGrepRe "grep-v --regex=moo *"
prop_checkGrepRe6 = verifyNot checkGrepRe "grep foo \\*.mp3" prop_checkGrepRe6 = verifyNot checkGrepRe "grep foo \\*.mp3"
prop_checkGrepRe7 = verify checkGrepRe "grep *foo* file" prop_checkGrepRe7 = verify checkGrepRe "grep *foo* file"
prop_checkGrepRe8 = verify checkGrepRe "ls | grep foo*.jpg" prop_checkGrepRe8 = verify checkGrepRe "ls | grep foo*.jpg"
prop_checkGrepRe9 = verifyNot checkGrepRe "grep '[0-9]*' file" prop_checkGrepRe9 = verifyNot checkGrepRe "grep '[0-9]*' file"
prop_checkGrepRe10= verifyNot checkGrepRe "grep '^aa*' file" prop_checkGrepRe10 = verifyNot checkGrepRe "grep '^aa*' file"
prop_checkGrepRe11= verifyNot checkGrepRe "grep --include=*.png foo" prop_checkGrepRe11 = verifyNot checkGrepRe "grep --include=*.png foo"
prop_checkGrepRe12= verifyNot checkGrepRe "grep -F 'Foo*' file" prop_checkGrepRe12 = verifyNot checkGrepRe "grep -F 'Foo*' file"
prop_checkGrepRe13= verifyNot checkGrepRe "grep -- -foo bar*" prop_checkGrepRe13 = verifyNot checkGrepRe "grep -- -foo bar*"
prop_checkGrepRe14= verifyNot checkGrepRe "grep -e -foo bar*" prop_checkGrepRe14 = verifyNot checkGrepRe "grep -e -foo bar*"
prop_checkGrepRe15= verifyNot checkGrepRe "grep --regex -foo bar*" prop_checkGrepRe15 = verifyNot checkGrepRe "grep --regex -foo bar*"
prop_checkGrepRe16= verifyNot checkGrepRe "grep --include 'Foo*' file" prop_checkGrepRe16 = verifyNot checkGrepRe "grep --include 'Foo*' file"
prop_checkGrepRe17= verifyNot checkGrepRe "grep --exclude 'Foo*' file" prop_checkGrepRe17 = verifyNot checkGrepRe "grep --exclude 'Foo*' file"
prop_checkGrepRe18= verifyNot checkGrepRe "grep --exclude-dir 'Foo*' file" prop_checkGrepRe18 = verifyNot checkGrepRe "grep --exclude-dir 'Foo*' file"
prop_checkGrepRe19= verify checkGrepRe "grep -- 'Foo*' file" prop_checkGrepRe19 = verify checkGrepRe "grep -- 'Foo*' file"
prop_checkGrepRe20= verifyNot checkGrepRe "grep --fixed-strings 'Foo*' file" prop_checkGrepRe20 = verifyNot checkGrepRe "grep --fixed-strings 'Foo*' file"
prop_checkGrepRe21= verifyNot checkGrepRe "grep -o 'x*' file" prop_checkGrepRe21 = verifyNot checkGrepRe "grep -o 'x*' file"
prop_checkGrepRe22= verifyNot checkGrepRe "grep --only-matching 'x*' file" prop_checkGrepRe22 = verifyNot checkGrepRe "grep --only-matching 'x*' file"
prop_checkGrepRe23= verifyNot checkGrepRe "grep '.*' file" prop_checkGrepRe23 = verifyNot checkGrepRe "grep '.*' file"
checkGrepRe = CommandCheck (Basename "grep") check where checkGrepRe = CommandCheck (Basename "grep") check where
check cmd = f cmd (arguments cmd) check cmd = f cmd (arguments cmd)
-- --regex=*(extglob) doesn't work. Fixme? -- --regex=*(extglob) doesn't work. Fixme?
skippable s = not ("--regex=" `isPrefixOf` s) && "-" `isPrefixOf` s skippable s = not ("--regex=" `isPrefixOf` s) && "-" `isPrefixOf` s
f _ [] = return () f _ [] = return ()
f cmd (x:r) = f cmd (x:r) =
let str = getLiteralStringDef "_" x let str = getLiteralStringDef "_" x
in in
if str `elem` ["--", "-e", "--regex"] if str `elem` ["--", "-e", "--regex"]
skipping to change at line 386 skipping to change at line 395
candidates = candidates =
sampleWords ++ map (\(x:r) -> toUpper x : r) sampleWords sampleWords ++ map (\(x:r) -> toUpper x : r) sampleWords
getSuspiciousRegexWildcard str = case matchRegex suspicious str of getSuspiciousRegexWildcard str = case matchRegex suspicious str of
Just [[c]] | not (str `matches` contra) -> Just c Just [[c]] | not (str `matches` contra) -> Just c
_ -> fail "looks good" _ -> fail "looks good"
suspicious = mkRegex "([A-Za-z1-9])\\*" suspicious = mkRegex "([A-Za-z1-9])\\*"
contra = mkRegex "[^a-zA-Z1-9]\\*|[][^$+\\\\]" contra = mkRegex "[^a-zA-Z1-9]\\*|[][^$+\\\\]"
prop_checkTrapQuotes1 = verify checkTrapQuotes "trap \"echo $num\" INT" prop_checkTrapQuotes1 = verify checkTrapQuotes "trap \"echo $num\" INT"
prop_checkTrapQuotes1a= verify checkTrapQuotes "trap \"echo `ls`\" INT" prop_checkTrapQuotes1a = verify checkTrapQuotes "trap \"echo `ls`\" INT"
prop_checkTrapQuotes2 = verifyNot checkTrapQuotes "trap 'echo $num' INT" prop_checkTrapQuotes2 = verifyNot checkTrapQuotes "trap 'echo $num' INT"
prop_checkTrapQuotes3 = verify checkTrapQuotes "trap \"echo $((1+num))\" EXIT DE BUG" prop_checkTrapQuotes3 = verify checkTrapQuotes "trap \"echo $((1+num))\" EXIT DE BUG"
checkTrapQuotes = CommandCheck (Exactly "trap") (f . arguments) where checkTrapQuotes = CommandCheck (Exactly "trap") (f . arguments) where
f (x:_) = checkTrap x f (x:_) = checkTrap x
f _ = return () f _ = return ()
checkTrap (T_NormalWord _ [T_DoubleQuoted _ rs]) = mapM_ checkExpansions rs checkTrap (T_NormalWord _ [T_DoubleQuoted _ rs]) = mapM_ checkExpansions rs
checkTrap _ = return () checkTrap _ = return ()
warning id = warn id 2064 "Use single quotes, otherwise this expands now rat her than when signalled." warning id = warn id 2064 "Use single quotes, otherwise this expands now rat her than when signalled."
checkExpansions (T_DollarExpansion id _) = warning id checkExpansions (T_DollarExpansion id _) = warning id
checkExpansions (T_Backticked id _) = warning id checkExpansions (T_Backticked id _) = warning id
skipping to change at line 464 skipping to change at line 473
guard $ cmdS `matches` commandRegex guard $ cmdS `matches` commandRegex
return $ warn (getId exec) 2150 "-exec does not invoke a shell. Rewrite or use -exec sh -c .. ." return $ warn (getId exec) 2150 "-exec does not invoke a shell. Rewrite or use -exec sh -c .. ."
check _ = Nothing check _ = Nothing
commandRegex = mkRegex "[ |;]" commandRegex = mkRegex "[ |;]"
prop_checkUnusedEchoEscapes1 = verify checkUnusedEchoEscapes "echo 'foo\\nbar\\n '" prop_checkUnusedEchoEscapes1 = verify checkUnusedEchoEscapes "echo 'foo\\nbar\\n '"
prop_checkUnusedEchoEscapes2 = verifyNot checkUnusedEchoEscapes "echo -e 'foi\\n bar'" prop_checkUnusedEchoEscapes2 = verifyNot checkUnusedEchoEscapes "echo -e 'foi\\n bar'"
prop_checkUnusedEchoEscapes3 = verify checkUnusedEchoEscapes "echo \"n:\\t42\"" prop_checkUnusedEchoEscapes3 = verify checkUnusedEchoEscapes "echo \"n:\\t42\""
prop_checkUnusedEchoEscapes4 = verifyNot checkUnusedEchoEscapes "echo lol" prop_checkUnusedEchoEscapes4 = verifyNot checkUnusedEchoEscapes "echo lol"
prop_checkUnusedEchoEscapes5 = verifyNot checkUnusedEchoEscapes "echo -n -e '\n' " prop_checkUnusedEchoEscapes5 = verifyNot checkUnusedEchoEscapes "echo -n -e '\n' "
prop_checkUnusedEchoEscapes6 = verify checkUnusedEchoEscapes "echo '\\506'"
prop_checkUnusedEchoEscapes7 = verify checkUnusedEchoEscapes "echo '\\5a'"
prop_checkUnusedEchoEscapes8 = verifyNot checkUnusedEchoEscapes "echo '\\8a'"
prop_checkUnusedEchoEscapes9 = verifyNot checkUnusedEchoEscapes "echo '\\d5a'"
prop_checkUnusedEchoEscapes10 = verify checkUnusedEchoEscapes "echo '\\x4a'"
prop_checkUnusedEchoEscapes11 = verify checkUnusedEchoEscapes "echo '\\xat'"
prop_checkUnusedEchoEscapes12 = verifyNot checkUnusedEchoEscapes "echo '\\xth'"
checkUnusedEchoEscapes = CommandCheck (Basename "echo") f checkUnusedEchoEscapes = CommandCheck (Basename "echo") f
where where
hasEscapes = mkRegex "\\\\[rnt]" hasEscapes = mkRegex "\\\\([rntabefv\\']|[0-7]{1,3}|x([0-9]|[A-F]|[a-f]){1,2 })"
f cmd = f cmd =
whenShell [Sh, Bash, Ksh] $ whenShell [Sh, Bash, Ksh] $
unless (cmd `hasFlag` "e") $ unless (cmd `hasFlag` "e") $
mapM_ examine $ arguments cmd mapM_ examine $ arguments cmd
examine token = do examine token = do
let str = onlyLiteralString token let str = onlyLiteralString token
when (str `matches` hasEscapes) $ when (str `matches` hasEscapes) $
info (getId token) 2028 "echo may not expand escape sequences. Use p rintf." info (getId token) 2028 "echo may not expand escape sequences. Use p rintf."
skipping to change at line 633 skipping to change at line 649
prop_checkPrintfVar1 = verify checkPrintfVar "printf \"Lol: $s\"" prop_checkPrintfVar1 = verify checkPrintfVar "printf \"Lol: $s\""
prop_checkPrintfVar2 = verifyNot checkPrintfVar "printf 'Lol: $s'" prop_checkPrintfVar2 = verifyNot checkPrintfVar "printf 'Lol: $s'"
prop_checkPrintfVar3 = verify checkPrintfVar "printf -v cow $(cmd)" prop_checkPrintfVar3 = verify checkPrintfVar "printf -v cow $(cmd)"
prop_checkPrintfVar4 = verifyNot checkPrintfVar "printf \"%${count}s\" var" prop_checkPrintfVar4 = verifyNot checkPrintfVar "printf \"%${count}s\" var"
prop_checkPrintfVar5 = verify checkPrintfVar "printf '%s %s %s' foo bar" prop_checkPrintfVar5 = verify checkPrintfVar "printf '%s %s %s' foo bar"
prop_checkPrintfVar6 = verify checkPrintfVar "printf foo bar baz" prop_checkPrintfVar6 = verify checkPrintfVar "printf foo bar baz"
prop_checkPrintfVar7 = verify checkPrintfVar "printf -- foo bar baz" prop_checkPrintfVar7 = verify checkPrintfVar "printf -- foo bar baz"
prop_checkPrintfVar8 = verifyNot checkPrintfVar "printf '%s %s %s' \"${var[@]}\" " prop_checkPrintfVar8 = verifyNot checkPrintfVar "printf '%s %s %s' \"${var[@]}\" "
prop_checkPrintfVar9 = verifyNot checkPrintfVar "printf '%s %s %s\\n' *.png" prop_checkPrintfVar9 = verifyNot checkPrintfVar "printf '%s %s %s\\n' *.png"
prop_checkPrintfVar10= verifyNot checkPrintfVar "printf '%s %s %s' foo bar baz" prop_checkPrintfVar10 = verifyNot checkPrintfVar "printf '%s %s %s' foo bar baz"
prop_checkPrintfVar11= verifyNot checkPrintfVar "printf '%(%s%s)T' -1" prop_checkPrintfVar11 = verifyNot checkPrintfVar "printf '%(%s%s)T' -1"
prop_checkPrintfVar12= verify checkPrintfVar "printf '%s %s\\n' 1 2 3" prop_checkPrintfVar12 = verify checkPrintfVar "printf '%s %s\\n' 1 2 3"
prop_checkPrintfVar13= verifyNot checkPrintfVar "printf '%s %s\\n' 1 2 3 4" prop_checkPrintfVar13 = verifyNot checkPrintfVar "printf '%s %s\\n' 1 2 3 4"
prop_checkPrintfVar14= verify checkPrintfVar "printf '%*s\\n' 1" prop_checkPrintfVar14 = verify checkPrintfVar "printf '%*s\\n' 1"
prop_checkPrintfVar15= verifyNot checkPrintfVar "printf '%*s\\n' 1 2" prop_checkPrintfVar15 = verifyNot checkPrintfVar "printf '%*s\\n' 1 2"
prop_checkPrintfVar16= verifyNot checkPrintfVar "printf $'string'" prop_checkPrintfVar16 = verifyNot checkPrintfVar "printf $'string'"
prop_checkPrintfVar17= verify checkPrintfVar "printf '%-*s\\n' 1" prop_checkPrintfVar17 = verify checkPrintfVar "printf '%-*s\\n' 1"
prop_checkPrintfVar18= verifyNot checkPrintfVar "printf '%-*s\\n' 1 2" prop_checkPrintfVar18 = verifyNot checkPrintfVar "printf '%-*s\\n' 1 2"
prop_checkPrintfVar19= verifyNot checkPrintfVar "printf '%(%s)T'" prop_checkPrintfVar19 = verifyNot checkPrintfVar "printf '%(%s)T'"
prop_checkPrintfVar20= verifyNot checkPrintfVar "printf '%d %(%s)T' 42" prop_checkPrintfVar20 = verifyNot checkPrintfVar "printf '%d %(%s)T' 42"
prop_checkPrintfVar21= verify checkPrintfVar "printf '%d %(%s)T'" prop_checkPrintfVar21 = verify checkPrintfVar "printf '%d %(%s)T'"
prop_checkPrintfVar22= verify checkPrintfVar "printf '%s\n%s' foo" prop_checkPrintfVar22 = verify checkPrintfVar "printf '%s\n%s' foo"
checkPrintfVar = CommandCheck (Exactly "printf") (f . arguments) where checkPrintfVar = CommandCheck (Exactly "printf") (f . arguments) where
f (doubledash:rest) | getLiteralString doubledash == Just "--" = f rest f (doubledash:rest) | getLiteralString doubledash == Just "--" = f rest
f (dashv:var:rest) | getLiteralString dashv == Just "-v" = f rest f (dashv:var:rest) | getLiteralString dashv == Just "-v" = f rest
f (format:params) = check format params f (format:params) = check format params
f _ = return () f _ = return ()
check format more = do check format more = do
sequence_ $ do sequence_ $ do
string <- getLiteralString format string <- getLiteralString format
let formats = getPrintfFormats string let formats = getPrintfFormats string
let formatCount = length formats let formatCount = length formats
let argCount = length more let argCount = length more
let pluraliseIfMany word n = if n > 1 then word ++ "s" else word
return $ if return $ if
| argCount == 0 && formatCount == 0 -> | argCount == 0 && formatCount == 0 ->
return () -- This is fine return () -- This is fine
| formatCount == 0 && argCount > 0 -> | formatCount == 0 && argCount > 0 ->
err (getId format) 2182 err (getId format) 2182
"This printf format string has no variables. Other argum ents are ignored." "This printf format string has no variables. Other argum ents are ignored."
| any mayBecomeMultipleArgs more -> | any mayBecomeMultipleArgs more ->
return () -- We don't know so trust the user return () -- We don't know so trust the user
| argCount < formatCount && onlyTrailingTs formats argCount -> | argCount < formatCount && onlyTrailingTs formats argCount ->
return () -- Allow trailing %()Ts since they use the current time return () -- Allow trailing %()Ts since they use the current time
| argCount > 0 && argCount `mod` formatCount == 0 -> | argCount > 0 && argCount `mod` formatCount == 0 ->
return () -- Great: a suitable number of arguments return () -- Great: a suitable number of arguments
| otherwise -> | otherwise ->
warn (getId format) 2183 $ warn (getId format) 2183 $
"This format string has " ++ show formatCount ++ " varia "This format string has " ++ show formatCount ++ " " ++
bles, but is passed " ++ show argCount ++ " arguments." pluraliseIfMany "variable" formatCount ++
", but is passed " ++ show argCount ++ pluraliseIfMany "
argument" argCount ++ "."
unless ('%' `elem` concat (oversimplify format) || isLiteral format) $ unless ('%' `elem` concat (oversimplify format) || isLiteral format) $
info (getId format) 2059 info (getId format) 2059
"Don't use variables in the printf format string. Use printf '..%s ..' \"$foo\"." "Don't use variables in the printf format string. Use printf '..%s ..' \"$foo\"."
where where
onlyTrailingTs format argCount = onlyTrailingTs format argCount =
all (== 'T') $ drop argCount format all (== 'T') $ drop argCount format
prop_checkGetPrintfFormats1 = getPrintfFormats "%s" == "s" prop_checkGetPrintfFormats1 = getPrintfFormats "%s" == "s"
prop_checkGetPrintfFormats2 = getPrintfFormats "%0*s" == "*s" prop_checkGetPrintfFormats2 = getPrintfFormats "%0*s" == "*s"
skipping to change at line 719 skipping to change at line 737
(if width == "*" then "*" else "") ++ (if width == "*" then "*" else "") ++
(if precision == "*" then "*" else "") ++ (if precision == "*" then "*" else "") ++
typ ++ getFormats rest typ ++ getFormats rest
Nothing -> take 1 rest ++ getFormats rest Nothing -> take 1 rest ++ getFormats rest
where where
-- constructed based on specifications in "man printf" -- constructed based on specifications in "man printf"
re = mkRegex "#?-?\\+? ?0?(\\*|\\d*)\\.?(\\d*|\\*)([diouxXfFeEgGaAcsbq]) ((\n|.)*)" re = mkRegex "#?-?\\+? ?0?(\\*|\\d*)\\.?(\\d*|\\*)([diouxXfFeEgGaAcsbq]) ((\n|.)*)"
-- \____ _____/\___ ____/ \____ ____/\_________ _________/ \______ / -- \____ _____/\___ ____/ \____ ____/\_________ _________/ \______ /
-- V V V V V -- V V V V V
-- flags field width precision format character rest -- flags field width precision format character rest
-- field width and precision can be specified with a '*' instead of a di git, -- field width and precision can be specified with an '*' instead of a d igit,
-- in which case printf will accept one more argument for each '*' used -- in which case printf will accept one more argument for each '*' used
prop_checkUuoeCmd1 = verify checkUuoeCmd "echo $(date)" prop_checkUuoeCmd1 = verify checkUuoeCmd "echo $(date)"
prop_checkUuoeCmd2 = verify checkUuoeCmd "echo `date`" prop_checkUuoeCmd2 = verify checkUuoeCmd "echo `date`"
prop_checkUuoeCmd3 = verify checkUuoeCmd "echo \"$(date)\"" prop_checkUuoeCmd3 = verify checkUuoeCmd "echo \"$(date)\""
prop_checkUuoeCmd4 = verify checkUuoeCmd "echo \"`date`\"" prop_checkUuoeCmd4 = verify checkUuoeCmd "echo \"`date`\""
prop_checkUuoeCmd5 = verifyNot checkUuoeCmd "echo \"The time is $(date)\"" prop_checkUuoeCmd5 = verifyNot checkUuoeCmd "echo \"The time is $(date)\""
prop_checkUuoeCmd6 = verifyNot checkUuoeCmd "echo \"$(<file)\"" prop_checkUuoeCmd6 = verifyNot checkUuoeCmd "echo \"$(<file)\""
checkUuoeCmd = CommandCheck (Exactly "echo") (f . arguments) where checkUuoeCmd = CommandCheck (Exactly "echo") (f . arguments) where
msg id = style id 2005 "Useless echo? Instead of 'echo $(cmd)', just use 'cm d'." msg id = style id 2005 "Useless echo? Instead of 'echo $(cmd)', just use 'cm d'."
skipping to change at line 918 skipping to change at line 936
_ -> return False _ -> return False
prop_checkLocalScope1 = verify checkLocalScope "local foo=3" prop_checkLocalScope1 = verify checkLocalScope "local foo=3"
prop_checkLocalScope2 = verifyNot checkLocalScope "f() { local foo=3; }" prop_checkLocalScope2 = verifyNot checkLocalScope "f() { local foo=3; }"
checkLocalScope = CommandCheck (Exactly "local") $ \t -> checkLocalScope = CommandCheck (Exactly "local") $ \t ->
whenShell [Bash, Dash] $ do -- Ksh allows it, Sh doesn't support local whenShell [Bash, Dash] $ do -- Ksh allows it, Sh doesn't support local
path <- getPathM t path <- getPathM t
unless (any isFunctionLike path) $ unless (any isFunctionLike path) $
err (getId $ getCommandTokenOrThis t) 2168 "'local' is only valid in functions." err (getId $ getCommandTokenOrThis t) 2168 "'local' is only valid in functions."
prop_checkMultipleDeclaring1 = verify (checkMultipleDeclaring "local") "q() { lo
cal readonly var=1; }"
prop_checkMultipleDeclaring2 = verifyNot (checkMultipleDeclaring "local") "q() {
local var=1; }"
prop_checkMultipleDeclaring3 = verify (checkMultipleDeclaring "readonly") "reado
nly local foo=5"
prop_checkMultipleDeclaring4 = verify (checkMultipleDeclaring "export") "export
readonly foo=5"
prop_checkMultipleDeclaring5 = verifyNot (checkMultipleDeclaring "local") "f() {
local -r foo=5; }"
prop_checkMultipleDeclaring6 = verifyNot (checkMultipleDeclaring "declare") "dec
lare -rx foo=5"
prop_checkMultipleDeclaring7 = verifyNot (checkMultipleDeclaring "readonly") "re
adonly 'local' foo=5"
checkMultipleDeclaring cmd = CommandCheck (Exactly cmd) (mapM_ check . arguments
)
where
check t = sequence_ $ do
lit <- getUnquotedLiteral t
guard $ lit `elem` declaringCommands
return $ err (getId $ getCommandTokenOrThis t) 2316 $
"This applies " ++ cmd ++ " to the variable named " ++ lit ++
", which is probably not what you want. Use a separate command
or the appropriate `declare` options instead."
prop_checkDeprecatedTempfile1 = verify checkDeprecatedTempfile "var=$(tempfile)" prop_checkDeprecatedTempfile1 = verify checkDeprecatedTempfile "var=$(tempfile)"
prop_checkDeprecatedTempfile2 = verifyNot checkDeprecatedTempfile "tempfile=$(mk temp)" prop_checkDeprecatedTempfile2 = verifyNot checkDeprecatedTempfile "tempfile=$(mk temp)"
checkDeprecatedTempfile = CommandCheck (Basename "tempfile") $ checkDeprecatedTempfile = CommandCheck (Basename "tempfile") $
\t -> warn (getId $ getCommandTokenOrThis t) 2186 "tempfile is deprecated. U se mktemp instead." \t -> warn (getId $ getCommandTokenOrThis t) 2186 "tempfile is deprecated. U se mktemp instead."
prop_checkDeprecatedEgrep = verify checkDeprecatedEgrep "egrep '.+'" prop_checkDeprecatedEgrep = verify checkDeprecatedEgrep "egrep '.+'"
checkDeprecatedEgrep = CommandCheck (Basename "egrep") $ checkDeprecatedEgrep = CommandCheck (Basename "egrep") $
\t -> info (getId $ getCommandTokenOrThis t) 2196 "egrep is non-standard and deprecated. Use grep -E instead." \t -> info (getId $ getCommandTokenOrThis t) 2196 "egrep is non-standard and deprecated. Use grep -E instead."
prop_checkDeprecatedFgrep = verify checkDeprecatedFgrep "fgrep '*' files" prop_checkDeprecatedFgrep = verify checkDeprecatedFgrep "fgrep '*' files"
skipping to change at line 964 skipping to change at line 998
guard $ caseVar == getoptsVar guard $ caseVar == getoptsVar
-- Make sure the variable isn't modified -- Make sure the variable isn't modified
guard . not $ modifiesVariable params (T_BraceGroup (Id 0) body) get optsVar guard . not $ modifiesVariable params (T_BraceGroup (Id 0) body) get optsVar
return $ check (getId arg1) (map (:[]) $ filter (/= ':') options) ca seCmd return $ check (getId arg1) (map (:[]) $ filter (/= ':') options) ca seCmd
f _ = return () f _ = return ()
check :: Id -> [String] -> Token -> Analysis check :: Id -> [String] -> Token -> Analysis
check optId opts (T_CaseExpression id _ list) = do check optId opts (T_CaseExpression id _ list) = do
unless (Nothing `Map.member` handledMap) $ do unless (Nothing `M.member` handledMap) $ do
mapM_ (warnUnhandled optId id) $ catMaybes $ Map.keys notHandled mapM_ (warnUnhandled optId id) $ catMaybes $ M.keys notHandled
unless (any (`Map.member` handledMap) [Just "*",Just "?"]) $ unless (any (`M.member` handledMap) [Just "*",Just "?"]) $
warn id 2220 "Invalid flags are not handled. Add a *) case." warn id 2220 "Invalid flags are not handled. Add a *) case."
mapM_ warnRedundant $ Map.toList notRequested mapM_ warnRedundant $ M.toList notRequested
where where
handledMap = Map.fromList (concatMap getHandledStrings list) handledMap = M.fromList (concatMap getHandledStrings list)
requestedMap = Map.fromList $ map (\x -> (Just x, ())) opts requestedMap = M.fromList $ map (\x -> (Just x, ())) opts
notHandled = Map.difference requestedMap handledMap notHandled = M.difference requestedMap handledMap
notRequested = Map.difference handledMap requestedMap notRequested = M.difference handledMap requestedMap
warnUnhandled optId caseId str = warnUnhandled optId caseId str =
warn caseId 2213 $ "getopts specified -" ++ (e4m str) ++ ", but it's not handled by this 'case'." warn caseId 2213 $ "getopts specified -" ++ (e4m str) ++ ", but it's not handled by this 'case'."
warnRedundant (Just str, expr) warnRedundant (Just str, expr)
| str `notElem` ["*", ":", "?"] = | str `notElem` ["*", ":", "?"] =
warn (getId expr) 2214 "This case is not specified by getopts." warn (getId expr) 2214 "This case is not specified by getopts."
warnRedundant _ = return () warnRedundant _ = return ()
getHandledStrings (_, globs, _) = getHandledStrings (_, globs, _) =
skipping to change at line 1021 skipping to change at line 1055
T_Redirecting _ _ x@(T_CaseExpression {}) -> return x T_Redirecting _ _ x@(T_CaseExpression {}) -> return x
_ -> Nothing _ -> Nothing
prop_checkCatastrophicRm1 = verify checkCatastrophicRm "rm -r $1/$2" prop_checkCatastrophicRm1 = verify checkCatastrophicRm "rm -r $1/$2"
prop_checkCatastrophicRm2 = verify checkCatastrophicRm "rm -r /home/$foo" prop_checkCatastrophicRm2 = verify checkCatastrophicRm "rm -r /home/$foo"
prop_checkCatastrophicRm3 = verifyNot checkCatastrophicRm "rm -r /home/${USER:?} /*" prop_checkCatastrophicRm3 = verifyNot checkCatastrophicRm "rm -r /home/${USER:?} /*"
prop_checkCatastrophicRm4 = verify checkCatastrophicRm "rm -fr /home/$(whoami)/* " prop_checkCatastrophicRm4 = verify checkCatastrophicRm "rm -fr /home/$(whoami)/* "
prop_checkCatastrophicRm5 = verifyNot checkCatastrophicRm "rm -r /home/${USER:-t hing}/*" prop_checkCatastrophicRm5 = verifyNot checkCatastrophicRm "rm -r /home/${USER:-t hing}/*"
prop_checkCatastrophicRm6 = verify checkCatastrophicRm "rm --recursive /etc/*$co nfig*" prop_checkCatastrophicRm6 = verify checkCatastrophicRm "rm --recursive /etc/*$co nfig*"
prop_checkCatastrophicRm8 = verify checkCatastrophicRm "rm -rf /home" prop_checkCatastrophicRm8 = verify checkCatastrophicRm "rm -rf /home"
prop_checkCatastrophicRm10= verifyNot checkCatastrophicRm "rm -r \"${DIR}\"/{.gi prop_checkCatastrophicRm10 = verifyNot checkCatastrophicRm "rm -r \"${DIR}\"/{.g
tignore,.gitattributes,ci}" itignore,.gitattributes,ci}"
prop_checkCatastrophicRm11= verify checkCatastrophicRm "rm -r /{bin,sbin}/$exec" prop_checkCatastrophicRm11 = verify checkCatastrophicRm "rm -r /{bin,sbin}/$exec
prop_checkCatastrophicRm12= verify checkCatastrophicRm "rm -r /{{usr,},{bin,sbin "
}}/$exec" prop_checkCatastrophicRm12 = verify checkCatastrophicRm "rm -r /{{usr,},{bin,sbi
prop_checkCatastrophicRm13= verifyNot checkCatastrophicRm "rm -r /{{a,b},{c,d}}/ n}}/$exec"
$exec" prop_checkCatastrophicRm13 = verifyNot checkCatastrophicRm "rm -r /{{a,b},{c,d}}
/$exec"
prop_checkCatastrophicRmA = verify checkCatastrophicRm "rm -rf /usr /lib/nvidia- current/xorg/xorg" prop_checkCatastrophicRmA = verify checkCatastrophicRm "rm -rf /usr /lib/nvidia- current/xorg/xorg"
prop_checkCatastrophicRmB = verify checkCatastrophicRm "rm -rf \"$STEAMROOT/\"*" prop_checkCatastrophicRmB = verify checkCatastrophicRm "rm -rf \"$STEAMROOT/\"*"
checkCatastrophicRm = CommandCheck (Basename "rm") $ \t -> checkCatastrophicRm = CommandCheck (Basename "rm") $ \t ->
when (isRecursive t) $ when (isRecursive t) $
mapM_ (mapM_ checkWord . braceExpand) $ arguments t mapM_ (mapM_ checkWord . braceExpand) $ arguments t
where where
isRecursive = any ((`elem` ["r", "R", "recursive"]) . snd) . getAllFlags isRecursive = any ((`elem` ["r", "R", "recursive"]) . snd) . getAllFlags
checkWord token = checkWord token =
case getLiteralString token of case getLiteralString token of
skipping to change at line 1226 skipping to change at line 1260
(option, value) <- lookup "i" opts (option, value) <- lookup "i" opts
return $ info (getId option) 2267 "GNU xargs -i is deprecated in favor o f -I{}" return $ info (getId option) 2267 "GNU xargs -i is deprecated in favor o f -I{}"
parseOpts = getBsdOpts "0oprtxadR:S:J:L:l:n:P:s:e:E:i:I:" parseOpts = getBsdOpts "0oprtxadR:S:J:L:l:n:P:s:e:E:i:I:"
prop_checkArgComparison1 = verify (checkArgComparison "declare") "declare a = b" prop_checkArgComparison1 = verify (checkArgComparison "declare") "declare a = b"
prop_checkArgComparison2 = verify (checkArgComparison "declare") "declare a =b" prop_checkArgComparison2 = verify (checkArgComparison "declare") "declare a =b"
prop_checkArgComparison3 = verifyNot (checkArgComparison "declare") "declare a=b " prop_checkArgComparison3 = verifyNot (checkArgComparison "declare") "declare a=b "
prop_checkArgComparison4 = verify (checkArgComparison "export") "export a +=b" prop_checkArgComparison4 = verify (checkArgComparison "export") "export a +=b"
prop_checkArgComparison7 = verifyNot (checkArgComparison "declare") "declare -a +i foo" prop_checkArgComparison7 = verifyNot (checkArgComparison "declare") "declare -a +i foo"
prop_checkArgComparison8 = verify (checkArgComparison "let") "let x = 0" prop_checkArgComparison8 = verify (checkArgComparison "let") "let x = 0"
prop_checkArgComparison9 = verify (checkArgComparison "alias") "alias x =0"
-- This mirrors checkSecondArgIsComparison but for arguments to local/readonly/d eclare/export -- This mirrors checkSecondArgIsComparison but for arguments to local/readonly/d eclare/export
checkArgComparison cmd = CommandCheck (Exactly cmd) wordsWithEqual checkArgComparison cmd = CommandCheck (Exactly cmd) wordsWithEqual
where where
wordsWithEqual t = mapM_ check $ arguments t wordsWithEqual t = mapM_ check $ arguments t
check arg = do check arg = do
sequence_ $ do sequence_ $ do
str <- getLeadingUnquotedString arg str <- getLeadingUnquotedString arg
case str of case str of
'=':_ -> '=':_ ->
return $ err (headId arg) 2290 $ return $ err (headId arg) 2290 $
skipping to change at line 1324 skipping to change at line 1359
prop_checkUnquotedEchoSpaces4 = verifyNot checkUnquotedEchoSpaces "echo 'foo bar'" prop_checkUnquotedEchoSpaces4 = verifyNot checkUnquotedEchoSpaces "echo 'foo bar'"
prop_checkUnquotedEchoSpaces5 = verifyNot checkUnquotedEchoSpaces "echo a > myfi le.txt b" prop_checkUnquotedEchoSpaces5 = verifyNot checkUnquotedEchoSpaces "echo a > myfi le.txt b"
prop_checkUnquotedEchoSpaces6 = verifyNot checkUnquotedEchoSpaces " echo foo\\\n bar" prop_checkUnquotedEchoSpaces6 = verifyNot checkUnquotedEchoSpaces " echo foo\\\n bar"
checkUnquotedEchoSpaces = CommandCheck (Basename "echo") check checkUnquotedEchoSpaces = CommandCheck (Basename "echo") check
where where
check t = do check t = do
let args = arguments t let args = arguments t
m <- asks tokenPositions m <- asks tokenPositions
redir <- getClosestCommandM t redir <- getClosestCommandM t
sequence_ $ do sequence_ $ do
let positions = mapMaybe (\c -> Map.lookup (getId c) m) args let positions = mapMaybe (\c -> M.lookup (getId c) m) args
let pairs = zip positions (drop 1 positions) let pairs = zip positions (drop 1 positions)
(T_Redirecting _ redirTokens _) <- redir (T_Redirecting _ redirTokens _) <- redir
let redirPositions = mapMaybe (\c -> fst <$> Map.lookup (getId c) m) redirTokens let redirPositions = mapMaybe (\c -> fst <$> M.lookup (getId c) m) r edirTokens
guard $ any (hasSpacesBetween redirPositions) pairs guard $ any (hasSpacesBetween redirPositions) pairs
return $ info (getId t) 2291 "Quote repeated spaces to avoid them co llapsing into one." return $ info (getId t) 2291 "Quote repeated spaces to avoid them co llapsing into one."
hasSpacesBetween redirs ((a,b), (c,d)) = hasSpacesBetween redirs ((a,b), (c,d)) =
posLine a == posLine d posLine a == posLine d
&& ((posColumn c) - (posColumn b)) >= 4 && ((posColumn c) - (posColumn b)) >= 4
&& not (any (\x -> b < x && x < c) redirs) && not (any (\x -> b < x && x < c) redirs)
prop_checkEvalArray1 = verify checkEvalArray "eval $@" prop_checkEvalArray1 = verify checkEvalArray "eval $@"
prop_checkEvalArray2 = verify checkEvalArray "eval \"${args[@]}\"" prop_checkEvalArray2 = verify checkEvalArray "eval \"${args[@]}\""
skipping to change at line 1355 skipping to change at line 1390
if isEscaped t if isEscaped t
then style (getId t) 2293 "When eval'ing @Q-quoted words, use * rath er than @ as the index." then style (getId t) 2293 "When eval'ing @Q-quoted words, use * rath er than @ as the index."
else warn (getId t) 2294 "eval negates the benefit of arrays. Drop e val to preserve whitespace/symbols (or eval as string)." else warn (getId t) 2294 "eval negates the benefit of arrays. Drop e val to preserve whitespace/symbols (or eval as string)."
isEscaped q = isEscaped q =
case q of case q of
-- Match ${arr[@]@Q} and ${@@Q} and such -- Match ${arr[@]@Q} and ${@@Q} and such
T_DollarBraced _ _ l -> 'Q' `elem` getBracedModifier (concat $ overs implify l) T_DollarBraced _ _ l -> 'Q' `elem` getBracedModifier (concat $ overs implify l)
_ -> False _ -> False
prop_checkBackreferencingDeclaration1 = verify (checkBackreferencingDeclaration
"declare") "declare x=1 y=foo$x"
prop_checkBackreferencingDeclaration2 = verify (checkBackreferencingDeclaration
"readonly") "readonly x=1 y=$((1+x))"
prop_checkBackreferencingDeclaration3 = verify (checkBackreferencingDeclaration
"local") "local x=1 y=$(echo $x)"
prop_checkBackreferencingDeclaration4 = verify (checkBackreferencingDeclaration
"local") "local x=1 y[$x]=z"
prop_checkBackreferencingDeclaration5 = verify (checkBackreferencingDeclaration
"declare") "declare x=var $x=1"
prop_checkBackreferencingDeclaration6 = verify (checkBackreferencingDeclaration
"declare") "declare x=var $x=1"
prop_checkBackreferencingDeclaration7 = verify (checkBackreferencingDeclaration
"declare") "declare x=var $k=$x"
checkBackreferencingDeclaration cmd = CommandCheck (Exactly cmd) check
where
check t = foldM_ perArg M.empty $ arguments t
perArg leftArgs t =
case t of
T_Assignment id _ name idx t -> do
warnIfBackreferencing leftArgs $ t:idx
return $ M.insert name id leftArgs
t -> do
warnIfBackreferencing leftArgs [t]
return leftArgs
warnIfBackreferencing backrefs l = do
references <- findReferences l
let reused = M.intersection backrefs references
mapM msg $ M.toList reused
msg (name, id) = warn id 2318 $ "This assignment is used again in this '" ++
cmd ++ "', but won't have taken effect. Use two '" ++ cmd ++ "'s."
findReferences list = do
cfga <- asks cfgAnalysis
let graph = CF.graph cfga
let nodesMap = CF.tokenToNodes cfga
let nodes = S.unions $ map (\id -> M.findWithDefault S.empty id nodesMap
) $ map getId $ list
let labels = mapMaybe (G.lab graph) $ S.toList nodes
let references = M.fromList $ concatMap refFromLabel labels
return references
refFromLabel lab =
case lab of
CFApplyEffects effects -> mapMaybe refFromEffect effects
_ -> []
refFromEffect e =
case e of
IdTagged id (CFReadVariable name) -> return (name, id)
_ -> Nothing
return [] return []
runTests = $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSucces s = 1 }) ) |]) runTests = $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSucces s = 1 }) ) |])
 End of changes. 38 change blocks. 
68 lines changed or deleted 168 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)