diff --git a/ShellCheck/Analytics.hs b/ShellCheck/Analytics.hs index 7ed43be..da251ae 100644 --- a/ShellCheck/Analytics.hs +++ b/ShellCheck/Analytics.hs @@ -1195,12 +1195,20 @@ prop_checkArithmeticDeref4 = verifyNot checkArithmeticDeref "(( ! $? ))" prop_checkArithmeticDeref5 = verifyNot checkArithmeticDeref "(($1))" prop_checkArithmeticDeref6 = verify checkArithmeticDeref "(( a[$i] ))" prop_checkArithmeticDeref7 = verifyNot checkArithmeticDeref "(( 10#$n ))" +prop_checkArithmeticDeref8 = verifyNot checkArithmeticDeref "let i=$i+1" checkArithmeticDeref params t@(TA_Expansion _ [T_DollarBraced id l]) = - unless (excepting $ bracedString l) $ + unless ((isException $ bracedString l) || (not isNormal)) $ style id 2004 "$ on variables in (( )) is unnecessary." where - excepting [] = True - excepting s = any (`elem` "/.:#%?*@") s || isDigit (head s) + isException [] = True + isException s = any (`elem` "/.:#%?*@") s || isDigit (head s) + isNormal = fromMaybe True $ msum $ map isNormalContext $ (parents params t) + isNormalContext t = + case t of + T_Arithmetic {} -> return True + T_DollarArithmetic {} -> return True + T_SimpleCommand {} -> return False + _ -> fail "Irrelevant" checkArithmeticDeref _ _ = return () prop_checkArithmeticBadOctal1 = verify checkArithmeticBadOctal "(( 0192 ))" @@ -2265,6 +2273,7 @@ prop_checkUnused15= verifyNotTree checkUnusedAssignments "x=(1); n=0; (( x[n] )) prop_checkUnused16= verifyNotTree checkUnusedAssignments "foo=5; declare -x foo" prop_checkUnused17= verifyNotTree checkUnusedAssignments "read -i 'foo' -e -p 'Input: ' bar; $bar;" prop_checkUnused18= verifyNotTree checkUnusedAssignments "a=1; arr=( [$a]=42 ); echo \"${arr[@]}\"" +prop_checkUnused19= verifyNotTree checkUnusedAssignments "a=1; let b=a+1; echo $b" checkUnusedAssignments params t = snd $ runWriter (mapM_ checkAssignment flow) where flow = variableFlow params diff --git a/ShellCheck/Parser.hs b/ShellCheck/Parser.hs index 4aa18f8..cb45a1d 100644 --- a/ShellCheck/Parser.hs +++ b/ShellCheck/Parser.hs @@ -1345,20 +1345,20 @@ readSimpleCommand = called "simple command" $ do case cmd of Nothing -> return $ makeSimpleCommand id1 id2 prefix [] [] Just cmd -> do - suffix <- option [] $ - if isModifierCommand cmd - then readModifierSuffix - else if isTimeCommand cmd - then readTimeSuffix - else readCmdSuffix + suffix <- option [] $ getParser readCmdSuffix cmd [ + (["declare", "export", "local", "readonly", "typeset"], readModifierSuffix), + (["time"], readTimeSuffix), + (["let"], readLetSuffix) + ] return $ makeSimpleCommand id1 id2 prefix [cmd] suffix where - isModifierCommand (T_NormalWord _ [T_Literal _ s]) = - s `elem` ["declare", "export", "local", "readonly", "typeset"] - isModifierCommand _ = False - -- Might not belong in T_SimpleCommand. Fixme? - isTimeCommand (T_NormalWord _ [T_Literal _ "time"]) = True - isTimeCommand _ = False + isCommand strings (T_NormalWord _ [T_Literal _ s]) = s `elem` strings + isCommand _ _ = False + getParser def cmd [] = def + getParser def cmd ((list, action):rest) = + if isCommand list cmd + then action + else getParser def cmd rest prop_readPipeline = isOk readPipeline "! cat /etc/issue | grep -i ubuntu" prop_readPipeline2 = isWarning readPipeline "!cat /etc/issue | grep -i ubuntu" @@ -1782,6 +1782,22 @@ readTimeSuffix = do lookAhead $ char '-' readCmdWord +-- Fixme: this is a hack that doesn't handle let '++c' or let a\>b +readLetSuffix = many1 (readIoRedirect <|> try readLetExpression <|> readCmdWord) + where + readLetExpression = do + startPos <- getPosition + expression <- readStringForParser readCmdWord + subParse startPos readArithmeticContents expression + +-- Get whatever a parser would parse as a string +readStringForParser parser = do + pos <- lookAhead (parser >> getPosition) + s <- readUntil pos + return s + where + readUntil endPos = anyChar `reluctantlyTill` (getPosition >>= guard . (== endPos)) + prop_readAssignmentWord = isOk readAssignmentWord "a=42" prop_readAssignmentWord2 = isOk readAssignmentWord "b=(1 2 3)" prop_readAssignmentWord3 = isWarning readAssignmentWord "$b = 13"