Added more [[]]-related checks

This commit is contained in:
Vidar Holen 2012-11-08 20:19:34 -08:00
parent 977cf427ca
commit 97e886e6dd
4 changed files with 59 additions and 26 deletions

View File

@ -31,6 +31,8 @@ basicChecks = [
,checkStderrRedirect ,checkStderrRedirect
,checkMissingPositionalQuotes ,checkMissingPositionalQuotes
,checkSingleQuotedVariables ,checkSingleQuotedVariables
,checkUnquotedZN
,checkNumberComparisons
] ]
modifyMap = modify modifyMap = modify
@ -197,6 +199,32 @@ checkSingleQuotedVariables _ = return ()
checkSingleQuotedVariablesRe = mkRegex "(\\$[0-9a-zA-Z_]+)" checkSingleQuotedVariablesRe = mkRegex "(\\$[0-9a-zA-Z_]+)"
prop_checkUnquotedZN = verify checkUnquotedZN "if [ -z $foo ]; then echo cow; fi"
prop_checkUnquotedZN2 = verify checkUnquotedZN "[ -n $cow ]"
prop_checkUnquotedZN3 = verifyNot checkUnquotedZN "[[ -z $foo ]] && echo cow"
checkUnquotedZN (T_Condition _ SingleBracket (TC_Unary _ SingleBracket op (T_NormalWord id [t]))) | ( op == "-z" || op == "-n" ) && willSplit t =
addNoteFor id $ Note ErrorC "Always true because you failed to quote. Use [[ ]] instead."
checkUnquotedZN _ = return ()
prop_checkNumberComparisons1 = verify checkNumberComparisons "[[ $foo < 3 ]]"
prop_checkNumberComparisons2 = verify checkNumberComparisons "[[ 0 >= $(cmd) ]]"
prop_checkNumberComparisons3 = verifyNot checkNumberComparisons "[[ $foo ]] > 3"
prop_checkNumberComparisons4 = verify checkNumberComparisons "[ $foo > $bar ]"
prop_checkNumberComparisons5 = verify checkNumberComparisons "until [ $n <= $z ]; do echo foo; done"
checkNumberComparisons (TC_Binary id typ op lhs rhs)
| op `elem` ["<", ">", "<=", ">="] = do
when (isNum lhs || isNum rhs) $ addNoteFor id $ Note ErrorC $ "\"" ++ op ++ "\" is for string comparisons. Use " ++ (eqv op)
when (typ == SingleBracket) $ addNoteFor id $ Note ErrorC $ "Can't use " ++ op ++" in [ ]. Use [[ ]]."
where
isNum t = case deadSimple t of [v] -> all isDigit v
_ -> False
eqv "<" = "-lt"
eqv ">" = "-gt"
eqv "<=" = "-le"
eqv ">=" = "-ge"
eqv _ = "the numerical equivalent"
checkNumberComparisons _ = return ()
allModifiedVariables t = snd $ runState (doAnalysis (\x -> modify $ (++) (getModifiedVariables t)) t) [] allModifiedVariables t = snd $ runState (doAnalysis (\x -> modify $ (++) (getModifiedVariables t)) t) []
--- Subshell detection --- Subshell detection

View File

