SC2229: Warn about 'read $var'

This commit is contained in:
Vidar Holen 2018-02-25 13:27:01 -08:00
parent 72044a79c6
commit b7a8b090d2
3 changed files with 70 additions and 11 deletions

View File

@ -1,5 +1,6 @@
## Latest - ???
### Added
- SC2229: Warn about 'read $var'
- SC2227: Warn about redirections in the middle of 'find' commands
- SC2224,SC2225,SC2226: Warn when using mv/cp/ln without a destination
- SC2223: Quote warning specific to `: ${var=value}`

View File

@ -837,7 +837,36 @@ isQuotedAlternativeReference t =
where
re = mkRegex "(^|\\]):?\\+"
-- getOpts "erd:u:" will parse a SimpleCommand like
-- read -re -d : -u 3 bar
-- into
-- Just [("r", -re), ("e", -re), ("d", :), ("u", 3), ("", bar)]
-- where flags with arguments map to arguments, while others map to themselves.
-- Any unrecognized flag will result in Nothing.
getOpts :: String -> Token -> Maybe [(String, Token)]
getOpts string cmd = process flags
where
flags = getAllFlags cmd
flagList (c:':':rest) = ([c], True) : flagList rest
flagList (c:rest) = ([c], False) : flagList rest
flagList [] = []
flagMap = Map.fromList $ ("", False) : flagList string
process [] = return []
process [(token, flag)] = do
takesArg <- Map.lookup flag flagMap
guard $ not takesArg
return [(flag, token)]
process ((token1, flag1):rest2@((token2, flag2):rest)) = do
takesArg <- Map.lookup flag1 flagMap
if takesArg
then do
guard $ flag2 == ""
more <- process rest
return $ (flag1, token2) : more
else do
more <- process rest2
return $ (flag1, token1) : more
return []
runTests = $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |])

View File

@ -90,6 +90,7 @@ commandChecks = [
,checkLetUsage
,checkMvArguments, checkCpArguments, checkLnArguments
,checkFindRedirections
,checkReadExpansions
]
buildCommandMap :: [CommandCheck] -> Map.Map CommandName (Token -> Analysis)
@ -588,19 +589,47 @@ prop_checkExportedExpansions1 = verify checkExportedExpansions "export $foo"
prop_checkExportedExpansions2 = verify checkExportedExpansions "export \"$foo\""
prop_checkExportedExpansions3 = verifyNot checkExportedExpansions "export foo"
prop_checkExportedExpansions4 = verifyNot checkExportedExpansions "export ${foo?}"
checkExportedExpansions = CommandCheck (Exactly "export") (check . arguments)
checkExportedExpansions = CommandCheck (Exactly "export") (mapM_ check . arguments)
where
check = mapM_ checkForVariables
checkForVariables f =
case getWordParts f of
[t@(T_DollarBraced {})] -> potentially $ do
let contents = bracedString t
let name = getBracedReference contents
guard $ name == contents
return . warn (getId t) 2163 $
"This does not export '" ++ name ++ "'. Remove $/${} for that, or use ${var?} to quiet."
_ -> return ()
check t = potentially $ do
var <- getSingleUnmodifiedVariable t
let name = bracedString var
return . warn (getId t) 2163 $
"This does not export '" ++ name ++ "'. Remove $/${} for that, or use ${var?} to quiet."
prop_checkReadExpansions1 = verify checkReadExpansions "read $var"
prop_checkReadExpansions2 = verify checkReadExpansions "read -r $var"
prop_checkReadExpansions3 = verifyNot checkReadExpansions "read -p $var"
prop_checkReadExpansions4 = verifyNot checkReadExpansions "read -rd $delim name"
prop_checkReadExpansions5 = verify checkReadExpansions "read \"$var\""
prop_checkReadExpansions6 = verify checkReadExpansions "read -a $var"
prop_checkReadExpansions7 = verifyNot checkReadExpansions "read $1"
prop_checkReadExpansions8 = verifyNot checkReadExpansions "read ${var?}"
checkReadExpansions = CommandCheck (Exactly "read") check
where
options = getOpts "sreu:n:N:i:p:a:"
getVars cmd = fromMaybe [] $ do
opts <- options cmd
return . map snd $ filter (\(x,_) -> x == "" || x == "a") opts
check cmd = mapM_ warning $ getVars cmd
warning t = potentially $ do
var <- getSingleUnmodifiedVariable t
let name = bracedString var
guard $ isVariableName name -- e.g. not $1
return . warn (getId t) 2229 $
"This does not read '" ++ name ++ "'. Remove $/${} for that, or use ${var?} to quiet."
-- Return the single variable expansion that makes up this word, if any.
-- e.g. $foo -> $foo, "$foo"'' -> $foo , "hello $name" -> Nothing
getSingleUnmodifiedVariable :: Token -> Maybe Token
getSingleUnmodifiedVariable word =
case getWordParts word of
[t@(T_DollarBraced {})] ->
let contents = bracedString t
name = getBracedReference contents
in guard (contents == name) >> return t
_ -> Nothing
prop_checkAliasesUsesArgs1 = verify checkAliasesUsesArgs "alias a='cp $1 /a'"
prop_checkAliasesUsesArgs2 = verifyNot checkAliasesUsesArgs "alias $1='foo'"