Improve checks for = in command names (fixes #2102)
This commit is contained in:
parent
bd3299edd3
commit
fbb14d6b38
|
@ -20,6 +20,7 @@
|
||||||
- POSIX/dash unsupported feature warnings now have individual SC3xxx codes
|
- POSIX/dash unsupported feature warnings now have individual SC3xxx codes
|
||||||
- SC1090: A leading `$x/` or `$(x)/` is now treated as `./` when locating files
|
- SC1090: A leading `$x/` or `$(x)/` is now treated as `./` when locating files
|
||||||
- SC2154: Variables appearing in -z/-n tests are no longer considered unassigned
|
- SC2154: Variables appearing in -z/-n tests are no longer considered unassigned
|
||||||
|
- SC2270-SC2285: Improved warnings about misused =, e.g. `${var}=42`
|
||||||
|
|
||||||
|
|
||||||
## v0.7.1 - 2020-04-04
|
## v0.7.1 - 2020-04-04
|
||||||
|
|
|
@ -278,6 +278,12 @@ getUnquotedLiteral (T_NormalWord _ list) =
|
||||||
str _ = Nothing
|
str _ = Nothing
|
||||||
getUnquotedLiteral _ = Nothing
|
getUnquotedLiteral _ = Nothing
|
||||||
|
|
||||||
|
isQuotes t =
|
||||||
|
case t of
|
||||||
|
T_DoubleQuoted {} -> True
|
||||||
|
T_SingleQuoted {} -> True
|
||||||
|
_ -> False
|
||||||
|
|
||||||
-- Get the last unquoted T_Literal in a word like "${var}foo"THIS
|
-- Get the last unquoted T_Literal in a word like "${var}foo"THIS
|
||||||
-- or nothing if the word does not end in an unquoted literal.
|
-- or nothing if the word does not end in an unquoted literal.
|
||||||
getTrailingUnquotedLiteral :: Token -> Maybe Token
|
getTrailingUnquotedLiteral :: Token -> Maybe Token
|
||||||
|
@ -296,8 +302,11 @@ getTrailingUnquotedLiteral t =
|
||||||
getLeadingUnquotedString :: Token -> Maybe String
|
getLeadingUnquotedString :: Token -> Maybe String
|
||||||
getLeadingUnquotedString t =
|
getLeadingUnquotedString t =
|
||||||
case t of
|
case t of
|
||||||
T_NormalWord _ ((T_Literal _ s) : _) -> return s
|
T_NormalWord _ ((T_Literal _ s) : rest) -> return $ s ++ from rest
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
|
where
|
||||||
|
from ((T_Literal _ s):rest) = s ++ from rest
|
||||||
|
from _ = ""
|
||||||
|
|
||||||
-- Maybe get the literal string of this token and any globs in it.
|
-- Maybe get the literal string of this token and any globs in it.
|
||||||
getGlobOrLiteralString = getLiteralStringExt f
|
getGlobOrLiteralString = getLiteralStringExt f
|
||||||
|
|
|
@ -194,6 +194,8 @@ nodeChecks = [
|
||||||
,checkBlatantRecursion
|
,checkBlatantRecursion
|
||||||
,checkBadTestAndOr
|
,checkBadTestAndOr
|
||||||
,checkAssignToSelf
|
,checkAssignToSelf
|
||||||
|
,checkEqualsInCommand
|
||||||
|
,checkSecondArgIsComparison
|
||||||
]
|
]
|
||||||
|
|
||||||
optionalChecks = map fst optionalTreeChecks
|
optionalChecks = map fst optionalTreeChecks
|
||||||
|
@ -277,15 +279,23 @@ checkUnqualifiedCommand str f t@(T_SimpleCommand id _ (cmd:rest))
|
||||||
| t `isUnqualifiedCommand` str = f cmd rest
|
| t `isUnqualifiedCommand` str = f cmd rest
|
||||||
checkUnqualifiedCommand _ _ _ = return ()
|
checkUnqualifiedCommand _ _ _ = return ()
|
||||||
|
|
||||||
|
verifyCodes :: (Parameters -> Token -> Writer [TokenComment] ()) -> [Code] -> String -> Bool
|
||||||
|
verifyCodes f l s = codes == Just l
|
||||||
|
where
|
||||||
|
treeCheck = runNodeAnalysis f
|
||||||
|
comments = runAndGetComments treeCheck s
|
||||||
|
codes = map (cCode . tcComment) <$> comments
|
||||||
|
|
||||||
checkNode f = producesComments (runNodeAnalysis f)
|
checkNode f = producesComments (runNodeAnalysis f)
|
||||||
producesComments :: (Parameters -> Token -> [TokenComment]) -> String -> Maybe Bool
|
producesComments :: (Parameters -> Token -> [TokenComment]) -> String -> Maybe Bool
|
||||||
producesComments f s = do
|
producesComments f s = not . null <$> runAndGetComments f s
|
||||||
|
|
||||||
|
runAndGetComments f s = do
|
||||||
let pr = pScript s
|
let pr = pScript s
|
||||||
prRoot pr
|
prRoot pr
|
||||||
let spec = defaultSpec pr
|
let spec = defaultSpec pr
|
||||||
let params = makeParameters spec
|
let params = makeParameters spec
|
||||||
return . not . null $
|
return $
|
||||||
filterByAnnotation spec params $
|
filterByAnnotation spec params $
|
||||||
runList spec [f]
|
runList spec [f]
|
||||||
|
|
||||||
|
@ -364,6 +374,19 @@ replaceEnd id params n r =
|
||||||
repPrecedence = depth,
|
repPrecedence = depth,
|
||||||
repInsertionPoint = InsertBefore
|
repInsertionPoint = InsertBefore
|
||||||
}
|
}
|
||||||
|
replaceToken id params r =
|
||||||
|
let tp = tokenPositions params
|
||||||
|
(start, end) = tp Map.! id
|
||||||
|
depth = length $ getPath (parentMap params) (T_EOF id)
|
||||||
|
in
|
||||||
|
newReplacement {
|
||||||
|
repStartPos = start,
|
||||||
|
repEndPos = end,
|
||||||
|
repString = r,
|
||||||
|
repPrecedence = depth,
|
||||||
|
repInsertionPoint = InsertBefore
|
||||||
|
}
|
||||||
|
|
||||||
surroundWidth id params s = fixWith [replaceStart id params 0 s, replaceEnd id params 0 s]
|
surroundWidth id params s = fixWith [replaceStart id params 0 s, replaceEnd id params 0 s]
|
||||||
fixWith fixes = newFix { fixReplacements = fixes }
|
fixWith fixes = newFix { fixReplacements = fixes }
|
||||||
|
|
||||||
|
@ -1855,6 +1878,7 @@ prop_checkSpacefulness39= verifyNotTree checkSpacefulness "a=''\"\"''; b=x$a; ec
|
||||||
prop_checkSpacefulness40= verifyNotTree checkSpacefulness "a=$((x+1)); echo $a"
|
prop_checkSpacefulness40= verifyNotTree checkSpacefulness "a=$((x+1)); echo $a"
|
||||||
prop_checkSpacefulness41= verifyNotTree checkSpacefulness "exec $1 --flags"
|
prop_checkSpacefulness41= verifyNotTree checkSpacefulness "exec $1 --flags"
|
||||||
prop_checkSpacefulness42= verifyNotTree checkSpacefulness "run $1 --flags"
|
prop_checkSpacefulness42= verifyNotTree checkSpacefulness "run $1 --flags"
|
||||||
|
prop_checkSpacefulness43= verifyNotTree checkSpacefulness "$foo=42"
|
||||||
|
|
||||||
data SpaceStatus = SpaceSome | SpaceNone | SpaceEmpty deriving (Eq)
|
data SpaceStatus = SpaceSome | SpaceNone | SpaceEmpty deriving (Eq)
|
||||||
instance Semigroup SpaceStatus where
|
instance Semigroup SpaceStatus where
|
||||||
|
@ -1879,6 +1903,7 @@ checkSpacefulness params = checkSpacefulness' onFind params
|
||||||
emit $ makeComment InfoC (getId token) 2223
|
emit $ makeComment InfoC (getId token) 2223
|
||||||
"This default assignment may cause DoS due to globbing. Quote it."
|
"This default assignment may cause DoS due to globbing. Quote it."
|
||||||
else
|
else
|
||||||
|
unless (quotesMayConflictWithSC2281 params token) $
|
||||||
emit $ makeCommentWithFix InfoC (getId token) 2086
|
emit $ makeCommentWithFix InfoC (getId token) 2086
|
||||||
"Double quote to prevent globbing and word splitting."
|
"Double quote to prevent globbing and word splitting."
|
||||||
(addDoubleQuotesAround params token)
|
(addDoubleQuotesAround params token)
|
||||||
|
@ -1896,14 +1921,25 @@ prop_checkSpacefulness4v= verifyTree checkVerboseSpacefulness "foo=3; foo=$(echo
|
||||||
prop_checkSpacefulness8v= verifyTree checkVerboseSpacefulness "a=foo\\ bar; a=foo; rm $a"
|
prop_checkSpacefulness8v= verifyTree checkVerboseSpacefulness "a=foo\\ bar; a=foo; rm $a"
|
||||||
prop_checkSpacefulness28v = verifyTree checkVerboseSpacefulness "exec {n}>&1; echo $n"
|
prop_checkSpacefulness28v = verifyTree checkVerboseSpacefulness "exec {n}>&1; echo $n"
|
||||||
prop_checkSpacefulness36v = verifyTree checkVerboseSpacefulness "arg=$#; echo $arg"
|
prop_checkSpacefulness36v = verifyTree checkVerboseSpacefulness "arg=$#; echo $arg"
|
||||||
|
prop_checkSpacefulness44v = verifyNotTree checkVerboseSpacefulness "foo=3; $foo=4"
|
||||||
checkVerboseSpacefulness params = checkSpacefulness' onFind params
|
checkVerboseSpacefulness params = checkSpacefulness' onFind params
|
||||||
where
|
where
|
||||||
onFind spaces token name =
|
onFind spaces token name =
|
||||||
when (spaces == SpaceNone && name `notElem` specialVariablesWithoutSpaces) $
|
when (spaces == SpaceNone
|
||||||
|
&& name `notElem` specialVariablesWithoutSpaces
|
||||||
|
&& not (quotesMayConflictWithSC2281 params token)) $
|
||||||
tell [makeCommentWithFix StyleC (getId token) 2248
|
tell [makeCommentWithFix StyleC (getId token) 2248
|
||||||
"Prefer double quoting even when variables don't contain special characters."
|
"Prefer double quoting even when variables don't contain special characters."
|
||||||
(addDoubleQuotesAround params token)]
|
(addDoubleQuotesAround params token)]
|
||||||
|
|
||||||
|
-- Don't suggest quotes if this will instead be autocorrected
|
||||||
|
-- from $foo=bar to foo=bar. This is not pretty but ok.
|
||||||
|
quotesMayConflictWithSC2281 params t =
|
||||||
|
case getPath (parentMap params) t of
|
||||||
|
_ : T_NormalWord parentId (me:T_Literal _ ('=':_):_) : T_SimpleCommand _ _ (cmd:_) : _ ->
|
||||||
|
(getId t) == (getId me) && (parentId == getId cmd)
|
||||||
|
_ -> False
|
||||||
|
|
||||||
addDoubleQuotesAround params token = (surroundWidth (getId token) params "\"")
|
addDoubleQuotesAround params token = (surroundWidth (getId token) params "\"")
|
||||||
checkSpacefulness'
|
checkSpacefulness'
|
||||||
:: (SpaceStatus -> Token -> String -> Writer [TokenComment] ()) ->
|
:: (SpaceStatus -> Token -> String -> Writer [TokenComment] ()) ->
|
||||||
|
@ -1978,8 +2014,9 @@ prop_CheckVariableBraces1 = verify checkVariableBraces "a='123'; echo $a"
|
||||||
prop_CheckVariableBraces2 = verifyNot checkVariableBraces "a='123'; echo ${a}"
|
prop_CheckVariableBraces2 = verifyNot checkVariableBraces "a='123'; echo ${a}"
|
||||||
prop_CheckVariableBraces3 = verifyNot checkVariableBraces "#shellcheck disable=SC2016\necho '$a'"
|
prop_CheckVariableBraces3 = verifyNot checkVariableBraces "#shellcheck disable=SC2016\necho '$a'"
|
||||||
prop_CheckVariableBraces4 = verifyNot checkVariableBraces "echo $* $1"
|
prop_CheckVariableBraces4 = verifyNot checkVariableBraces "echo $* $1"
|
||||||
|
prop_CheckVariableBraces5 = verifyNot checkVariableBraces "$foo=42"
|
||||||
checkVariableBraces params t@(T_DollarBraced id False l)
|
checkVariableBraces params t@(T_DollarBraced id False l)
|
||||||
| name `notElem` unbracedVariables =
|
| name `notElem` unbracedVariables && not (quotesMayConflictWithSC2281 params t) =
|
||||||
styleWithFix id 2250
|
styleWithFix id 2250
|
||||||
"Prefer putting braces around variable references even when not strictly required."
|
"Prefer putting braces around variable references even when not strictly required."
|
||||||
(fixFor t)
|
(fixFor t)
|
||||||
|
@ -4003,5 +4040,212 @@ checkAssignToSelf _ t =
|
||||||
msg id = info id 2269 "This variable is assigned to itself, so the assignment does nothing."
|
msg id = info id 2269 "This variable is assigned to itself, so the assignment does nothing."
|
||||||
|
|
||||||
|
|
||||||
|
prop_checkEqualsInCommand1a = verifyCodes checkEqualsInCommand [2277] "#!/bin/bash\n0='foo'"
|
||||||
|
prop_checkEqualsInCommand2a = verifyCodes checkEqualsInCommand [2278] "#!/bin/ksh \n$0='foo'"
|
||||||
|
prop_checkEqualsInCommand3a = verifyCodes checkEqualsInCommand [2279] "#!/bin/dash\n${0}='foo'"
|
||||||
|
prop_checkEqualsInCommand4a = verifyCodes checkEqualsInCommand [2280] "#!/bin/sh \n0='foo'"
|
||||||
|
|
||||||
|
prop_checkEqualsInCommand1b = verifyCodes checkEqualsInCommand [2270] "1='foo'"
|
||||||
|
prop_checkEqualsInCommand2b = verifyCodes checkEqualsInCommand [2270] "${2}='foo'"
|
||||||
|
|
||||||
|
prop_checkEqualsInCommand1c = verifyCodes checkEqualsInCommand [2271] "var$((n+1))=value"
|
||||||
|
prop_checkEqualsInCommand2c = verifyCodes checkEqualsInCommand [2271] "var${x}=value"
|
||||||
|
prop_checkEqualsInCommand3c = verifyCodes checkEqualsInCommand [2271] "var$((cmd))x='foo'"
|
||||||
|
prop_checkEqualsInCommand4c = verifyCodes checkEqualsInCommand [2271] "$(cmd)='foo'"
|
||||||
|
|
||||||
|
prop_checkEqualsInCommand1d = verifyCodes checkEqualsInCommand [2273] "======="
|
||||||
|
prop_checkEqualsInCommand2d = verifyCodes checkEqualsInCommand [2274] "======= Here ======="
|
||||||
|
prop_checkEqualsInCommand3d = verifyCodes checkEqualsInCommand [2275] "foo\n=42"
|
||||||
|
|
||||||
|
prop_checkEqualsInCommand1e = verifyCodes checkEqualsInCommand [] "--foo=bar"
|
||||||
|
prop_checkEqualsInCommand2e = verifyCodes checkEqualsInCommand [] "$(cmd)'=foo'"
|
||||||
|
prop_checkEqualsInCommand3e = verifyCodes checkEqualsInCommand [2276] "var${x}/=value"
|
||||||
|
prop_checkEqualsInCommand4e = verifyCodes checkEqualsInCommand [2276] "${}=value"
|
||||||
|
prop_checkEqualsInCommand5e = verifyCodes checkEqualsInCommand [2276] "${#x}=value"
|
||||||
|
|
||||||
|
prop_checkEqualsInCommand1f = verifyCodes checkEqualsInCommand [2281] "$var=foo"
|
||||||
|
prop_checkEqualsInCommand2f = verifyCodes checkEqualsInCommand [2281] "$a=$b"
|
||||||
|
prop_checkEqualsInCommand3f = verifyCodes checkEqualsInCommand [2281] "${var}=foo"
|
||||||
|
prop_checkEqualsInCommand4f = verifyCodes checkEqualsInCommand [2281] "${var[42]}=foo"
|
||||||
|
prop_checkEqualsInCommand5f = verifyCodes checkEqualsInCommand [2281] "$var+=foo"
|
||||||
|
|
||||||
|
prop_checkEqualsInCommand1g = verifyCodes checkEqualsInCommand [2282] "411toppm=true"
|
||||||
|
|
||||||
|
checkEqualsInCommand params originalToken =
|
||||||
|
case originalToken of
|
||||||
|
T_SimpleCommand _ _ (word:_) -> check word
|
||||||
|
_ -> return ()
|
||||||
|
where
|
||||||
|
hasEquals t =
|
||||||
|
case t of
|
||||||
|
T_Literal _ s -> '=' `elem` s
|
||||||
|
_ -> False
|
||||||
|
|
||||||
|
check t@(T_NormalWord _ list) | any hasEquals list =
|
||||||
|
case break hasEquals list of
|
||||||
|
(leading, (eq:_)) -> msg t (stripSinglePlus leading) eq
|
||||||
|
_ -> return ()
|
||||||
|
check _ = return ()
|
||||||
|
|
||||||
|
-- This is a workaround for the parser adding + and = as separate literals
|
||||||
|
stripSinglePlus l =
|
||||||
|
case reverse l of
|
||||||
|
(T_Literal _ "+"):rest -> reverse rest
|
||||||
|
_ -> l
|
||||||
|
|
||||||
|
positionalAssignmentRe = mkRegex "^[0-9][0-9]?="
|
||||||
|
positionalMsg id =
|
||||||
|
err id 2270 "To assign positional parameters, use 'set -- first second ..' (or use [ ] to compare)."
|
||||||
|
indirectionMsg id =
|
||||||
|
err id 2271 "For indirection, use arrays, declare \"var$n=value\", or (for sh) read/eval."
|
||||||
|
badComparisonMsg id =
|
||||||
|
err id 2272 "Command name contains ==. For comparison, use [ \"$var\" = value ]."
|
||||||
|
conflictMarkerMsg id =
|
||||||
|
err id 2273 "Sequence of ===s found. Merge conflict or intended as a commented border?"
|
||||||
|
borderMsg id =
|
||||||
|
err id 2274 "Command name starts with ===. Intended as a commented border?"
|
||||||
|
prefixMsg id =
|
||||||
|
err id 2275 "Command name starts with =. Bad line break?"
|
||||||
|
genericMsg id =
|
||||||
|
err id 2276 "This is interpreted as a command name containing '='. Bad assignment or comparison?"
|
||||||
|
assign0Msg id bashfix =
|
||||||
|
case shellType params of
|
||||||
|
Bash -> errWithFix id 2277 "Use BASH_ARGV0 to assign to $0 in bash (or use [ ] to compare)." bashfix
|
||||||
|
Ksh -> err id 2278 "$0 can't be assigned in Ksh (but it does reflect the current function)."
|
||||||
|
Dash -> err id 2279 "$0 can't be assigned in Dash. This becomes a command name."
|
||||||
|
_ -> err id 2280 "$0 can't be assigned this way, and there is no portable alternative."
|
||||||
|
leadingNumberMsg id =
|
||||||
|
err id 2282 "Variable names can't start with numbers, so this is interpreted as a command."
|
||||||
|
|
||||||
|
isExpansion t =
|
||||||
|
case t of
|
||||||
|
T_Arithmetic {} -> True
|
||||||
|
_ -> isQuoteableExpansion t
|
||||||
|
|
||||||
|
isConflictMarker cmd = fromMaybe False $ do
|
||||||
|
str <- getUnquotedLiteral cmd
|
||||||
|
guard $ all (== '=') str
|
||||||
|
guard $ length str >= 4 && length str <= 12 -- Git uses 7 but who knows
|
||||||
|
return True
|
||||||
|
|
||||||
|
mayBeVariableName l = fromMaybe False $ do
|
||||||
|
guard . not $ any isQuotes l
|
||||||
|
guard . not $ any willBecomeMultipleArgs l
|
||||||
|
str <- getLiteralStringExt (\_ -> Just "x") (T_NormalWord (Id 0) l)
|
||||||
|
return $ isVariableName str
|
||||||
|
|
||||||
|
isLeadingNumberVar s =
|
||||||
|
let lead = takeWhile (/= '=') s
|
||||||
|
in not (null lead) && isDigit (head lead)
|
||||||
|
&& all isVariableChar lead && not (all isDigit lead)
|
||||||
|
|
||||||
|
msg cmd leading (T_Literal litId s) = do
|
||||||
|
-- There are many different cases, and the order of the branches matter.
|
||||||
|
case leading of
|
||||||
|
-- --foo=42
|
||||||
|
[] | "-" `isPrefixOf` s -> -- There's SC2215 for these
|
||||||
|
return ()
|
||||||
|
|
||||||
|
-- ======Hello======
|
||||||
|
[] | "=" `isPrefixOf` s ->
|
||||||
|
case originalToken of
|
||||||
|
T_SimpleCommand _ [] [word] | isConflictMarker word ->
|
||||||
|
conflictMarkerMsg (getId originalToken)
|
||||||
|
_ | "===" `isPrefixOf` s -> borderMsg (getId originalToken)
|
||||||
|
_ -> prefixMsg (getId cmd)
|
||||||
|
|
||||||
|
-- $var==42
|
||||||
|
_ | "==" `isInfixOf` s ->
|
||||||
|
badComparisonMsg (getId cmd)
|
||||||
|
|
||||||
|
-- ${foo[x]}=42 and $foo=42
|
||||||
|
[T_DollarBraced id braced l] | "=" `isPrefixOf` s -> do
|
||||||
|
let variableStr = concat $ oversimplify l
|
||||||
|
let variableReference = getBracedReference variableStr
|
||||||
|
let variableModifier = getBracedModifier variableStr
|
||||||
|
let isPlain = isVariableName variableStr
|
||||||
|
let isPositional = all isDigit variableStr
|
||||||
|
|
||||||
|
let isArray = variableReference /= ""
|
||||||
|
&& "[" `isPrefixOf` variableModifier
|
||||||
|
&& "]" `isSuffixOf` variableModifier
|
||||||
|
|
||||||
|
case () of
|
||||||
|
-- $foo=bar should already have caused a parse-time SC1066
|
||||||
|
-- _ | not braced && isPlain ->
|
||||||
|
-- return ()
|
||||||
|
|
||||||
|
_ | variableStr == "" -> -- Don't try to fix ${}=foo
|
||||||
|
genericMsg (getId cmd)
|
||||||
|
|
||||||
|
-- $#=42 or ${#var}=42
|
||||||
|
_ | "#" `isPrefixOf` variableStr ->
|
||||||
|
genericMsg (getId cmd)
|
||||||
|
|
||||||
|
-- ${0}=42
|
||||||
|
_ | variableStr == "0" ->
|
||||||
|
assign0Msg id $ fixWith [replaceToken id params "BASH_ARGV0"]
|
||||||
|
|
||||||
|
-- $2=2
|
||||||
|
_ | isPositional ->
|
||||||
|
positionalMsg id
|
||||||
|
|
||||||
|
_ | isArray || isPlain ->
|
||||||
|
errWithFix id 2281
|
||||||
|
("Don't use " ++ (if braced then "${}" else "$") ++ " on the left side of assignments.") $
|
||||||
|
fixWith $
|
||||||
|
if braced
|
||||||
|
then [ replaceStart id params 2 "", replaceEnd id params 1 "" ]
|
||||||
|
else [ replaceStart id params 1 "" ]
|
||||||
|
|
||||||
|
_ -> indirectionMsg id
|
||||||
|
|
||||||
|
-- 2=42
|
||||||
|
[] | s `matches` positionalAssignmentRe ->
|
||||||
|
if "0=" `isPrefixOf` s
|
||||||
|
then
|
||||||
|
assign0Msg litId $ fixWith [replaceStart litId params 1 "BASH_ARGV0"]
|
||||||
|
else
|
||||||
|
positionalMsg litId
|
||||||
|
|
||||||
|
-- 9foo=42
|
||||||
|
[] | isLeadingNumberVar s ->
|
||||||
|
leadingNumberMsg (getId cmd)
|
||||||
|
|
||||||
|
-- var${foo}x=42
|
||||||
|
(_:_) | mayBeVariableName leading && (all isVariableChar $ takeWhile (/= '=') s) ->
|
||||||
|
indirectionMsg (getId cmd)
|
||||||
|
|
||||||
|
_ -> genericMsg (getId cmd)
|
||||||
|
|
||||||
|
|
||||||
|
prop_checkSecondArgIsComparison1 = verify checkSecondArgIsComparison "foo = $bar"
|
||||||
|
prop_checkSecondArgIsComparison2 = verify checkSecondArgIsComparison "$foo = $bar"
|
||||||
|
prop_checkSecondArgIsComparison3 = verify checkSecondArgIsComparison "2f == $bar"
|
||||||
|
prop_checkSecondArgIsComparison4 = verify checkSecondArgIsComparison "'var' =$bar"
|
||||||
|
prop_checkSecondArgIsComparison5 = verify checkSecondArgIsComparison "foo ='$bar'"
|
||||||
|
prop_checkSecondArgIsComparison6 = verify checkSecondArgIsComparison "$foo =$bar"
|
||||||
|
prop_checkSecondArgIsComparison7 = verify checkSecondArgIsComparison "2f ==$bar"
|
||||||
|
prop_checkSecondArgIsComparison8 = verify checkSecondArgIsComparison "'var' =$bar"
|
||||||
|
prop_checkSecondArgIsComparison9 = verify checkSecondArgIsComparison "var += $(foo)"
|
||||||
|
prop_checkSecondArgIsComparison10 = verify checkSecondArgIsComparison "var +=$(foo)"
|
||||||
|
checkSecondArgIsComparison _ t =
|
||||||
|
case t of
|
||||||
|
T_SimpleCommand _ _ (lhs:arg:_) -> sequence_ $ do
|
||||||
|
argString <- getLeadingUnquotedString arg
|
||||||
|
case argString of
|
||||||
|
'=':'=':'=':'=':_ -> Nothing -- Don't warn about `echo ======` and such
|
||||||
|
'+':'=':_ ->
|
||||||
|
return $ err (getId t) 2285 $
|
||||||
|
"Remove spaces around += to assign (or quote '+=' if literal)."
|
||||||
|
'=':'=':_ ->
|
||||||
|
return $ err (getId t) 2284 $
|
||||||
|
"Use [ x = y ] to compare values (or quote '==' if literal)."
|
||||||
|
'=':_ ->
|
||||||
|
return $ err (getId t) 2283 $
|
||||||
|
"Use [ ] to compare values, or remove spaces around = to assign (or quote '=' if literal)."
|
||||||
|
_ -> Nothing
|
||||||
|
_ -> return ()
|
||||||
|
|
||||||
return []
|
return []
|
||||||
runTests = $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |])
|
runTests = $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |])
|
||||||
|
|
|
@ -2850,15 +2850,13 @@ readAssignmentWordExt lenient = called "variable assignment" $ do
|
||||||
(id, variable, op, indices) <- try $ do
|
(id, variable, op, indices) <- try $ do
|
||||||
start <- startSpan
|
start <- startSpan
|
||||||
pos <- getPosition
|
pos <- getPosition
|
||||||
|
-- Check for a leading $ at parse time, to warn for $foo=(bar) which
|
||||||
|
-- would otherwise cause a parse failure so it can't be checked later.
|
||||||
leadingDollarPos <-
|
leadingDollarPos <-
|
||||||
if lenient
|
if lenient
|
||||||
then optionMaybe $ getSpanPositionsFor (char '$')
|
then optionMaybe $ getSpanPositionsFor (char '$')
|
||||||
else return Nothing
|
else return Nothing
|
||||||
variable <- readVariableName
|
variable <- readVariableName
|
||||||
middleDollarPos <-
|
|
||||||
if lenient
|
|
||||||
then optionMaybe $ getSpanPositionsFor readNormalDollar
|
|
||||||
else return Nothing
|
|
||||||
indices <- many readArrayIndex
|
indices <- many readArrayIndex
|
||||||
hasLeftSpace <- fmap (not . null) spacing
|
hasLeftSpace <- fmap (not . null) spacing
|
||||||
opStart <- getPosition
|
opStart <- getPosition
|
||||||
|
@ -2866,20 +2864,12 @@ readAssignmentWordExt lenient = called "variable assignment" $ do
|
||||||
op <- readAssignmentOp
|
op <- readAssignmentOp
|
||||||
opEnd <- getPosition
|
opEnd <- getPosition
|
||||||
|
|
||||||
when (isJust leadingDollarPos || isJust middleDollarPos || hasLeftSpace) $ do
|
when (isJust leadingDollarPos || hasLeftSpace) $ do
|
||||||
|
hasParen <- isFollowedBy (spacing >> char '(')
|
||||||
|
when hasParen $
|
||||||
sequence_ $ do
|
sequence_ $ do
|
||||||
(l, r) <- leadingDollarPos
|
(l, r) <- leadingDollarPos
|
||||||
return $ parseProblemAtWithEnd l r ErrorC 1066 "Don't use $ on the left side of assignments."
|
return $ parseProblemAtWithEnd l r ErrorC 1066 "Don't use $ on the left side of assignments."
|
||||||
sequence_ $ do
|
|
||||||
(l, r) <- middleDollarPos
|
|
||||||
return $ parseProblemAtWithEnd l r ErrorC 1067 "For indirection, use arrays, declare \"var$n=value\", or (for sh) read/eval."
|
|
||||||
when hasLeftSpace $ do
|
|
||||||
parseProblemAtWithEnd opStart opEnd ErrorC 1068 $
|
|
||||||
"Don't put spaces around the "
|
|
||||||
++ (if op == Append
|
|
||||||
then "+= when appending"
|
|
||||||
else "= in assignments")
|
|
||||||
++ " (or quote to make it literal)."
|
|
||||||
|
|
||||||
-- Fail so that this is not parsed as an assignment.
|
-- Fail so that this is not parsed as an assignment.
|
||||||
fail ""
|
fail ""
|
||||||
|
|
Loading…
Reference in New Issue