@ -1,6 +1,6 @@
{-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE NoMonomorphismRestriction #-}
module ShellCheck.Parser (Token(..), Id(..), Note(..), Severity(..), parseShell, ParseResult(..), ParseNote(..), notesFromMap, Metadata(..), doAnalysis, doStackAnalysis, doTransform, sortNotes) where module ShellCheck.Parser (Token(..), ConditionType(..), Id(..), Note(..), Severity(..), parseShell, ParseResult(..), ParseNote(..), notesFromMap, Metadata(..), doAnalysis, doStackAnalysis, doTransform, sortNotes) where
import Text.Parsec import Text.Parsec
import Debug.Trace import Debug.Trace
@ -153,14 +153,15 @@ readConditionContents single = do
parseProblemAt pos WarningC "To check a command, skip [] and just do 'if foo | grep bar; then'.") parseProblemAt pos WarningC "To check a command, skip [] and just do 'if foo | grep bar; then'.")
where where
typ = if single then SingleBracket else DoubleBracket
readCondBinaryOp = try $ do readCondBinaryOp = try $ do
op <- choice $ (map tryOp ["-nt", "-ot", "-ef", "=", "==", "!=", "<", ">", "-eq", "-ne", "-lt", "-le", "-gt", "-ge", "=~"]) op <- choice $ (map tryOp ["-nt", "-ot", "-ef", "=", "==", "!=", "<=", ">=", "-eq", "-ne", "-lt", "-le", "-gt", "-ge", "=~", ">", "<"])
hardCondSpacing hardCondSpacing
return op return op
where tryOp s = try $ do where tryOp s = try $ do
id <- getNextId id <- getNextId
string s string s
return $ TC_Binary id s return $ TC_Binary id typ s
readCondUnaryExp = do readCondUnaryExp = do
op <- readCondUnaryOp op <- readCondUnaryOp
@ -181,7 +182,7 @@ readConditionContents single = do
where tryOp s = try $ do where tryOp s = try $ do
id <- getNextId id <- getNextId
string s string s
return $ TC_Unary id s return $ TC_Unary id typ s
readCondWord = do readCondWord = do
notFollowedBy (try (spacing >> (string "]"))) notFollowedBy (try (spacing >> (string "]")))
@ -205,7 +206,8 @@ readConditionContents single = do
when (single && x == "&&") $ addNoteFor id $ Note ErrorC "You can't use && inside [..]. Use [[..]] instead." when (single && x == "&&") $ addNoteFor id $ Note ErrorC "You can't use && inside [..]. Use [[..]] instead."
when (not single && x == "-a") $ addNoteFor id $ Note ErrorC "In [[..]], use && instead of -a" when (not single && x == "-a") $ addNoteFor id $ Note ErrorC "In [[..]], use && instead of -a"
softCondSpacing softCondSpacing
return $ TC_And id x return $ TC_And id typ x
readCondOrOp = do readCondOrOp = do
id <- getNextId id <- getNextId
@ -213,7 +215,7 @@ readConditionContents single = do
when (single && x == "||") $ addNoteFor id $ Note ErrorC "You can't use || inside [..]. Use [[..]] instead." when (single && x == "||") $ addNoteFor id $ Note ErrorC "You can't use || inside [..]. Use [[..]] instead."
when (not single && x == "-o") $ addNoteFor id $ Note ErrorC "In [[..]], use && instead of -o" when (not single && x == "-o") $ addNoteFor id $ Note ErrorC "In [[..]], use && instead of -o"
softCondSpacing softCondSpacing
return $ TC_Or id x return $ TC_Or id typ x
readCondNoaryOrBinary = do readCondNoaryOrBinary = do
id <- getNextId id <- getNextId
@ -229,7 +231,7 @@ readConditionContents single = do
op <- readCondBinaryOp op <- readCondBinaryOp
y <- readCondWord <|> ( (parseProblemAt pos ErrorC $ "Expected another argument for this operator") >> mzero) y <- readCondWord <|> ( (parseProblemAt pos ErrorC $ "Expected another argument for this operator") >> mzero)
return (x `op` y) return (x `op` y)
) <|> (return $ TC_Noary id x) ) <|> (return $ TC_Noary id typ x)
readCondGroup = do readCondGroup = do
id <- getNextId id <- getNextId
@ -242,7 +244,7 @@ readConditionContents single = do
rparen <- string ")" <|> string "\\)" rparen <- string ")" <|> string "\\)"
when (single && rparen == ")") $ parseProblemAt pos ErrorC "In [..] you have to escape (). Use [[..]] instead." when (single && rparen == ")") $ parseProblemAt pos ErrorC "In [..] you have to escape (). Use [[..]] instead."
when (isEscaped lparen `xor` isEscaped rparen) $ parseProblemAt pos ErrorC "Did you just escape one half of () but not the other?" when (isEscaped lparen `xor` isEscaped rparen) $ parseProblemAt pos ErrorC "Did you just escape one half of () but not the other?"
return $ TC_Group id x return $ TC_Group id typ x
where where
isEscaped ('\\':_) = True isEscaped ('\\':_) = True
isEscaped _ = False isEscaped _ = False
@ -254,7 +256,7 @@ readConditionContents single = do
char '!' char '!'
softCondSpacing softCondSpacing
expr <- readCondExpr expr <- readCondExpr
return $ TC_Not id expr return $ TC_Not id typ expr
readCondExpr = readCondExpr =
readCondGroup <|> readCondUnaryExp <|> readCondNoaryOrBinary readCondGroup <|> readCondUnaryExp <|> readCondNoaryOrBinary
@ -278,7 +280,7 @@ readCondition = do
close <- (try $ string "]]") <|> (string "]") close <- (try $ string "]]") <|> (string "]")
when (open == "[[" && close /= "]]") $ parseProblemAt cpos ErrorC "Did you mean ]] ?" when (open == "[[" && close /= "]]") $ parseProblemAt cpos ErrorC "Did you mean ]] ?"
when (open == "[" && close /= "]" ) $ parseProblemAt opos ErrorC "Did you mean [[ ?" when (open == "[" && close /= "]" ) $ parseProblemAt opos ErrorC "Did you mean [[ ?"
return $ T_Condition id condition return $ T_Condition id (if single then SingleBracket else DoubleBracket) condition
hardCondSpacing = condSpacingMsg False "You need a space here" hardCondSpacing = condSpacingMsg False "You need a space here"
@ -290,9 +292,11 @@ condSpacingMsg soft msg = do
-- Horrifying AST -- Horrifying AST
data Token = T_AND_IF Id | T_OR_IF Id | T_DSEMI Id | T_Semi Id | T_DLESS Id | T_DGREAT Id | T_LESSAND Id | T_GREATAND Id | T_LESSGREAT Id | T_DLESSDASH Id | T_CLOBBER Id | T_If Id | T_Then Id | T_Else Id | T_Elif Id | T_Fi Id | T_Do Id | T_Done Id | T_Case Id | T_Esac Id | T_While Id | T_Until Id | T_For Id | T_Lbrace Id | T_Rbrace Id | T_Lparen Id | T_Rparen Id | T_Bang Id | T_In Id | T_NEWLINE Id | T_EOF Id | T_Less Id | T_Greater Id | T_SingleQuoted Id String | T_Literal Id String | T_NormalWord Id [Token] | T_DoubleQuoted Id [Token] | T_DollarExpansion Id [Token] | T_DollarBraced Id String | T_DollarArithmetic Id String | T_BraceExpansion Id String | T_IoFile Id Token Token | T_HereDoc Id Bool Bool String | T_HereString Id Token | T_FdRedirect Id String Token | T_Assignment Id String Token | T_Array Id [Token] | T_Redirecting Id [Token] Token | T_SimpleCommand Id [Token] [Token] | T_Pipeline Id [Token] | T_Banged Id Token | T_AndIf Id (Token) (Token) | T_OrIf Id (Token) (Token) | T_Backgrounded Id Token | T_IfExpression Id [([Token],[Token])] [Token] | T_Subshell Id [Token] | T_BraceGroup Id [Token] | T_WhileExpression Id [Token] [Token] | T_UntilExpression Id [Token] [Token] | T_ForIn Id String [Token] [Token] | T_CaseExpression Id Token [([Token],[Token])] | T_Function Id String Token | T_Arithmetic Id String | T_Script Id [Token] | data Token = T_AND_IF Id | T_OR_IF Id | T_DSEMI Id | T_Semi Id | T_DLESS Id | T_DGREAT Id | T_LESSAND Id | T_GREATAND Id | T_LESSGREAT Id | T_DLESSDASH Id | T_CLOBBER Id | T_If Id | T_Then Id | T_Else Id | T_Elif Id | T_Fi Id | T_Do Id | T_Done Id | T_Case Id | T_Esac Id | T_While Id | T_Until Id | T_For Id | T_Lbrace Id | T_Rbrace Id | T_Lparen Id | T_Rparen Id | T_Bang Id | T_In Id | T_NEWLINE Id | T_EOF Id | T_Less Id | T_Greater Id | T_SingleQuoted Id String | T_Literal Id String | T_NormalWord Id [Token] | T_DoubleQuoted Id [Token] | T_DollarExpansion Id [Token] | T_DollarBraced Id String | T_DollarArithmetic Id String | T_BraceExpansion Id String | T_IoFile Id Token Token | T_HereDoc Id Bool Bool String | T_HereString Id Token | T_FdRedirect Id String Token | T_Assignment Id String Token | T_Array Id [Token] | T_Redirecting Id [Token] Token | T_SimpleCommand Id [Token] [Token] | T_Pipeline Id [Token] | T_Banged Id Token | T_AndIf Id (Token) (Token) | T_OrIf Id (Token) (Token) | T_Backgrounded Id Token | T_IfExpression Id [([Token],[Token])] [Token] | T_Subshell Id [Token] | T_BraceGroup Id [Token] | T_WhileExpression Id [Token] [Token] | T_UntilExpression Id [Token] [Token] | T_ForIn Id String [Token] [Token] | T_CaseExpression Id Token [([Token],[Token])] | T_Function Id String Token | T_Arithmetic Id String | T_Script Id [Token] |
T_Condition Id Token | TC_And Id String Token Token | TC_Or Id String Token Token | TC_Not Id Token | TC_Group Id Token | TC_Binary Id String Token Token | TC_Unary Id String Token | TC_Noary Id Token T_Condition Id ConditionType Token | TC_And Id ConditionType String Token Token | TC_Or Id ConditionType String Token Token | TC_Not Id ConditionType Token | TC_Group Id ConditionType Token | TC_Binary Id ConditionType String Token Token | TC_Unary Id ConditionType String Token | TC_Noary Id ConditionType Token
deriving (Show) deriving (Show)
data ConditionType = DoubleBracket | SingleBracket deriving (Show, Eq)
analyzeScopes f g i = mapM (analyze f g i) analyzeScopes f g i = mapM (analyze f g i)
analyze f g i s@(T_NormalWord id list) = do analyze f g i s@(T_NormalWord id list) = do
@ -457,50 +461,50 @@ analyze f g i s@(T_Function id name body) = do
g s g s
return . i $ T_Function id name a return . i $ T_Function id name a
analyze f g i s@(T_Condition id token) = do analyze f g i s@(T_Condition id typ token) = do
f s f s
a <- analyze f g i token a <- analyze f g i token
g s g s
return . i $ T_Condition id a return . i $ T_Condition id typ a
analyze f g i s@(TC_And id str t1 t2) = do analyze f g i s@(TC_And id typ str t1 t2) = do
f s f s
a <- analyze f g i t1 a <- analyze f g i t1
b <- analyze f g i t2 b <- analyze f g i t2
g s g s
return . i $ TC_And id str a b return . i $ TC_And id typ str a b
analyze f g i s@(TC_Or id str t1 t2) = do analyze f g i s@(TC_Or id typ str t1 t2) = do
f s f s
a <- analyze f g i t1 a <- analyze f g i t1
b <- analyze f g i t2 b <- analyze f g i t2
g s g s
return . i $ TC_Or id str a b return . i $ TC_Or id typ str a b
analyze f g i s@(TC_Group id token) = do analyze f g i s@(TC_Group id typ token) = do
f s f s
a <- analyze f g i token a <- analyze f g i token
g s g s
return . i $ TC_Group id a return . i $ TC_Group id typ a
analyze f g i s@(TC_Binary id op lhs rhs) = do analyze f g i s@(TC_Binary id typ op lhs rhs) = do
f s f s
a <- analyze f g i lhs a <- analyze f g i lhs
b <- analyze f g i rhs b <- analyze f g i rhs
g s g s
return . i $ TC_Binary id op a b return . i $ TC_Binary id typ op a b
analyze f g i s@(TC_Unary id op token) = do analyze f g i s@(TC_Unary id typ op token) = do
f s f s
a <- analyze f g i token a <- analyze f g i token
g s g s
return . i $ TC_Unary id op a return . i $ TC_Unary id typ op a
analyze f g i s@(TC_Noary id token) = do analyze f g i s@(TC_Noary id typ token) = do
f s f s
a <- analyze f g i token a <- analyze f g i token
g s g s
return . i $ TC_Noary id a return . i $ TC_Noary id typ a
analyze f g i t = do analyze f g i t = do
f t f t

View File

@ -0,0 +1 @@
until [ $var > $foo ]; do var=$(cow); done

View File

@ -13,7 +13,7 @@ ansi n = "\x1B[" ++ (show n) ++ "m"
colorForLevel "error" = 31 -- red colorForLevel "error" = 31 -- red
colorForLevel "warning" = 33 -- yellow colorForLevel "warning" = 33 -- yellow
colorForLevel "info" = 33 -- yellow colorForLevel "info" = 32 -- green
colorForLevel "style" = 32 -- green colorForLevel "style" = 32 -- green
colorForLevel "message" = 1 -- bold colorForLevel "message" = 1 -- bold
colorForLevel "source" = 0 -- none colorForLevel "source" = 0 -- none