Moved the various AST convenience functions to a separate module.

This commit is contained in:
Vidar Holen 2015-08-16 12:53:23 -07:00
parent 07747b30fb
commit 0dd61b65d8
4 changed files with 302 additions and 241 deletions

View File

@ -47,6 +47,7 @@ library
QuickCheck >= 2.7.4 QuickCheck >= 2.7.4
exposed-modules: exposed-modules:
ShellCheck.AST ShellCheck.AST
ShellCheck.ASTLib
ShellCheck.Analytics ShellCheck.Analytics
ShellCheck.Analyzer ShellCheck.Analyzer
ShellCheck.Checker ShellCheck.Checker

View File

@ -357,10 +357,3 @@ doAnalysis f = analyze f blank id
doStackAnalysis startToken endToken = analyze startToken endToken id doStackAnalysis startToken endToken = analyze startToken endToken id
doTransform i = runIdentity . analyze blank blank i doTransform i = runIdentity . analyze blank blank i
isLoop t = case t of
T_WhileExpression {} -> True
T_UntilExpression {} -> True
T_ForIn {} -> True
T_ForArithmetic {} -> True
T_SelectIn {} -> True
_ -> False

240
ShellCheck/ASTLib.hs Normal file
View File

@ -0,0 +1,240 @@
{-
Copyright 2012-2015 Vidar Holen
This file is part of ShellCheck.
http://www.vidarholen.net/contents/shellcheck
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 <http://www.gnu.org/licenses/>.
-}
module ShellCheck.ASTLib where
import ShellCheck.AST
import Control.Monad
import Data.List
import Data.Maybe
-- Is this a type of loop?
isLoop t = case t of
T_WhileExpression {} -> True
T_UntilExpression {} -> True
T_ForIn {} -> True
T_ForArithmetic {} -> True
T_SelectIn {} -> True
_ -> False
-- Will this split into multiple words when used as an argument?
willSplit x =
case x of
T_DollarBraced {} -> True
T_DollarExpansion {} -> True
T_Backticked {} -> True
T_BraceExpansion {} -> True
T_Glob {} -> True
T_Extglob {} -> True
T_NormalWord _ l -> any willSplit l
_ -> False
isGlob (T_Extglob {}) = True
isGlob (T_Glob {}) = True
isGlob (T_NormalWord _ l) = any isGlob l
isGlob _ = False
-- Is this shell word a constant?
isConstant token =
case token of
T_NormalWord _ l -> all isConstant l
T_DoubleQuoted _ l -> all isConstant l
T_SingleQuoted _ _ -> True
T_Literal _ _ -> True
_ -> False
-- Is this an empty literal?
isEmpty token =
case token of
T_NormalWord _ l -> all isEmpty l
T_DoubleQuoted _ l -> all isEmpty l
T_SingleQuoted _ "" -> True
T_Literal _ "" -> True
_ -> False
-- Quick&lazy oversimplification of commands, throwing away details
-- and returning a list like ["find", ".", "-name", "${VAR}*" ].
oversimplify token =
case token of
(T_NormalWord _ l) -> [concat (concatMap oversimplify l)]
(T_DoubleQuoted _ l) -> [concat (concatMap oversimplify l)]
(T_SingleQuoted _ s) -> [s]
(T_DollarBraced _ _) -> ["${VAR}"]
(T_DollarArithmetic _ _) -> ["${VAR}"]
(T_DollarExpansion _ _) -> ["${VAR}"]
(T_Backticked _ _) -> ["${VAR}"]
(T_Glob _ s) -> [s]
(T_Pipeline _ _ [x]) -> oversimplify x
(T_Literal _ x) -> [x]
(T_SimpleCommand _ vars words) -> concatMap oversimplify words
(T_Redirecting _ _ foo) -> oversimplify foo
(T_DollarSingleQuoted _ s) -> [s]
(T_Annotation _ _ s) -> oversimplify s
-- Workaround for let "foo = bar" parsing
(TA_Sequence _ [TA_Expansion _ v]) -> concatMap oversimplify v
otherwise -> []
-- Turn a SimpleCommand foo -avz --bar=baz into args "a", "v", "z", "bar",
-- each in a tuple of (token, stringFlag).
getFlagsUntil stopCondition (T_SimpleCommand _ _ (_:args)) =
let textArgs = takeWhile (not . stopCondition . snd) $ map (\x -> (x, concat $ oversimplify x)) args in
concatMap flag textArgs
where
flag (x, '-':'-':arg) = [ (x, takeWhile (/= '=') arg) ]
flag (x, '-':args) = map (\v -> (x, [v])) args
flag _ = []
getFlagsUntil _ _ = error "Internal shellcheck error, please report! (getFlags on non-command)"
-- Get all flags in a GNU way, up until --
getAllFlags = getFlagsUntil (== "--")
-- Get all flags in a BSD way, up until first non-flag argument
getLeadingFlags = getFlagsUntil (not . ("-" `isPrefixOf`))
-- Given a T_DollarBraced, return a simplified version of the string contents.
bracedString (T_DollarBraced _ l) = concat $ oversimplify l
bracedString _ = error "Internal shellcheck error, please report! (bracedString on non-variable)"
-- Is this an expansion of multiple items of an array?
isArrayExpansion t@(T_DollarBraced _ _) =
let string = bracedString t in
"@" `isPrefixOf` string ||
not ("#" `isPrefixOf` string) && "[@]" `isInfixOf` string
isArrayExpansion _ = False
-- Is it possible that this arg becomes multiple args?
mayBecomeMultipleArgs t = willBecomeMultipleArgs t || f t
where
f t@(T_DollarBraced _ _) =
let string = bracedString t in
"!" `isPrefixOf` string
f (T_DoubleQuoted _ parts) = any f parts
f (T_NormalWord _ parts) = any f parts
f _ = False
-- Is it certain that this word will becomes multiple words?
willBecomeMultipleArgs t = willConcatInAssignment t || f t
where
f (T_Extglob {}) = True
f (T_Glob {}) = True
f (T_BraceExpansion {}) = True
f (T_DoubleQuoted _ parts) = any f parts
f (T_NormalWord _ parts) = any f parts
f _ = False
-- This does token cause implicit concatenation in assignments?
willConcatInAssignment token =
case token of
t@(T_DollarBraced {}) -> isArrayExpansion t
(T_DoubleQuoted _ parts) -> any willConcatInAssignment parts
(T_NormalWord _ parts) -> any willConcatInAssignment parts
_ -> False
-- Maybe get the literal string corresponding to this token
getLiteralString :: Token -> Maybe String
getLiteralString = getLiteralStringExt (const Nothing)
-- Definitely get a literal string, skipping over all non-literals
onlyLiteralString :: Token -> String
onlyLiteralString = fromJust . getLiteralStringExt (const $ return "")
-- Maybe get a literal string, but only if it's an unquoted argument.
getUnquotedLiteral (T_NormalWord _ list) =
liftM concat $ mapM str list
where
str (T_Literal _ s) = return s
str _ = Nothing
getUnquotedLiteral _ = Nothing
-- Maybe get the literal string of this token and any globs in it.
getGlobOrLiteralString = getLiteralStringExt f
where
f (T_Glob _ str) = return str
f _ = Nothing
-- Maybe get the literal value of a token, using a custom function
-- to map unrecognized Tokens into strings.
getLiteralStringExt :: (Token -> Maybe String) -> Token -> Maybe String
getLiteralStringExt more = g
where
allInList = liftM concat . mapM g
g (T_DoubleQuoted _ l) = allInList l
g (T_DollarDoubleQuoted _ l) = allInList l
g (T_NormalWord _ l) = allInList l
g (TA_Expansion _ l) = allInList l
g (T_SingleQuoted _ s) = return s
g (T_Literal _ s) = return s
g x = more x
-- Is this token a string literal?
isLiteral t = isJust $ getLiteralString t
-- Turn a NormalWord like foo="bar $baz" into a series of constituent elements like [foo=,bar ,$baz]
getWordParts (T_NormalWord _ l) = concatMap getWordParts l
getWordParts (T_DoubleQuoted _ l) = l
getWordParts other = [other]
-- Return a list of NormalWords that would result from brace expansion
braceExpand (T_NormalWord id list) = take 1000 $ do
items <- mapM part list
return $ T_NormalWord id items
where
part (T_BraceExpansion id items) = do
item <- items
braceExpand item
part x = return x
-- Maybe get the command name of a token representing a command
getCommandName t =
case t of
T_Redirecting _ _ w -> getCommandName w
T_SimpleCommand _ _ (w:_) -> getLiteralString w
T_Annotation _ _ t -> getCommandName t
otherwise -> Nothing
-- Get the basename of a token representing a command
getCommandBasename = liftM basename . getCommandName
where
basename = reverse . takeWhile (/= '/') . reverse
isAssignment t =
case t of
T_Redirecting _ _ w -> isAssignment w
T_SimpleCommand _ (w:_) [] -> True
T_Assignment {} -> True
T_Annotation _ _ w -> isAssignment w
otherwise -> False
-- Get the list of commands from tokens that contain them, such as
-- the body of while loops and if statements.
getCommandSequences t =
case t of
T_Script _ _ cmds -> [cmds]
T_BraceGroup _ cmds -> [cmds]
T_Subshell _ cmds -> [cmds]
T_WhileExpression _ _ cmds -> [cmds]
T_UntilExpression _ _ cmds -> [cmds]
T_ForIn _ _ _ cmds -> [cmds]
T_ForArithmetic _ _ _ _ cmds -> [cmds]
T_IfExpression _ thens elses -> map snd thens ++ [elses]
otherwise -> []

