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.
### 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
- 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
return id
getSpanPositionsFor m = do
start <- getPosition
m
end <- getPosition
return (start, end)
addToHereDocMap id list = do
state <- getState
let map = hereDocMap state
@ -2788,17 +2794,13 @@ readLiteralForParser parser = do
prop_readAssignmentWord = isOk readAssignmentWord "a=42"
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_readAssignmentWord6 = isWarning readAssignmentWord "b += (1 2 3)"
prop_readAssignmentWord7 = isOk readAssignmentWord "a[3$n'']=42"
prop_readAssignmentWord8 = isOk readAssignmentWord "a[4''$(cat foo)]=42"
prop_readAssignmentWord9 = isOk readAssignmentWord "IFS= "
prop_readAssignmentWord9a= isOk readAssignmentWord "foo="
prop_readAssignmentWord9b= isOk readAssignmentWord "foo= "
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_readAssignmentWord12= isOk readAssignmentWord "a[b <<= 3 + c]='thing'"
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))"
readAssignmentWord = readAssignmentWordExt True
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
pos <- getPosition
when lenient $
optional (char '$' >> parseNote ErrorC 1066 "Don't use $ on the left side of assignments.")
leadingDollarPos <-
if lenient
then optionMaybe $ getSpanPositionsFor (char '$')
else return Nothing
variable <- readVariableName
when lenient $
optional (readNormalDollar >> parseNoteAt pos ErrorC
1067 "For indirection, use arrays, declare \"var$n=value\", or (for sh) read/eval.")
middleDollarPos <-
if lenient
then optionMaybe $ getSpanPositionsFor readNormalDollar
else return Nothing
indices <- many readArrayIndex
hasLeftSpace <- fmap (not . null) spacing
pos <- getPosition
opStart <- getPosition
id <- endSpan start
op <- readAssignmentOp
hasRightSpace <- fmap (not . null) spacing
isEndOfCommand <- fmap isJust $ optionMaybe (try . lookAhead $ (void (oneOf "\r\n;&|)") <|> eof))
if not hasLeftSpace && (hasRightSpace || isEndOfCommand)
then do
when (variable /= "IFS" && hasRightSpace && not isEndOfCommand) $
parseNoteAt pos 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
when (hasLeftSpace || hasRightSpace) $
parseNoteAt pos ErrorC 1068 $
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
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
spacing
return $ T_Assignment id op variable indices value
where
readAssignmentOp = do
pos <- getPosition
unexpecting "" $ string "==="
-- This is probably some kind of ascii art border
unexpecting "===" (string "===")
choice [
string "+=" >> return Append,
do
try (string "==")
parseProblemAt pos ErrorC 1097
"Unexpected ==. For assignment, use =. For comparison, use [/[[."
return Assign,
string "=" >> return Assign
]