Moved the various AST convenience functions to a separate module.
This commit is contained in:
parent
07747b30fb
commit
0dd61b65d8
|
@ -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
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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 -> []
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue