"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "src/ShellCheck/Parser.hs" between
shellcheck-0.8.0.tar.gz and shellcheck-0.9.0.tar.gz

About: ShellCheck is a static analysis and linting tool for sh/bash scripts (written in Haskell).

Parser.hs  (shellcheck-0.8.0):Parser.hs  (shellcheck-0.9.0)
{- {-
Copyright 2012-2021 Vidar Holen Copyright 2012-2022 Vidar Holen
This file is part of ShellCheck. This file is part of ShellCheck.
https://www.shellcheck.net https://www.shellcheck.net
ShellCheck is free software: you can redistribute it and/or modify ShellCheck is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
ShellCheck is distributed in the hope that it will be useful, ShellCheck is distributed in the hope that it will be useful,
skipping to change at line 30 skipping to change at line 30
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE NoMonomorphismRestriction #-} {-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiWayIf #-} {-# LANGUAGE MultiWayIf #-}
module ShellCheck.Parser (parseScript, runTests) where module ShellCheck.Parser (parseScript, runTests) where
import ShellCheck.AST import ShellCheck.AST
import ShellCheck.ASTLib hiding (runTests) import ShellCheck.ASTLib hiding (runTests)
import ShellCheck.Data import ShellCheck.Data
import ShellCheck.Interface import ShellCheck.Interface
import ShellCheck.Prelude
import Control.Applicative ((<*), (*>)) import Control.Applicative ((<*), (*>))
import Control.Monad import Control.Monad
import Control.Monad.Identity import Control.Monad.Identity
import Control.Monad.Trans import Control.Monad.Trans
import Data.Char import Data.Char
import Data.Functor import Data.Functor
import Data.List (isPrefixOf, isInfixOf, isSuffixOf, partition, sortBy, intercal ate, nub, find) import Data.List (isPrefixOf, isInfixOf, isSuffixOf, partition, sortBy, intercal ate, nub, find)
import Data.Maybe import Data.Maybe
import Data.Monoid import Data.Monoid
import Debug.Trace -- STRIP
import GHC.Exts (sortWith) import GHC.Exts (sortWith)
import Prelude hiding (readList) import Prelude hiding (readList)
import System.IO import System.IO
import Text.Parsec hiding (runParser, (<?>)) import Text.Parsec hiding (runParser, (<?>))
import Text.Parsec.Error import Text.Parsec.Error
import Text.Parsec.Pos import Text.Parsec.Pos
import qualified Control.Monad.Reader as Mr import qualified Control.Monad.Reader as Mr
import qualified Control.Monad.State as Ms import qualified Control.Monad.State as Ms
import qualified Data.Map as Map import qualified Data.Map.Strict as Map
import Test.QuickCheck.All (quickCheckAll) import Test.QuickCheck.All (quickCheckAll)
type SCBase m = Mr.ReaderT (Environment m) (Ms.StateT SystemState m) type SCBase m = Mr.ReaderT (Environment m) (Ms.StateT SystemState m)
type SCParser m v = ParsecT String UserState (SCBase m) v type SCParser m v = ParsecT String UserState (SCBase m) v
backslash :: Monad m => SCParser m Char backslash :: Monad m => SCParser m Char
backslash = char '\\' backslash = char '\\'
linefeed :: Monad m => SCParser m Char linefeed :: Monad m => SCParser m Char
linefeed = do linefeed = do
skipping to change at line 213 skipping to change at line 213
case list of case list of
[] -> do [] -> do
pos <- getPosition pos <- getPosition
getNextIdBetween pos pos getNextIdBetween pos pos
(h:_) -> (h:_) ->
getNextIdSpanningTokens h (last list) getNextIdSpanningTokens h (last list)
-- Get the span covered by an id -- Get the span covered by an id
getSpanForId :: Monad m => Id -> SCParser m (SourcePos, SourcePos) getSpanForId :: Monad m => Id -> SCParser m (SourcePos, SourcePos)
getSpanForId id = getSpanForId id =
Map.findWithDefault (error "Internal error: no position for id. Please repor t!") id <$> Map.findWithDefault (error $ pleaseReport "no parser span for id") id <$>
getMap getMap
-- Create a new id with the same span as an existing one -- Create a new id with the same span as an existing one
getNewIdFor :: Monad m => Id -> SCParser m Id getNewIdFor :: Monad m => Id -> SCParser m Id
getNewIdFor id = getSpanForId id >>= uncurry getNextIdBetween getNewIdFor id = getSpanForId id >>= uncurry getNextIdBetween
data IncompleteInterval = IncompleteInterval SourcePos data IncompleteInterval = IncompleteInterval SourcePos
startSpan = IncompleteInterval <$> getPosition startSpan = IncompleteInterval <$> getPosition
skipping to change at line 457 skipping to change at line 457
parsecBracket (getCurrentContexts <* setCurrentContexts contexts) parsecBracket (getCurrentContexts <* setCurrentContexts contexts)
setCurrentContexts setCurrentContexts
(const p) (const p)
withContext entry p = parsecBracket (pushContext entry) (const popContext) (cons t p) withContext entry p = parsecBracket (pushContext entry) (const popContext) (cons t p)
called s p = do called s p = do
pos <- getPosition pos <- getPosition
withContext (ContextName pos s) p withContext (ContextName pos s) p
withAnnotations anns = withAnnotations anns p =
withContext (ContextAnnotation anns) if null anns then p else withContext (ContextAnnotation anns) p
readConditionContents single = readConditionContents single =
readCondContents `attempting` lookAhead (do readCondContents `attempting` lookAhead (do
pos <- getPosition pos <- getPosition
s <- readVariableName s <- readVariableName
spacing1 spacing1
when (s `elem` commonCommands) $ when (s `elem` commonCommands) $
parseProblemAt pos WarningC 1014 "Use 'if cm d; then ..' to check exit code, or 'if [[ $(cmd) == .. ]]' to check output.") parseProblemAt pos WarningC 1014 "Use 'if cm d; then ..' to check exit code, or 'if [[ $(cmd) == .. ]]' to check output.")
where where
skipping to change at line 555 skipping to change at line 555
pos <- getPosition pos <- getPosition
oneOf "\x058A\x05BE\x2010\x2011\x2012\x2013\x2014\x2015\xFE63\xFF0D" oneOf "\x058A\x05BE\x2010\x2011\x2012\x2013\x2014\x2015\xFE63\xFF0D"
parseProblemAt pos ErrorC 1100 parseProblemAt pos ErrorC 1100
"This is a unicode dash. Delete and retype as ASCII minus." "This is a unicode dash. Delete and retype as ASCII minus."
return '-' return '-'
readCondWord = do readCondWord = do
notFollowedBy2 (try (spacing >> string "]")) notFollowedBy2 (try (spacing >> string "]"))
x <- readNormalWord x <- readNormalWord
pos <- getPosition pos <- getPosition
when (endedWith "]" x && notArrayIndex x) $ do when (notArrayIndex x && endedWith "]" x && not (x `containsLiteral` "[" )) $ do
parseProblemAt pos ErrorC 1020 $ parseProblemAt pos ErrorC 1020 $
"You need a space before the " ++ (if single then "]" else "]]") ++ "." "You need a space before the " ++ (if single then "]" else "]]") ++ "."
fail "Missing space before ]" fail "Missing space before ]"
when (single && endedWith ")" x) $ do when (single && endedWith ")" x) $ do
parseProblemAt pos ErrorC 1021 parseProblemAt pos ErrorC 1021
"You need a space before the \\)" "You need a space before the \\)"
fail "Missing space before )" fail "Missing space before )"
void spacing void spacing
return x return x
where endedWith str (T_NormalWord id s@(_:_)) = where endedWith str (T_NormalWord id s@(_:_)) =
case last s of T_Literal id s -> str `isSuffixOf` s case last s of T_Literal id s -> str `isSuffixOf` s
_ -> False _ -> False
endedWith _ _ = False endedWith _ _ = False
notArrayIndex (T_NormalWord id s@(_:T_Literal _ t:_)) = t /= "[" notArrayIndex (T_NormalWord id s@(_:T_Literal _ t:_)) = t /= "["
notArrayIndex _ = True notArrayIndex _ = True
containsLiteral x s = s `isInfixOf` onlyLiteralString x
readCondAndOp = readAndOrOp TC_And "&&" False <|> readAndOrOp TC_And "-a" Tr ue readCondAndOp = readAndOrOp TC_And "&&" False <|> readAndOrOp TC_And "-a" Tr ue
readCondOrOp = do readCondOrOp = do
optional guardArithmetic optional guardArithmetic
readAndOrOp TC_Or "||" False <|> readAndOrOp TC_Or "-o" True readAndOrOp TC_Or "||" False <|> readAndOrOp TC_Or "-o" True
readAndOrOp node op requiresSpacing = do readAndOrOp node op requiresSpacing = do
optional $ lookAhead weirdDash optional $ lookAhead weirdDash
start <- startSpan start <- startSpan
skipping to change at line 714 skipping to change at line 715
prop_a1 = isOk readArithmeticContents " n++ + ++c" prop_a1 = isOk readArithmeticContents " n++ + ++c"
prop_a2 = isOk readArithmeticContents "$N*4-(3,2)" prop_a2 = isOk readArithmeticContents "$N*4-(3,2)"
prop_a3 = isOk readArithmeticContents "n|=2<<1" prop_a3 = isOk readArithmeticContents "n|=2<<1"
prop_a4 = isOk readArithmeticContents "n &= 2 **3" prop_a4 = isOk readArithmeticContents "n &= 2 **3"
prop_a5 = isOk readArithmeticContents "1 |= 4 && n >>= 4" prop_a5 = isOk readArithmeticContents "1 |= 4 && n >>= 4"
prop_a6 = isOk readArithmeticContents " 1 | 2 ||3|4" prop_a6 = isOk readArithmeticContents " 1 | 2 ||3|4"
prop_a7 = isOk readArithmeticContents "3*2**10" prop_a7 = isOk readArithmeticContents "3*2**10"
prop_a8 = isOk readArithmeticContents "3" prop_a8 = isOk readArithmeticContents "3"
prop_a9 = isOk readArithmeticContents "a^!-b" prop_a9 = isOk readArithmeticContents "a^!-b"
prop_a10= isOk readArithmeticContents "! $?" prop_a10 = isOk readArithmeticContents "! $?"
prop_a11= isOk readArithmeticContents "10#08 * 16#f" prop_a11 = isOk readArithmeticContents "10#08 * 16#f"
prop_a12= isOk readArithmeticContents "\"$((3+2))\" + '37'" prop_a12 = isOk readArithmeticContents "\"$((3+2))\" + '37'"
prop_a13= isOk readArithmeticContents "foo[9*y+x]++" prop_a13 = isOk readArithmeticContents "foo[9*y+x]++"
prop_a14= isOk readArithmeticContents "1+`echo 2`" prop_a14 = isOk readArithmeticContents "1+`echo 2`"
prop_a15= isOk readArithmeticContents "foo[`echo foo | sed s/foo/4/g` * 3] + 4" prop_a15 = isOk readArithmeticContents "foo[`echo foo | sed s/foo/4/g` * 3] + 4"
prop_a16= isOk readArithmeticContents "$foo$bar" prop_a16 = isOk readArithmeticContents "$foo$bar"
prop_a17= isOk readArithmeticContents "i<(0+(1+1))" prop_a17 = isOk readArithmeticContents "i<(0+(1+1))"
prop_a18= isOk readArithmeticContents "a?b:c" prop_a18 = isOk readArithmeticContents "a?b:c"
prop_a19= isOk readArithmeticContents "\\\n3 +\\\n 2" prop_a19 = isOk readArithmeticContents "\\\n3 +\\\n 2"
prop_a20= isOk readArithmeticContents "a ? b ? c : d : e" prop_a20 = isOk readArithmeticContents "a ? b ? c : d : e"
prop_a21= isOk readArithmeticContents "a ? b : c ? d : e" prop_a21 = isOk readArithmeticContents "a ? b : c ? d : e"
prop_a22= isOk readArithmeticContents "!!a" prop_a22 = isOk readArithmeticContents "!!a"
prop_a23= isOk readArithmeticContents "~0" prop_a23 = isOk readArithmeticContents "~0"
readArithmeticContents :: Monad m => SCParser m Token readArithmeticContents :: Monad m => SCParser m Token
readArithmeticContents = readArithmeticContents =
readSequence readSequence
where where
spacing = spacing =
let lf = try (string "\\\n") >> return '\n' let lf = try (string "\\\n") >> return '\n'
in many (whitespace <|> lf) in many (whitespace <|> lf)
splitBy x ops = chainl1 x (readBinary ops) splitBy x ops = chainl1 x (readBinary ops)
readBinary ops = readComboOp ops TA_Binary readBinary ops = readComboOp ops TA_Binary
skipping to change at line 816 skipping to change at line 817
readBraced, readBraced,
readUnquotedBackTicked, readUnquotedBackTicked,
literal "#", literal "#",
readNormalLiteral "+-*/=%^,]?:" readNormalLiteral "+-*/=%^,]?:"
] ]
id <- endSpan start id <- endSpan start
spacing spacing
return $ TA_Expansion id pieces return $ TA_Expansion id pieces
readGroup = do readGroup = do
start <- startSpan
char '(' char '('
s <- readSequence s <- readSequence
char ')' char ')'
id <- endSpan start
spacing spacing
return s return $ TA_Parentesis id s
readArithTerm = readGroup <|> readVariable <|> readExpansion readArithTerm = readGroup <|> readVariable <|> readExpansion
readSequence = do readSequence = do
spacing spacing
start <- startSpan start <- startSpan
l <- readAssignment `sepBy` (char ',' >> spacing) l <- readAssignment `sepBy` (char ',' >> spacing)
id <- endSpan start id <- endSpan start
return $ TA_Sequence id l return $ TA_Sequence id l
skipping to change at line 918 skipping to change at line 921
prop_readCondition3 = isOk readCondition "[[ $c = [[:alpha:].~-] ]]" prop_readCondition3 = isOk readCondition "[[ $c = [[:alpha:].~-] ]]"
prop_readCondition4 = isOk readCondition "[[ $c =~ *foo* ]]" prop_readCondition4 = isOk readCondition "[[ $c =~ *foo* ]]"
prop_readCondition5 = isOk readCondition "[[ $c =~ f( ]] )* ]]" prop_readCondition5 = isOk readCondition "[[ $c =~ f( ]] )* ]]"
prop_readCondition5a = isOk readCondition "[[ $c =~ a(b) ]]" prop_readCondition5a = isOk readCondition "[[ $c =~ a(b) ]]"
prop_readCondition5b = isOk readCondition "[[ $c =~ f( ($var ]]) )* ]]" prop_readCondition5b = isOk readCondition "[[ $c =~ f( ($var ]]) )* ]]"
prop_readCondition6 = isOk readCondition "[[ $c =~ ^[yY]$ ]]" prop_readCondition6 = isOk readCondition "[[ $c =~ ^[yY]$ ]]"
prop_readCondition7 = isOk readCondition "[[ ${line} =~ ^[[:space:]]*# ]]" prop_readCondition7 = isOk readCondition "[[ ${line} =~ ^[[:space:]]*# ]]"
prop_readCondition8 = isOk readCondition "[[ $l =~ ogg|flac ]]" prop_readCondition8 = isOk readCondition "[[ $l =~ ogg|flac ]]"
prop_readCondition9 = isOk readCondition "[ foo -a -f bar ]" prop_readCondition9 = isOk readCondition "[ foo -a -f bar ]"
prop_readCondition10 = isOk readCondition "[[\na == b\n||\nc == d ]]" prop_readCondition10 = isOk readCondition "[[\na == b\n||\nc == d ]]"
prop_readCondition10a= isOk readCondition "[[\na == b ||\nc == d ]]" prop_readCondition10a = isOk readCondition "[[\na == b ||\nc == d ]]"
prop_readCondition10b= isOk readCondition "[[ a == b\n||\nc == d ]]" prop_readCondition10b = isOk readCondition "[[ a == b\n||\nc == d ]]"
prop_readCondition11 = isOk readCondition "[[ a == b ||\n c == d ]]" prop_readCondition11 = isOk readCondition "[[ a == b ||\n c == d ]]"
prop_readCondition12 = isWarning readCondition "[ a == b \n -o c == d ]" prop_readCondition12 = isWarning readCondition "[ a == b \n -o c == d ]"
prop_readCondition13 = isOk readCondition "[[ foo =~ ^fo{1,3}$ ]]" prop_readCondition13 = isOk readCondition "[[ foo =~ ^fo{1,3}$ ]]"
prop_readCondition14 = isOk readCondition "[ foo '>' bar ]" prop_readCondition14 = isOk readCondition "[ foo '>' bar ]"
prop_readCondition15 = isOk readCondition "[ foo \">=\" bar ]" prop_readCondition15 = isOk readCondition "[ foo \">=\" bar ]"
prop_readCondition16 = isOk readCondition "[ foo \\< bar ]" prop_readCondition16 = isOk readCondition "[ foo \\< bar ]"
prop_readCondition17 = isOk readCondition "[[ ${file::1} = [-.\\|/\\\\] ]]" prop_readCondition17 = isOk readCondition "[[ ${file::1} = [-.\\|/\\\\] ]]"
prop_readCondition18 = isOk readCondition "[ ]" prop_readCondition18 = isOk readCondition "[ ]"
prop_readCondition19 = isOk readCondition "[ '(' x \")\" ]" prop_readCondition19 = isOk readCondition "[ '(' x \")\" ]"
prop_readCondition20 = isOk readCondition "[[ echo_rc -eq 0 ]]" prop_readCondition20 = isOk readCondition "[[ echo_rc -eq 0 ]]"
prop_readCondition21 = isOk readCondition "[[ $1 =~ ^(a\\ b)$ ]]" prop_readCondition21 = isOk readCondition "[[ $1 =~ ^(a\\ b)$ ]]"
prop_readCondition22 = isOk readCondition "[[ $1 =~ \\.a\\.(\\.b\\.)\\.c\\. ]]" prop_readCondition22 = isOk readCondition "[[ $1 =~ \\.a\\.(\\.b\\.)\\.c\\. ]]"
prop_readCondition23 = isOk readCondition "[[ -v arr[$var] ]]" prop_readCondition23 = isOk readCondition "[[ -v arr[$var] ]]"
prop_readCondition25 = isOk readCondition "[[ lex.yy.c -ot program.l ]]" prop_readCondition25 = isOk readCondition "[[ lex.yy.c -ot program.l ]]"
prop_readCondition26 = isOk readScript "[[ foo ]]\\\n && bar" prop_readCondition26 = isOk readScript "[[ foo ]]\\\n && bar"
prop_readCondition27 = not $ isOk readConditionCommand "[[ x ]] foo" prop_readCondition27 = not $ isOk readConditionCommand "[[ x ]] foo"
prop_readCondition28 = isOk readCondition "[[ x = [\"$1\"] ]]"
prop_readCondition29 = isOk readCondition "[[ x = [*] ]]"
readCondition = called "test expression" $ do readCondition = called "test expression" $ do
opos <- getPosition opos <- getPosition
start <- startSpan start <- startSpan
open <- try (string "[[") <|> string "[" open <- try (string "[[") <|> string "["
let single = open == "[" let single = open == "["
let typ = if single then SingleBracket else DoubleBracket let typ = if single then SingleBracket else DoubleBracket
pos <- getPosition pos <- getPosition
space <- allspacing space <- allspacing
when (null space) $ when (null space) $
skipping to change at line 980 skipping to change at line 986
string "shellcheck" string "shellcheck"
prop_readAnnotation1 = isOk readAnnotation "# shellcheck disable=1234,5678\n" prop_readAnnotation1 = isOk readAnnotation "# shellcheck disable=1234,5678\n"
prop_readAnnotation2 = isOk readAnnotation "# shellcheck disable=SC1234 disable= SC5678\n" prop_readAnnotation2 = isOk readAnnotation "# shellcheck disable=SC1234 disable= SC5678\n"
prop_readAnnotation3 = isOk readAnnotation "# shellcheck disable=SC1234 source=/ dev/null disable=SC5678\n" prop_readAnnotation3 = isOk readAnnotation "# shellcheck disable=SC1234 source=/ dev/null disable=SC5678\n"
prop_readAnnotation4 = isWarning readAnnotation "# shellcheck cats=dogs disable= SC1234\n" prop_readAnnotation4 = isWarning readAnnotation "# shellcheck cats=dogs disable= SC1234\n"
prop_readAnnotation5 = isOk readAnnotation "# shellcheck disable=SC2002 # All ca ts are precious\n" prop_readAnnotation5 = isOk readAnnotation "# shellcheck disable=SC2002 # All ca ts are precious\n"
prop_readAnnotation6 = isOk readAnnotation "# shellcheck disable=SC1234 # shellc heck foo=bar\n" prop_readAnnotation6 = isOk readAnnotation "# shellcheck disable=SC1234 # shellc heck foo=bar\n"
prop_readAnnotation7 = isOk readAnnotation "# shellcheck disable=SC1000,SC2000-S C3000,SC1001\n" prop_readAnnotation7 = isOk readAnnotation "# shellcheck disable=SC1000,SC2000-S C3000,SC1001\n"
prop_readAnnotation8 = isOk readAnnotation "# shellcheck disable=all\n" prop_readAnnotation8 = isOk readAnnotation "# shellcheck disable=all\n"
prop_readAnnotation9 = isOk readAnnotation "# shellcheck source='foo bar' source
-path=\"baz etc\"\n"
prop_readAnnotation10 = isOk readAnnotation "# shellcheck disable='SC1234,SC2345
' enable=\"foo\" shell='bash'\n"
prop_readAnnotation11 = isOk (readAnnotationWithoutPrefix False) "external-sourc
es='true'"
readAnnotation = called "shellcheck directive" $ do readAnnotation = called "shellcheck directive" $ do
try readAnnotationPrefix try readAnnotationPrefix
many1 linewhitespace many1 linewhitespace
readAnnotationWithoutPrefix True readAnnotationWithoutPrefix True
readAnnotationWithoutPrefix sandboxed = do readAnnotationWithoutPrefix sandboxed = do
values <- many1 readKey values <- many1 readKey
optional readAnyComment optional readAnyComment
void linefeed <|> eof <|> do void linefeed <|> eof <|> do
parseNote ErrorC 1125 "Invalid key=value pair? Ignoring the rest of this directive starting here." parseNote ErrorC 1125 "Invalid key=value pair? Ignoring the rest of this directive starting here."
many (noneOf "\n") many (noneOf "\n")
void linefeed <|> eof void linefeed <|> eof
many linewhitespace many linewhitespace
return $ concat values return $ concat values
where where
plainOrQuoted p = quoted p <|> p
quoted p = do
c <- oneOf "'\""
start <- getPosition
str <- many1 $ noneOf (c:"\n")
char c <|> fail "Missing terminating quote for directive."
subParse start p str
readKey = do readKey = do
keyPos <- getPosition keyPos <- getPosition
key <- many1 (letter <|> char '-') key <- many1 (letter <|> char '-')
char '=' <|> fail "Expected '=' after directive key" char '=' <|> fail "Expected '=' after directive key"
annotations <- case key of annotations <- case key of
"disable" -> readElement `sepBy` char ',' "disable" -> plainOrQuoted $ readElement `sepBy` char ','
where where
readElement = readRange <|> readAll readElement = readRange <|> readAll
readAll = do readAll = do
string "all" string "all"
return $ DisableComment 0 1000000 return $ DisableComment 0 1000000
readRange = do readRange = do
from <- readCode from <- readCode
to <- choice [ char '-' *> readCode, return $ from+1 ] to <- choice [ char '-' *> readCode, return $ from+1 ]
return $ DisableComment from to return $ DisableComment from to
readCode = do readCode = do
optional $ string "SC" optional $ string "SC"
int <- many1 digit int <- many1 digit
return $ read int return $ read int
"enable" -> readName `sepBy` char ',' "enable" -> plainOrQuoted $ readName `sepBy` char ','
where where
readName = EnableComment <$> many1 (letter <|> char '-') readName = EnableComment <$> many1 (letter <|> char '-')
"source" -> do "source" -> do
filename <- many1 $ noneOf " \n" filename <- quoted (many1 anyChar) <|> (many1 $ noneOf " \n")
return [SourceOverride filename] return [SourceOverride filename]
"source-path" -> do "source-path" -> do
dirname <- many1 $ noneOf " \n" dirname <- quoted (many1 anyChar) <|> (many1 $ noneOf " \n")
return [SourcePath dirname] return [SourcePath dirname]
"shell" -> do "shell" -> do
pos <- getPosition pos <- getPosition
shell <- many1 $ noneOf " \n" shell <- quoted (many1 anyChar) <|> (many1 $ noneOf " \n")
when (isNothing $ shellForExecutable shell) $ when (isNothing $ shellForExecutable shell) $
parseNoteAt pos ErrorC 1103 parseNoteAt pos ErrorC 1103
"This shell type is unknown. Use e.g. sh or bash." "This shell type is unknown. Use e.g. sh or bash."
return [ShellOverride shell] return [ShellOverride shell]
"external-sources" -> do "external-sources" -> do
pos <- getPosition pos <- getPosition
value <- many1 letter value <- plainOrQuoted $ many1 letter
case value of case value of
"true" -> "true" ->
if sandboxed if sandboxed
then do then do
parseNoteAt pos ErrorC 1144 "external-sources can on ly be enabled in .shellcheckrc, not in individual files." parseNoteAt pos ErrorC 1144 "external-sources can on ly be enabled in .shellcheckrc, not in individual files."
return [] return []
else return [ExternalSources True] else return [ExternalSources True]
"false" -> return [ExternalSources False] "false" -> return [ExternalSources False]
_ -> do _ -> do
parseNoteAt pos ErrorC 1145 "Unknown external-sources va lue. Expected true/false." parseNoteAt pos ErrorC 1145 "Unknown external-sources va lue. Expected true/false."
skipping to change at line 1685 skipping to change at line 1702
prop_readDollarBraced3 = isOk readDollarBraced "${foo%%$(echo cow\\})}" prop_readDollarBraced3 = isOk readDollarBraced "${foo%%$(echo cow\\})}"
prop_readDollarBraced4 = isOk readDollarBraced "${foo#\\}}" prop_readDollarBraced4 = isOk readDollarBraced "${foo#\\}}"
readDollarBraced = called "parameter expansion" $ do readDollarBraced = called "parameter expansion" $ do
start <- startSpan start <- startSpan
try (string "${") try (string "${")
word <- readDollarBracedWord word <- readDollarBracedWord
char '}' char '}'
id <- endSpan start id <- endSpan start
return $ T_DollarBraced id True word return $ T_DollarBraced id True word
prop_readDollarExpansion1= isOk readDollarExpansion "$(echo foo; ls\n)" prop_readDollarExpansion1 = isOk readDollarExpansion "$(echo foo; ls\n)"
prop_readDollarExpansion2= isOk readDollarExpansion "$( )" prop_readDollarExpansion2 = isOk readDollarExpansion "$( )"
prop_readDollarExpansion3= isOk readDollarExpansion "$( command \n#comment \n)" prop_readDollarExpansion3 = isOk readDollarExpansion "$( command \n#comment \n)"
readDollarExpansion = called "command expansion" $ do readDollarExpansion = called "command expansion" $ do
start <- startSpan start <- startSpan
try (string "$(") try (string "$(")
cmds <- readCompoundListOrEmpty cmds <- readCompoundListOrEmpty
char ')' <|> fail "Expected end of $(..) expression" char ')' <|> fail "Expected end of $(..) expression"
id <- endSpan start id <- endSpan start
return $ T_DollarExpansion id cmds return $ T_DollarExpansion id cmds
prop_readDollarVariable = isOk readDollarVariable "$@" prop_readDollarVariable = isOk readDollarVariable "$@"
prop_readDollarVariable2 = isOk (readDollarVariable >> anyChar) "$?!" prop_readDollarVariable2 = isOk (readDollarVariable >> anyChar) "$?!"
skipping to change at line 1777 skipping to change at line 1794
prop_readHereDoc = isOk readScript "cat << foo\nlol\ncow\nfoo" prop_readHereDoc = isOk readScript "cat << foo\nlol\ncow\nfoo"
prop_readHereDoc2 = isNotOk readScript "cat <<- EOF\n cow\n EOF" prop_readHereDoc2 = isNotOk readScript "cat <<- EOF\n cow\n EOF"
prop_readHereDoc3 = isOk readScript "cat << foo\n$\"\nfoo" prop_readHereDoc3 = isOk readScript "cat << foo\n$\"\nfoo"
prop_readHereDoc4 = isNotOk readScript "cat << foo\n`\nfoo" prop_readHereDoc4 = isNotOk readScript "cat << foo\n`\nfoo"
prop_readHereDoc5 = isOk readScript "cat <<- !foo\nbar\n!foo" prop_readHereDoc5 = isOk readScript "cat <<- !foo\nbar\n!foo"
prop_readHereDoc6 = isOk readScript "cat << foo\\ bar\ncow\nfoo bar" prop_readHereDoc6 = isOk readScript "cat << foo\\ bar\ncow\nfoo bar"
prop_readHereDoc7 = isOk readScript "cat << foo\n\\$(f ())\nfoo" prop_readHereDoc7 = isOk readScript "cat << foo\n\\$(f ())\nfoo"
prop_readHereDoc8 = isOk readScript "cat <<foo>>bar\netc\nfoo" prop_readHereDoc8 = isOk readScript "cat <<foo>>bar\netc\nfoo"
prop_readHereDoc9 = isOk readScript "if true; then cat << foo; fi\nbar\nfoo\n" prop_readHereDoc9 = isOk readScript "if true; then cat << foo; fi\nbar\nfoo\n"
prop_readHereDoc10= isOk readScript "if true; then cat << foo << bar; fi\nfoo\nb prop_readHereDoc10 = isOk readScript "if true; then cat << foo << bar; fi\nfoo\n
ar\n" bar\n"
prop_readHereDoc11= isOk readScript "cat << foo $(\nfoo\n)lol\nfoo\n" prop_readHereDoc11 = isOk readScript "cat << foo $(\nfoo\n)lol\nfoo\n"
prop_readHereDoc12= isOk readScript "cat << foo|cat\nbar\nfoo" prop_readHereDoc12 = isOk readScript "cat << foo|cat\nbar\nfoo"
prop_readHereDoc13= isOk readScript "cat <<'#!'\nHello World\n#!\necho Done" prop_readHereDoc13 = isOk readScript "cat <<'#!'\nHello World\n#!\necho Done"
prop_readHereDoc14= isWarning readScript "cat << foo\nbar\nfoo \n" prop_readHereDoc14 = isWarning readScript "cat << foo\nbar\nfoo \n"
prop_readHereDoc15= isWarning readScript "cat <<foo\nbar\nfoo bar\nfoo" prop_readHereDoc15 = isWarning readScript "cat <<foo\nbar\nfoo bar\nfoo"
prop_readHereDoc16= isOk readScript "cat <<- ' foo'\nbar\n foo\n" prop_readHereDoc16 = isOk readScript "cat <<- ' foo'\nbar\n foo\n"
prop_readHereDoc17= isWarning readScript "cat <<- ' foo'\nbar\n foo\n foo\n" prop_readHereDoc17 = isWarning readScript "cat <<- ' foo'\nbar\n foo\n foo\n"
prop_readHereDoc18= isOk readScript "cat <<'\"foo'\nbar\n\"foo\n" prop_readHereDoc18 = isOk readScript "cat <<'\"foo'\nbar\n\"foo\n"
prop_readHereDoc20= isWarning readScript "cat << foo\n foo\n()\nfoo\n" prop_readHereDoc20 = isWarning readScript "cat << foo\n foo\n()\nfoo\n"
prop_readHereDoc21= isOk readScript "# shellcheck disable=SC1039\ncat << foo\n prop_readHereDoc21 = isOk readScript "# shellcheck disable=SC1039\ncat << foo\n
foo\n()\nfoo\n" foo\n()\nfoo\n"
prop_readHereDoc22 = isWarning readScript "cat << foo\r\ncow\r\nfoo\r\n" prop_readHereDoc22 = isWarning readScript "cat << foo\r\ncow\r\nfoo\r\n"
prop_readHereDoc23 = isNotOk readScript "cat << foo \r\ncow\r\nfoo\r\n" prop_readHereDoc23 = isNotOk readScript "cat << foo \r\ncow\r\nfoo\r\n"
readHereDoc = called "here document" $ do readHereDoc = called "here document" $ do
pos <- getPosition pos <- getPosition
try $ string "<<" try $ string "<<"
dashed <- (char '-' >> return Dashed) <|> return Undashed dashed <- (char '-' >> return Dashed) <|> return Undashed
sp <- spacing sp <- spacing
optional $ do optional $ do
try . lookAhead $ char '(' try . lookAhead $ char '('
let message = "Shells are space sensitive. Use '< <(cmd)', not '<<" ++ s p ++ "(cmd)'." let message = "Shells are space sensitive. Use '< <(cmd)', not '<<" ++ s p ++ "(cmd)'."
skipping to change at line 1901 skipping to change at line 1918
ppt 1122 "Nothing allowed after end token. To continue a command, put it on the line with the <<." ppt 1122 "Nothing allowed after end token. To continue a command, put it on the line with the <<."
foundCause foundCause
| leaderIsOk && hasTrailingSpace && not hasTrailer -> do | leaderIsOk && hasTrailingSpace && not hasTrailer -> do
parseProblemAt trailingSpacePos ErrorC 1118 "Delete whit espace after the here-doc end token." parseProblemAt trailingSpacePos ErrorC 1118 "Delete whit espace after the here-doc end token."
-- Parse as if it's the actual end token. Will koala_man regret this once again? -- Parse as if it's the actual end token. Will koala_man regret this once again?
return (True, True) return (True, True)
| not hasTrailingSpace && hasTrailer -> | not hasTrailingSpace && hasTrailer ->
-- The end token is just a prefix -- The end token is just a prefix
skipLine skipLine
| hasTrailer -> | hasTrailer ->
error "ShellCheck bug, please report (here doc trailer). " error $ pleaseReport "unexpected heredoc trailer"
-- The following cases assume no trailing text: -- The following cases assume no trailing text:
| dashed == Undashed && (not $ null leadingSpace) -> do | dashed == Undashed && (not $ null leadingSpace) -> do
parseProblemAt leadingSpacePos ErrorC 1039 "Remove inden tation before end token (or use <<- and indent with tabs)." parseProblemAt leadingSpacePos ErrorC 1039 "Remove inden tation before end token (or use <<- and indent with tabs)."
foundCause foundCause
| dashed == Dashed && not leadingSpacesAreTabs -> do | dashed == Dashed && not leadingSpacesAreTabs -> do
parseProblemAt leadingSpacePos ErrorC 1040 "When using < <-, you can only indent with tabs." parseProblemAt leadingSpacePos ErrorC 1040 "When using < <-, you can only indent with tabs."
foundCause foundCause
| True -> skipLine | True -> skipLine
skipping to change at line 2080 skipping to change at line 2097
prop_readSimpleCommand6 = isOk readSimpleCommand "time -p ( ls -l; )" prop_readSimpleCommand6 = isOk readSimpleCommand "time -p ( ls -l; )"
prop_readSimpleCommand7 = isOk readSimpleCommand "\\ls" prop_readSimpleCommand7 = isOk readSimpleCommand "\\ls"
prop_readSimpleCommand7b = isOk readSimpleCommand "\\:" prop_readSimpleCommand7b = isOk readSimpleCommand "\\:"
prop_readSimpleCommand8 = isWarning readSimpleCommand "// Lol" prop_readSimpleCommand8 = isWarning readSimpleCommand "// Lol"
prop_readSimpleCommand9 = isWarning readSimpleCommand "/* Lolbert */" prop_readSimpleCommand9 = isWarning readSimpleCommand "/* Lolbert */"
prop_readSimpleCommand10 = isWarning readSimpleCommand "/**** Lolbert */" prop_readSimpleCommand10 = isWarning readSimpleCommand "/**** Lolbert */"
prop_readSimpleCommand11 = isOk readSimpleCommand "/\\* foo" prop_readSimpleCommand11 = isOk readSimpleCommand "/\\* foo"
prop_readSimpleCommand12 = isWarning readSimpleCommand "elsif foo" prop_readSimpleCommand12 = isWarning readSimpleCommand "elsif foo"
prop_readSimpleCommand13 = isWarning readSimpleCommand "ElseIf foo" prop_readSimpleCommand13 = isWarning readSimpleCommand "ElseIf foo"
prop_readSimpleCommand14 = isWarning readSimpleCommand "elseif[$i==2]" prop_readSimpleCommand14 = isWarning readSimpleCommand "elseif[$i==2]"
prop_readSimpleCommand15 = isWarning readSimpleCommand "trap 'foo\"bar' INT"
readSimpleCommand = called "simple command" $ do readSimpleCommand = called "simple command" $ do
prefix <- option [] readCmdPrefix prefix <- option [] readCmdPrefix
skipAnnotationAndWarn skipAnnotationAndWarn
cmd <- option Nothing $ Just <$> readCmdName cmd <- option Nothing $ Just <$> readCmdName
when (null prefix && isNothing cmd) $ fail "Expected a command" when (null prefix && isNothing cmd) $ fail "Expected a command"
case cmd of case cmd of
Nothing -> do Nothing -> do
id1 <- getNextIdSpanningTokenList prefix id1 <- getNextIdSpanningTokenList prefix
id2 <- getNewIdFor id1 id2 <- getNewIdFor id1
skipping to change at line 2109 skipping to change at line 2127
(["declare", "export", "local", "readonly", "typeset"], readModifierSuffix), (["declare", "export", "local", "readonly", "typeset"], readModifierSuffix),
(["time"], readTimeSuffix), (["time"], readTimeSuffix),
(["let"], readLetSuffix), (["let"], readLetSuffix),
(["eval"], readEvalSuffix) (["eval"], readEvalSuffix)
] ]
id1 <- getNextIdSpanningTokenList (prefix ++ (cmd:suffix)) id1 <- getNextIdSpanningTokenList (prefix ++ (cmd:suffix))
id2 <- getNewIdFor id1 id2 <- getNewIdFor id1
let result = makeSimpleCommand id1 id2 prefix [cmd] suffix let result = makeSimpleCommand id1 id2 prefix [cmd] suffix
if isCommand ["source", "."] cmd case () of
then readSource result _ | isCommand ["source", "."] cmd -> readSource result
else return result _ | isCommand ["trap"] cmd -> do
syntaxCheckTrap result
return result
_ -> return result
where where
isCommand strings (T_NormalWord _ [T_Literal _ s]) = s `elem` strings isCommand strings (T_NormalWord _ [T_Literal _ s]) = s `elem` strings
isCommand _ _ = False isCommand _ _ = False
getParser def cmd [] = def getParser def cmd [] = def
getParser def cmd ((list, action):rest) = getParser def cmd ((list, action):rest) =
if isCommand list cmd if isCommand list cmd
then action then action
else getParser def cmd rest else getParser def cmd rest
validateCommand cmd = validateCommand cmd =
case cmd of case cmd of
(T_NormalWord _ [T_Literal _ "//"]) -> commentWarning (getId cmd) (T_NormalWord _ [T_Literal _ "//"]) -> commentWarning (getId cmd)
(T_NormalWord _ (T_Literal _ "/" : T_Glob _ "*" :_)) -> commentWarni ng (getId cmd) (T_NormalWord _ (T_Literal _ "/" : T_Glob _ "*" :_)) -> commentWarni ng (getId cmd)
(T_NormalWord _ (T_Literal _ str:_)) -> do (T_NormalWord _ (T_Literal _ str:_)) -> do
let cmdString = map toLower $ takeWhile isAlpha str let cmdString = map toLower $ takeWhile isAlpha str
when (cmdString `elem` ["elsif", "elseif"]) $ when (cmdString `elem` ["elsif", "elseif"]) $
parseProblemAtId (getId cmd) ErrorC 1131 "Use 'elif' to star t another branch." parseProblemAtId (getId cmd) ErrorC 1131 "Use 'elif' to star t another branch."
_ -> return () _ -> return ()
syntaxCheckTrap cmd =
case cmd of
(T_Redirecting _ _ (T_SimpleCommand _ _ (cmd:arg:_))) -> checkArg ar
g (getLiteralString arg)
_ -> return ()
where
checkArg _ Nothing = return ()
checkArg arg (Just ('-':_)) = return ()
checkArg arg (Just str) = do
(start,end) <- getSpanForId (getId arg)
subParse start (tryWithErrors (readCompoundListOrEmpty >> verifyEof)
<|> return ()) str
commentWarning id = commentWarning id =
parseProblemAtId id ErrorC 1127 "Was this intended as a comment? Use # i n sh." parseProblemAtId id ErrorC 1127 "Was this intended as a comment? Use # i n sh."
makeSimpleCommand id1 id2 prefix cmd suffix = makeSimpleCommand id1 id2 prefix cmd suffix =
let let
(preAssigned, preRest) = partition assignment prefix (preAssigned, preRest) = partition assignment prefix
(preRedirected, preRest2) = partition redirection preRest (preRedirected, preRest2) = partition redirection preRest
(postRedirected, postRest) = partition redirection suffix (postRedirected, postRest) = partition redirection suffix
redirs = preRedirected ++ postRedirected redirs = preRedirected ++ postRedirected
skipping to change at line 2265 skipping to change at line 2297
start <- startSpan start <- startSpan
apos <- getPosition apos <- getPosition
annotations <- readAnnotations annotations <- readAnnotations
aid <- endSpan start aid <- endSpan start
unless (null annotations) $ optional $ do unless (null annotations) $ optional $ do
try . lookAhead $ readKeyword try . lookAhead $ readKeyword
parseProblemAt apos ErrorC 1123 "ShellCheck directives are only valid in front of complete compound commands, like 'if', not e.g. individual 'elif' bran ches." parseProblemAt apos ErrorC 1123 "ShellCheck directives are only valid in front of complete compound commands, like 'if', not e.g. individual 'elif' bran ches."
andOr <- withAnnotations annotations $ andOr <- withAnnotations annotations $
chainr1 readPipeline $ do chainl1 readPipeline $ do
op <- g_AND_IF <|> g_OR_IF op <- g_AND_IF <|> g_OR_IF
readLineBreak readLineBreak
return $ case op of T_AND_IF id -> T_AndIf id return $ case op of T_AND_IF id -> T_AndIf id
T_OR_IF id -> T_OrIf id T_OR_IF id -> T_OrIf id
return $ if null annotations return $ if null annotations
then andOr then andOr
else T_Annotation aid annotations andOr else T_Annotation aid annotations andOr
readTermOrNone = do readTermOrNone = do
skipping to change at line 2464 skipping to change at line 2496
lookAhead $ char '}' lookAhead $ char '}'
parseProblemAt pos ErrorC 1055 "You need at least one command here. Use 'true;' as a no-op." parseProblemAt pos ErrorC 1055 "You need at least one command here. Use 'true;' as a no-op."
list <- readTerm list <- readTerm
char '}' <|> do char '}' <|> do
parseProblem ErrorC 1056 "Expected a '}'. If you have one, try a ; or \\ n in front of it." parseProblem ErrorC 1056 "Expected a '}'. If you have one, try a ; or \\ n in front of it."
fail "Missing '}'" fail "Missing '}'"
id <- endSpan start id <- endSpan start
spacing spacing
return $ T_BraceGroup id list return $ T_BraceGroup id list
prop_readBatsTest = isOk readBatsTest "@test 'can parse' {\n true\n}" prop_readBatsTest1 = isOk readBatsTest "@test 'can parse' {\n true\n}"
prop_readBatsTest2 = isOk readBatsTest "@test random text !(@*$Y&! {\n true\n}"
prop_readBatsTest3 = isOk readBatsTest "@test foo { bar { baz {\n true\n}"
prop_readBatsTest4 = isNotOk readBatsTest "@test foo \n{\n true\n}"
readBatsTest = called "bats @test" $ do readBatsTest = called "bats @test" $ do
start <- startSpan start <- startSpan
try $ string "@test" try $ string "@test "
spacing spacing
name <- readNormalWord name <- readBatsName
spacing spacing
test <- readBraceGroup test <- readBraceGroup
id <- endSpan start id <- endSpan start
return $ T_BatsTest id name test return $ T_BatsTest id name test
where
readBatsName = do
line <- try . lookAhead $ many1 $ noneOf "\n"
let name = reverse $ f $ reverse line
string name
-- We want everything before the last " {" in a string, so we find everythin
g after "{ " in its reverse
f ('{':' ':rest) = dropWhile isSpace rest
f (a:rest) = f rest
f [] = ""
prop_readWhileClause = isOk readWhileClause "while [[ -e foo ]]; do sleep 1; don e" prop_readWhileClause = isOk readWhileClause "while [[ -e foo ]]; do sleep 1; don e"
readWhileClause = called "while loop" $ do readWhileClause = called "while loop" $ do
start <- startSpan start <- startSpan
kwId <- getId <$> g_While kwId <- getId <$> g_While
condition <- readTerm condition <- readTerm
statements <- readDoGroup kwId statements <- readDoGroup kwId
id <- endSpan start id <- endSpan start
return $ T_WhileExpression id condition statements return $ T_WhileExpression id condition statements
skipping to change at line 2502 skipping to change at line 2547
readDoGroup kwId = do readDoGroup kwId = do
optional (do optional (do
try . lookAhead $ g_Done try . lookAhead $ g_Done
parseProblemAtId kwId ErrorC 1057 "Did you forget the 'do' for t his loop?") parseProblemAtId kwId ErrorC 1057 "Did you forget the 'do' for t his loop?")
doKw <- g_Do `orFail` do doKw <- g_Do `orFail` do
parseProblem ErrorC 1058 "Expected 'do'." parseProblem ErrorC 1058 "Expected 'do'."
return "Expected 'do'" return "Expected 'do'"
acceptButWarn g_Semi ErrorC 1059 "No semicolons directly after 'do'." acceptButWarn g_Semi ErrorC 1059 "Semicolon is not allowed directly after 'd o'. You can just delete it."
allspacing allspacing
optional (do optional (do
try . lookAhead $ g_Done try . lookAhead $ g_Done
parseProblemAtId (getId doKw) ErrorC 1060 "Can't have empty do c lauses (use 'true' as a no-op).") parseProblemAtId (getId doKw) ErrorC 1060 "Can't have empty do c lauses (use 'true' as a no-op).")
commands <- readCompoundList commands <- readCompoundList
g_Done `orFail` do g_Done `orFail` do
parseProblemAtId (getId doKw) ErrorC 1061 "Couldn't find 'done' for this 'do'." parseProblemAtId (getId doKw) ErrorC 1061 "Couldn't find 'done' for this 'do'."
parseProblem ErrorC 1062 "Expected 'done' matching previously mentio ned 'do'." parseProblem ErrorC 1062 "Expected 'done' matching previously mentio ned 'do'."
skipping to change at line 2530 skipping to change at line 2575
prop_readForClause = isOk readForClause "for f in *; do rm \"$f\"; done" prop_readForClause = isOk readForClause "for f in *; do rm \"$f\"; done"
prop_readForClause1 = isOk readForClause "for f in *; { rm \"$f\"; }" prop_readForClause1 = isOk readForClause "for f in *; { rm \"$f\"; }"
prop_readForClause3 = isOk readForClause "for f; do foo; done" prop_readForClause3 = isOk readForClause "for f; do foo; done"
prop_readForClause4 = isOk readForClause "for((i=0; i<10; i++)); do echo $i; don e" prop_readForClause4 = isOk readForClause "for((i=0; i<10; i++)); do echo $i; don e"
prop_readForClause5 = isOk readForClause "for ((i=0;i<10 && n>x;i++,--n))\ndo \n echo $i\ndone" prop_readForClause5 = isOk readForClause "for ((i=0;i<10 && n>x;i++,--n))\ndo \n echo $i\ndone"
prop_readForClause6 = isOk readForClause "for ((;;))\ndo echo $i\ndone" prop_readForClause6 = isOk readForClause "for ((;;))\ndo echo $i\ndone"
prop_readForClause7 = isOk readForClause "for ((;;)) do echo $i\ndone" prop_readForClause7 = isOk readForClause "for ((;;)) do echo $i\ndone"
prop_readForClause8 = isOk readForClause "for ((;;)) ; do echo $i\ndone" prop_readForClause8 = isOk readForClause "for ((;;)) ; do echo $i\ndone"
prop_readForClause9 = isOk readForClause "for i do true; done" prop_readForClause9 = isOk readForClause "for i do true; done"
prop_readForClause10= isOk readForClause "for ((;;)) { true; }" prop_readForClause10 = isOk readForClause "for ((;;)) { true; }"
prop_readForClause12= isWarning readForClause "for $a in *; do echo \"$a\"; done prop_readForClause12 = isWarning readForClause "for $a in *; do echo \"$a\"; don
" e"
prop_readForClause13= isOk readForClause "for foo\nin\\\n bar\\\n baz\ndo true prop_readForClause13 = isOk readForClause "for foo\nin\\\n bar\\\n baz\ndo tru
; done" e; done"
readForClause = called "for loop" $ do readForClause = called "for loop" $ do
pos <- getPosition pos <- getPosition
(T_For id) <- g_For (T_For id) <- g_For
spacing spacing
readArithmetic id <|> readRegular id readArithmetic id <|> readRegular id
where where
readArithmetic id = called "arithmetic for condition" $ do readArithmetic id = called "arithmetic for condition" $ do
readArithmeticDelimiter '(' "Missing second '(' to start arithmetic for ((;;)) loop" readArithmeticDelimiter '(' "Missing second '(' to start arithmetic for ((;;)) loop"
x <- readArithmeticContents x <- readArithmeticContents
char ';' >> spacing char ';' >> spacing
skipping to change at line 2664 skipping to change at line 2709
] ]
prop_readFunctionDefinition = isOk readFunctionDefinition "foo() { command foo - -lol \"$@\"; }" prop_readFunctionDefinition = isOk readFunctionDefinition "foo() { command foo - -lol \"$@\"; }"
prop_readFunctionDefinition1 = isOk readFunctionDefinition "foo (){ command fo o --lol \"$@\"; }" prop_readFunctionDefinition1 = isOk readFunctionDefinition "foo (){ command fo o --lol \"$@\"; }"
prop_readFunctionDefinition4 = isWarning readFunctionDefinition "foo(a, b) { tru e; }" prop_readFunctionDefinition4 = isWarning readFunctionDefinition "foo(a, b) { tru e; }"
prop_readFunctionDefinition5 = isOk readFunctionDefinition ":(){ :|:;}" prop_readFunctionDefinition5 = isOk readFunctionDefinition ":(){ :|:;}"
prop_readFunctionDefinition6 = isOk readFunctionDefinition "?(){ foo; }" prop_readFunctionDefinition6 = isOk readFunctionDefinition "?(){ foo; }"
prop_readFunctionDefinition7 = isOk readFunctionDefinition "..(){ cd ..; }" prop_readFunctionDefinition7 = isOk readFunctionDefinition "..(){ cd ..; }"
prop_readFunctionDefinition8 = isOk readFunctionDefinition "foo() (ls)" prop_readFunctionDefinition8 = isOk readFunctionDefinition "foo() (ls)"
prop_readFunctionDefinition9 = isOk readFunctionDefinition "function foo { true; }" prop_readFunctionDefinition9 = isOk readFunctionDefinition "function foo { true; }"
prop_readFunctionDefinition10= isOk readFunctionDefinition "function foo () { tr prop_readFunctionDefinition10 = isOk readFunctionDefinition "function foo () { t
ue; }" rue; }"
prop_readFunctionDefinition11= isWarning readFunctionDefinition "function foo{\n prop_readFunctionDefinition11 = isWarning readFunctionDefinition "function foo{\
true\n}" ntrue\n}"
prop_readFunctionDefinition12= isOk readFunctionDefinition "function []!() { tru prop_readFunctionDefinition12 = isOk readFunctionDefinition "function []!() { tr
e; }" ue; }"
prop_readFunctionDefinition13= isOk readFunctionDefinition "@require(){ true; }" prop_readFunctionDefinition13 = isOk readFunctionDefinition "@require(){ true; }
"
readFunctionDefinition = called "function" $ do readFunctionDefinition = called "function" $ do
start <- startSpan start <- startSpan
functionSignature <- try readFunctionSignature functionSignature <- try readFunctionSignature
allspacing allspacing
void (lookAhead $ oneOf "{(") <|> parseProblem ErrorC 1064 "Expected a { to open the function definition." void (lookAhead $ oneOf "{(") <|> parseProblem ErrorC 1064 "Expected a { to open the function definition."
group <- readBraceGroup <|> readSubshell group <- readBraceGroup <|> readSubshell
id <- endSpan start id <- endSpan start
return $ functionSignature id group return $ functionSignature id group
where where
readFunctionSignature = readFunctionSignature =
skipping to change at line 2863 skipping to change at line 2908
str <- readStringForParser parser str <- readStringForParser parser
id <- endSpan start id <- endSpan start
return $ T_Literal id str return $ T_Literal id str
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_readAssignmentWord5 = isOk readAssignmentWord "b+=lol" prop_readAssignmentWord5 = isOk readAssignmentWord "b+=lol"
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_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) )"
prop_readAssignmentWord14= isOk readAssignmentWord "var=( 1 [2]=(3 4) )" 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 = called "variable assignment" $ do readAssignmentWordExt lenient = called "variable assignment" $ do
-- Parse up to and including the = in a 'try' -- Parse up to and including the = in a 'try'
(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 -- 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. -- would otherwise cause a parse failure so it can't be checked later.
leadingDollarPos <- leadingDollarPos <-
skipping to change at line 3217 skipping to change at line 3262
return $ concat annotations return $ concat annotations
anySpacingOrComment = anySpacingOrComment =
many (void allspacingOrFail <|> void readAnyComment) many (void allspacingOrFail <|> void readAnyComment)
prop_readScript1 = isOk readScript "#!/bin/bash\necho hello world\n" prop_readScript1 = isOk readScript "#!/bin/bash\necho hello world\n"
prop_readScript2 = isWarning readScript "#!/bin/bash\r\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_readScript3 = isWarning readScript "#!/bin/bash\necho hello\xA0world"
prop_readScript4 = isWarning readScript "#!/usr/bin/perl\nfoo=(" prop_readScript4 = isWarning readScript "#!/usr/bin/perl\nfoo=("
prop_readScript5 = isOk readScript "#!/bin/bash\n#This is an empty script\n\n" prop_readScript5 = isOk readScript "#!/bin/bash\n#This is an empty script\n\n"
prop_readScript6 = isOk readScript "#!/usr/bin/env -S X=FOO bash\n#This is an em pty script\n\n" prop_readScript6 = isOk readScript "#!/usr/bin/env -S X=FOO bash\n#This is an em pty script\n\n"
prop_readScript7 = isOk readScript "#!/bin/zsh\n# shellcheck disable=SC1071\nfor f (a b); echo $f\n"
readScriptFile sourced = do readScriptFile sourced = do
start <- startSpan start <- startSpan
pos <- getPosition pos <- getPosition
optional $ do
readUtf8Bom
parseProblem ErrorC 1082
"This file has a UTF-8 BOM. Remove it with: LC_CTYPE=C sed '1s/^.../
/' < yourscript ."
shebang <- readShebang <|> readEmptyLiteral
let (T_Literal _ shebangString) = shebang
allspacing
annotationStart <- startSpan
fileAnnotations <- readAnnotations
rcAnnotations <- if sourced rcAnnotations <- if sourced
then return [] then return []
else do else do
filename <- Mr.asks currentFilename filename <- Mr.asks currentFilename
readConfigFile filename readConfigFile filename
let annotations = fileAnnotations ++ rcAnnotations
annotationId <- endSpan annotationStart -- Put the rc annotations on the stack so that one can ignore e.g. SC1084 in
let shellAnnotationSpecified = .shellcheckrc
any (\x -> case x of ShellOverride {} -> True; _ -> False) annotatio withAnnotations rcAnnotations $ do
ns hasBom <- wasIncluded readUtf8Bom
shellFlagSpecified <- isJust <$> Mr.asks shellTypeOverride shebang <- readShebang <|> readEmptyLiteral
let ignoreShebang = shellAnnotationSpecified || shellFlagSpecified let (T_Literal _ shebangString) = shebang
allspacing
unless ignoreShebang $ annotationStart <- startSpan
verifyShebang pos (executableFromShebang shebangString) fileAnnotations <- readAnnotations
if ignoreShebang || isValidShell (executableFromShebang shebangString) /= Ju
st False -- Similarly put the filewide annotations on the stack to allow earlier
then do suppression
commands <- withAnnotations annotations readCompoundListOrEmpty withAnnotations fileAnnotations $ do
id <- endSpan start when (hasBom) $
verifyEof parseProblemAt pos ErrorC 1082
let script = T_Annotation annotationId annotations $ "This file has a UTF-8 BOM. Remove it with: LC_CTYPE=C sed '
T_Script id shebang commands 1s/^...//' < yourscript ."
reparseIndices script let annotations = fileAnnotations ++ rcAnnotations
else do annotationId <- endSpan annotationStart
many anyChar let shellAnnotationSpecified =
id <- endSpan start any (\x -> case x of ShellOverride {} -> True; _ -> False) a
return $ T_Script id shebang [] nnotations
shellFlagSpecified <- isJust <$> Mr.asks shellTypeOverride
let ignoreShebang = shellAnnotationSpecified || shellFlagSpecified
unless ignoreShebang $
verifyShebang pos (executableFromShebang shebangString)
if ignoreShebang || isValidShell (executableFromShebang shebangStrin
g) /= Just False
then do
commands <- readCompoundListOrEmpty
id <- endSpan start
verifyEof
let script = T_Annotation annotationId annotations $
T_Script id shebang commands
reparseIndices script
else do
many anyChar
id <- endSpan start
return $ T_Script id shebang []
where where
verifyShebang pos s = do verifyShebang pos s = do
case isValidShell s of case isValidShell s of
Just True -> return () Just True -> return ()
Just False -> parseProblemAt pos ErrorC 1071 "ShellCheck only suppor ts sh/bash/dash/ksh scripts. Sorry!" Just False -> parseProblemAt pos ErrorC 1071 "ShellCheck only suppor ts sh/bash/dash/ksh scripts. Sorry!"
Nothing -> parseProblemAt pos ErrorC 1008 "This shebang was unrecogn ized. ShellCheck only supports sh/bash/dash/ksh. Add a 'shell' directive to spec ify." Nothing -> parseProblemAt pos ErrorC 1008 "This shebang was unrecogn ized. ShellCheck only supports sh/bash/dash/ksh. Add a 'shell' directive to spec ify."
isValidShell s = isValidShell s =
let good = null s || any (`isPrefixOf` s) goodShells let good = null s || any (`isPrefixOf` s) goodShells
skipping to change at line 3346 skipping to change at line 3398
isNotOk p s = parsesCleanly p s == Nothing -- The string does not parse isNotOk p s = parsesCleanly p s == Nothing -- The string does not parse
parsesCleanly parser string = runIdentity $ do parsesCleanly parser string = runIdentity $ do
(res, sys) <- runParser testEnvironment (res, sys) <- runParser testEnvironment
(parser >> eof >> getState) "-" string (parser >> eof >> getState) "-" string
case (res, sys) of case (res, sys) of
(Right userState, systemState) -> (Right userState, systemState) ->
return $ Just . null $ parseNotes userState ++ parseProblems systemS tate return $ Just . null $ parseNotes userState ++ parseProblems systemS tate
(Left _, _) -> return Nothing (Left _, _) -> return Nothing
dump :: Show a => a -> a -- STRIP
dump x = trace (show x) x -- STRIP
dumps :: Show x => x -> a -> a -- STRIP
dumps t = trace (show t) -- STRIP
parseWithNotes parser = do parseWithNotes parser = do
item <- parser item <- parser
state <- getState state <- getState
return (item, state) return (item, state)
compareNotes (ParseNote pos1 pos1' level1 _ s1) (ParseNote pos2 pos2' level2 _ s 2) = compare (pos1, pos1', level1) (pos2, pos2', level2) compareNotes (ParseNote pos1 pos1' level1 _ s1) (ParseNote pos2 pos2' level2 _ s 2) = compare (pos1, pos1', level1) (pos2, pos2', level2)
sortNotes = sortBy compareNotes sortNotes = sortBy compareNotes
makeErrorFor parsecError = makeErrorFor parsecError =
ParseNote pos pos ErrorC 1072 $ ParseNote pos pos ErrorC 1072 $
skipping to change at line 3434 skipping to change at line 3476
where where
isName (ContextName _ _) = True isName (ContextName _ _) = True
isName _ = False isName _ = False
first (ContextName pos str) = ParseNote pos pos ErrorC 1073 $ first (ContextName pos str) = ParseNote pos pos ErrorC 1073 $
"Couldn't parse this " ++ str ++ ". Fix to allow more checks." "Couldn't parse this " ++ str ++ ". Fix to allow more checks."
second (ContextName pos str) = ParseNote pos pos InfoC 1009 $ second (ContextName pos str) = ParseNote pos pos InfoC 1009 $
"The mentioned syntax error was in this " ++ str ++ "." "The mentioned syntax error was in this " ++ str ++ "."
-- Go over all T_UnparsedIndex and reparse them as either arithmetic or text -- Go over all T_UnparsedIndex and reparse them as either arithmetic or text
-- depending on declare -A statements. -- depending on declare -A statements.
reparseIndices root = reparseIndices root = process root
analyze blank blank f root
where where
process = analyze blank blank f
associative = getAssociativeArrays root associative = getAssociativeArrays root
isAssociative s = s `elem` associative isAssociative s = s `elem` associative
f (T_Assignment id mode name indices value) = do f (T_Assignment id mode name indices value) = do
newIndices <- mapM (fixAssignmentIndex name) indices newIndices <- mapM (fixAssignmentIndex name) indices
newValue <- case value of newValue <- case value of
(T_Array id2 words) -> do (T_Array id2 words) -> do
newWords <- mapM (fixIndexElement name) words newWords <- mapM (fixIndexElement name) words
return $ T_Array id2 newWords return $ T_Array id2 newWords
x -> return x x -> return x
return $ T_Assignment id mode name newIndices newValue return $ T_Assignment id mode name newIndices newValue
skipping to change at line 3461 skipping to change at line 3503
fixIndexElement name word = fixIndexElement name word =
case word of case word of
T_IndexedElement id indices value -> do T_IndexedElement id indices value -> do
new <- mapM (fixAssignmentIndex name) indices new <- mapM (fixAssignmentIndex name) indices
return $ T_IndexedElement id new value return $ T_IndexedElement id new value
_ -> return word _ -> return word
fixAssignmentIndex name word = fixAssignmentIndex name word =
case word of case word of
T_UnparsedIndex id pos src -> T_UnparsedIndex id pos src -> do
parsed name pos src idx <- parsed name pos src
process idx -- Recursively parse for cases like x[y[z=1]]=1
_ -> return word _ -> return word
parsed name pos src = parsed name pos src =
if isAssociative name if isAssociative name
then subParse pos (called "associative array index" $ readIndexSpan) src then subParse pos (called "associative array index" $ readIndexSpan) src
else subParse pos (called "arithmetic array index expression" $ optional space >> readArithmeticContents) src else subParse pos (called "arithmetic array index expression" $ optional space >> readArithmeticContents) src
reattachHereDocs root map = reattachHereDocs root map =
doTransform f root doTransform f root
where where
 End of changes. 44 change blocks. 
118 lines changed or deleted 174 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)