View File

@ -21,6 +21,7 @@
module ShellCheck.Analytics (runAnalytics, ShellCheck.Analytics.runTests) where module ShellCheck.Analytics (runAnalytics, ShellCheck.Analytics.runTests) where
import ShellCheck.AST import ShellCheck.AST
import ShellCheck.ASTLib
import ShellCheck.Data import ShellCheck.Data
import ShellCheck.Parser import ShellCheck.Parser
import ShellCheck.Interface import ShellCheck.Interface
@ -104,11 +105,10 @@ runList spec list = notes
} }
notes = concatMap (\f -> f params root) list notes = concatMap (\f -> f params root) list
getCode (TokenComment _ (Comment _ c _)) = c
checkList l t = concatMap (\f -> f t) l checkList l t = concatMap (\f -> f t) l
getCode (TokenComment _ (Comment _ c _)) = c
prop_determineShell0 = determineShell (T_Script (Id 0) "#!/bin/sh" []) == Sh prop_determineShell0 = determineShell (T_Script (Id 0) "#!/bin/sh" []) == Sh
prop_determineShell1 = determineShell (T_Script (Id 0) "#!/usr/bin/env ksh" []) == Ksh prop_determineShell1 = determineShell (T_Script (Id 0) "#!/usr/bin/env ksh" []) == Ksh
prop_determineShell2 = determineShell (T_Script (Id 0) "" []) == Bash prop_determineShell2 = determineShell (T_Script (Id 0) "" []) == Bash
@ -251,22 +251,6 @@ isVariableName _ = False
potentially = fromMaybe (return ()) potentially = fromMaybe (return ())
willSplit x =
case x of
T_DollarBraced {} -> True
T_DollarExpansion {} -> True
T_Backticked {} -> True
T_BraceExpansion {} -> True
T_Glob {} -> True
T_Extglob {} -> True
T_NormalWord _ l -> any willSplit l
_ -> False
isGlob (T_Extglob {}) = True
isGlob (T_Glob {}) = True
isGlob (T_NormalWord _ l) = any isGlob l
isGlob _ = False
wouldHaveBeenGlob s = '*' `elem` s wouldHaveBeenGlob s = '*' `elem` s
isConfusedGlobRegex ('*':_) = True isConfusedGlobRegex ('*':_) = True
@ -288,59 +272,6 @@ getSuspiciousRegexWildcard str =
headOrDefault _ (a:_) = a headOrDefault _ (a:_) = a
headOrDefault def _ = def headOrDefault def _ = def
isConstant token =
case token of
T_NormalWord _ l -> all isConstant l
T_DoubleQuoted _ l -> all isConstant l
T_SingleQuoted _ _ -> True
T_Literal _ _ -> True
_ -> False
isEmpty token =
case token of
T_NormalWord _ l -> all isEmpty l
T_DoubleQuoted _ l -> all isEmpty l
T_SingleQuoted _ "" -> True
T_Literal _ "" -> True
_ -> False
makeSimple (T_NormalWord _ [f]) = f
makeSimple (T_Redirecting _ _ f) = f
makeSimple (T_Annotation _ _ f) = f
makeSimple t = t
simplify = doTransform makeSimple
deadSimple (T_NormalWord _ l) = [concat (concatMap deadSimple l)]
deadSimple (T_DoubleQuoted _ l) = [concat (concatMap deadSimple l)]
deadSimple (T_SingleQuoted _ s) = [s]
deadSimple (T_DollarBraced _ _) = ["${VAR}"]
deadSimple (T_DollarArithmetic _ _) = ["${VAR}"]
deadSimple (T_DollarExpansion _ _) = ["${VAR}"]
deadSimple (T_Backticked _ _) = ["${VAR}"]
deadSimple (T_Glob _ s) = [s]
deadSimple (T_Pipeline _ _ [x]) = deadSimple x
deadSimple (T_Literal _ x) = [x]
deadSimple (T_SimpleCommand _ vars words) = concatMap deadSimple words
deadSimple (T_Redirecting _ _ foo) = deadSimple foo
deadSimple (T_DollarSingleQuoted _ s) = [s]
deadSimple (T_Annotation _ _ s) = deadSimple s
-- Workaround for let "foo = bar" parsing
deadSimple (TA_Sequence _ [TA_Expansion _ v]) = concatMap deadSimple v
deadSimple _ = []
-- Turn a SimpleCommand foo -avz --bar=baz into args ["a", "v", "z", "bar"]
getFlagsUntil stopCondition (T_SimpleCommand _ _ (_:args)) =
let textArgs = takeWhile (not . stopCondition . snd) $ map (\x -> (x, concat $ deadSimple x)) args in
concatMap flag textArgs
where
flag (x, '-':'-':arg) = [ (x, takeWhile (/= '=') arg) ]
flag (x, '-':args) = map (\v -> (x, [v])) args
flag _ = []
getFlagsUntil _ _ = error "Internal shellcheck error, please report! (getFlags on non-command)"
getAllFlags = getFlagsUntil (== "--")
getLeadingFlags = getFlagsUntil (not . ("-" `isPrefixOf`))
(!!!) list i = (!!!) list i =
case drop i list of case drop i list of
@ -425,8 +356,8 @@ checkEchoWc _ (T_Pipeline id _ [a, b]) =
["wc", "-m"] -> countMsg ["wc", "-m"] -> countMsg
_ -> return () _ -> return ()
where where
acmd = deadSimple a acmd = oversimplify a
bcmd = deadSimple b bcmd = oversimplify b
countMsg = style id 2000 "See if you can use ${#variable} instead." countMsg = style id 2000 "See if you can use ${#variable} instead."
checkEchoWc _ _ = return () checkEchoWc _ _ = return ()
@ -447,8 +378,8 @@ checkEchoSed _ (T_Pipeline id _ [a, b]) =
guard $ length delimiters == 2 guard $ length delimiters == 2
return True return True
acmd = deadSimple a acmd = oversimplify a
bcmd = deadSimple b bcmd = oversimplify b
checkIn s = checkIn s =
when (isSimpleSed s) $ when (isSimpleSed s) $
style id 2001 "See if you can use ${variable//search/replace} instead." style id 2001 "See if you can use ${variable//search/replace} instead."
@ -467,7 +398,7 @@ prop_checkAssignAteCommand3 = verify checkAssignAteCommand "A=cat foo | grep bar
prop_checkAssignAteCommand4 = verifyNot checkAssignAteCommand "A=foo ls -l" prop_checkAssignAteCommand4 = verifyNot checkAssignAteCommand "A=foo ls -l"
prop_checkAssignAteCommand5 = verifyNot checkAssignAteCommand "PAGER=cat grep bar" prop_checkAssignAteCommand5 = verifyNot checkAssignAteCommand "PAGER=cat grep bar"
checkAssignAteCommand _ (T_SimpleCommand id (T_Assignment _ _ _ _ assignmentTerm:[]) (firstWord:_)) = checkAssignAteCommand _ (T_SimpleCommand id (T_Assignment _ _ _ _ assignmentTerm:[]) (firstWord:_)) =
when ("-" `isPrefixOf` concat (deadSimple firstWord) || when ("-" `isPrefixOf` concat (oversimplify firstWord) ||
isCommonCommand (getLiteralString assignmentTerm) isCommonCommand (getLiteralString assignmentTerm)
&& not (isCommonCommand (getLiteralString firstWord))) $ && not (isCommonCommand (getLiteralString firstWord))) $
warn id 2037 "To assign the output of a command, use var=$(cmd) ." warn id 2037 "To assign the output of a command, use var=$(cmd) ."
@ -551,7 +482,7 @@ prop_checkPipePitfalls7 = verifyNot checkPipePitfalls "find . -printf '%s\\n' |
checkPipePitfalls _ (T_Pipeline id _ commands) = do checkPipePitfalls _ (T_Pipeline id _ commands) = do
for ["find", "xargs"] $ for ["find", "xargs"] $
\(find:xargs:_) -> \(find:xargs:_) ->
let args = deadSimple xargs ++ deadSimple find let args = oversimplify xargs ++ oversimplify find
in in
unless (any ($ args) [ unless (any ($ args) [
hasShortParameter '0', hasShortParameter '0',
@ -578,12 +509,12 @@ checkPipePitfalls _ (T_Pipeline id _ commands) = do
] ]
unless didLs $ do unless didLs $ do
for ["ls", "?"] $ for ["ls", "?"] $
\(ls:_) -> unless (hasShortParameter 'N' (deadSimple ls)) $ \(ls:_) -> unless (hasShortParameter 'N' (oversimplify ls)) $
info (getId ls) 2012 "Use find instead of ls to better handle non-alphanumeric filenames." info (getId ls) 2012 "Use find instead of ls to better handle non-alphanumeric filenames."
return () return ()
where where
for l f = for l f =
let indices = indexOfSublists l (map (headOrDefault "" . deadSimple) commands) let indices = indexOfSublists l (map (headOrDefault "" . oversimplify) commands)
in do in do
mapM_ (f . (\ n -> take (length l) $ drop n commands)) indices mapM_ (f . (\ n -> take (length l) $ drop n commands)) indices
return . not . null $ indices return . not . null $ indices
@ -609,39 +540,6 @@ indexOfSublists sub = f 0
match _ _ = False match _ _ = False
bracedString l = concat $ deadSimple l
isArrayExpansion (T_DollarBraced _ l) =
let string = bracedString l in
"@" `isPrefixOf` string ||
not ("#" `isPrefixOf` string) && "[@]" `isInfixOf` string
isArrayExpansion _ = False
-- Is it certain that this arg will becomes multiple args?
willBecomeMultipleArgs t = willConcatInAssignment t || f t
where
f (T_Extglob {}) = True
f (T_Glob {}) = True
f (T_BraceExpansion {}) = True
f (T_DoubleQuoted _ parts) = any f parts
f (T_NormalWord _ parts) = any f parts
f _ = False
willConcatInAssignment t@(T_DollarBraced {}) = isArrayExpansion t
willConcatInAssignment (T_DoubleQuoted _ parts) = any willConcatInAssignment parts
willConcatInAssignment (T_NormalWord _ parts) = any willConcatInAssignment parts
willConcatInAssignment _ = False
-- Is it possible that this arg becomes multiple args?
mayBecomeMultipleArgs t = willBecomeMultipleArgs t || f t
where
f (T_DollarBraced _ l) =
let string = bracedString l in
"!" `isPrefixOf` string
f (T_DoubleQuoted _ parts) = any f parts
f (T_NormalWord _ parts) = any f parts
f _ = False
prop_checkShebangParameters1 = verifyTree checkShebangParameters "#!/usr/bin/env bash -x\necho cow" prop_checkShebangParameters1 = verifyTree checkShebangParameters "#!/usr/bin/env bash -x\necho cow"
prop_checkShebangParameters2 = verifyNotTree checkShebangParameters "#! /bin/sh -l " prop_checkShebangParameters2 = verifyNotTree checkShebangParameters "#! /bin/sh -l "
checkShebangParameters _ (T_Script id sb _) = checkShebangParameters _ (T_Script id sb _) =
@ -723,8 +621,8 @@ checkBashisms _ = bashism
mapM_ check expansion mapM_ check expansion
when (var `elem` bashVars) $ warnMsg id $ var ++ " is" when (var `elem` bashVars) $ warnMsg id $ var ++ " is"
where where
str = concat $ deadSimple token str = bracedString t
var = getBracedReference (bracedString token) var = getBracedReference str
check (regex, feature) = check (regex, feature) =
when (isJust $ matchRegex regex str) $ warnMsg id feature when (isJust $ matchRegex regex str) $ warnMsg id feature
@ -741,9 +639,9 @@ checkBashisms _ = bashism
| t `isCommand` "echo" && "-" `isPrefixOf` argString = | t `isCommand` "echo" && "-" `isPrefixOf` argString =
unless ("--" `isPrefixOf` argString) $ -- echo "-------" unless ("--" `isPrefixOf` argString) $ -- echo "-------"
warnMsg (getId arg) "echo flags are" warnMsg (getId arg) "echo flags are"
where argString = concat $ deadSimple arg where argString = concat $ oversimplify arg
bashism t@(T_SimpleCommand _ _ (cmd:arg:_)) bashism t@(T_SimpleCommand _ _ (cmd:arg:_))
| t `isCommand` "exec" && "-" `isPrefixOf` concat (deadSimple arg) = | t `isCommand` "exec" && "-" `isPrefixOf` concat (oversimplify arg) =
warnMsg (getId arg) "exec flags are" warnMsg (getId arg) "exec flags are"
bashism t@(T_SimpleCommand id _ _) bashism t@(T_SimpleCommand id _ _)
| t `isCommand` "let" = warnMsg id "'let' is" | t `isCommand` "let" = warnMsg id "'let' is"
@ -844,7 +742,7 @@ checkForInLs _ = try
check id f x check id f x
try _ = return () try _ = return ()
check id f x = check id f x =
case deadSimple x of case oversimplify x of
("ls":n) -> ("ls":n) ->
let warntype = if any ("-" `isPrefixOf`) n then warn else err in let warntype = if any ("-" `isPrefixOf`) n then warn else err in
warntype id 2045 "Iterating over ls output is fragile. Use globs." warntype id 2045 "Iterating over ls output is fragile. Use globs."
@ -941,7 +839,7 @@ checkRedirectToSame params s@(T_Pipeline _ _ list) =
T_DGREAT _ -> [file] T_DGREAT _ -> [file]
_ -> [] _ -> []
getRedirs _ = [] getRedirs _ = []
special x = "/dev/" `isPrefixOf` concat (deadSimple x) special x = "/dev/" `isPrefixOf` concat (oversimplify x)
isOutput t = isOutput t =
case drop 1 $ getPath (parentMap params) t of case drop 1 $ getPath (parentMap params) t of
T_IoFile _ op _:_ -> T_IoFile _ op _:_ ->
@ -971,8 +869,8 @@ checkShorthandIf _ _ = return ()
prop_checkDollarStar = verify checkDollarStar "for f in $*; do ..; done" prop_checkDollarStar = verify checkDollarStar "for f in $*; do ..; done"
prop_checkDollarStar2 = verifyNot checkDollarStar "a=$*" prop_checkDollarStar2 = verifyNot checkDollarStar "a=$*"
checkDollarStar p t@(T_NormalWord _ [T_DollarBraced id l]) checkDollarStar p t@(T_NormalWord _ [b@(T_DollarBraced id _)])
| bracedString l == "*" = | bracedString b == "*" =
unless isAssigned $ unless isAssigned $
warn id 2048 "Use \"$@\" (with quotes) to prevent whitespace problems." warn id 2048 "Use \"$@\" (with quotes) to prevent whitespace problems."
where where
@ -998,7 +896,7 @@ checkUnquotedDollarAt p word@(T_NormalWord _ parts) | not $ isStrictlyQuoteFree
"Double quote array expansions to avoid re-splitting elements." "Double quote array expansions to avoid re-splitting elements."
where where
-- Fixme: should detect whether the alterantive is quoted -- Fixme: should detect whether the alterantive is quoted
isAlternative (T_DollarBraced _ t) = ":+" `isInfixOf` bracedString t isAlternative b@(T_DollarBraced _ t) = ":+" `isInfixOf` bracedString b
isAlternative _ = False isAlternative _ = False
checkUnquotedDollarAt _ _ = return () checkUnquotedDollarAt _ _ = return ()
@ -1227,11 +1125,11 @@ checkNumberComparisons params (TC_Binary id typ op lhs rhs) = do
op ++ " is for integer comparisons. Use " ++ seqv op ++ " instead." op ++ " is for integer comparisons. Use " ++ seqv op ++ " instead."
isNum t = isNum t =
case deadSimple t of case oversimplify t of
[v] -> all isDigit v [v] -> all isDigit v
_ -> False _ -> False
isFraction t = isFraction t =
case deadSimple t of case oversimplify t of
[v] -> isJust $ matchRegex floatRegex v [v] -> isJust $ matchRegex floatRegex v
_ -> False _ -> False
@ -1318,7 +1216,7 @@ prop_checkGlobbedRegex2a = verify checkGlobbedRegex "[[ $foo =~ \\#* ]]"
prop_checkGlobbedRegex3 = verifyNot checkGlobbedRegex "[[ $foo =~ $foo ]]" prop_checkGlobbedRegex3 = verifyNot checkGlobbedRegex "[[ $foo =~ $foo ]]"
prop_checkGlobbedRegex4 = verifyNot checkGlobbedRegex "[[ $foo =~ ^c.* ]]" prop_checkGlobbedRegex4 = verifyNot checkGlobbedRegex "[[ $foo =~ ^c.* ]]"
checkGlobbedRegex _ (TC_Binary _ DoubleBracket "=~" _ rhs) = checkGlobbedRegex _ (TC_Binary _ DoubleBracket "=~" _ rhs) =
let s = concat $ deadSimple rhs in let s = concat $ oversimplify rhs in
when (isConfusedGlobRegex s) $ when (isConfusedGlobRegex s) $
warn (getId rhs) 2049 "=~ is for regex. Use == for globs." warn (getId rhs) 2049 "=~ is for regex. Use == for globs."
checkGlobbedRegex _ _ = return () checkGlobbedRegex _ _ = return ()
@ -1436,8 +1334,8 @@ prop_checkArithmeticDeref10= verifyNot checkArithmeticDeref "(( a[\\$foo] ))"
prop_checkArithmeticDeref11= verifyNot checkArithmeticDeref "a[$foo]=wee" prop_checkArithmeticDeref11= verifyNot checkArithmeticDeref "a[$foo]=wee"
prop_checkArithmeticDeref12= verify checkArithmeticDeref "for ((i=0; $i < 3; i)); do true; done" prop_checkArithmeticDeref12= verify checkArithmeticDeref "for ((i=0; $i < 3; i)); do true; done"
prop_checkArithmeticDeref13= verifyNot checkArithmeticDeref "(( $$ ))" prop_checkArithmeticDeref13= verifyNot checkArithmeticDeref "(( $$ ))"
checkArithmeticDeref params t@(TA_Expansion _ [T_DollarBraced id l]) = checkArithmeticDeref params t@(TA_Expansion _ [b@(T_DollarBraced id _)]) =
unless (isException $ bracedString l) getWarning unless (isException $ bracedString b) getWarning
where where
isException [] = True isException [] = True
isException s = any (`elem` "/.:#%?*@$") s || isDigit (head s) isException s = any (`elem` "/.:#%?*@$") s || isDigit (head s)
@ -1634,52 +1532,6 @@ checkUnqualifiedCommand str f t@(T_SimpleCommand id _ (cmd:rest)) =
when (t `isUnqualifiedCommand` str) $ f cmd rest when (t `isUnqualifiedCommand` str) $ f cmd rest
checkUnqualifiedCommand _ _ _ = return () checkUnqualifiedCommand _ _ _ = return ()
getLiteralString = getLiteralStringExt (const Nothing)
getGlobOrLiteralString = getLiteralStringExt f
where
f (T_Glob _ str) = return str
f _ = Nothing
getLiteralStringExt more = g
where
allInList = liftM concat . mapM g
g (T_DoubleQuoted _ l) = allInList l
g (T_DollarDoubleQuoted _ l) = allInList l
g (T_NormalWord _ l) = allInList l
g (TA_Expansion _ l) = allInList l
g (T_SingleQuoted _ s) = return s
g (T_Literal _ s) = return s
g x = more x
isLiteral t = isJust $ getLiteralString t
-- Get a literal string ignoring all non-literals
onlyLiteralString :: Token -> String
onlyLiteralString = fromJust . getLiteralStringExt (const $ return "")
-- Turn a NormalWord like foo="bar $baz" into a series of constituent elements like [foo=,bar ,$baz]
getWordParts (T_NormalWord _ l) = concatMap getWordParts l
getWordParts (T_DoubleQuoted _ l) = l
getWordParts other = [other]
getUnquotedLiteral (T_NormalWord _ list) =
liftM concat $ mapM str list
where
str (T_Literal _ s) = return s
str _ = Nothing
getUnquotedLiteral _ = Nothing
-- Return a list of NormalWords resulting from brace expansion
braceExpand (T_NormalWord id list) = take 1000 $ do
items <- mapM part list
return $ T_NormalWord id items
where
part (T_BraceExpansion id items) = do
item <- items
braceExpand item
part x = return x
isCommand token str = isCommandMatch token (\cmd -> cmd == str || ('/' : str) `isSuffixOf` cmd) isCommand token str = isCommandMatch token (\cmd -> cmd == str || ('/' : str) `isSuffixOf` cmd)
isUnqualifiedCommand token str = isCommandMatch token (== str) isUnqualifiedCommand token str = isCommandMatch token (== str)
@ -1687,22 +1539,6 @@ isCommandMatch token matcher = fromMaybe False $ do
cmd <- getCommandName token cmd <- getCommandName token
return $ matcher cmd return $ matcher cmd
getCommandName (T_Redirecting _ _ w) =
getCommandName w
getCommandName (T_SimpleCommand _ _ (w:_)) =
getLiteralString w
getCommandName (T_Annotation _ _ t) = getCommandName t
getCommandName _ = Nothing
getCommandBasename = liftM basename . getCommandName
basename = reverse . takeWhile (/= '/') . reverse
isAssignment (T_Annotation _ _ w) = isAssignment w
isAssignment (T_Redirecting _ _ w) = isAssignment w
isAssignment (T_SimpleCommand _ (w:_) []) = True
isAssignment (T_Assignment {}) = True
isAssignment _ = False
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)"
@ -1712,10 +1548,19 @@ checkPrintfVar _ = checkUnqualifiedCommand "printf" (const f) where
f (format:params) = check format f (format:params) = check format
f _ = return () f _ = return ()
check format = check format =
unless ('%' `elem` concat (deadSimple format) || isLiteral format) $ unless ('%' `elem` concat (oversimplify format) || isLiteral format) $
warn (getId format) 2059 warn (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\"."
-- Check whether a word is entirely output from a single command
tokenIsJustCommandOutput t = case t of
T_NormalWord id [T_DollarExpansion _ _] -> True
T_NormalWord id [T_DoubleQuoted _ [T_DollarExpansion _ _]] -> True
T_NormalWord id [T_Backticked _ _] -> True
T_NormalWord id [T_DoubleQuoted _ [T_Backticked _ _]] -> True
_ -> False
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)\""
@ -1726,14 +1571,6 @@ checkUuoeCmd _ = checkUnqualifiedCommand "echo" (const f) where
f [token] = when (tokenIsJustCommandOutput token) $ msg (getId token) f [token] = when (tokenIsJustCommandOutput token) $ msg (getId token)
f _ = return () f _ = return ()
-- Check whether a word is entirely output from a single command
tokenIsJustCommandOutput t = case t of
T_NormalWord id [T_DollarExpansion _ _] -> True
T_NormalWord id [T_DoubleQuoted _ [T_DollarExpansion _ _]] -> True
T_NormalWord id [T_Backticked _ _] -> True
T_NormalWord id [T_DoubleQuoted _ [T_Backticked _ _]] -> True
_ -> False
prop_checkUuoeVar1 = verify checkUuoeVar "for f in $(echo $tmp); do echo lol; done" prop_checkUuoeVar1 = verify checkUuoeVar "for f in $(echo $tmp); do echo lol; done"
prop_checkUuoeVar2 = verify checkUuoeVar "date +`echo \"$format\"`" prop_checkUuoeVar2 = verify checkUuoeVar "date +`echo \"$format\"`"
prop_checkUuoeVar3 = verifyNot checkUuoeVar "foo \"$(echo -e '\r')\"" prop_checkUuoeVar3 = verifyNot checkUuoeVar "foo \"$(echo -e '\r')\""
@ -1837,7 +1674,7 @@ checkGrepRe _ = checkCommand "grep" (const f) where
f (re:_) = do f (re:_) = do
when (isGlob re) $ when (isGlob re) $
warn (getId re) 2062 "Quote the grep pattern so the shell won't interpret it." warn (getId re) 2062 "Quote the grep pattern so the shell won't interpret it."
let string = concat $ deadSimple re let string = concat $ oversimplify re
if isConfusedGlobRegex string then if isConfusedGlobRegex string then
warn (getId re) 2063 "Grep uses regex, but this looks like a glob." warn (getId re) 2063 "Grep uses regex, but this looks like a glob."
else potentially $ do else potentially $ do
@ -1871,7 +1708,7 @@ prop_checkTimeParameters1 = verify checkTimeParameters "time -f lol sleep 10"
prop_checkTimeParameters2 = verifyNot checkTimeParameters "time sleep 10" prop_checkTimeParameters2 = verifyNot checkTimeParameters "time sleep 10"
prop_checkTimeParameters3 = verifyNot checkTimeParameters "time -p foo" prop_checkTimeParameters3 = verifyNot checkTimeParameters "time -p foo"
checkTimeParameters _ = checkUnqualifiedCommand "time" f where checkTimeParameters _ = checkUnqualifiedCommand "time" f where
f cmd (x:_) = let s = concat $ deadSimple x in f cmd (x:_) = let s = concat $ oversimplify x in
when ("-" `isPrefixOf` s && s /= "-p") $ when ("-" `isPrefixOf` s && s /= "-p") $
info (getId cmd) 2023 "The shell may override 'time' as seen in man time(1). Use 'command time ..' for that one." info (getId cmd) 2023 "The shell may override 'time' as seen in man time(1). Use 'command time ..' for that one."
f _ _ = return () f _ _ = return ()
@ -1907,7 +1744,7 @@ checkSudoRedirect _ (T_Redirecting _ redirs cmd) | cmd `isCommand` "sudo" =
"sudo doesn't affect redirects. Use .. | sudo tee -a file" "sudo doesn't affect redirects. Use .. | sudo tee -a file"
_ -> return () _ -> return ()
warnAbout _ = return () warnAbout _ = return ()
special file = concat (deadSimple file) == "/dev/null" special file = concat (oversimplify file) == "/dev/null"
checkSudoRedirect _ _ = return () checkSudoRedirect _ _ = return ()
prop_checkReturn1 = verifyNot checkReturn "return" prop_checkReturn1 = verifyNot checkReturn "return"
@ -1952,7 +1789,7 @@ prop_checkPS18 = verifyNot checkPS1Assignments "PS1='\\[\\e\\]'"
checkPS1Assignments _ (T_Assignment _ _ "PS1" _ word) = warnFor word checkPS1Assignments _ (T_Assignment _ _ "PS1" _ word) = warnFor word
where where
warnFor word = warnFor word =
let contents = concat $ deadSimple word in let contents = concat $ oversimplify word in
when (containsUnescaped contents) $ when (containsUnescaped contents) $
info (getId word) 2025 "Make sure all escape sequences are enclosed in \\[..\\] to prevent line wrapping issues" info (getId word) 2025 "Make sure all escape sequences are enclosed in \\[..\\] to prevent line wrapping issues"
containsUnescaped s = containsUnescaped s =
@ -2120,7 +1957,7 @@ checkUnusedEchoEscapes _ = checkCommand "echo" (const f)
where where
isDashE = mkRegex "^-.*e" isDashE = mkRegex "^-.*e"
hasEscapes = mkRegex "\\\\[rnt]" hasEscapes = mkRegex "\\\\[rnt]"
f args | concat (concatMap deadSimple allButLast) `matches` isDashE = f args | concat (concatMap oversimplify allButLast) `matches` isDashE =
return () return ()
where allButLast = reverse . drop 1 . reverse $ args where allButLast = reverse . drop 1 . reverse $ args
f args = mapM_ checkEscapes args f args = mapM_ checkEscapes args
@ -2164,7 +2001,7 @@ prop_checkSshCmdStr3 = verifyNot checkSshCommandString "ssh \"$host\""
checkSshCommandString _ = checkCommand "ssh" (const f) checkSshCommandString _ = checkCommand "ssh" (const f)
where where
nonOptions = nonOptions =
filter (\x -> not $ "-" `isPrefixOf` concat (deadSimple x)) filter (\x -> not $ "-" `isPrefixOf` concat (oversimplify x))
f args = f args =
case nonOptions args of case nonOptions args of
(hostport:r@(_:_)) -> checkArg $ last r (hostport:r@(_:_)) -> checkArg $ last r
@ -2370,7 +2207,7 @@ getModifiedVariableCommand base@(T_SimpleCommand _ _ (T_NormalWord _ (T_Literal
if var == "" if var == ""
then [] then []
else [(base, token, var, DataString $ SourceFrom [stripEqualsFrom token])] else [(base, token, var, DataString $ SourceFrom [stripEqualsFrom token])]
where var = takeWhile isVariableChar $ dropWhile (`elem` "+-") $ concat $ deadSimple token where var = takeWhile isVariableChar $ dropWhile (`elem` "+-") $ concat $ oversimplify token
getSetParams (t:_:rest) | getLiteralString t == Just "-o" = getSetParams rest getSetParams (t:_:rest) | getLiteralString t == Just "-o" = getSetParams rest
getSetParams (t:rest) = getSetParams (t:rest) =
@ -2431,7 +2268,7 @@ getIndexReferences s = fromMaybe [] $ do
getReferencedVariables t = getReferencedVariables t =
case t of case t of
T_DollarBraced id l -> let str = bracedString l in T_DollarBraced id l -> let str = bracedString t in
(t, t, getBracedReference str) : (t, t, getBracedReference str) :
map (\x -> (l, l, x)) (getIndexReferences str) map (\x -> (l, l, x)) (getIndexReferences str)
TA_Expansion id _ -> getIfReference t t TA_Expansion id _ -> getIfReference t t
@ -2614,7 +2451,7 @@ checkSpacefulness params t =
parents = parentMap params parents = parentMap params
isCounting (T_DollarBraced id token) = isCounting (T_DollarBraced id token) =
case concat $ deadSimple token of case concat $ oversimplify token of
'#':_ -> True '#':_ -> True
_ -> False _ -> False
isCounting _ = False isCounting _ = False
@ -2622,8 +2459,8 @@ checkSpacefulness params t =
-- FIXME: doesn't handle ${a:+$var} vs ${a:+"$var"} -- FIXME: doesn't handle ${a:+$var} vs ${a:+"$var"}
isQuotedAlternative t = isQuotedAlternative t =
case t of case t of
T_DollarBraced _ l -> T_DollarBraced _ _ ->
":+" `isInfixOf` bracedString l ":+" `isInfixOf` bracedString t
_ -> False _ -> False
isSpacefulWord :: (String -> Bool) -> [Token] -> Bool isSpacefulWord :: (String -> Bool) -> [Token] -> Bool
@ -2637,7 +2474,7 @@ checkSpacefulness params t =
T_Extglob {} -> True T_Extglob {} -> True
T_Literal _ s -> s `containsAny` globspace T_Literal _ s -> s `containsAny` globspace
T_SingleQuoted _ s -> s `containsAny` globspace T_SingleQuoted _ s -> s `containsAny` globspace
T_DollarBraced _ l -> spacefulF $ getBracedReference $ bracedString l T_DollarBraced _ _ -> spacefulF $ getBracedReference $ bracedString x
T_NormalWord _ w -> isSpacefulWord spacefulF w T_NormalWord _ w -> isSpacefulWord spacefulF w
T_DoubleQuoted _ w -> isSpacefulWord spacefulF w T_DoubleQuoted _ w -> isSpacefulWord spacefulF w
_ -> False _ -> False
@ -2676,13 +2513,13 @@ checkQuotesInLiterals params t =
forToken map (T_DollarBraced id t) = forToken map (T_DollarBraced id t) =
-- skip getBracedReference here to avoid false positives on PE -- skip getBracedReference here to avoid false positives on PE
Map.lookup (concat . deadSimple $ t) map Map.lookup (concat . oversimplify $ t) map
forToken quoteMap (T_DoubleQuoted id tokens) = forToken quoteMap (T_DoubleQuoted id tokens) =
msum $ map (forToken quoteMap) tokens msum $ map (forToken quoteMap) tokens
forToken quoteMap (T_NormalWord id tokens) = forToken quoteMap (T_NormalWord id tokens) =
msum $ map (forToken quoteMap) tokens msum $ map (forToken quoteMap) tokens
forToken _ t = forToken _ t =
if containsQuotes (concat $ deadSimple t) if containsQuotes (concat $ oversimplify t)
then return $ getId t then return $ getId t
else Nothing else Nothing
@ -2734,7 +2571,7 @@ checkFunctionsUsedExternally params t =
| t `isUnqualifiedCommand` "alias" = mapM_ getAlias args | t `isUnqualifiedCommand` "alias" = mapM_ getAlias args
findFunctions _ = return () findFunctions _ = return ()
getAlias arg = getAlias arg =
let string = concat $ deadSimple arg let string = concat $ oversimplify arg
in when ('=' `elem` string) $ in when ('=' `elem` string) $
modify ((takeWhile (/= '=') string, getId arg):) modify ((takeWhile (/= '=') string, getId arg):)
checkArg cmd arg = potentially $ do checkArg cmd arg = potentially $ do
@ -2871,13 +2708,13 @@ checkUnassignedReferences params t = warnings
isInArray var t = any isArray $ getPath (parentMap params) t isInArray var t = any isArray $ getPath (parentMap params) t
where where
isArray (T_Array {}) = True isArray (T_Array {}) = True
isArray (T_DollarBraced _ l) | var /= getBracedReference (bracedString l) = True isArray b@(T_DollarBraced _ _) | var /= getBracedReference (bracedString b) = True
isArray _ = False isArray _ = False
isGuarded (T_DollarBraced _ v) = isGuarded (T_DollarBraced _ v) =
any (`isPrefixOf` rest) ["-", ":-", "?", ":?"] any (`isPrefixOf` rest) ["-", ":-", "?", ":?"]
where where
name = concat $ deadSimple v name = concat $ oversimplify v
rest = dropWhile isVariableChar $ dropWhile (`elem` "#!") name rest = dropWhile isVariableChar $ dropWhile (`elem` "#!") name
isGuarded _ = False isGuarded _ = False
@ -2895,12 +2732,12 @@ checkGlobsAsOptions _ (T_SimpleCommand _ _ args) =
where where
check v@(T_NormalWord _ (T_Glob id s:_)) | s == "*" || s == "?" = check v@(T_NormalWord _ (T_Glob id s:_)) | s == "*" || s == "?" =
info id 2035 $ info id 2035 $
"Use ./" ++ concat (deadSimple v) "Use ./" ++ concat (oversimplify v)
++ " so names with dashes won't become options." ++ " so names with dashes won't become options."
check _ = return () check _ = return ()
isEndOfArgs t = isEndOfArgs t =
case concat $ deadSimple t of case concat $ oversimplify t of
"--" -> True "--" -> True
":::" -> True ":::" -> True
"::::" -> True "::::" -> True
@ -2924,7 +2761,7 @@ checkWhileReadPitfalls _ (T_WhileExpression id [command] contents)
munchers = [ "ssh", "ffmpeg", "mplayer" ] munchers = [ "ssh", "ffmpeg", "mplayer" ]
isStdinReadCommand (T_Pipeline _ _ [T_Redirecting id redirs cmd]) = isStdinReadCommand (T_Pipeline _ _ [T_Redirecting id redirs cmd]) =
let plaintext = deadSimple cmd let plaintext = oversimplify cmd
in head (plaintext ++ [""]) == "read" in head (plaintext ++ [""]) == "read"
&& ("-u" `notElem` plaintext) && ("-u" `notElem` plaintext)
&& all (not . stdinRedirect) redirs && all (not . stdinRedirect) redirs
@ -2956,7 +2793,7 @@ prop_checkPrefixAssign2 = verifyNot checkPrefixAssignmentReference "var=$(echo $
checkPrefixAssignmentReference params t@(T_DollarBraced id value) = checkPrefixAssignmentReference params t@(T_DollarBraced id value) =
check path check path
where where
name = getBracedReference $ bracedString value name = getBracedReference $ bracedString t
path = getPath (parentMap params) t path = getPath (parentMap params) t
idPath = map getId path idPath = map getId path
@ -3013,7 +2850,7 @@ checkCdAndBack params = doLists
doLists _ = return () doLists _ = return ()
isCdRevert t = isCdRevert t =
case deadSimple t of case oversimplify t of
["cd", p] -> p `elem` ["..", "-"] ["cd", p] -> p `elem` ["..", "-"]
_ -> False _ -> False
@ -3093,7 +2930,7 @@ checkCatastrophicRm params t@(T_SimpleCommand id _ tokens) | t `isCommand` "rm"
when (any isRecursiveFlag simpleArgs) $ when (any isRecursiveFlag simpleArgs) $
mapM_ (mapM_ checkWord . braceExpand) tokens mapM_ (mapM_ checkWord . braceExpand) tokens
where where
simpleArgs = deadSimple t simpleArgs = oversimplify t
checkWord token = checkWord token =
case getLiteralString token of case getLiteralString token of
@ -3277,7 +3114,7 @@ checkOverridingPath _ (T_SimpleCommand _ vars []) =
mapM_ checkVar vars mapM_ checkVar vars
where where
checkVar (T_Assignment id Assign "PATH" Nothing word) = checkVar (T_Assignment id Assign "PATH" Nothing word) =
let string = concat $ deadSimple word let string = concat $ oversimplify word
in unless (any (`isInfixOf` string) ["/bin", "/sbin" ]) $ do in unless (any (`isInfixOf` string) ["/bin", "/sbin" ]) $ do
when ('/' `elem` string && ':' `notElem` string) $ notify id when ('/' `elem` string && ':' `notElem` string) $ notify id
when (isLiteral word && ':' `notElem` string && '/' `notElem` string) $ notify id when (isLiteral word && ':' `notElem` string && '/' `notElem` string) $ notify id
@ -3324,16 +3161,6 @@ shellSupport t =
forCase _ = ("", []) forCase _ = ("", [])
getCommandSequences (T_Script _ _ cmds) = [cmds]
getCommandSequences (T_BraceGroup _ cmds) = [cmds]
getCommandSequences (T_Subshell _ cmds) = [cmds]
getCommandSequences (T_WhileExpression _ _ cmds) = [cmds]
getCommandSequences (T_UntilExpression _ _ cmds) = [cmds]
getCommandSequences (T_ForIn _ _ _ cmds) = [cmds]
getCommandSequences (T_ForArithmetic _ _ _ _ cmds) = [cmds]
getCommandSequences (T_IfExpression _ thens elses) = map snd thens ++ [elses]
getCommandSequences _ = []
groupWith f = groupBy ((==) `on` f) groupWith f = groupBy ((==) `on` f)
prop_checkMultipleAppends1 = verify checkMultipleAppends "foo >> file; bar >> file; baz >> file;" prop_checkMultipleAppends1 = verify checkMultipleAppends "foo >> file; bar >> file; baz >> file;"
@ -3365,7 +3192,7 @@ checkAliasesExpandEarly params =
checkUnqualifiedCommand "alias" (const f) checkUnqualifiedCommand "alias" (const f)
where where
f = mapM_ checkArg f = mapM_ checkArg
checkArg arg | '=' `elem` concat (deadSimple arg) = checkArg arg | '=' `elem` concat (oversimplify arg) =
forM_ (take 1 $ filter (not . isLiteral) $ getWordParts arg) $ forM_ (take 1 $ filter (not . isLiteral) $ getWordParts arg) $
\x -> warn (getId x) 2139 "This expands when defined, not when used. Consider escaping." \x -> warn (getId x) 2139 "This expands when defined, not when used. Consider escaping."
checkArg _ = return () checkArg _ = return ()
@ -3563,7 +3390,7 @@ checkUncheckedCd params root =
&& not (isCondition $ getPath (parentMap params) t)) $ && not (isCondition $ getPath (parentMap params) t)) $
warn (getId t) 2164 "Use cd ... || exit in case cd fails." warn (getId t) 2164 "Use cd ... || exit in case cd fails."
checkElement _ = return () checkElement _ = return ()
isCdDotDot t = deadSimple t == ["cd", ".."] isCdDotDot t = oversimplify t == ["cd", ".."]
hasSetE = isNothing $ doAnalysis (guard . not . isSetE) root hasSetE = isNothing $ doAnalysis (guard . not . isSetE) root
isSetE t = isSetE t =
case t of case t of