mirror of
https://github.com/koalaman/shellcheck.git
synced 2025-09-30 16:59:20 +08:00
Compare commits
37 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
2f7bd556e8 | ||
|
081751c1b5 | ||
|
cc86aab3f1 | ||
|
9f1f00cdd1 | ||
|
93debd3556 | ||
|
47b971c582 | ||
|
f25ae90746 | ||
|
3daa47c0f2 | ||
|
ed56a837c3 | ||
|
80cf5d9852 | ||
|
8e554ae3d4 | ||
|
0a80188363 | ||
|
0e1a64b6ba | ||
|
0a2cf208c8 | ||
|
dcc10bbdf6 | ||
|
2c2e41952f | ||
|
0d74140650 | ||
|
955ad60823 | ||
|
2573332d77 | ||
|
00c470f323 | ||
|
63188282e9 | ||
|
61b4b65184 | ||
|
39b2bf4378 | ||
|
2fe117728d | ||
|
cde3ba8769 | ||
|
33c78b7c95 | ||
|
a485482979 | ||
|
895d83afc5 | ||
|
39bc011757 | ||
|
fe0a398239 | ||
|
1be0f1ea75 | ||
|
c9aa133282 | ||
|
7b70500d41 | ||
|
8bed447411 | ||
|
22710bf4d8 | ||
|
a354685ab1 | ||
|
a8ff7a02fd |
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
http://www.shellcheck.net
|
http://www.shellcheck.net
|
||||||
|
|
||||||
Copyright 2012-2014, Vidar 'koala_man' Holen
|
Copyright 2012-2015, Vidar 'koala_man' Holen
|
||||||
Licensed under the GNU Affero General Public License, v3
|
Licensed under the GNU Affero General Public License, v3
|
||||||
|
|
||||||
The goals of ShellCheck are:
|
The goals of ShellCheck are:
|
||||||
|
45
Setup.hs
45
Setup.hs
@@ -1,2 +1,43 @@
|
|||||||
import Distribution.Simple
|
import Distribution.PackageDescription (
|
||||||
main = defaultMain
|
HookedBuildInfo,
|
||||||
|
emptyHookedBuildInfo )
|
||||||
|
import Distribution.Simple (
|
||||||
|
Args,
|
||||||
|
UserHooks ( preSDist ),
|
||||||
|
defaultMainWithHooks,
|
||||||
|
simpleUserHooks )
|
||||||
|
import Distribution.Simple.Setup ( SDistFlags )
|
||||||
|
|
||||||
|
-- | This requires the process package from,
|
||||||
|
--
|
||||||
|
-- https://hackage.haskell.org/package/process
|
||||||
|
--
|
||||||
|
import System.Process ( callCommand )
|
||||||
|
|
||||||
|
|
||||||
|
-- | This will use almost the default implementation, except we switch
|
||||||
|
-- out the default pre-sdist hook with our own, 'myPreSDist'.
|
||||||
|
--
|
||||||
|
main = defaultMainWithHooks myHooks
|
||||||
|
where
|
||||||
|
myHooks = simpleUserHooks { preSDist = myPreSDist }
|
||||||
|
|
||||||
|
|
||||||
|
-- | This hook will be executed before e.g. @cabal sdist@. It runs
|
||||||
|
-- pandoc to create the man page from shellcheck.1.md. If the pandoc
|
||||||
|
-- command is not found, this will fail with an error message:
|
||||||
|
--
|
||||||
|
-- /bin/sh: pandoc: command not found
|
||||||
|
--
|
||||||
|
-- Since the man page is listed in the Extra-Source-Files section of
|
||||||
|
-- our cabal file, a failure here should result in a failure to
|
||||||
|
-- create the distribution tarball (that's a good thing).
|
||||||
|
--
|
||||||
|
myPreSDist :: Args -> SDistFlags -> IO HookedBuildInfo
|
||||||
|
myPreSDist _ _ = do
|
||||||
|
putStrLn "Building the man page..."
|
||||||
|
putStrLn pandoc_cmd
|
||||||
|
callCommand pandoc_cmd
|
||||||
|
return emptyHookedBuildInfo
|
||||||
|
where
|
||||||
|
pandoc_cmd = "pandoc -s -t man shellcheck.1.md -o shellcheck.1"
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
Name: ShellCheck
|
Name: ShellCheck
|
||||||
Version: 0.3.5
|
Version: 0.3.7
|
||||||
Synopsis: Shell script analysis tool
|
Synopsis: Shell script analysis tool
|
||||||
License: AGPL-3
|
License: AGPL-3
|
||||||
License-file: LICENSE
|
License-file: LICENSE
|
||||||
@@ -26,6 +26,8 @@ Extra-Source-Files:
|
|||||||
-- documentation
|
-- documentation
|
||||||
README.md
|
README.md
|
||||||
shellcheck.1.md
|
shellcheck.1.md
|
||||||
|
-- built with a cabal sdist hook
|
||||||
|
shellcheck.1
|
||||||
-- tests
|
-- tests
|
||||||
test/shellcheck.hs
|
test/shellcheck.hs
|
||||||
|
|
||||||
@@ -41,7 +43,7 @@ library
|
|||||||
json,
|
json,
|
||||||
mtl,
|
mtl,
|
||||||
parsec,
|
parsec,
|
||||||
regex-compat,
|
regex-tdfa,
|
||||||
QuickCheck >= 2.7.4
|
QuickCheck >= 2.7.4
|
||||||
exposed-modules:
|
exposed-modules:
|
||||||
ShellCheck.Analytics
|
ShellCheck.Analytics
|
||||||
@@ -49,6 +51,7 @@ library
|
|||||||
ShellCheck.Data
|
ShellCheck.Data
|
||||||
ShellCheck.Options
|
ShellCheck.Options
|
||||||
ShellCheck.Parser
|
ShellCheck.Parser
|
||||||
|
ShellCheck.Regex
|
||||||
ShellCheck.Simple
|
ShellCheck.Simple
|
||||||
other-modules:
|
other-modules:
|
||||||
Paths_ShellCheck
|
Paths_ShellCheck
|
||||||
@@ -62,7 +65,7 @@ executable shellcheck
|
|||||||
json,
|
json,
|
||||||
mtl,
|
mtl,
|
||||||
parsec,
|
parsec,
|
||||||
regex-compat,
|
regex-tdfa,
|
||||||
transformers,
|
transformers,
|
||||||
QuickCheck >= 2.7.4
|
QuickCheck >= 2.7.4
|
||||||
main-is: shellcheck.hs
|
main-is: shellcheck.hs
|
||||||
@@ -77,7 +80,7 @@ test-suite test-shellcheck
|
|||||||
json,
|
json,
|
||||||
mtl,
|
mtl,
|
||||||
parsec,
|
parsec,
|
||||||
regex-compat,
|
regex-tdfa,
|
||||||
transformers,
|
transformers,
|
||||||
QuickCheck >= 2.7.4
|
QuickCheck >= 2.7.4
|
||||||
main-is: test/shellcheck.hs
|
main-is: test/shellcheck.hs
|
||||||
|
@@ -19,7 +19,7 @@ module ShellCheck.AST where
|
|||||||
|
|
||||||
import Control.Monad
|
import Control.Monad
|
||||||
import Control.Monad.Identity
|
import Control.Monad.Identity
|
||||||
import qualified Text.Regex as Re
|
import qualified ShellCheck.Regex as Re
|
||||||
|
|
||||||
data Id = Id Int deriving (Show, Eq, Ord)
|
data Id = Id Int deriving (Show, Eq, Ord)
|
||||||
|
|
||||||
@@ -28,7 +28,6 @@ data Dashed = Dashed | Undashed deriving (Show, Eq)
|
|||||||
data AssignmentMode = Assign | Append deriving (Show, Eq)
|
data AssignmentMode = Assign | Append deriving (Show, Eq)
|
||||||
data FunctionKeyword = FunctionKeyword Bool deriving (Show, Eq)
|
data FunctionKeyword = FunctionKeyword Bool deriving (Show, Eq)
|
||||||
data FunctionParentheses = FunctionParentheses Bool deriving (Show, Eq)
|
data FunctionParentheses = FunctionParentheses Bool deriving (Show, Eq)
|
||||||
data ForInType = NormalForIn | ShortForIn deriving (Show, Eq)
|
|
||||||
data CaseType = CaseBreak | CaseFallThrough | CaseContinue deriving (Show, Eq)
|
data CaseType = CaseBreak | CaseFallThrough | CaseContinue deriving (Show, Eq)
|
||||||
|
|
||||||
data Token =
|
data Token =
|
||||||
@@ -49,7 +48,6 @@ data Token =
|
|||||||
| T_Arithmetic Id Token
|
| T_Arithmetic Id Token
|
||||||
| T_Array Id [Token]
|
| T_Array Id [Token]
|
||||||
| T_IndexedElement Id Token Token
|
| T_IndexedElement Id Token Token
|
||||||
| T_ Id [Token]
|
|
||||||
| T_Assignment Id AssignmentMode String (Maybe Token) Token
|
| T_Assignment Id AssignmentMode String (Maybe Token) Token
|
||||||
| T_Backgrounded Id Token
|
| T_Backgrounded Id Token
|
||||||
| T_Backticked Id [Token]
|
| T_Backticked Id [Token]
|
||||||
@@ -83,7 +81,7 @@ data Token =
|
|||||||
| T_Fi Id
|
| T_Fi Id
|
||||||
| T_For Id
|
| T_For Id
|
||||||
| T_ForArithmetic Id Token Token Token [Token]
|
| T_ForArithmetic Id Token Token Token [Token]
|
||||||
| T_ForIn Id ForInType [String] [Token] [Token]
|
| T_ForIn Id String [Token] [Token]
|
||||||
| T_Function Id FunctionKeyword FunctionParentheses String Token
|
| T_Function Id FunctionKeyword FunctionParentheses String Token
|
||||||
| T_GREATAND Id
|
| T_GREATAND Id
|
||||||
| T_Glob Id String
|
| T_Glob Id String
|
||||||
@@ -123,16 +121,20 @@ data Token =
|
|||||||
| T_WhileExpression Id [Token] [Token]
|
| T_WhileExpression Id [Token] [Token]
|
||||||
| T_Annotation Id [Annotation] Token
|
| T_Annotation Id [Annotation] Token
|
||||||
| T_Pipe Id String
|
| T_Pipe Id String
|
||||||
|
| T_CoProc Id (Maybe String) Token
|
||||||
|
| T_CoProcBody Id Token
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
data Annotation = DisableComment Integer deriving (Show, Eq)
|
data Annotation = DisableComment Integer deriving (Show, Eq)
|
||||||
data ConditionType = DoubleBracket | SingleBracket deriving (Show, Eq)
|
data ConditionType = DoubleBracket | SingleBracket deriving (Show, Eq)
|
||||||
|
|
||||||
-- I apologize for nothing!
|
-- This is an abomination.
|
||||||
lolHax s = Re.subRegex (Re.mkRegex "(Id [0-9]+)") (show s) "(Id 0)"
|
tokenEquals :: Token -> Token -> Bool
|
||||||
instance Eq Token where
|
tokenEquals a b = kludge a == kludge b
|
||||||
(==) a b = lolHax a == lolHax b
|
where kludge s = Re.subRegex (Re.mkRegex "\\(Id [0-9]+\\)") (show s) "(Id 0)"
|
||||||
|
|
||||||
|
instance Eq Token where
|
||||||
|
(==) = tokenEquals
|
||||||
|
|
||||||
analyze :: Monad m => (Token -> m ()) -> (Token -> m ()) -> (Token -> Token) -> Token -> m Token
|
analyze :: Monad m => (Token -> m ()) -> (Token -> m ()) -> (Token -> Token) -> Token -> m Token
|
||||||
analyze f g i =
|
analyze f g i =
|
||||||
@@ -205,7 +207,7 @@ analyze f g i =
|
|||||||
delve (T_BraceGroup id l) = dl l $ T_BraceGroup id
|
delve (T_BraceGroup id l) = dl l $ T_BraceGroup id
|
||||||
delve (T_WhileExpression id c l) = dll c l $ T_WhileExpression id
|
delve (T_WhileExpression id c l) = dll c l $ T_WhileExpression id
|
||||||
delve (T_UntilExpression id c l) = dll c l $ T_UntilExpression id
|
delve (T_UntilExpression id c l) = dll c l $ T_UntilExpression id
|
||||||
delve (T_ForIn id t v w l) = dll w l $ T_ForIn id t v
|
delve (T_ForIn id v w l) = dll w l $ T_ForIn id v
|
||||||
delve (T_SelectIn id v w l) = dll w l $ T_SelectIn id v
|
delve (T_SelectIn id v w l) = dll w l $ T_SelectIn id v
|
||||||
delve (T_CaseExpression id word cases) = do
|
delve (T_CaseExpression id word cases) = do
|
||||||
newWord <- round word
|
newWord <- round word
|
||||||
@@ -248,6 +250,8 @@ analyze f g i =
|
|||||||
delve (TA_Expansion id t) = dl t $ TA_Expansion id
|
delve (TA_Expansion id t) = dl t $ TA_Expansion id
|
||||||
delve (TA_Index id t) = d1 t $ TA_Index id
|
delve (TA_Index id t) = d1 t $ TA_Index id
|
||||||
delve (T_Annotation id anns t) = d1 t $ T_Annotation id anns
|
delve (T_Annotation id anns t) = d1 t $ T_Annotation id anns
|
||||||
|
delve (T_CoProc id var body) = d1 body $ T_CoProc id var
|
||||||
|
delve (T_CoProcBody id t) = d1 t $ T_CoProcBody id
|
||||||
delve t = return t
|
delve t = return t
|
||||||
|
|
||||||
getId t = case t of
|
getId t = case t of
|
||||||
@@ -312,7 +316,7 @@ getId t = case t of
|
|||||||
T_BraceGroup id _ -> id
|
T_BraceGroup id _ -> id
|
||||||
T_WhileExpression id _ _ -> id
|
T_WhileExpression id _ _ -> id
|
||||||
T_UntilExpression id _ _ -> id
|
T_UntilExpression id _ _ -> id
|
||||||
T_ForIn id _ _ _ _ -> id
|
T_ForIn id _ _ _ -> id
|
||||||
T_SelectIn id _ _ _ -> id
|
T_SelectIn id _ _ _ -> id
|
||||||
T_CaseExpression id _ _ -> id
|
T_CaseExpression id _ _ -> id
|
||||||
T_Function id _ _ _ _ -> id
|
T_Function id _ _ _ _ -> id
|
||||||
@@ -341,6 +345,8 @@ getId t = case t of
|
|||||||
T_DollarBracket id _ -> id
|
T_DollarBracket id _ -> id
|
||||||
T_Annotation id _ _ -> id
|
T_Annotation id _ _ -> id
|
||||||
T_Pipe id _ -> id
|
T_Pipe id _ -> id
|
||||||
|
T_CoProc id _ _ -> id
|
||||||
|
T_CoProcBody id _ -> id
|
||||||
|
|
||||||
blank :: Monad m => Token -> m ()
|
blank :: Monad m => Token -> m ()
|
||||||
blank = const $ return ()
|
blank = const $ return ()
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -27,22 +27,10 @@ internalVariables = [
|
|||||||
"LC_MESSAGES", "LC_NUMERIC", "LINES", "MAIL", "MAILCHECK", "MAILPATH",
|
"LC_MESSAGES", "LC_NUMERIC", "LINES", "MAIL", "MAILCHECK", "MAILPATH",
|
||||||
"OPTERR", "PATH", "POSIXLY_CORRECT", "PROMPT_COMMAND",
|
"OPTERR", "PATH", "POSIXLY_CORRECT", "PROMPT_COMMAND",
|
||||||
"PROMPT_DIRTRIM", "PS1", "PS2", "PS3", "PS4", "SHELL", "TIMEFORMAT",
|
"PROMPT_DIRTRIM", "PS1", "PS2", "PS3", "PS4", "SHELL", "TIMEFORMAT",
|
||||||
"TMOUT", "TMPDIR", "auto_resume", "histchars",
|
"TMOUT", "TMPDIR", "auto_resume", "histchars", "COPROC",
|
||||||
|
|
||||||
-- Zsh
|
-- Other
|
||||||
"ARGV0", "BAUD", "cdpath", "COLUMNS", "CORRECT_IGNORE",
|
"USER", "TZ", "TERM"
|
||||||
"DIRSTACKSIZE", "ENV", "FCEDIT", "fignore", "fpath", "histchars",
|
|
||||||
"HISTCHARS", "HISTFILE", "HISTSIZE", "HOME", "IFS", "KEYBOARD_HACK",
|
|
||||||
"KEYTIMEOUT", "LANG", "LC_ALL", "LC_COLLATE", "LC_CTYPE",
|
|
||||||
"LC_MESSAGES", "LC_NUMERIC", "LC_TIME", "LINES", "LISTMAX",
|
|
||||||
"LOGCHECK", "MAIL", "MAILCHECK", "mailpath", "manpath", "module_path",
|
|
||||||
"NULLCMD", "path", "POSTEDIT", "PROMPT", "PROMPT2", "PROMPT3",
|
|
||||||
"PROMPT4", "prompt", "PROMPT_EOL_MARK", "PS1", "PS2", "PS3", "PS4",
|
|
||||||
"psvar", "READNULLCMD", "REPORTTIME", "REPLY", "reply", "RPROMPT",
|
|
||||||
"RPS1", "RPROMPT2", "RPS2", "SAVEHIST", "SPROMPT", "STTY", "TERM",
|
|
||||||
"TERMINFO", "TIMEFMT", "TMOUT", "TMPPREFIX", "watch", "WATCHFMT",
|
|
||||||
"WORDCHARS", "ZBEEP", "ZDOTDIR", "ZLE_LINE_ABORTED",
|
|
||||||
"ZLE_REMOVE_SUFFIX_CHARS", "ZLE_SPACE_SUFFIX_CHARS"
|
|
||||||
]
|
]
|
||||||
|
|
||||||
variablesWithoutSpaces = [
|
variablesWithoutSpaces = [
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
module ShellCheck.Options where
|
module ShellCheck.Options where
|
||||||
|
|
||||||
data Shell = Ksh | Zsh | Sh | Bash
|
data Shell = Ksh | Sh | Bash
|
||||||
deriving (Show, Eq)
|
deriving (Show, Eq)
|
||||||
|
|
||||||
data AnalysisOptions = AnalysisOptions {
|
data AnalysisOptions = AnalysisOptions {
|
||||||
|
@@ -15,11 +15,12 @@
|
|||||||
You should have received a copy of the GNU Affero General Public License
|
You should have received a copy of the GNU Affero General Public License
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
-}
|
-}
|
||||||
{-# LANGUAGE NoMonomorphismRestriction, TemplateHaskell #-}
|
{-# LANGUAGE NoMonomorphismRestriction, TemplateHaskell, FlexibleContexts #-}
|
||||||
module ShellCheck.Parser (Note(..), Severity(..), parseShell, ParseResult(..), ParseNote(..), sortNotes, noteToParseNote, runTests, readScript) where
|
module ShellCheck.Parser (Note(..), Severity(..), parseShell, ParseResult(..), ParseNote(..), sortNotes, noteToParseNote, runTests, readScript) where
|
||||||
|
|
||||||
import ShellCheck.AST
|
import ShellCheck.AST
|
||||||
import ShellCheck.Data
|
import ShellCheck.Data
|
||||||
|
import ShellCheck.Options
|
||||||
import Text.Parsec
|
import Text.Parsec
|
||||||
import Debug.Trace
|
import Debug.Trace
|
||||||
import Control.Monad
|
import Control.Monad
|
||||||
@@ -468,14 +469,15 @@ 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_aA = isOk readArithmeticContents "! $?"
|
prop_a10= isOk readArithmeticContents "! $?"
|
||||||
prop_aB = isOk readArithmeticContents "10#08 * 16#f"
|
prop_a11= isOk readArithmeticContents "10#08 * 16#f"
|
||||||
prop_aC = isOk readArithmeticContents "\"$((3+2))\" + '37'"
|
prop_a12= isOk readArithmeticContents "\"$((3+2))\" + '37'"
|
||||||
prop_aD = isOk readArithmeticContents "foo[9*y+x]++"
|
prop_a13= isOk readArithmeticContents "foo[9*y+x]++"
|
||||||
prop_aE = isOk readArithmeticContents "1+`echo 2`"
|
prop_a14= isOk readArithmeticContents "1+`echo 2`"
|
||||||
prop_aF = 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_a10= isOk readArithmeticContents "$foo$bar"
|
prop_a16= isOk readArithmeticContents "$foo$bar"
|
||||||
prop_a11= isOk readArithmeticContents "i<(0+(1+1))"
|
prop_a17= isOk readArithmeticContents "i<(0+(1+1))"
|
||||||
|
prop_a18= isOk readArithmeticContents "a?b:c"
|
||||||
readArithmeticContents =
|
readArithmeticContents =
|
||||||
readSequence
|
readSequence
|
||||||
where
|
where
|
||||||
@@ -517,7 +519,7 @@ readArithmeticContents =
|
|||||||
readNormalDollar,
|
readNormalDollar,
|
||||||
readBraced,
|
readBraced,
|
||||||
readBackTicked,
|
readBackTicked,
|
||||||
readNormalLiteral "+-*/=%^,]"
|
readNormalLiteral "+-*/=%^,]?:"
|
||||||
]
|
]
|
||||||
spacing
|
spacing
|
||||||
return $ TA_Expansion id pieces
|
return $ TA_Expansion id pieces
|
||||||
@@ -763,11 +765,10 @@ readDollarBracedLiteral = do
|
|||||||
|
|
||||||
prop_readProcSub1 = isOk readProcSub "<(echo test | wc -l)"
|
prop_readProcSub1 = isOk readProcSub "<(echo test | wc -l)"
|
||||||
prop_readProcSub2 = isOk readProcSub "<( if true; then true; fi )"
|
prop_readProcSub2 = isOk readProcSub "<( if true; then true; fi )"
|
||||||
prop_readProcSub3 = isOk readProcSub "=(ls)"
|
|
||||||
readProcSub = called "process substitution" $ do
|
readProcSub = called "process substitution" $ do
|
||||||
id <- getNextId
|
id <- getNextId
|
||||||
dir <- try $ do
|
dir <- try $ do
|
||||||
x <- oneOf "<>="
|
x <- oneOf "<>"
|
||||||
char '('
|
char '('
|
||||||
return [x]
|
return [x]
|
||||||
allspacing
|
allspacing
|
||||||
@@ -949,7 +950,7 @@ readNormalEscaped = called "escaped char" $ do
|
|||||||
pos <- getPosition
|
pos <- getPosition
|
||||||
backslash
|
backslash
|
||||||
do
|
do
|
||||||
next <- quotable <|> oneOf "?*@!+[]{}.,"
|
next <- quotable <|> oneOf "?*@!+[]{}.,~#"
|
||||||
return $ if next == '\n' then "" else [next]
|
return $ if next == '\n' then "" else [next]
|
||||||
<|>
|
<|>
|
||||||
do
|
do
|
||||||
@@ -1094,6 +1095,7 @@ readDollarBracket = called "$[..] expression" $ do
|
|||||||
string "]"
|
string "]"
|
||||||
return (T_DollarBracket id c)
|
return (T_DollarBracket id c)
|
||||||
|
|
||||||
|
prop_readArithmeticExpression = isOk readArithmeticExpression "((a?b:c))"
|
||||||
readArithmeticExpression = called "((..)) command" $ do
|
readArithmeticExpression = called "((..)) command" $ do
|
||||||
id <- getNextId
|
id <- getNextId
|
||||||
try (string "((")
|
try (string "((")
|
||||||
@@ -1121,23 +1123,33 @@ readDollarExpansion = called "command expansion" $ do
|
|||||||
return $ T_DollarExpansion id cmds
|
return $ T_DollarExpansion id cmds
|
||||||
|
|
||||||
prop_readDollarVariable = isOk readDollarVariable "$@"
|
prop_readDollarVariable = isOk readDollarVariable "$@"
|
||||||
|
prop_readDollarVariable2 = isOk (readDollarVariable >> anyChar) "$?!"
|
||||||
|
prop_readDollarVariable3 = isWarning (readDollarVariable >> anyChar) "$10"
|
||||||
|
prop_readDollarVariable4 = isWarning (readDollarVariable >> string "[@]") "$arr[@]"
|
||||||
|
|
||||||
readDollarVariable = do
|
readDollarVariable = do
|
||||||
id <- getNextId
|
id <- getNextId
|
||||||
|
pos <- getPosition
|
||||||
|
|
||||||
let singleCharred p = do
|
let singleCharred p = do
|
||||||
n <- p
|
n <- p
|
||||||
value <- wrap [n]
|
value <- wrap [n]
|
||||||
return (T_DollarBraced id value) `attempting` do
|
return (T_DollarBraced id value)
|
||||||
pos <- getPosition
|
|
||||||
num <- lookAhead $ many1 p
|
let positional = do
|
||||||
parseNoteAt pos ErrorC 1037 $ "$" ++ (n:num) ++ " is equivalent to ${" ++ [n] ++ "}"++ num ++"."
|
value <- singleCharred digit
|
||||||
|
return value `attempting` do
|
||||||
|
lookAhead digit
|
||||||
|
parseNoteAt pos ErrorC 1037 "Braces are required for positionals over 9, e.g. ${10}."
|
||||||
|
|
||||||
let positional = singleCharred digit
|
|
||||||
let special = singleCharred specialVariable
|
let special = singleCharred specialVariable
|
||||||
|
|
||||||
let regular = do
|
let regular = do
|
||||||
name <- readVariableName
|
name <- readVariableName
|
||||||
value <- wrap name
|
value <- wrap name
|
||||||
return $ T_DollarBraced id value
|
return (T_DollarBraced id value) `attempting` do
|
||||||
|
lookAhead $ void (string "[@]") <|> void (string "[*]") <|> void readArrayIndex
|
||||||
|
parseNoteAt pos ErrorC 1087 "Braces are required when expanding arrays, as in ${array[idx]}."
|
||||||
|
|
||||||
try $ char '$' >> (positional <|> special <|> regular)
|
try $ char '$' >> (positional <|> special <|> regular)
|
||||||
|
|
||||||
@@ -1345,7 +1357,6 @@ prop_readSimpleCommand3 = isOk readSimpleCommand "export foo=(bar baz)"
|
|||||||
prop_readSimpleCommand4 = isOk readSimpleCommand "typeset -a foo=(lol)"
|
prop_readSimpleCommand4 = isOk readSimpleCommand "typeset -a foo=(lol)"
|
||||||
prop_readSimpleCommand5 = isOk readSimpleCommand "time if true; then echo foo; fi"
|
prop_readSimpleCommand5 = isOk readSimpleCommand "time if true; then echo foo; fi"
|
||||||
prop_readSimpleCommand6 = isOk readSimpleCommand "time -p ( ls -l; )"
|
prop_readSimpleCommand6 = isOk readSimpleCommand "time -p ( ls -l; )"
|
||||||
prop_readSimpleCommand7 = isOk readSimpleCommand "cat =(ls)"
|
|
||||||
readSimpleCommand = called "simple command" $ do
|
readSimpleCommand = called "simple command" $ do
|
||||||
id1 <- getNextId
|
id1 <- getNextId
|
||||||
id2 <- getNextId
|
id2 <- getNextId
|
||||||
@@ -1449,7 +1460,11 @@ readPipe = do
|
|||||||
spacing
|
spacing
|
||||||
return $ T_Pipe id ('|':qualifier)
|
return $ T_Pipe id ('|':qualifier)
|
||||||
|
|
||||||
readCommand = readCompoundCommand <|> readSimpleCommand
|
readCommand = choice [
|
||||||
|
readCompoundCommand,
|
||||||
|
readCoProc,
|
||||||
|
readSimpleCommand
|
||||||
|
]
|
||||||
|
|
||||||
readCmdName = do
|
readCmdName = do
|
||||||
f <- readNormalWord
|
f <- readNormalWord
|
||||||
@@ -1617,7 +1632,6 @@ 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_readForClause11= isOk readForClause "for a b in *; do echo $a $b; done"
|
|
||||||
prop_readForClause12= isWarning readForClause "for $a in *; do echo \"$a\"; done"
|
prop_readForClause12= isWarning readForClause "for $a in *; do echo \"$a\"; done"
|
||||||
readForClause = called "for loop" $ do
|
readForClause = called "for loop" $ do
|
||||||
pos <- getPosition
|
pos <- getPosition
|
||||||
@@ -1646,25 +1660,10 @@ readForClause = called "for loop" $ do
|
|||||||
readRegular id pos = do
|
readRegular id pos = do
|
||||||
acceptButWarn (char '$') ErrorC 1086
|
acceptButWarn (char '$') ErrorC 1086
|
||||||
"Don't use $ on the iterator name in for loops."
|
"Don't use $ on the iterator name in for loops."
|
||||||
names <- readNames
|
name <- readVariableName `thenSkip` spacing
|
||||||
readShort names <|> readLong names
|
values <- readInClause <|> (optional readSequentialSep >> return [])
|
||||||
where
|
group <- readDoGroup pos
|
||||||
readLong names = do
|
return $ T_ForIn id name values group
|
||||||
values <- readInClause <|> (optional readSequentialSep >> return [])
|
|
||||||
group <- readDoGroup pos
|
|
||||||
return $ T_ForIn id NormalForIn names values group
|
|
||||||
readShort names = do
|
|
||||||
char '('
|
|
||||||
allspacing
|
|
||||||
words <- many (readNormalWord `thenSkip` allspacing)
|
|
||||||
char ')'
|
|
||||||
allspacing
|
|
||||||
command <- readAndOr
|
|
||||||
return $ T_ForIn id ShortForIn names words [command]
|
|
||||||
|
|
||||||
readNames =
|
|
||||||
reluctantlyTill1 (readVariableName `thenSkip` spacing) $
|
|
||||||
disregard g_Do <|> disregard readInClause <|> disregard readSequentialSep
|
|
||||||
|
|
||||||
prop_readSelectClause1 = isOk readSelectClause "select foo in *; do echo $foo; done"
|
prop_readSelectClause1 = isOk readSelectClause "select foo in *; do echo $foo; done"
|
||||||
prop_readSelectClause2 = isOk readSelectClause "select foo; do echo $foo; done"
|
prop_readSelectClause2 = isOk readSelectClause "select foo; do echo $foo; done"
|
||||||
@@ -1785,7 +1784,31 @@ readFunctionDefinition = called "function" $ do
|
|||||||
g_Rparen
|
g_Rparen
|
||||||
return ()
|
return ()
|
||||||
|
|
||||||
readFunctionName = many functionChars
|
readFunctionName = many1 functionChars
|
||||||
|
|
||||||
|
prop_readCoProc1 = isOk readCoProc "coproc foo { echo bar; }"
|
||||||
|
prop_readCoProc2 = isOk readCoProc "coproc { echo bar; }"
|
||||||
|
prop_readCoProc3 = isOk readCoProc "coproc echo bar"
|
||||||
|
readCoProc = called "coproc" $ do
|
||||||
|
id <- getNextId
|
||||||
|
try $ do
|
||||||
|
string "coproc"
|
||||||
|
whitespace
|
||||||
|
choice [ try $ readCompoundCoProc id, readSimpleCoProc id ]
|
||||||
|
where
|
||||||
|
readCompoundCoProc id = do
|
||||||
|
var <- optionMaybe $
|
||||||
|
readVariableName `thenSkip` whitespace
|
||||||
|
body <- readBody readCompoundCommand
|
||||||
|
return $ T_CoProc id var body
|
||||||
|
readSimpleCoProc id = do
|
||||||
|
body <- readBody readSimpleCommand
|
||||||
|
return $ T_CoProc id Nothing body
|
||||||
|
readBody parser = do
|
||||||
|
id <- getNextId
|
||||||
|
body <- parser
|
||||||
|
return $ T_CoProcBody id body
|
||||||
|
|
||||||
|
|
||||||
readPattern = (readNormalWord `thenSkip` spacing) `sepBy1` (char '|' `thenSkip` spacing)
|
readPattern = (readNormalWord `thenSkip` spacing) `sepBy1` (char '|' `thenSkip` spacing)
|
||||||
|
|
||||||
@@ -1842,6 +1865,7 @@ 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_readAssignmentWord10= isWarning readAssignmentWord "foo$n=42"
|
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'"
|
||||||
@@ -1850,25 +1874,23 @@ readAssignmentWord = try $ do
|
|||||||
pos <- getPosition
|
pos <- getPosition
|
||||||
optional (char '$' >> parseNote ErrorC 1066 "Don't use $ on the left side of assignments.")
|
optional (char '$' >> parseNote ErrorC 1066 "Don't use $ on the left side of assignments.")
|
||||||
variable <- readVariableName
|
variable <- readVariableName
|
||||||
notFollowedBy2 $ do -- Special case for zsh =(..) syntax
|
|
||||||
spacing1
|
|
||||||
string "=("
|
|
||||||
optional (readNormalDollar >> parseNoteAt pos ErrorC
|
optional (readNormalDollar >> parseNoteAt pos ErrorC
|
||||||
1067 "For indirection, use (associative) arrays or 'read \"var$n\" <<< \"value\"'")
|
1067 "For indirection, use (associative) arrays or 'read \"var$n\" <<< \"value\"'")
|
||||||
index <- optionMaybe readArrayIndex
|
index <- optionMaybe readArrayIndex
|
||||||
space <- spacing
|
hasLeftSpace <- liftM (not . null) spacing
|
||||||
pos <- getPosition
|
pos <- getPosition
|
||||||
op <- readAssignmentOp
|
op <- readAssignmentOp
|
||||||
space2 <- spacing
|
hasRightSpace <- liftM (not . null) spacing
|
||||||
if space == "" && space2 /= ""
|
isEndOfCommand <- liftM isJust $ optionMaybe (try . lookAhead $ (disregard (oneOf "\r\n;&|)") <|> eof))
|
||||||
|
if not hasLeftSpace && (hasRightSpace || isEndOfCommand)
|
||||||
then do
|
then do
|
||||||
when (variable /= "IFS") $
|
when (variable /= "IFS" && hasRightSpace) $
|
||||||
parseNoteAt pos WarningC 1007
|
parseNoteAt pos 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 index value
|
return $ T_Assignment id op variable index value
|
||||||
else do
|
else do
|
||||||
when (space /= "" || space2 /= "") $
|
when (hasLeftSpace || hasRightSpace) $
|
||||||
parseNoteAt pos ErrorC 1068 "Don't put spaces around the = in assignments."
|
parseNoteAt pos ErrorC 1068 "Don't put spaces around the = in assignments."
|
||||||
value <- readArray <|> readNormalWord
|
value <- readArray <|> readNormalWord
|
||||||
spacing
|
spacing
|
||||||
@@ -2050,8 +2072,8 @@ readScript = do
|
|||||||
verifyShell pos s =
|
verifyShell pos s =
|
||||||
case isValidShell s of
|
case isValidShell s of
|
||||||
Just True -> return ()
|
Just True -> return ()
|
||||||
Just False -> parseProblemAt pos ErrorC 1071 "ShellCheck only supports Bourne based shell scripts, sorry!"
|
Just False -> parseProblemAt pos ErrorC 1071 "ShellCheck only supports sh/bash/ksh scripts. Sorry!"
|
||||||
Nothing -> parseProblemAt pos InfoC 1008 "This shebang was unrecognized. Note that ShellCheck only handles Bourne based shells."
|
Nothing -> parseProblemAt pos InfoC 1008 "This shebang was unrecognized. Note that ShellCheck only handles sh/bash/ksh."
|
||||||
|
|
||||||
isValidShell s =
|
isValidShell s =
|
||||||
let good = s == "" || any (`isPrefixOf` s) goodShells
|
let good = s == "" || any (`isPrefixOf` s) goodShells
|
||||||
@@ -2065,17 +2087,20 @@ readScript = do
|
|||||||
|
|
||||||
goodShells = [
|
goodShells = [
|
||||||
"sh",
|
"sh",
|
||||||
|
"ash",
|
||||||
|
"dash",
|
||||||
"bash",
|
"bash",
|
||||||
"ksh",
|
"ksh"
|
||||||
"zsh"
|
|
||||||
]
|
]
|
||||||
badShells = [
|
badShells = [
|
||||||
"awk",
|
"awk",
|
||||||
"csh",
|
"csh",
|
||||||
|
"expect",
|
||||||
"perl",
|
"perl",
|
||||||
"python",
|
"python",
|
||||||
"ruby",
|
"ruby",
|
||||||
"tcsh"
|
"tcsh",
|
||||||
|
"zsh"
|
||||||
]
|
]
|
||||||
|
|
||||||
readUtf8Bom = called "Byte Order Mark" $ string "\xFEFF"
|
readUtf8Bom = called "Byte Order Mark" $ string "\xFEFF"
|
||||||
@@ -2119,13 +2144,13 @@ getStringFromParsec errors =
|
|||||||
Message s -> if null s then Nothing else return $ s ++ "."
|
Message s -> if null s then Nothing else return $ s ++ "."
|
||||||
unexpected s = "Unexpected " ++ (if null s then "eof" else s) ++ "."
|
unexpected s = "Unexpected " ++ (if null s then "eof" else s) ++ "."
|
||||||
|
|
||||||
parseShell filename contents =
|
parseShell options filename contents =
|
||||||
case rp (parseWithNotes readScript) filename contents of
|
case rp (parseWithNotes readScript) filename contents of
|
||||||
(Right (script, map, notes), (parsenotes, _)) ->
|
(Right (script, map, notes), (parsenotes, _)) ->
|
||||||
ParseResult (Just (script, map)) (nub $ sortNotes $ notes ++ parsenotes)
|
ParseResult (Just (script, map)) (nub . sortNotes . excludeNotes $ notes ++ parsenotes)
|
||||||
(Left err, (p, context)) ->
|
(Left err, (p, context)) ->
|
||||||
ParseResult Nothing
|
ParseResult Nothing
|
||||||
(nub $ sortNotes $ p ++ notesForContext context ++ [makeErrorFor err])
|
(nub . sortNotes . excludeNotes $ p ++ notesForContext context ++ [makeErrorFor err])
|
||||||
where
|
where
|
||||||
isName (ContextName _ _) = True
|
isName (ContextName _ _) = True
|
||||||
isName _ = False
|
isName _ = False
|
||||||
@@ -2134,6 +2159,7 @@ parseShell filename contents =
|
|||||||
"Couldn't parse this " ++ str ++ "."
|
"Couldn't parse this " ++ str ++ "."
|
||||||
second (ContextName pos str) = ParseNote pos InfoC 1009 $
|
second (ContextName pos str) = ParseNote pos InfoC 1009 $
|
||||||
"The mentioned parser error was in this " ++ str ++ "."
|
"The mentioned parser error was in this " ++ str ++ "."
|
||||||
|
excludeNotes = filter (\c -> codeForParseNote c `notElem` optionExcludes options)
|
||||||
|
|
||||||
lt x = trace (show x) x
|
lt x = trace (show x) x
|
||||||
ltt t = trace (show t)
|
ltt t = trace (show t)
|
||||||
|
71
ShellCheck/Regex.hs
Normal file
71
ShellCheck/Regex.hs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
{-
|
||||||
|
This file is part of ShellCheck.
|
||||||
|
http://www.vidarholen.net/contents/shellcheck
|
||||||
|
|
||||||
|
ShellCheck is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
ShellCheck is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
-}
|
||||||
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
|
||||||
|
-- Basically Text.Regex based on regex-tdfa instead of the buggy regex-posix.
|
||||||
|
module ShellCheck.Regex where
|
||||||
|
|
||||||
|
import Data.List
|
||||||
|
import Data.Maybe
|
||||||
|
import Control.Monad
|
||||||
|
import Text.Regex.TDFA
|
||||||
|
|
||||||
|
-- Precompile the regex
|
||||||
|
mkRegex :: String -> Regex
|
||||||
|
mkRegex str =
|
||||||
|
let make :: RegexMaker Regex CompOption ExecOption String => String -> Regex
|
||||||
|
make = makeRegex
|
||||||
|
in
|
||||||
|
make str
|
||||||
|
|
||||||
|
-- Does the regex match?
|
||||||
|
matches :: String -> Regex -> Bool
|
||||||
|
matches = flip match
|
||||||
|
|
||||||
|
-- Get all subgroups of the first match
|
||||||
|
matchRegex :: Regex -> String -> Maybe [String]
|
||||||
|
matchRegex re str = do
|
||||||
|
(_, _, _, groups) <- matchM re str :: Maybe (String,String,String,[String])
|
||||||
|
return groups
|
||||||
|
|
||||||
|
-- Get all full matches
|
||||||
|
matchAllStrings :: Regex -> String -> [String]
|
||||||
|
matchAllStrings re = unfoldr f
|
||||||
|
where
|
||||||
|
f :: String -> Maybe (String, String)
|
||||||
|
f str = do
|
||||||
|
(_, match, rest, _) <- matchM re str :: Maybe (String, String, String, [String])
|
||||||
|
return (match, rest)
|
||||||
|
|
||||||
|
-- Get all subgroups from all matches
|
||||||
|
matchAllSubgroups :: Regex -> String -> [[String]]
|
||||||
|
matchAllSubgroups re = unfoldr f
|
||||||
|
where
|
||||||
|
f :: String -> Maybe ([String], String)
|
||||||
|
f str = do
|
||||||
|
(_, _, rest, groups) <- matchM re str :: Maybe (String, String, String, [String])
|
||||||
|
return (groups, rest)
|
||||||
|
|
||||||
|
-- Replace regex in input with string
|
||||||
|
subRegex :: Regex -> String -> String -> String
|
||||||
|
subRegex re input replacement = f input
|
||||||
|
where
|
||||||
|
f str = fromMaybe str $ do
|
||||||
|
(before, match, after) <- matchM re str :: Maybe (String, String, String)
|
||||||
|
when (null match) $ error ("Internal error: substituted empty in " ++ str)
|
||||||
|
return $ before ++ replacement ++ f after
|
@@ -28,7 +28,7 @@ import Text.Parsec.Pos
|
|||||||
|
|
||||||
shellCheck :: AnalysisOptions -> String -> [ShellCheckComment]
|
shellCheck :: AnalysisOptions -> String -> [ShellCheckComment]
|
||||||
shellCheck options script =
|
shellCheck options script =
|
||||||
let (ParseResult result notes) = parseShell "-" script in
|
let (ParseResult result notes) = parseShell options "-" script in
|
||||||
let allNotes = notes ++ concat (maybeToList $ do
|
let allNotes = notes ++ concat (maybeToList $ do
|
||||||
(tree, posMap) <- result
|
(tree, posMap) <- result
|
||||||
let list = runAnalytics options tree
|
let list = runAnalytics options tree
|
||||||
@@ -72,6 +72,9 @@ prop_commentDisablesAnalysisIssue2 =
|
|||||||
prop_optionDisablesIssue1 =
|
prop_optionDisablesIssue1 =
|
||||||
null $ shellCheck (defaultAnalysisOptions { optionExcludes = [2086, 2148] }) "echo $1"
|
null $ shellCheck (defaultAnalysisOptions { optionExcludes = [2086, 2148] }) "echo $1"
|
||||||
|
|
||||||
|
prop_optionDisablesIssue2 =
|
||||||
|
null $ shellCheck (defaultAnalysisOptions { optionExcludes = [2148, 1037] }) "echo \"$10\""
|
||||||
|
|
||||||
return []
|
return []
|
||||||
runTests = $quickCheckAll
|
runTests = $quickCheckAll
|
||||||
|
|
||||||
|
@@ -16,6 +16,20 @@ errors and pitfalls where the shell just gives a cryptic error message or
|
|||||||
strange behavior, but it also reports on a few more advanced issues where
|
strange behavior, but it also reports on a few more advanced issues where
|
||||||
corner cases can cause delayed failures.
|
corner cases can cause delayed failures.
|
||||||
|
|
||||||
|
ShellCheck gives shell specific advice. Consider the line:
|
||||||
|
|
||||||
|
(( area = 3.14*r*r ))
|
||||||
|
|
||||||
|
+ For scripts starting with `#!/bin/sh` (or when using `-s sh`), ShellCheck
|
||||||
|
will warn that `(( .. ))` is not POSIX compliant (similar to checkbashisms).
|
||||||
|
|
||||||
|
+ For scripts starting with `#!/bin/bash` (or using `-s bash`), ShellCheck
|
||||||
|
will warn that decimals are not supported.
|
||||||
|
|
||||||
|
+ For scripts starting with `#!/bin/ksh` (or using `-s ksh`), ShellCheck will
|
||||||
|
not warn at all, as `ksh` supports decimals in arithmetic contexts.
|
||||||
|
|
||||||
|
|
||||||
# OPTIONS
|
# OPTIONS
|
||||||
|
|
||||||
**-e**\ *CODE1*[,*CODE2*...],\ **--exclude=***CODE1*[,*CODE2*...]
|
**-e**\ *CODE1*[,*CODE2*...],\ **--exclude=***CODE1*[,*CODE2*...]
|
||||||
@@ -32,9 +46,9 @@ corner cases can cause delayed failures.
|
|||||||
|
|
||||||
**-s**\ *shell*,\ **--shell=***shell*
|
**-s**\ *shell*,\ **--shell=***shell*
|
||||||
|
|
||||||
: Specify Bourne shell dialect. Valid values are *sh*, *bash*, *ksh* and
|
: Specify Bourne shell dialect. Valid values are *sh*, *bash* and *ksh*.
|
||||||
*zsh*. The default is to use the file's shebang, or *bash* if the target
|
The default is to use the file's shebang, or *bash* if the target shell
|
||||||
shell can't be determined.
|
can't be determined.
|
||||||
|
|
||||||
**-V**\ *version*,\ **--version**
|
**-V**\ *version*,\ **--version**
|
||||||
|
|
||||||
@@ -83,11 +97,12 @@ corner cases can cause delayed failures.
|
|||||||
|
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"line": line,
|
"file": "filename",
|
||||||
"column": column,
|
"line": lineNumber,
|
||||||
"level": level,
|
"column": columnNumber,
|
||||||
"code": ####,
|
"level": "severitylevel",
|
||||||
"message": message
|
"code": errorCode,
|
||||||
|
"message": "warning message"
|
||||||
},
|
},
|
||||||
...
|
...
|
||||||
]
|
]
|
||||||
@@ -104,6 +119,14 @@ For example, to suppress SC2035 about using `./*.jpg`:
|
|||||||
# shellcheck disable=SC2035
|
# shellcheck disable=SC2035
|
||||||
echo "Files: " *.jpg
|
echo "Files: " *.jpg
|
||||||
|
|
||||||
|
Here a shell brace group is used to suppress on multiple lines:
|
||||||
|
|
||||||
|
# shellcheck disable=SC2016
|
||||||
|
{
|
||||||
|
echo 'Modifying $PATH'
|
||||||
|
echo 'PATH=foo:$PATH' >> ~/.bashrc
|
||||||
|
}
|
||||||
|
|
||||||
Valid keys are:
|
Valid keys are:
|
||||||
|
|
||||||
**disable**
|
**disable**
|
||||||
|
@@ -19,6 +19,7 @@ import Control.Exception
|
|||||||
import Control.Monad
|
import Control.Monad
|
||||||
import Control.Monad.Trans
|
import Control.Monad.Trans
|
||||||
import Control.Monad.Trans.Error
|
import Control.Monad.Trans.Error
|
||||||
|
import Control.Monad.Trans.List
|
||||||
import Data.Char
|
import Data.Char
|
||||||
import Data.Maybe
|
import Data.Maybe
|
||||||
import Data.Monoid
|
import Data.Monoid
|
||||||
@@ -40,6 +41,8 @@ import qualified Data.Map as Map
|
|||||||
data Flag = Flag String String
|
data Flag = Flag String String
|
||||||
data Status = NoProblems | SomeProblems | BadInput | SupportFailure | SyntaxFailure | RuntimeException deriving (Ord, Eq)
|
data Status = NoProblems | SomeProblems | BadInput | SupportFailure | SyntaxFailure | RuntimeException deriving (Ord, Eq)
|
||||||
|
|
||||||
|
data JsonComment = JsonComment FilePath ShellCheckComment
|
||||||
|
|
||||||
instance Error Status where
|
instance Error Status where
|
||||||
noMsg = RuntimeException
|
noMsg = RuntimeException
|
||||||
|
|
||||||
@@ -54,7 +57,7 @@ options = [
|
|||||||
Option "f" ["format"]
|
Option "f" ["format"]
|
||||||
(ReqArg (Flag "format") "FORMAT") "output format",
|
(ReqArg (Flag "format") "FORMAT") "output format",
|
||||||
Option "s" ["shell"]
|
Option "s" ["shell"]
|
||||||
(ReqArg (Flag "shell") "SHELLNAME") "Specify dialect (bash,sh,ksh,zsh)",
|
(ReqArg (Flag "shell") "SHELLNAME") "Specify dialect (bash,sh,ksh)",
|
||||||
Option "V" ["version"]
|
Option "V" ["version"]
|
||||||
(NoArg $ Flag "version" "true") "Print version information"
|
(NoArg $ Flag "version" "true") "Print version information"
|
||||||
]
|
]
|
||||||
@@ -62,8 +65,9 @@ options = [
|
|||||||
printErr = hPutStrLn stderr
|
printErr = hPutStrLn stderr
|
||||||
|
|
||||||
|
|
||||||
instance JSON ShellCheckComment where
|
instance JSON (JsonComment) where
|
||||||
showJSON c = makeObj [
|
showJSON (JsonComment filename c) = makeObj [
|
||||||
|
("file", showJSON $ filename),
|
||||||
("line", showJSON $ scLine c),
|
("line", showJSON $ scLine c),
|
||||||
("column", showJSON $ scColumn c),
|
("column", showJSON $ scColumn c),
|
||||||
("level", showJSON $ scSeverity c),
|
("level", showJSON $ scSeverity c),
|
||||||
@@ -152,10 +156,12 @@ forTty options files = do
|
|||||||
term <- hIsTerminalDevice stdout
|
term <- hIsTerminalDevice stdout
|
||||||
return $ if term then colorComment else const id
|
return $ if term then colorComment else const id
|
||||||
|
|
||||||
-- This totally ignores the filenames. Fixme?
|
|
||||||
forJson :: AnalysisOptions -> [FilePath] -> IO Status
|
forJson :: AnalysisOptions -> [FilePath] -> IO Status
|
||||||
forJson options files = catchExceptions $ do
|
forJson options files = catchExceptions $ do
|
||||||
comments <- liftM concat $ mapM (commentsFor options) files
|
comments <- runListT $ do
|
||||||
|
file <- ListT $ return files
|
||||||
|
comment <- ListT $ commentsFor options file
|
||||||
|
return $ JsonComment file comment
|
||||||
putStrLn $ encodeStrict comments
|
putStrLn $ encodeStrict comments
|
||||||
return $ checkComments comments
|
return $ checkComments comments
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user