Add option to look for sources in alternate root paths

Add a new optional flag "-r|--root ROOTPATHS", where ROOTPATHS is a
colon separated list of paths, that will look for external sources in
alternate roots.

This is particular useful when the run-time environment does not fully
match the development environment. The #shellcheck source=file directive
is useful, but has its limitations in certain scenarios. Also, in many
cases the directive could be removed from scripts when the root flag is
used.

Script example.bash:
  #!/bin/bash
  source /etc/foo/config

Example usage where etc/foo/config exists in skel/foo:
  # shellcheck -x -r skel/foo:skel/core example.bash
This commit is contained in:
Pontus Andersson 2019-04-22 14:34:38 +02:00
parent b824294961
commit af46758ff1
3 changed files with 37 additions and 1 deletions

View File

@ -69,6 +69,7 @@ instance Monoid Status where
data Options = Options { data Options = Options {
checkSpec :: CheckSpec, checkSpec :: CheckSpec,
externalSources :: Bool, externalSources :: Bool,
rootPaths :: [FilePath],
formatterOptions :: FormatterOptions, formatterOptions :: FormatterOptions,
minSeverity :: Severity minSeverity :: Severity
} }
@ -76,6 +77,7 @@ data Options = Options {
defaultOptions = Options { defaultOptions = Options {
checkSpec = emptyCheckSpec, checkSpec = emptyCheckSpec,
externalSources = False, externalSources = False,
rootPaths = [],
formatterOptions = newFormatterOptions { formatterOptions = newFormatterOptions {
foColorOption = ColorAuto foColorOption = ColorAuto
}, },
@ -98,6 +100,9 @@ options = [
"Output format (" ++ formatList ++ ")", "Output format (" ++ formatList ++ ")",
Option "" ["norc"] Option "" ["norc"]
(NoArg $ Flag "norc" "true") "Don't look for .shellcheckrc files", (NoArg $ Flag "norc" "true") "Don't look for .shellcheckrc files",
Option "r" ["root"]
(ReqArg (Flag "root") "ROOTPATHS")
"Specify alternate root path(s) when looking for sources (colon separated)",
Option "s" ["shell"] Option "s" ["shell"]
(ReqArg (Flag "shell") "SHELLNAME") (ReqArg (Flag "shell") "SHELLNAME")
"Specify dialect (sh, bash, dash, ksh)", "Specify dialect (sh, bash, dash, ksh)",
@ -311,6 +316,12 @@ parseOption flag options =
} }
} }
Flag "root" str -> do
let paths = filter (not . null) $ split ':' str
return options {
rootPaths = paths
}
Flag "sourced" _ -> Flag "sourced" _ ->
return options { return options {
checkSpec = (checkSpec options) { checkSpec = (checkSpec options) {
@ -362,8 +373,10 @@ ioInterface options files = do
inputs <- mapM normalize files inputs <- mapM normalize files
cache <- newIORef emptyCache cache <- newIORef emptyCache
configCache <- newIORef ("", Nothing) configCache <- newIORef ("", Nothing)
let rootPathsCache = rootPaths options
return SystemInterface { return SystemInterface {
siReadFile = get cache inputs, siReadFile = get cache inputs,
siFindSource = findSourceFile rootPathsCache,
siGetConfig = getConfig configCache siGetConfig = getConfig configCache
} }
where where
@ -455,6 +468,23 @@ ioInterface options files = do
putStrLn $ file ++ ": " ++ show err putStrLn $ file ++ ": " ++ show err
return ("", True) return ("", True)
findSourceFile rootPaths file = do
case file of
('/':root) -> do
source <- find root
return source
_ ->
return file
where
find root = do
sources <- filterM doesFileExist paths
case sources of
[] -> return file
(first:_) -> return first
where
paths = map join rootPaths
join path = joinPath [path, root]
inputFile file = do inputFile file = do
(handle, shouldCache) <- (handle, shouldCache) <-
if file == "-" if file == "-"

View File

@ -73,6 +73,8 @@ import qualified Data.Map as Map
data SystemInterface m = SystemInterface { data SystemInterface m = SystemInterface {
-- Read a file by filename, or return an error -- Read a file by filename, or return an error
siReadFile :: String -> m (Either ErrorMessage String), siReadFile :: String -> m (Either ErrorMessage String),
-- Find source file in alternate root paths
siFindSource :: String -> m (FilePath),
-- Get the configuration file (name, contents) for a filename -- Get the configuration file (name, contents) for a filename
siGetConfig :: String -> m (Maybe (FilePath, String)) siGetConfig :: String -> m (Maybe (FilePath, String))
} }
@ -287,6 +289,7 @@ data ColorOption =
mockedSystemInterface :: [(String, String)] -> SystemInterface Identity mockedSystemInterface :: [(String, String)] -> SystemInterface Identity
mockedSystemInterface files = SystemInterface { mockedSystemInterface files = SystemInterface {
siReadFile = rf, siReadFile = rf,
siFindSource = fs,
siGetConfig = const $ return Nothing siGetConfig = const $ return Nothing
} }
where where
@ -294,6 +297,7 @@ mockedSystemInterface files = SystemInterface {
case filter ((== file) . fst) files of case filter ((== file) . fst) files of
[] -> return $ Left "File not included in mock." [] -> return $ Left "File not included in mock."
[(_, contents)] -> return $ Right contents [(_, contents)] -> return $ Right contents
fs file = return file
mockRcFile rcfile mock = mock { mockRcFile rcfile mock = mock {
siGetConfig = const . return $ Just (".shellcheckrc", rcfile) siGetConfig = const . return $ Just (".shellcheckrc", rcfile)

View File

@ -2087,7 +2087,9 @@ readSource t@(T_Redirecting _ _ (T_SimpleCommand cmdId _ (cmd:file':rest'))) = d
input <- input <-
if filename == "/dev/null" -- always allow /dev/null if filename == "/dev/null" -- always allow /dev/null
then return (Right "") then return (Right "")
else system $ siReadFile sys filename else do
filename' <- system $ siFindSource sys filename
system $ siReadFile sys filename'
case input of case input of
Left err -> do Left err -> do
parseNoteAtId (getId file) InfoC 1091 $ parseNoteAtId (getId file) InfoC 1091 $