Add support for -a: emit for sourced files.

This commit is contained in:
Vidar Holen 2017-08-13 19:34:45 -07:00
commit 8dd40efb44
11 changed files with 182 additions and 71 deletions

View file

@ -32,7 +32,7 @@ import qualified ShellCheck.Checks.ShellSupport
analyzeScript :: AnalysisSpec -> AnalysisResult
analyzeScript spec = AnalysisResult {
arComments =
filterByAnnotation (asScript spec) . nub $
filterByAnnotation spec params . nub $
runAnalytics spec
++ runChecker params (checkers params)
}

View file

@ -109,6 +109,7 @@ data VariableState = Dead Token String | Alive deriving (Show)
defaultSpec root = AnalysisSpec {
asScript = root,
asShellType = Nothing,
asCheckSourced = False,
asExecutionMode = Executed
}
@ -116,7 +117,8 @@ pScript s =
let
pSpec = ParseSpec {
psFilename = "script",
psScript = s
psScript = s,
psCheckSourced = False
}
in prRoot . runIdentity $ parseScript (mockedSystemInterface []) pSpec
@ -801,9 +803,10 @@ whenShell l c = do
when (shell `elem` l ) c
filterByAnnotation token =
filterByAnnotation asSpec params =
filter (not . shouldIgnore)
where
token = asScript asSpec
idFor (TokenComment id _) = id
shouldIgnore note =
any (shouldIgnoreFor (getCode note)) $
@ -813,9 +816,9 @@ filterByAnnotation token =
where
hasNum (DisableComment ts) = num == ts
hasNum _ = False
shouldIgnoreFor _ T_Include {} = True -- Ignore included files
shouldIgnoreFor _ T_Include {} = not $ asCheckSourced asSpec
shouldIgnoreFor _ _ = False
parents = getParentTree token
parents = parentMap params
getCode (TokenComment _ (Comment _ c _)) = c
-- Is this a ${#anything}, to get string length or array count?

View file

@ -54,7 +54,8 @@ checkScript sys spec = do
checkScript contents = do
result <- parseScript sys ParseSpec {
psFilename = csFilename spec,
psScript = contents
psScript = contents,
psCheckSourced = csCheckSourced spec
}
let parseMessages = prComments result
let analysisMessages =
@ -77,6 +78,7 @@ checkScript sys spec = do
AnalysisSpec {
asScript = root,
asShellType = csShellTypeOverride spec,
asCheckSourced = csCheckSourced spec,
asExecutionMode = Executed
}
@ -88,13 +90,21 @@ getErrors sys spec =
check = checkWithIncludes []
checkWithSpec includes =
getErrors (mockedSystemInterface includes)
checkWithIncludes includes src =
getErrors
(mockedSystemInterface includes)
emptyCheckSpec {
csScript = src,
csExcludedWarnings = [2148]
}
checkWithSpec includes emptyCheckSpec {
csScript = src,
csExcludedWarnings = [2148]
}
checkRecursive includes src =
checkWithSpec includes emptyCheckSpec {
csScript = src,
csExcludedWarnings = [2148],
csCheckSourced = True
}
prop_findsParseIssue = check "echo \"$12\"" == [1037]
@ -153,6 +163,12 @@ prop_cantSourceDynamic2 =
prop_canSourceDynamicWhenRedirected =
null $ checkWithIncludes [("lib", "")] "#shellcheck source=lib\n. \"$1\""
prop_recursiveAnalysis =
[2086] == checkRecursive [("lib", "echo $1")] "source lib"
prop_recursiveParsing =
[1037] == checkRecursive [("lib", "echo \"$10\"")] "source lib"
prop_sourceDirectiveDoesntFollowFile =
null $ checkWithIncludes
[("foo", "source bar"), ("bar", "baz=3")]

View file

@ -34,14 +34,27 @@ format = return Formatter {
putStrLn "<checkstyle version='4.3'>",
onFailure = outputError,
onResult = outputResult,
onResult = outputResults,
footer = putStrLn "</checkstyle>"
}
outputResult result contents = do
let comments = makeNonVirtual (crComments result) contents
putStrLn . formatFile (crFilename result) $ comments
outputResults cr sys =
if null comments
then outputFile (crFilename cr) "" []
else mapM_ outputGroup fileGroups
where
comments = crComments cr
fileGroups = groupWith sourceFile comments
outputGroup group = do
let filename = sourceFile (head group)
result <- (siReadFile sys) filename
let contents = either (const "") id result
outputFile filename contents group
outputFile filename contents warnings = do
let comments = makeNonVirtual warnings contents
putStrLn . formatFile filename $ comments
formatFile name comments = concat [
"<file ", attr "name" name, ">\n",

View file

@ -25,11 +25,12 @@ import ShellCheck.Interface
-- A formatter that carries along an arbitrary piece of data
data Formatter = Formatter {
header :: IO (),
onResult :: CheckResult -> String -> IO (),
onResult :: CheckResult -> SystemInterface IO -> IO (),
onFailure :: FilePath -> ErrorMessage -> IO (),
footer :: IO ()
}
sourceFile (PositionedComment pos _ _) = posFile pos
lineNo (PositionedComment pos _ _) = posLine pos
endLineNo (PositionedComment _ end _) = posLine end
colNo (PositionedComment pos _ _) = posColumn pos

View file

@ -31,14 +31,25 @@ format = return Formatter {
header = return (),
footer = return (),
onFailure = outputError,
onResult = outputResult
onResult = outputAll
}
outputError file error = hPutStrLn stderr $ file ++ ": " ++ error
outputResult result contents = do
let comments = makeNonVirtual (crComments result) contents
mapM_ (putStrLn . formatComment (crFilename result)) comments
outputAll cr sys = mapM_ f groups
where
comments = crComments cr
groups = groupWith sourceFile comments
f :: [PositionedComment] -> IO ()
f group = do
let filename = sourceFile (head group)
result <- (siReadFile sys) filename
let contents = either (const "") id result
outputResult filename contents group
outputResult filename contents warnings = do
let comments = makeNonVirtual warnings contents
mapM_ (putStrLn . formatComment filename) comments
formatComment filename c = concat [
filename, ":",

View file

@ -43,15 +43,22 @@ colorForLevel level =
"style" -> 32 -- green
"message" -> 1 -- bold
"source" -> 0 -- none
otherwise -> 0 -- none
_ -> 0 -- none
outputError options file error = do
color <- getColorFunc $ foColorOption options
hPutStrLn stderr $ color "error" $ file ++ ": " ++ error
outputResult options result contents = do
outputResult options result sys = do
color <- getColorFunc $ foColorOption options
let comments = crComments result
let fileGroups = groupWith sourceFile comments
mapM_ (outputForFile color sys) fileGroups
outputForFile color sys comments = do
let fileName = sourceFile (head comments)
result <- (siReadFile sys) fileName
let contents = either (const "") id result
let fileLines = lines contents
let lineCount = fromIntegral $ length fileLines
let groups = groupWith lineNo comments
@ -62,7 +69,7 @@ outputResult options result contents = do
else fileLines !! fromIntegral (lineNum - 1)
putStrLn ""
putStrLn $ color "message" $
"In " ++ crFilename result ++" line " ++ show lineNum ++ ":"
"In " ++ fileName ++" line " ++ show lineNum ++ ":"
putStrLn (color "source" line)
mapM_ (\c -> putStrLn (color (severityText c) $ cuteIndent c)) x
putStrLn ""

View file

@ -33,6 +33,7 @@ newtype SystemInterface m = SystemInterface {
data CheckSpec = CheckSpec {
csFilename :: String,
csScript :: String,
csCheckSourced :: Bool,
csExcludedWarnings :: [Integer],
csShellTypeOverride :: Maybe Shell
} deriving (Show, Eq)
@ -46,6 +47,7 @@ emptyCheckSpec :: CheckSpec
emptyCheckSpec = CheckSpec {
csFilename = "",
csScript = "",
csCheckSourced = False,
csExcludedWarnings = [],
csShellTypeOverride = Nothing
}
@ -53,7 +55,8 @@ emptyCheckSpec = CheckSpec {
-- Parser input and output
data ParseSpec = ParseSpec {
psFilename :: String,
psScript :: String
psScript :: String,
psCheckSourced :: Bool
} deriving (Show, Eq)
data ParseResult = ParseResult {
@ -66,7 +69,8 @@ data ParseResult = ParseResult {
data AnalysisSpec = AnalysisSpec {
asScript :: Token,
asShellType :: Maybe Shell,
asExecutionMode :: ExecutionMode
asExecutionMode :: ExecutionMode,
asCheckSourced :: Bool
}
newtype AnalysisResult = AnalysisResult {

View file

@ -47,7 +47,7 @@ import qualified Data.Map as Map
import Test.QuickCheck.All (quickCheckAll)
type SCBase m = Mr.ReaderT (SystemInterface 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
backslash :: Monad m => SCParser m Char
@ -248,12 +248,14 @@ addParseNote n = do
shouldIgnoreCode code = do
context <- getCurrentContexts
return $ any disabling context
checkSourced <- Mr.asks checkSourced
return $ any (disabling checkSourced) context
where
disabling (ContextAnnotation list) =
any disabling' list
disabling (ContextSource _) = True -- Don't add messages for sourced files
disabling _ = False
disabling checkSourced item =
case item of
ContextAnnotation list -> any disabling' list
ContextSource _ -> not $ checkSourced
_ -> False
disabling' (DisableComment n) = code == n
disabling' _ = False
@ -297,6 +299,11 @@ initialSystemState = SystemState {
parseProblems = []
}
data Environment m = Environment {
systemInterface :: SystemInterface m,
checkSourced :: Bool
}
parseProblem level code msg = do
pos <- getPosition
parseProblemAt pos level code msg
@ -1879,7 +1886,7 @@ readSource pos t@(T_Redirecting _ _ (T_SimpleCommand _ _ (cmd:file:_))) = do
"This file appears to be recursively sourced. Ignoring."
return t
else do
sys <- Mr.ask
sys <- Mr.asks systemInterface
input <-
if filename == "/dev/null" -- always allow /dev/null
then return (Right "")
@ -2788,16 +2795,22 @@ readScript = do
-- Interactively run a parser in ghci:
-- debugParse readScript "echo 'hello world'"
debugParse p string = runIdentity $ do
(res, _) <- runParser (mockedSystemInterface []) p "-" string
(res, _) <- runParser testEnvironment p "-" string
return res
testEnvironment =
Environment {
systemInterface = (mockedSystemInterface []),
checkSourced = False
}
isOk p s = parsesCleanly p s == Just True -- The string parses with no warnings
isWarning p s = parsesCleanly p s == Just False -- The string parses with warnings
isNotOk p s = parsesCleanly p s == Nothing -- The string does not parse
parsesCleanly parser string = runIdentity $ do
(res, sys) <- runParser (mockedSystemInterface [])
(res, sys) <- runParser testEnvironment
(parser >> eof >> getState) "-" string
case (res, sys) of
(Right userState, systemState) ->
@ -2842,22 +2855,22 @@ getStringFromParsec errors =
Message s -> if null s then Nothing else return $ s ++ "."
runParser :: Monad m =>
SystemInterface m ->
Environment m ->
SCParser m v ->
String ->
String ->
m (Either ParseError v, SystemState)
runParser sys p filename contents =
runParser env p filename contents =
Ms.runStateT
(Mr.runReaderT
(runParserT p initialUserState filename contents)
sys)
env)
initialSystemState
system = lift . lift . lift
parseShell sys name contents = do
(result, state) <- runParser sys (parseWithNotes readScript) name contents
parseShell env name contents = do
(result, state) <- runParser env (parseWithNotes readScript) name contents
case result of
Right (script, userstate) ->
return ParseResult {
@ -2943,7 +2956,12 @@ posToPos sp = Position {
parseScript :: Monad m =>
SystemInterface m -> ParseSpec -> m ParseResult
parseScript sys spec =
parseShell sys (psFilename spec) (psScript spec)
parseShell env (psFilename spec) (psScript spec)
where
env = Environment {
systemInterface = sys,
checkSourced = psCheckSourced spec
}
return []