Parse assignments according to spec (fixes #2022)

This commit is contained in:
Vidar Holen 2020-08-23 18:46:13 -07:00
parent fb89cdf4ad
commit c9be7ab2eb
2 changed files with 60 additions and 36 deletions

View file

@ -11,6 +11,7 @@
is still purely cosmetic and does not allow ShellCheck to continue. is still purely cosmetic and does not allow ShellCheck to continue.
### Changed ### Changed
- Assignments are now parsed to spec, without leniency for leading $ or spaces
- 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

View file

@ -212,6 +212,12 @@ endSpan (IncompleteInterval start) = do
id <- getNextIdBetween start endPos id <- getNextIdBetween start endPos
return id return id
getSpanPositionsFor m = do
start <- getPosition
m
end <- getPosition
return (start, end)
addToHereDocMap id list = do addToHereDocMap id list = do
state <- getState state <- getState
let map = hereDocMap state let map = hereDocMap state
@ -2788,17 +2794,13 @@ readLiteralForParser parser = do
prop_readAssignmentWord = isOk readAssignmentWord "a=42" prop_readAssignmentWord = isOk readAssignmentWord "a=42"
prop_readAssignmentWord2 = isOk readAssignmentWord "b=(1 2 3)" prop_readAssignmentWord2 = isOk readAssignmentWord "b=(1 2 3)"
prop_readAssignmentWord3 = isWarning readAssignmentWord "$b = 13"
prop_readAssignmentWord4 = isWarning readAssignmentWord "b = $(lol)"
prop_readAssignmentWord5 = isOk readAssignmentWord "b+=lol" prop_readAssignmentWord5 = isOk readAssignmentWord "b+=lol"
prop_readAssignmentWord6 = isWarning readAssignmentWord "b += (1 2 3)"
prop_readAssignmentWord7 = isOk readAssignmentWord "a[3$n'']=42" prop_readAssignmentWord7 = isOk readAssignmentWord "a[3$n'']=42"
prop_readAssignmentWord8 = isOk readAssignmentWord "a[4''$(cat foo)]=42" prop_readAssignmentWord8 = isOk readAssignmentWord "a[4''$(cat foo)]=42"
prop_readAssignmentWord9 = isOk readAssignmentWord "IFS= " prop_readAssignmentWord9 = isOk readAssignmentWord "IFS= "
prop_readAssignmentWord9a= isOk readAssignmentWord "foo=" prop_readAssignmentWord9a= isOk readAssignmentWord "foo="
prop_readAssignmentWord9b= isOk readAssignmentWord "foo= " prop_readAssignmentWord9b= isOk readAssignmentWord "foo= "
prop_readAssignmentWord9c= isOk readAssignmentWord "foo= #bar" prop_readAssignmentWord9c= isOk readAssignmentWord "foo= #bar"
prop_readAssignmentWord10= isWarning readAssignmentWord "foo$n=42"
prop_readAssignmentWord11= isOk readAssignmentWord "foo=([a]=b [c] [d]= [e f )" prop_readAssignmentWord11= isOk readAssignmentWord "foo=([a]=b [c] [d]= [e f )"
prop_readAssignmentWord12= isOk readAssignmentWord "a[b <<= 3 + c]='thing'" prop_readAssignmentWord12= isOk readAssignmentWord "a[b <<= 3 + c]='thing'"
prop_readAssignmentWord13= isOk readAssignmentWord "var=( (1 2) (3 4) )" prop_readAssignmentWord13= isOk readAssignmentWord "var=( (1 2) (3 4) )"
@ -2806,52 +2808,73 @@ prop_readAssignmentWord14= isOk readAssignmentWord "var=( 1 [2]=(3 4) )"
prop_readAssignmentWord15= isOk readAssignmentWord "var=(1 [2]=(3 4))" prop_readAssignmentWord15= isOk readAssignmentWord "var=(1 [2]=(3 4))"
readAssignmentWord = readAssignmentWordExt True readAssignmentWord = readAssignmentWordExt True
readWellFormedAssignment = readAssignmentWordExt False readWellFormedAssignment = readAssignmentWordExt False
readAssignmentWordExt lenient = try $ do readAssignmentWordExt lenient = called "variable assignment" $ do
-- Parse up to and including the = in a 'try'
(id, variable, op, indices) <- try $ do
start <- startSpan start <- startSpan
pos <- getPosition pos <- getPosition
when lenient $ leadingDollarPos <-
optional (char '$' >> parseNote ErrorC 1066 "Don't use $ on the left side of assignments.") if lenient
then optionMaybe $ getSpanPositionsFor (char '$')
else return Nothing
variable <- readVariableName variable <- readVariableName
when lenient $ middleDollarPos <-
optional (readNormalDollar >> parseNoteAt pos ErrorC if lenient
1067 "For indirection, use arrays, declare \"var$n=value\", or (for sh) read/eval.") then optionMaybe $ getSpanPositionsFor readNormalDollar
else return Nothing
indices <- many readArrayIndex indices <- many readArrayIndex
hasLeftSpace <- fmap (not . null) spacing hasLeftSpace <- fmap (not . null) spacing
pos <- getPosition opStart <- getPosition
id <- endSpan start id <- endSpan start
op <- readAssignmentOp op <- readAssignmentOp
hasRightSpace <- fmap (not . null) spacing opEnd <- getPosition
isEndOfCommand <- fmap isJust $ optionMaybe (try . lookAhead $ (void (oneOf "\r\n;&|)") <|> eof))
if not hasLeftSpace && (hasRightSpace || isEndOfCommand) when (isJust leadingDollarPos || isJust middleDollarPos || hasLeftSpace) $ do
then do sequence_ $ do
when (variable /= "IFS" && hasRightSpace && not isEndOfCommand) $ (l, r) <- leadingDollarPos
parseNoteAt pos WarningC 1007 return $ parseProblemAtWithEnd l r ErrorC 1066 "Don't use $ on the left side of assignments."
"Remove space after = if trying to assign a value (for empty string, use var='' ... )." sequence_ $ do
value <- readEmptyLiteral (l, r) <- middleDollarPos
return $ T_Assignment id op variable indices value return $ parseProblemAtWithEnd l r ErrorC 1067 "For indirection, use arrays, declare \"var$n=value\", or (for sh) read/eval."
else do when hasLeftSpace $ do
when (hasLeftSpace || hasRightSpace) $ parseProblemAtWithEnd opStart opEnd ErrorC 1068 $
parseNoteAt pos ErrorC 1068 $
"Don't put spaces around the " "Don't put spaces around the "
++ (if op == Append ++ (if op == Append
then "+= when appending" then "+= when appending"
else "= in assignments") else "= in assignments")
++ " (or quote to make it literal)." ++ " (or quote to make it literal)."
-- Fail so that this is not parsed as an assignment.
fail ""
-- At this point we know for sure.
return (id, variable, op, indices)
rightPosStart <- getPosition
hasRightSpace <- fmap (not . null) spacing
rightPosEnd <- getPosition
isEndOfCommand <- fmap isJust $ optionMaybe (try . lookAhead $ (void (oneOf "\r\n;&|)") <|> eof))
if hasRightSpace || isEndOfCommand
then do
when (variable /= "IFS" && hasRightSpace && not isEndOfCommand) $ do
parseProblemAtWithEnd rightPosStart rightPosEnd WarningC 1007
"Remove space after = if trying to assign a value (for empty string, use var='' ... )."
value <- readEmptyLiteral
return $ T_Assignment id op variable indices value
else do
optional $ do
lookAhead $ char '='
parseProblem ErrorC 1097 "Unexpected ==. For assignment, use =. For comparison, use [/[[. Or quote for literal string."
value <- readArray <|> readNormalWord value <- readArray <|> readNormalWord
spacing spacing
return $ T_Assignment id op variable indices value return $ T_Assignment id op variable indices value
where where
readAssignmentOp = do readAssignmentOp = do
pos <- getPosition -- This is probably some kind of ascii art border
unexpecting "" $ string "===" unexpecting "===" (string "===")
choice [ choice [
string "+=" >> return Append, string "+=" >> return Append,
do
try (string "==")
parseProblemAt pos ErrorC 1097
"Unexpected ==. For assignment, use =. For comparison, use [/[[."
return Assign,
string "=" >> return Assign string "=" >> return Assign
] ]