diff --git a/ShellCheck/AST.hs b/ShellCheck/AST.hs index 89b7f28..cca4a05 100644 --- a/ShellCheck/AST.hs +++ b/ShellCheck/AST.hs @@ -53,7 +53,7 @@ data Token = | T_Backticked Id [Token] | T_Bang Id | T_Banged Id Token - | T_BraceExpansion Id String + | T_BraceExpansion Id [Token] | T_BraceGroup Id [Token] | T_CLOBBER Id | T_Case Id @@ -171,6 +171,7 @@ analyze f g i = delve (T_DoubleQuoted id list) = dl list $ T_DoubleQuoted id delve (T_DollarDoubleQuoted id list) = dl list $ T_DollarDoubleQuoted id delve (T_DollarExpansion id list) = dl list $ T_DollarExpansion id + delve (T_BraceExpansion id list) = dl list $ T_BraceExpansion id delve (T_Backticked id list) = dl list $ T_Backticked id delve (T_DollarArithmetic id c) = d1 c $ T_DollarArithmetic id delve (T_DollarBracket id c) = d1 c $ T_DollarBracket id diff --git a/ShellCheck/Analytics.hs b/ShellCheck/Analytics.hs index f664fcc..c505f60 100644 --- a/ShellCheck/Analytics.hs +++ b/ShellCheck/Analytics.hs @@ -1280,8 +1280,18 @@ checkConstantNoary _ _ = return () prop_checkBraceExpansionVars1 = verify checkBraceExpansionVars "echo {1..$n}" prop_checkBraceExpansionVars2 = verifyNot checkBraceExpansionVars "echo {1,3,$n}" -checkBraceExpansionVars _ (T_BraceExpansion id s) | "..$" `isInfixOf` s = - warn id 2051 "Bash doesn't support variables in brace range expansions." +checkBraceExpansionVars _ (T_BraceExpansion id list) = mapM_ check list + where + check element = + when ("..$" `isInfixOf` toString element) $ + warn id 2051 "Bash doesn't support variables in brace range expansions." + literalExt t = + case t of + T_DollarBraced {} -> return "$" + T_DollarExpansion {} -> return "$" + T_DollarArithmetic {} -> return "$" + otherwise -> return "-" + toString t = fromJust $ getLiteralStringExt literalExt t checkBraceExpansionVars _ _ = return () prop_checkForDecimals = verify checkForDecimals "((3.14*c))" @@ -2413,6 +2423,7 @@ prop_checkSpacefulness22= verifyNotTree checkSpacefulness "echo $\"$1\"" prop_checkSpacefulness23= verifyNotTree checkSpacefulness "a=(1); echo ${a[@]}" prop_checkSpacefulness24= verifyTree checkSpacefulness "a='a b'; cat <<< $a" prop_checkSpacefulness25= verifyTree checkSpacefulness "a='s/[0-9]//g'; sed $a" +prop_checkSpacefulness26= verifyTree checkSpacefulness "a='foo bar'; echo {1,2,$a}" checkSpacefulness params t = doVariableFlowAnalysis readF writeF (Map.fromList defaults) (variableFlow params) diff --git a/ShellCheck/Parser.hs b/ShellCheck/Parser.hs index fd2cbea..f8522b4 100644 --- a/ShellCheck/Parser.hs +++ b/ShellCheck/Parser.hs @@ -237,6 +237,12 @@ attempting rest branch = orFail parser errorAction = try parser <|> (errorAction >>= fail) +-- Construct a node with a parser, e.g. T_Literal `withParser` (readGenericLiteral ",") +withParser node parser = do + id <- getNextId + contents <- parser + return $ node id contents + wasIncluded p = option False (p >> return True) acceptButWarn parser level code note = @@ -1045,16 +1051,29 @@ readGenericEscaped = do prop_readBraced = isOk readBraced "{1..4}" prop_readBraced2 = isOk readBraced "{foo,bar,\"baz lol\"}" -readBraced = try $ do - let strip (T_Literal _ s) = return ("\"" ++ s ++ "\"") - id <- getNextId - char '{' - str <- many1 ((readDoubleQuotedLiteral >>= strip) <|> readGenericLiteral1 (oneOf "}\"" <|> whitespace)) - char '}' - let result = concat str - unless (',' `elem` result || ".." `isInfixOf` result) $ - fail "Not a brace expression" - return $ T_BraceExpansion id result +prop_readBraced3 = isOk readBraced "{1,\\},2}" +prop_readBraced4 = isOk readBraced "{1,{2,3}}" +prop_readBraced5 = isOk readBraced "{JP{,E}G,jp{,e}g}" +prop_readBraced6 = isOk readBraced "{foo,bar,$((${var}))}" +readBraced = try braceExpansion + where + braceExpansion = + T_BraceExpansion `withParser` do + char '{' + elements <- bracedElement `sepBy1` char ',' + char '}' + return elements + bracedElement = + T_NormalWord `withParser` do + many $ choice [ + braceExpansion, + readDollarExpression, + readSingleQuoted, + readDoubleQuoted, + braceLiteral + ] + braceLiteral = + T_Literal `withParser` readGenericLiteral1 (oneOf "{}\"$'," <|> whitespace) readNormalDollar = readDollarExpression <|> readDollarDoubleQuote <|> readDollarSingleQuote <|> readDollarLonely readDoubleQuotedDollar = readDollarExpression <|> readDollarLonely @@ -1078,7 +1097,6 @@ readDollarDoubleQuote = do doubleQuote "end of translated double quoted string" return $ T_DollarDoubleQuoted id x - prop_readDollarArithmetic = isOk readDollarArithmetic "$(( 3 * 4 +5))" prop_readDollarArithmetic2 = isOk readDollarArithmetic "$(((3*4)+(1*2+(3-1))))" readDollarArithmetic = called "$((..)) expression" $ do