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
start <- startSpan -- Parse up to and including the = in a 'try'
pos <- getPosition (id, variable, op, indices) <- try $ do
when lenient $ start <- startSpan
optional (char '$' >> parseNote ErrorC 1066 "Don't use $ on the left side of assignments.") pos <- getPosition
variable <- readVariableName leadingDollarPos <-
when lenient $ if lenient
optional (readNormalDollar >> parseNoteAt pos ErrorC then optionMaybe $ getSpanPositionsFor (char '$')
1067 "For indirection, use arrays, declare \"var$n=value\", or (for sh) read/eval.") else return Nothing
indices <- many readArrayIndex variable <- readVariableName
hasLeftSpace <- fmap (not . null) spacing middleDollarPos <-
pos <- getPosition if lenient
id <- endSpan start then optionMaybe $ getSpanPositionsFor readNormalDollar
op <- readAssignmentOp else return Nothing
indices <- many readArrayIndex
hasLeftSpace <- fmap (not . null) spacing
opStart <- getPosition
id <- endSpan start
op <- readAssignmentOp
opEnd <- getPosition
when (isJust leadingDollarPos || isJust middleDollarPos || hasLeftSpace) $ do
sequence_ $ do
(l, r) <- leadingDollarPos
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 ""
-- At this point we know for sure.
return (id, variable, op, indices)
rightPosStart <- getPosition
hasRightSpace <- fmap (not . null) spacing hasRightSpace <- fmap (not . null) spacing
rightPosEnd <- getPosition
isEndOfCommand <- fmap isJust $ optionMaybe (try . lookAhead $ (void (oneOf "\r\n;&|)") <|> eof)) isEndOfCommand <- fmap isJust $ optionMaybe (try . lookAhead $ (void (oneOf "\r\n;&|)") <|> eof))
if not hasLeftSpace && (hasRightSpace || isEndOfCommand)
if hasRightSpace || isEndOfCommand
then do then do
when (variable /= "IFS" && hasRightSpace && not isEndOfCommand) $ when (variable /= "IFS" && hasRightSpace && not isEndOfCommand) $ do
parseNoteAt pos WarningC 1007 parseProblemAtWithEnd rightPosStart rightPosEnd WarningC 1007
"Remove space after = if trying to assign a value (for empty string, use var='' ... )." "Remove space after = if trying to assign a value (for empty string, use var='' ... )."
value <- readEmptyLiteral value <- readEmptyLiteral
return $ T_Assignment id op variable indices value return $ T_Assignment id op variable indices value
else do else do
when (hasLeftSpace || hasRightSpace) $ optional $ do
parseNoteAt pos ErrorC 1068 $ lookAhead $ char '='
"Don't put spaces around the " parseProblem ErrorC 1097 "Unexpected ==. For assignment, use =. For comparison, use [/[[. Or quote for literal string."
++ (if op == Append
then "+= when appending"
else "= in assignments")
++ " (or quote to make it literal)."
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
] ]