diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cad51f..bb623f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ - Preliminary support for fix suggestions - Files containing Bats tests can now be checked - Directory wide directives can now be placed in a `.shellcheckrc` -- Verbose mode: Use `-S verbose` for especially pedantic suggestions +- Optional checks: Use `--list-optional` to show a list of tests, + Enable with `-o` flags or `enable=name` directives - Source paths: Use `-P dir1:dir2` or a `source-path=dir1` directive to specify search paths for sourced files. - SC2249: Warn about `case` with missing default case (verbose) diff --git a/shellcheck.1.md b/shellcheck.1.md index 220cc04..f8cbd44 100644 --- a/shellcheck.1.md +++ b/shellcheck.1.md @@ -63,10 +63,21 @@ not warn at all, as `ksh` supports decimals in arithmetic contexts. standard output. Subsequent **-f** options are ignored, see **FORMATS** below for more information. +**--list-optional** + +: Output a list of known optional checks. These can be enabled with **-o** + flags or **enable** directives. + **--norc** : Don't try to look for .shellcheckrc configuration files. +**-o**\ *NAME1*[,*NAME2*...],\ **--enable=***NAME1*[,*NAME2*...] + +: Enable optional checks. The special name *all* enables all of them. + Subsequent **-o** options accumulate. This is equivalent to specifying + **enable** directives. + **-P**\ *SOURCEPATH*,\ **--source-path=***SOURCEPATH* : Specify paths to search for sourced files, separated by `:` on Unix and @@ -83,7 +94,7 @@ not warn at all, as `ksh` supports decimals in arithmetic contexts. **-S**\ *SEVERITY*,\ **--severity=***severity* : Specify minimum severity of errors to consider. Valid values in order of - severity are *error*, *warning*, *info*, *style* and *verbose*. + severity are *error*, *warning*, *info* and *style*. The default is *style*. **-V**,\ **--version** @@ -163,8 +174,9 @@ not warn at all, as `ksh` supports decimals in arithmetic contexts. # DIRECTIVES -ShellCheck directives can be specified as comments in the shell script -before a command or block: +ShellCheck directives can be specified as comments in the shell script. +If they appear before the first command, they are considered file-wide. +Otherwise, they apply to the immediately following command or block: # shellcheck key=value key=value command-or-structure @@ -194,6 +206,10 @@ Valid keys are: The command can be a simple command like `echo foo`, or a compound command like a function definition, subshell block or loop. +**enable** +: Enable an optional check by name, as listed with **--list-optional**. + Only file-wide `enable` directives are considered. + **source** : Overrides the filename included by a `source`/`.` statement. This can be used to tell shellcheck where to look for a file whose name is determined @@ -224,6 +240,9 @@ Here is an example `.shellcheckrc`: source-path=SCRIPTDIR source-path=/mnt/chroot + # Turn on warnings for unquoted variables with safe values + enable=quote-safe-variables + # Allow using `which` since it gives full paths and is common enough disable=SC2230 diff --git a/shellcheck.hs b/shellcheck.hs index 137aaba..c8ea7fb 100644 --- a/shellcheck.hs +++ b/shellcheck.hs @@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . -} +import qualified ShellCheck.Analyzer import ShellCheck.Checker import ShellCheck.Data import ShellCheck.Interface @@ -98,8 +99,13 @@ options = [ Option "f" ["format"] (ReqArg (Flag "format") "FORMAT") $ "Output format (" ++ formatList ++ ")", + Option "" ["list-optional"] + (NoArg $ Flag "list-optional" "true") "List checks disabled by default", Option "" ["norc"] (NoArg $ Flag "norc" "true") "Don't look for .shellcheckrc files", + Option "o" ["enable"] + (ReqArg (Flag "enable") "check1,check2..") + "List of optional checks to enable (or 'all')", Option "P" ["source-path"] (ReqArg (Flag "source-path") "SOURCEPATHS") "Specify path when looking for sourced files (\"SCRIPTDIR\" for script's dir)", @@ -108,7 +114,7 @@ options = [ "Specify dialect (sh, bash, dash, ksh)", Option "S" ["severity"] (ReqArg (Flag "severity") "SEVERITY") - "Minimum severity of errors to consider (error, warning, info, style, verbose)", + "Minimum severity of errors to consider (error, warning, info, style)", Option "V" ["version"] (NoArg $ Flag "version" "true") "Print version information", Option "W" ["wiki-link-count"] @@ -259,8 +265,7 @@ parseSeverityOption value = ("error", ErrorC), ("warning", WarningC), ("info", InfoC), - ("style", StyleC), - ("verbose", VerboseC) + ("style", StyleC) ] parseOption flag options = @@ -299,6 +304,10 @@ parseOption flag options = liftIO printVersion throwError NoProblems + Flag "list-optional" _ -> do + liftIO printOptional + throwError NoProblems + Flag "help" _ -> do liftIO $ putStrLn getUsageInfo throwError NoProblems @@ -352,6 +361,13 @@ parseOption flag options = } } + Flag "enable" value -> + let cs = checkSpec options in return options { + checkSpec = cs { + csOptionalChecks = (csOptionalChecks cs) ++ split ',' value + } + } + -- This flag is handled specially in 'process' Flag "format" _ -> return options @@ -547,3 +563,14 @@ printVersion = do putStrLn $ "version: " ++ shellcheckVersion putStrLn "license: GNU General Public License, version 3" putStrLn "website: https://www.shellcheck.net" + +printOptional = do + mapM f list + where + list = sortOn cdName ShellCheck.Analyzer.optionalChecks + f item = do + putStrLn $ "name: " ++ cdName item + putStrLn $ "desc: " ++ cdDescription item + putStrLn $ "example: " ++ cdPositive item + putStrLn $ "fix: " ++ cdNegative item + putStrLn "" diff --git a/src/ShellCheck/AST.hs b/src/ShellCheck/AST.hs index 9ec892d..5b4254f 100644 --- a/src/ShellCheck/AST.hs +++ b/src/ShellCheck/AST.hs @@ -144,6 +144,7 @@ data Token = data Annotation = DisableComment Integer + | EnableComment String | SourceOverride String | ShellOverride String | SourcePath String diff --git a/src/ShellCheck/Analytics.hs b/src/ShellCheck/Analytics.hs index 766efae..ac8fcdb 100644 --- a/src/ShellCheck/Analytics.hs +++ b/src/ShellCheck/Analytics.hs @@ -19,7 +19,7 @@ -} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE FlexibleContexts #-} -module ShellCheck.Analytics (runAnalytics, ShellCheck.Analytics.runTests) where +module ShellCheck.Analytics (runAnalytics, optionalChecks, ShellCheck.Analytics.runTests) where import ShellCheck.AST import ShellCheck.ASTLib @@ -49,11 +49,9 @@ import Test.QuickCheck.Test (quickCheckWithResult, stdArgs, maxSuccess) -- Checks that are run on the AST root treeChecks :: [Parameters -> Token -> [TokenComment]] treeChecks = [ - runNodeAnalysis - (\p t -> (mapM_ ((\ f -> f t) . (\ f -> f p)) - nodeChecks)) + nodeChecksToTreeCheck nodeChecks ,subshellAssignmentCheck - ,checkVerboseSpacefulness + ,checkSpacefulness ,checkQuotesInLiterals ,checkShebangParameters ,checkFunctionsUsedExternally @@ -69,7 +67,14 @@ treeChecks = [ runAnalytics :: AnalysisSpec -> [TokenComment] runAnalytics options = - runList options treeChecks + runList options treeChecks ++ runList options optionalChecks + where + root = asScript options + optionals = getEnableDirectives root ++ asOptionalChecks options + optionalChecks = + if "all" `elem` optionals + then map snd optionalTreeChecks + else mapMaybe (\c -> Map.lookup c optionalCheckMap) optionals runList :: AnalysisSpec -> [Parameters -> Token -> [TokenComment]] -> [TokenComment] @@ -79,13 +84,27 @@ runList spec list = notes params = makeParameters spec notes = concatMap (\f -> f params root) list +getEnableDirectives root = + case root of + T_Annotation _ list _ -> mapMaybe getEnable list + _ -> [] + where + getEnable t = + case t of + EnableComment s -> return s + _ -> Nothing checkList l t = concatMap (\f -> f t) l - -- Checks that are run on each node in the AST runNodeAnalysis f p t = execWriter (doAnalysis (f p) t) +-- Perform multiple node checks in a single iteration over the tree +nodeChecksToTreeCheck checkList = + runNodeAnalysis + (\p t -> (mapM_ ((\ f -> f t) . (\ f -> f p)) + checkList)) + nodeChecks :: [Parameters -> Token -> Writer [TokenComment] ()] nodeChecks = [ checkUuoc @@ -170,11 +189,46 @@ nodeChecks = [ ,checkSubshelledTests ,checkInvertedStringTest ,checkRedirectionToCommand - ,checkNullaryExpansionTest ,checkDollarQuoteParen - ,checkDefaultCase ] +optionalChecks = map fst optionalTreeChecks + + +prop_verifyOptionalExamples = all check optionalTreeChecks + where + check (desc, check) = + verifyTree check (cdPositive desc) + && verifyNotTree check (cdNegative desc) + +optionalTreeChecks :: [(CheckDescription, (Parameters -> Token -> [TokenComment]))] +optionalTreeChecks = [ + (newCheckDescription { + cdName = "quote-safe-variables", + cdDescription = "Suggest quoting variables without metacharacters", + cdPositive = "var=hello; echo $var", + cdNegative = "var=hello; echo \"$var\"" + }, checkVerboseSpacefulness) + + ,(newCheckDescription { + cdName = "avoid-nullary-conditions", + cdDescription = "Suggest explicitly using -n in `[ $var ]`", + cdPositive = "[ \"$var\" ]", + cdNegative = "[ -n \"$var\" ]" + }, nodeChecksToTreeCheck [checkNullaryExpansionTest]) + + ,(newCheckDescription { + cdName = "add-default-case", + cdDescription = "Suggest adding a default case in `case` statements", + cdPositive = "case $? in 0) echo 'Success';; esac", + cdNegative = "case $? in 0) echo 'Success';; *) echo 'Fail' ;; esac" + }, nodeChecksToTreeCheck [checkDefaultCase]) + ] + +optionalCheckMap :: Map.Map String (Parameters -> Token -> [TokenComment]) +optionalCheckMap = Map.fromList $ map item optionalTreeChecks + where + item (desc, check) = (cdName desc, check) wouldHaveBeenGlob s = '*' `elem` s @@ -1650,12 +1704,10 @@ prop_checkSpacefulness2 = verifyNotTree checkSpacefulness "a='cow moo'; [[ $a ]] prop_checkSpacefulness3 = verifyNotTree checkSpacefulness "a='cow*.mp3'; echo \"$a\"" prop_checkSpacefulness4 = verifyTree checkSpacefulness "for f in *.mp3; do echo $f; done" prop_checkSpacefulness4a= verifyNotTree checkSpacefulness "foo=3; foo=$(echo $foo)" -prop_checkSpacefulness4v= verifyTree checkVerboseSpacefulness "foo=3; foo=$(echo $foo)" prop_checkSpacefulness5 = verifyTree checkSpacefulness "a='*'; b=$a; c=lol${b//foo/bar}; echo $c" prop_checkSpacefulness6 = verifyTree checkSpacefulness "a=foo$(lol); echo $a" prop_checkSpacefulness7 = verifyTree checkSpacefulness "a=foo\\ bar; rm $a" prop_checkSpacefulness8 = verifyNotTree checkSpacefulness "a=foo\\ bar; a=foo; rm $a" -prop_checkSpacefulness8v= verifyTree checkVerboseSpacefulness "a=foo\\ bar; a=foo; rm $a" prop_checkSpacefulness10= verifyTree checkSpacefulness "rm $1" prop_checkSpacefulness11= verifyTree checkSpacefulness "rm ${10//foo/bar}" prop_checkSpacefulness12= verifyNotTree checkSpacefulness "(( $1 + 3 ))" @@ -1675,7 +1727,6 @@ prop_checkSpacefulness25= verifyTree checkSpacefulness "a='s/[0-9]//g'; sed $a" prop_checkSpacefulness26= verifyTree checkSpacefulness "a='foo bar'; echo {1,2,$a}" prop_checkSpacefulness27= verifyNotTree checkSpacefulness "echo ${a:+'foo'}" prop_checkSpacefulness28= verifyNotTree checkSpacefulness "exec {n}>&1; echo $n" -prop_checkSpacefulness28v = verifyTree checkVerboseSpacefulness "exec {n}>&1; echo $n" prop_checkSpacefulness29= verifyNotTree checkSpacefulness "n=$(stuff); exec {n}>&-;" prop_checkSpacefulness30= verifyTree checkSpacefulness "file='foo bar'; echo foo > $file;" prop_checkSpacefulness31= verifyNotTree checkSpacefulness "echo \"`echo \\\"$1\\\"`\"" @@ -1684,22 +1735,53 @@ prop_checkSpacefulness33= verifyTree checkSpacefulness "for file; do echo $file; prop_checkSpacefulness34= verifyTree checkSpacefulness "declare foo$n=$1" prop_checkSpacefulness35= verifyNotTree checkSpacefulness "echo ${1+\"$1\"}" prop_checkSpacefulness36= verifyNotTree checkSpacefulness "arg=$#; echo $arg" -prop_checkSpacefulness36v = verifyTree checkVerboseSpacefulness "arg=$#; echo $arg" prop_checkSpacefulness37= verifyNotTree checkSpacefulness "@test 'status' {\n [ $status -eq 0 ]\n}" prop_checkSpacefulness37v = verifyTree checkVerboseSpacefulness "@test 'status' {\n [ $status -eq 0 ]\n}" --- This is slightly awkward because we want the tests to --- discriminate between normal and verbose output. -checkSpacefulness params t = checkSpacefulness' False params t -checkVerboseSpacefulness params t = checkSpacefulness' True params t -checkSpacefulness' alsoVerbose params t = +-- This is slightly awkward because we want to support structured +-- optional checks based on nearly the same logic +checkSpacefulness params = checkSpacefulness' onFind params + where + emit x = tell [x] + onFind spaces token _ = + when spaces $ + if isDefaultAssignment (parentMap params) token + then + emit $ makeComment InfoC (getId token) 2223 + "This default assignment may cause DoS due to globbing. Quote it." + else + emit $ makeCommentWithFix InfoC (getId token) 2086 + "Double quote to prevent globbing and word splitting." + (addDoubleQuotesAround params token) + + isDefaultAssignment parents token = + let modifier = getBracedModifier $ bracedString token in + any (`isPrefixOf` modifier) ["=", ":="] + && isParamTo parents ":" token + + +prop_checkSpacefulness4v= verifyTree checkVerboseSpacefulness "foo=3; foo=$(echo $foo)" +prop_checkSpacefulness8v= verifyTree checkVerboseSpacefulness "a=foo\\ bar; a=foo; rm $a" +prop_checkSpacefulness28v = verifyTree checkVerboseSpacefulness "exec {n}>&1; echo $n" +prop_checkSpacefulness36v = verifyTree checkVerboseSpacefulness "arg=$#; echo $arg" +checkVerboseSpacefulness params = checkSpacefulness' onFind params + where + onFind spaces token name = + when (not spaces && name `notElem` specialVariablesWithoutSpaces) $ + tell [makeCommentWithFix StyleC (getId token) 2248 + "Prefer double quoting even when variables don't contain special characters." + (addDoubleQuotesAround params token)] + +addDoubleQuotesAround params token = (surroundWidth (getId token) params "\"") +checkSpacefulness' + :: (Bool -> Token -> String -> Writer [TokenComment] ()) -> + Parameters -> Token -> [TokenComment] +checkSpacefulness' onFind params t = doVariableFlowAnalysis readF writeF (Map.fromList defaults) (variableFlow params) where defaults = zip variablesWithoutSpaces (repeat False) - hasSpaces name = do - map <- get - return $ Map.findWithDefault True name map + hasSpaces name = gets (Map.findWithDefault True name) setSpaces name bool = modify $ Map.insert name bool @@ -1714,24 +1796,9 @@ checkSpacefulness' alsoVerbose params t = && not (isQuotedAlternativeReference token) && not (usedAsCommandName parents token) - return . execWriter $ when needsQuoting $ - if spaces - then - if isDefaultAssignment (parentMap params) token - then - emit $ makeComment InfoC (getId token) 2223 - "This default assignment may cause DoS due to globbing. Quote it." - else - emit $ makeCommentWithFix InfoC (getId token) 2086 - "Double quote to prevent globbing and word splitting." - (fixFor token) - else - when (alsoVerbose && name `notElem` specialVariablesWithoutSpaces) $ - emit $ makeCommentWithFix VerboseC (getId token) 2248 - "Prefer double quoting even when variables don't contain special characters." - (fixFor token) + return . execWriter $ when needsQuoting $ onFind spaces token name + where - fixFor token = (surroundWidth (getId token) params "\"") emit x = tell [x] writeF _ _ name (DataString SourceExternal) = setSpaces name True >> return [] @@ -1771,12 +1838,6 @@ checkSpacefulness' alsoVerbose params t = globspace = "*?[] \t\n" containsAny s = any (`elem` s) - isDefaultAssignment parents token = - let modifier = getBracedModifier $ bracedString token in - isExpansion token - && any (`isPrefixOf` modifier) ["=", ":="] - && isParamTo parents ":" token - prop_checkQuotesInLiterals1 = verifyTree checkQuotesInLiterals "param='--foo=\"bar\"'; app $param" prop_checkQuotesInLiterals1a= verifyTree checkQuotesInLiterals "param=\"--foo='lolbar'\"; app $param" prop_checkQuotesInLiterals2 = verifyNotTree checkQuotesInLiterals "param='--foo=\"bar\"'; app \"$param\"" @@ -3224,11 +3285,11 @@ checkNullaryExpansionTest params t = TC_Nullary _ _ word -> case getWordParts word of [t] | isCommandSubstitution t -> - verboseWithFix id 2243 "Prefer explicit -n to check for output (or run command without [/[[ to check for success)." fix + styleWithFix id 2243 "Prefer explicit -n to check for output (or run command without [/[[ to check for success)." fix -- If they're constant, you get SC2157 &co x | all (not . isConstant) x -> - verboseWithFix id 2244 "Prefer explicit -n to check non-empty string (or use =/-ne to check boolean/integer)." fix + styleWithFix id 2244 "Prefer explicit -n to check non-empty string (or use =/-ne to check boolean/integer)." fix _ -> return () where id = getId word @@ -3256,7 +3317,7 @@ checkDefaultCase _ t = case t of T_CaseExpression id _ list -> unless (any canMatchAny list) $ - verbose id 2249 "Consider adding a default *) case, even if it just exits with error." + info id 2249 "Consider adding a default *) case, even if it just exits with error." _ -> return () where canMatchAny (_, list, _) = any canMatchAny' list diff --git a/src/ShellCheck/Analyzer.hs b/src/ShellCheck/Analyzer.hs index ffbc4e5..442daba 100644 --- a/src/ShellCheck/Analyzer.hs +++ b/src/ShellCheck/Analyzer.hs @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . -} -module ShellCheck.Analyzer (analyzeScript) where +module ShellCheck.Analyzer (analyzeScript, ShellCheck.Analyzer.optionalChecks) where import ShellCheck.Analytics import ShellCheck.AnalyzerLib @@ -43,3 +43,7 @@ checkers params = mconcat $ map ($ params) [ ShellCheck.Checks.Commands.checker, ShellCheck.Checks.ShellSupport.checker ] + +optionalChecks = mconcat $ [ + ShellCheck.Analytics.optionalChecks + ] diff --git a/src/ShellCheck/AnalyzerLib.hs b/src/ShellCheck/AnalyzerLib.hs index 01fcc8f..5783820 100644 --- a/src/ShellCheck/AnalyzerLib.hs +++ b/src/ShellCheck/AnalyzerLib.hs @@ -77,14 +77,22 @@ composeAnalyzers :: (a -> Analysis) -> (a -> Analysis) -> a -> Analysis composeAnalyzers f g x = f x >> g x data Parameters = Parameters { - hasLastpipe :: Bool, -- Whether this script has the 'lastpipe' option set/default. - hasSetE :: Bool, -- Whether this script has 'set -e' anywhere. - variableFlow :: [StackData], -- A linear (bad) analysis of data flow - parentMap :: Map.Map Id Token, -- A map from Id to parent Token - shellType :: Shell, -- The shell type, such as Bash or Ksh - shellTypeSpecified :: Bool, -- True if shell type was forced via flags - rootNode :: Token, -- The root node of the AST - tokenPositions :: Map.Map Id (Position, Position) -- map from token id to start and end position + -- Whether this script has the 'lastpipe' option set/default. + hasLastpipe :: Bool, + -- Whether this script has 'set -e' anywhere. + hasSetE :: Bool, + -- A linear (bad) analysis of data flow + variableFlow :: [StackData], + -- A map from Id to parent Token + parentMap :: Map.Map Id Token, + -- The shell type, such as Bash or Ksh + shellType :: Shell, + -- True if shell type was forced via flags + shellTypeSpecified :: Bool, + -- The root node of the AST + rootNode :: Token, + -- map from token id to start and end position + tokenPositions :: Map.Map Id (Position, Position) } deriving (Show) -- TODO: Cache results of common AST ops here @@ -154,14 +162,11 @@ warn id code str = addComment $ makeComment WarningC id code str err id code str = addComment $ makeComment ErrorC id code str info id code str = addComment $ makeComment InfoC id code str style id code str = addComment $ makeComment StyleC id code str -verbose id code str = addComment $ makeComment VerboseC id code str warnWithFix :: MonadWriter [TokenComment] m => Id -> Code -> String -> Fix -> m () warnWithFix = addCommentWithFix WarningC styleWithFix :: MonadWriter [TokenComment] m => Id -> Code -> String -> Fix -> m () styleWithFix = addCommentWithFix StyleC -verboseWithFix :: MonadWriter [TokenComment] m => Id -> Code -> String -> Fix -> m () -verboseWithFix = addCommentWithFix VerboseC addCommentWithFix :: MonadWriter [TokenComment] m => Severity -> Id -> Code -> String -> Fix -> m () addCommentWithFix severity id code str fix = diff --git a/src/ShellCheck/Checker.hs b/src/ShellCheck/Checker.hs index e73636d..a231242 100644 --- a/src/ShellCheck/Checker.hs +++ b/src/ShellCheck/Checker.hs @@ -84,7 +84,8 @@ checkScript sys spec = do asFallbackShell = shellFromFilename $ csFilename spec, asCheckSourced = csCheckSourced spec, asExecutionMode = Executed, - asTokenPositions = tokenPositions + asTokenPositions = tokenPositions, + asOptionalChecks = csOptionalChecks spec } where as = newAnalysisSpec root let analysisMessages = fromMaybe [] $ @@ -302,6 +303,14 @@ prop_sourcedFileUsesOriginalShellExtension = result == [2079] csCheckSourced = True } +prop_canEnableOptionalsWithSpec = result == [2244] + where + result = checkWithSpec [] emptyCheckSpec { + csFilename = "file.sh", + csScript = "#!/bin/sh\n[ \"$1\" ]", + csOptionalChecks = ["avoid-nullary-conditions"] + } + prop_optionIncludes1 = -- expect 2086, but not included, so nothing reported null $ checkOptionIncludes (Just [2080]) "#!/bin/sh\n var='a b'\n echo $var" @@ -347,6 +356,12 @@ prop_brokenRcGetsWarning = result == [1134, 2086] csIgnoreRC = False } +prop_canEnableOptionalsWithRc = result == [2244] + where + result = checkWithRc "enable=avoid-nullary-conditions" emptyCheckSpec { + csScript = "#!/bin/sh\n[ \"$1\" ]" + } + prop_sourcePathRedirectsName = result == [2086] where f "dir/myscript" _ "lib" = return "foo/lib" diff --git a/src/ShellCheck/Formatter/Format.hs b/src/ShellCheck/Formatter/Format.hs index bb513cd..57b9d71 100644 --- a/src/ShellCheck/Formatter/Format.hs +++ b/src/ShellCheck/Formatter/Format.hs @@ -47,7 +47,6 @@ severityText pc = WarningC -> "warning" InfoC -> "info" StyleC -> "style" - VerboseC -> "verbose" -- Realign comments from a tabstop of 8 to 1 makeNonVirtual comments contents = diff --git a/src/ShellCheck/Interface.hs b/src/ShellCheck/Interface.hs index 1d0cc6b..e60433e 100644 --- a/src/ShellCheck/Interface.hs +++ b/src/ShellCheck/Interface.hs @@ -21,18 +21,18 @@ module ShellCheck.Interface ( SystemInterface(..) - , CheckSpec(csFilename, csScript, csCheckSourced, csIncludedWarnings, csExcludedWarnings, csShellTypeOverride, csMinSeverity, csIgnoreRC) + , CheckSpec(csFilename, csScript, csCheckSourced, csIncludedWarnings, csExcludedWarnings, csShellTypeOverride, csMinSeverity, csIgnoreRC, csOptionalChecks) , CheckResult(crFilename, crComments) , ParseSpec(psFilename, psScript, psCheckSourced, psIgnoreRC, psShellTypeOverride) , ParseResult(prComments, prTokenPositions, prRoot) - , AnalysisSpec(asScript, asShellType, asFallbackShell, asExecutionMode, asCheckSourced, asTokenPositions) + , AnalysisSpec(asScript, asShellType, asFallbackShell, asExecutionMode, asCheckSourced, asTokenPositions, asOptionalChecks) , AnalysisResult(arComments) , FormatterOptions(foColorOption, foWikiLinkCount) , Shell(Ksh, Sh, Bash, Dash) , ExecutionMode(Executed, Sourced) , ErrorMessage , Code - , Severity(ErrorC, WarningC, InfoC, StyleC, VerboseC) + , Severity(ErrorC, WarningC, InfoC, StyleC) , Position(posFile, posLine, posColumn) , Comment(cSeverity, cCode, cMessage) , PositionedComment(pcStartPos , pcEndPos , pcComment, pcFix) @@ -56,6 +56,8 @@ module ShellCheck.Interface , InsertionPoint(InsertBefore, InsertAfter) , Replacement(repStartPos, repEndPos, repString, repPrecedence, repInsertionPoint) , newReplacement + , CheckDescription(cdName, cdDescription, cdPositive, cdNegative) + , newCheckDescription ) where import ShellCheck.AST @@ -92,7 +94,8 @@ data CheckSpec = CheckSpec { csExcludedWarnings :: [Integer], csIncludedWarnings :: Maybe [Integer], csShellTypeOverride :: Maybe Shell, - csMinSeverity :: Severity + csMinSeverity :: Severity, + csOptionalChecks :: [String] } deriving (Show, Eq) data CheckResult = CheckResult { @@ -115,7 +118,8 @@ emptyCheckSpec = CheckSpec { csExcludedWarnings = [], csIncludedWarnings = Nothing, csShellTypeOverride = Nothing, - csMinSeverity = StyleC + csMinSeverity = StyleC, + csOptionalChecks = [] } newParseSpec :: ParseSpec @@ -156,6 +160,7 @@ data AnalysisSpec = AnalysisSpec { asFallbackShell :: Maybe Shell, asExecutionMode :: ExecutionMode, asCheckSourced :: Bool, + asOptionalChecks :: [String], asTokenPositions :: Map.Map Id (Position, Position) } @@ -165,6 +170,7 @@ newAnalysisSpec token = AnalysisSpec { asFallbackShell = Nothing, asExecutionMode = Executed, asCheckSourced = False, + asOptionalChecks = [], asTokenPositions = Map.empty } @@ -187,6 +193,19 @@ newFormatterOptions = FormatterOptions { foWikiLinkCount = 3 } +data CheckDescription = CheckDescription { + cdName :: String, + cdDescription :: String, + cdPositive :: String, + cdNegative :: String + } + +newCheckDescription = CheckDescription { + cdName = "", + cdDescription = "", + cdPositive = "", + cdNegative = "" + } -- Supporting data types data Shell = Ksh | Sh | Bash | Dash deriving (Show, Eq) @@ -195,7 +214,7 @@ data ExecutionMode = Executed | Sourced deriving (Show, Eq) type ErrorMessage = String type Code = Integer -data Severity = ErrorC | WarningC | InfoC | StyleC | VerboseC +data Severity = ErrorC | WarningC | InfoC | StyleC deriving (Show, Eq, Ord, Generic, NFData) data Position = Position { posFile :: String, -- Filename diff --git a/src/ShellCheck/Parser.hs b/src/ShellCheck/Parser.hs index f5a52ed..a403bea 100644 --- a/src/ShellCheck/Parser.hs +++ b/src/ShellCheck/Parser.hs @@ -985,6 +985,10 @@ readAnnotationWithoutPrefix = do int <- many1 digit return $ DisableComment (read int) + "enable" -> readName `sepBy` char ',' + where + readName = EnableComment <$> many1 (letter <|> char '-') + "source" -> do filename <- many1 $ noneOf " \n" return [SourceOverride filename]