Adds a #shellcheck source=file directive to override source statements.

This commit is contained in:
Vidar Holen 2015-08-19 19:09:55 -07:00
parent ccb6bf1ed5
commit a01862bc12
4 changed files with 67 additions and 41 deletions

View File

@ -128,7 +128,7 @@ data Token =
| T_Include Id Token Token -- . & source: SimpleCommand T_Script | T_Include Id Token Token -- . & source: SimpleCommand T_Script
deriving (Show) deriving (Show)
data Annotation = DisableComment Integer deriving (Show, Eq) data Annotation = DisableComment Integer | SourceOverride String deriving (Show, Eq)
data ConditionType = DoubleBracket | SingleBracket deriving (Show, Eq) data ConditionType = DoubleBracket | SingleBracket deriving (Show, Eq)
-- This is an abomination. -- This is an abomination.

View File

@ -86,11 +86,13 @@ getErrors sys spec =
where where
getCode (PositionedComment _ (Comment _ code _)) = code getCode (PositionedComment _ (Comment _ code _)) = code
check str = check = checkWithIncludes []
checkWithIncludes includes src =
getErrors getErrors
(mockedSystemInterface []) (mockedSystemInterface includes)
emptyCheckSpec { emptyCheckSpec {
csScript = str, csScript = src,
csExcludedWarnings = [2148] csExcludedWarnings = [2148]
} }
@ -124,45 +126,34 @@ prop_optionDisablesIssue2 =
csExcludedWarnings = [2148, 1037] csExcludedWarnings = [2148, 1037]
} }
prop_canParseDevNull =
[] == check "source /dev/null"
prop_failsWhenNotSourcing = prop_failsWhenNotSourcing =
[1091, 2154] == getErrors [1091, 2154] == check "source lol; echo \"$bar\""
(mockedSystemInterface [])
emptyCheckSpec {
csScript = "source lob; echo \"$bar\"",
csExcludedWarnings = [2148]
}
prop_worksWhenSourcing = prop_worksWhenSourcing =
null $ getErrors null $ checkWithIncludes [("lib", "bar=1")] "source lib; echo \"$bar\""
(mockedSystemInterface [("lib", "bar=1")])
emptyCheckSpec {
csScript = "source lib; echo \"$bar\"",
csExcludedWarnings = [2148]
}
prop_worksWhenDotting = prop_worksWhenDotting =
null $ getErrors null $ checkWithIncludes [("lib", "bar=1")] ". lib; echo \"$bar\""
(mockedSystemInterface [("lib", "bar=1")])
emptyCheckSpec {
csScript = ". lib; echo \"$bar\"",
csExcludedWarnings = [2148]
}
prop_noInfiniteSourcing = prop_noInfiniteSourcing =
[] == getErrors [] == checkWithIncludes [("lib", "source lib")] "source lib"
(mockedSystemInterface [("lib", "source lib")])
emptyCheckSpec {
csScript = "source lib",
csExcludedWarnings = [2148]
}
prop_canSourceBadSyntax = prop_canSourceBadSyntax =
[1094, 2086] == getErrors [1094, 2086] == checkWithIncludes [("lib", "for f; do")] "source lib; echo $1"
(mockedSystemInterface [("lib", "for f; do")])
emptyCheckSpec { prop_cantSourceDynamic =
csScript = "source lib; echo $1", [1090] == checkWithIncludes [("lib", "")] ". \"$1\""
csExcludedWarnings = [2148]
} prop_canSourceDynamicWhenRedirected =
null $ checkWithIncludes [("lib", "")] "#shellcheck source=lib\n. \"$1\""
prop_sourceDirectiveDoesntFollowFile =
null $ checkWithIncludes
[("foo", "source bar"), ("bar", "baz=3")]
"#shellcheck source=foo\n. \"$1\"; echo \"$baz\""
return [] return []
runTests = $quickCheckAll runTests = $quickCheckAll

View File

