From ca37794b7c7a1ff420ce8008ecbacf89eb253100 Mon Sep 17 00:00:00 2001 From: Vidar Holen Date: Sun, 14 Jun 2015 14:30:11 -0700 Subject: [PATCH] Allow empty scripts, $()s and <()s. Also improves related error messages. --- ShellCheck/Parser.hs | 54 +++++++++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/ShellCheck/Parser.hs b/ShellCheck/Parser.hs index f8522b4..4f3a338 100644 --- a/ShellCheck/Parser.hs +++ b/ShellCheck/Parser.hs @@ -771,14 +771,14 @@ readDollarBracedLiteral = do prop_readProcSub1 = isOk readProcSub "<(echo test | wc -l)" prop_readProcSub2 = isOk readProcSub "<( if true; then true; fi )" +prop_readProcSub3 = isOk readProcSub "<( # nothing here \n)" readProcSub = called "process substitution" $ do id <- getNextId dir <- try $ do x <- oneOf "<>" char '(' return [x] - allspacing - list <- readCompoundList + list <- readCompoundListOrEmpty allspacing char ')' return $ T_ProcSub id dir list @@ -843,19 +843,23 @@ readBackTicked = called "backtick expansion" $ do suggestForgotClosingQuote startPos endPos "backtick expansion" -- Result positions may be off due to escapes - result <- subParse subStart readTermOrNone (unEscape subString) + result <- subParse subStart subParser (unEscape subString) return $ T_Backticked id result where unEscape [] = [] unEscape ('\\':x:rest) | x `elem` "$`\\" = x : unEscape rest unEscape ('\\':'\n':rest) = unEscape rest unEscape (c:rest) = c : unEscape rest + subParser = do + cmds <- readCompoundListOrEmpty + verifyEof + return cmds backtick = disregard (char '`') <|> do pos <- getPosition char '´' parseProblemAt pos ErrorC 1077 - "For command expansion, the tick should slant left (` vs ´)." + "For command expansion, the tick should slant left (` vs ´). Use $(..) instead." subParse pos parser input = do lastPosition <- getPosition @@ -1132,11 +1136,13 @@ readDollarBraced = called "parameter expansion" $ do char '}' return $ T_DollarBraced id word -prop_readDollarExpansion = isOk readDollarExpansion "$(echo foo; ls\n)" +prop_readDollarExpansion1= isOk readDollarExpansion "$(echo foo; ls\n)" +prop_readDollarExpansion2= isOk readDollarExpansion "$( )" +prop_readDollarExpansion3= isOk readDollarExpansion "$( command \n#comment \n)" readDollarExpansion = called "command expansion" $ do id <- getNextId try (string "$(") - cmds <- readCompoundList + cmds <- readCompoundListOrEmpty char ')' "end of $(..) expression" return $ T_DollarExpansion id cmds @@ -1845,6 +1851,9 @@ readCompoundCommand = do readCompoundList = readTerm +readCompoundListOrEmpty = do + allspacing + readTerm <|> return [] readCmdPrefix = many1 (readIoRedirect <|> readAssignmentWord) readCmdSuffix = many1 (readIoRedirect <|> readCmdWord) @@ -2048,10 +2057,25 @@ readShebang = do parseProblemAt pos ErrorC 1084 "Use #!, not !#, for the shebang." +verifyEof = eof <|> choice [ + ifParsable g_Lparen $ + parseProblem ErrorC 1088 "Parsing stopped here. Invalid use of parentheses?", + + ifParsable readKeyword $ + parseProblem ErrorC 1089 "Parsing stopped here. Is this keyword correctly matched up?", + + parseProblem ErrorC 1070 "Parsing stopped here. Mismatched keywords or invalid parentheses?" + ] + where + ifParsable p action = do + try (lookAhead p) + action + prop_readScript1 = isOk readScript "#!/bin/bash\necho hello world\n" prop_readScript2 = isWarning readScript "#!/bin/bash\r\necho hello world\n" prop_readScript3 = isWarning readScript "#!/bin/bash\necho hello\xA0world" prop_readScript4 = isWarning readScript "#!/usr/bin/perl\nfoo=(" +prop_readScript5 = isOk readScript "#!/bin/bash\n#This is an empty script\n\n" readScript = do id <- getNextId pos <- getPosition @@ -2062,19 +2086,13 @@ readScript = do sb <- option "" readShebang verifyShell pos (getShell sb) if isValidShell (getShell sb) /= Just False - then - do { - allspacing; - commands <- readTerm; - eof <|> parseProblem ErrorC 1070 "Parsing stopped here because of parsing errors."; - return $ T_Script id sb commands; - } <|> do { - parseProblem WarningC 1014 "Couldn't read any commands."; + then do + commands <- readCompoundListOrEmpty + verifyEof + return $ T_Script id sb commands + else do + many anyChar return $ T_Script id sb [] - } - else do - many anyChar - return $ T_Script id sb []; where basename s = reverse . takeWhile (/= '/') . reverse $ s -- GitLab