@ -32,6 +32,7 @@ import Data.Char
import Data.Functor import Data.Functor
import Data.List (isPrefixOf, isInfixOf, isSuffixOf, partition, sortBy, intercalate, nub) import Data.List (isPrefixOf, isInfixOf, isSuffixOf, partition, sortBy, intercalate, nub)
import Data.Maybe import Data.Maybe
import Data.Monoid
import Debug.Trace import Debug.Trace
import GHC.Exts (sortWith) import GHC.Exts (sortWith)
import Prelude hiding (readList) import Prelude hiding (readList)
@ -191,6 +192,7 @@ shouldIgnoreCode code = do
disabling (ContextSource _) = True -- Don't add messages for sourced files disabling (ContextSource _) = True -- Don't add messages for sourced files
disabling _ = False disabling _ = False
disabling' (DisableComment n) = code == n disabling' (DisableComment n) = code == n
disabling' _ = False
shouldFollow file = do shouldFollow file = do
context <- getCurrentContexts context <- getCurrentContexts
@ -209,6 +211,18 @@ shouldFollow file = do
isThisFile (ContextSource name) | name == file = True isThisFile (ContextSource name) | name == file = True
isThisFile _= False isThisFile _= False
getSourceOverride = do
context <- getCurrentContexts
return . msum . map findFile $ takeWhile isSameFile context
where
isSameFile (ContextSource _) = False
isSameFile _ = True
findFile (ContextAnnotation list) = msum $ map getFile list
findFile _ = Nothing
getFile (SourceOverride str) = Just str
getFile _ = Nothing
-- Store potential parse problems outside of parsec -- Store potential parse problems outside of parsec
data SystemState = SystemState { data SystemState = SystemState {
@ -722,10 +736,11 @@ readAnnotationPrefix = do
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"
readAnnotation = called "shellcheck annotation" $ do readAnnotation = called "shellcheck annotation" $ do
try readAnnotationPrefix try readAnnotationPrefix
many1 linewhitespace many1 linewhitespace
values <- many1 readDisable values <- many1 (readDisable <|> readSourceOverride)
linefeed linefeed
many linewhitespace many linewhitespace
return $ concat values return $ concat values
@ -737,6 +752,11 @@ readAnnotation = called "shellcheck annotation" $ do
optional $ string "SC" optional $ string "SC"
int <- many1 digit int <- many1 digit
return $ DisableComment (read int) return $ DisableComment (read int)
readSourceOverride = forKey "source" $ do
filename <- many1 $ noneOf " \n"
return [SourceOverride filename]
forKey s p = do forKey s p = do
try $ string s try $ string s
char '=' char '='
@ -1480,11 +1500,12 @@ readSimpleCommand = called "simple command" $ do
readSource :: Monad m => SourcePos -> Token -> SCParser m Token readSource :: Monad m => SourcePos -> Token -> SCParser m Token
readSource pos t@(T_Redirecting _ _ (T_SimpleCommand _ _ (cmd:file:_))) = do readSource pos t@(T_Redirecting _ _ (T_SimpleCommand _ _ (cmd:file:_))) = do
let literalFile = getLiteralString file override <- getSourceOverride
let literalFile = override `mplus` getLiteralString file
case literalFile of case literalFile of
Nothing -> do Nothing -> do
parseNoteAt pos InfoC 1090 parseNoteAt pos WarningC 1090
"This source will be skipped since it's not constant." "Can't follow non-constant source. Use a directive to specify location."
return t return t
Just filename -> do Just filename -> do
proceed <- shouldFollow filename proceed <- shouldFollow filename
@ -1495,7 +1516,10 @@ readSource pos t@(T_Redirecting _ _ (T_SimpleCommand _ _ (cmd:file:_))) = do
return t return t
else do else do
sys <- Mr.ask sys <- Mr.ask
input <- system $ siReadFile sys filename input <-
if filename == "/dev/null" -- always allow /dev/null
then return (Right "")
else system $ siReadFile sys filename
case input of case input of
Left err -> do Left err -> do
parseNoteAt pos InfoC 1091 $ parseNoteAt pos InfoC 1091 $

View File

@ -58,7 +58,8 @@ not warn at all, as `ksh` supports decimals in arithmetic contexts.
: Follow 'source' statements even when the file is not specified as input. : Follow 'source' statements even when the file is not specified as input.
By default, `shellcheck` will only follow files specified on the command By default, `shellcheck` will only follow files specified on the command
line. This option allows following any file the script may `source`. line (plus `/dev/null`). This option allows following any file the script
may `source`.
# FORMATS # FORMATS
@ -125,7 +126,12 @@ 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: To tell ShellCheck where to look for an otherwise dynamically determined file:
# shellcheck source=./lib.sh
source "$(find_install_dir)/lib.sh"
Here a shell brace group is used to suppress a warning on multiple lines:
# shellcheck disable=SC2016 # shellcheck disable=SC2016
{ {
@ -140,6 +146,11 @@ Valid keys are:
The command can be a simple command like `echo foo`, or a compound command The command can be a simple command like `echo foo`, or a compound command
like a function definition, subshell block or loop. like a function definition, subshell block or loop.
**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
at runtime, or to skip a source by telling it to use `/dev/null`.
# AUTHOR # AUTHOR
ShellCheck is written and maintained by Vidar Holen. ShellCheck is written and maintained by Vidar Holen.