mirror of
https://github.com/koalaman/shellcheck
synced 2025-07-08 05:51:09 -07:00
Example plumbing for Portage variables
This commit is contained in:
parent
90d3172dfe
commit
0138a6fafc
9 changed files with 96 additions and 59 deletions
|
@ -396,10 +396,12 @@ ioInterface options files = do
|
|||
inputs <- mapM normalize files
|
||||
cache <- newIORef emptyCache
|
||||
configCache <- newIORef ("", Nothing)
|
||||
portageVars <- newIORef Nothing
|
||||
return (newSystemInterface :: SystemInterface IO) {
|
||||
siReadFile = get cache inputs,
|
||||
siFindSource = findSourceFile inputs (sourcePaths options),
|
||||
siGetConfig = getConfig configCache
|
||||
siGetConfig = getConfig configCache,
|
||||
siGetPortageVariables = getOrLoadPortage portageVars
|
||||
}
|
||||
where
|
||||
emptyCache :: Map.Map FilePath String
|
||||
|
@ -523,6 +525,18 @@ ioInterface options files = do
|
|||
("SCRIPTDIR":rest) -> joinPath (scriptdir:rest)
|
||||
_ -> str
|
||||
|
||||
getOrLoadPortage cache = do
|
||||
x <- readIORef cache
|
||||
case x of
|
||||
Just m -> do
|
||||
hPutStrLn stderr "Reusing previous Portage variables"
|
||||
return m
|
||||
Nothing -> do
|
||||
hPutStrLn stderr "Computing Portage variables"
|
||||
vars <- return $ Map.fromList [("foo", ["bar", "baz"])] -- TODO: Actually read the variables
|
||||
writeIORef cache $ Just vars
|
||||
return vars
|
||||
|
||||
inputFile file = do
|
||||
(handle, shouldCache) <-
|
||||
if file == "-"
|
||||
|
|
|
@ -314,7 +314,7 @@ runAndGetComments f s = do
|
|||
let pr = pScript s
|
||||
root <- prRoot pr
|
||||
let spec = defaultSpec pr
|
||||
let params = makeParameters spec
|
||||
let params = runIdentity $ makeParameters (mockedSystemInterface []) spec
|
||||
return $
|
||||
filterByAnnotation spec params $
|
||||
f params root
|
||||
|
@ -2451,7 +2451,7 @@ checkUnassignedReferences = checkUnassignedReferences' False
|
|||
checkUnassignedReferences' includeGlobals params t = warnings
|
||||
where
|
||||
(readMap, writeMap) = execState (mapM tally $ variableFlow params) (Map.empty, Map.empty)
|
||||
defaultAssigned = Map.fromList $ map (\a -> (a, ())) $ filter (not . null) internalVariables
|
||||
defaultAssigned = Map.fromList $ map (\a -> (a, ())) $ filter (not . null) (internalVariables ++ additionalKnownVariables params)
|
||||
|
||||
tally (Assignment (_, _, name, _)) =
|
||||
modify (\(read, written) -> (read, Map.insert name () written))
|
||||
|
|
|
@ -31,14 +31,14 @@ import qualified ShellCheck.Checks.ShellSupport
|
|||
|
||||
|
||||
-- TODO: Clean up the cruft this is layered on
|
||||
analyzeScript :: AnalysisSpec -> AnalysisResult
|
||||
analyzeScript spec = newAnalysisResult {
|
||||
arComments =
|
||||
filterByAnnotation spec params . nub $
|
||||
runChecker params (checkers spec params)
|
||||
}
|
||||
where
|
||||
params = makeParameters spec
|
||||
analyzeScript :: Monad m => SystemInterface m -> AnalysisSpec -> m AnalysisResult
|
||||
analyzeScript sys spec = do
|
||||
params <- makeParameters sys spec
|
||||
return $ newAnalysisResult {
|
||||
arComments =
|
||||
filterByAnnotation spec params . nub $
|
||||
runChecker params (checkers spec params)
|
||||
}
|
||||
|
||||
checkers spec params = mconcat $ map ($ params) [
|
||||
ShellCheck.Analytics.checker spec,
|
||||
|
|
|
@ -103,7 +103,9 @@ data Parameters = Parameters {
|
|||
-- map from token id to start and end position
|
||||
tokenPositions :: Map.Map Id (Position, Position),
|
||||
-- Result from Control Flow Graph analysis (including data flow analysis)
|
||||
cfgAnalysis :: CF.CFGAnalysis
|
||||
cfgAnalysis :: CF.CFGAnalysis,
|
||||
-- A set of additional variables known to be set (TODO: make this more general?)
|
||||
additionalKnownVariables :: [String]
|
||||
} deriving (Show)
|
||||
|
||||
-- TODO: Cache results of common AST ops here
|
||||
|
@ -152,7 +154,7 @@ producesComments c s = do
|
|||
let pr = pScript s
|
||||
prRoot pr
|
||||
let spec = defaultSpec pr
|
||||
let params = makeParameters spec
|
||||
let params = runIdentity $ makeParameters (mockedSystemInterface []) spec
|
||||
return . not . null $ filterByAnnotation spec params $ runChecker params c
|
||||
|
||||
makeComment :: Severity -> Id -> Code -> String -> TokenComment
|
||||
|
@ -196,41 +198,54 @@ makeCommentWithFix severity id code str fix =
|
|||
}
|
||||
in force withFix
|
||||
|
||||
makeParameters spec = params
|
||||
makeParameters :: Monad m => SystemInterface m -> AnalysisSpec -> m Parameters
|
||||
makeParameters sys spec = do
|
||||
extraVars <-
|
||||
case shell of
|
||||
Bash -> do -- TODO: EBuild type
|
||||
vars <- siGetPortageVariables sys
|
||||
return $ Map.findWithDefault [] "foo" vars -- TODO: Determine what to look up in map
|
||||
_ -> return []
|
||||
return $ makeParams extraVars
|
||||
where
|
||||
params = Parameters {
|
||||
rootNode = root,
|
||||
shellType = fromMaybe (determineShell (asFallbackShell spec) root) $ asShellType spec,
|
||||
hasSetE = containsSetE root,
|
||||
hasLastpipe =
|
||||
case shellType params of
|
||||
Bash -> isOptionSet "lastpipe" root
|
||||
Dash -> False
|
||||
Sh -> False
|
||||
Ksh -> True,
|
||||
hasInheritErrexit =
|
||||
case shellType params of
|
||||
Bash -> isOptionSet "inherit_errexit" root
|
||||
Dash -> True
|
||||
Sh -> True
|
||||
Ksh -> False,
|
||||
hasPipefail =
|
||||
case shellType params of
|
||||
Bash -> isOptionSet "pipefail" root
|
||||
Dash -> True
|
||||
Sh -> True
|
||||
Ksh -> isOptionSet "pipefail" root,
|
||||
shellTypeSpecified = isJust (asShellType spec) || isJust (asFallbackShell spec),
|
||||
idMap = getTokenMap root,
|
||||
parentMap = getParentTree root,
|
||||
variableFlow = getVariableFlow params root,
|
||||
tokenPositions = asTokenPositions spec,
|
||||
cfgAnalysis = CF.analyzeControlFlow cfParams root
|
||||
}
|
||||
cfParams = CF.CFGParameters {
|
||||
CF.cfLastpipe = hasLastpipe params,
|
||||
CF.cfPipefail = hasPipefail params
|
||||
}
|
||||
shell = fromMaybe (determineShell (asFallbackShell spec) root) $ asShellType spec
|
||||
makeParams extraVars = params
|
||||
where
|
||||
params = Parameters {
|
||||
rootNode = root,
|
||||
shellType = shell,
|
||||
hasSetE = containsSetE root,
|
||||
hasLastpipe =
|
||||
case shellType params of
|
||||
Bash -> isOptionSet "lastpipe" root
|
||||
Dash -> False
|
||||
Sh -> False
|
||||
Ksh -> True,
|
||||
hasInheritErrexit =
|
||||
case shellType params of
|
||||
Bash -> isOptionSet "inherit_errexit" root
|
||||
Dash -> True
|
||||
Sh -> True
|
||||
Ksh -> False,
|
||||
hasPipefail =
|
||||
case shellType params of
|
||||
Bash -> isOptionSet "pipefail" root
|
||||
Dash -> True
|
||||
Sh -> True
|
||||
Ksh -> isOptionSet "pipefail" root,
|
||||
shellTypeSpecified = isJust (asShellType spec) || isJust (asFallbackShell spec),
|
||||
idMap = getTokenMap root,
|
||||
parentMap = getParentTree root,
|
||||
variableFlow = getVariableFlow params root,
|
||||
tokenPositions = asTokenPositions spec,
|
||||
cfgAnalysis = CF.analyzeControlFlow cfParams root,
|
||||
additionalKnownVariables = extraVars
|
||||
}
|
||||
cfParams = CF.CFGParameters {
|
||||
CF.cfLastpipe = hasLastpipe params,
|
||||
CF.cfPipefail = hasPipefail params,
|
||||
CF.cfAdditionalInitialVariables = additionalKnownVariables params
|
||||
}
|
||||
root = asScript spec
|
||||
|
||||
|
||||
|
|
|
@ -167,7 +167,9 @@ data CFGParameters = CFGParameters {
|
|||
-- Whether the last element in a pipeline runs in the current shell
|
||||
cfLastpipe :: Bool,
|
||||
-- Whether all elements in a pipeline count towards the exit status
|
||||
cfPipefail :: Bool
|
||||
cfPipefail :: Bool,
|
||||
-- Additional variables to consider defined
|
||||
cfAdditionalInitialVariables :: [String]
|
||||
}
|
||||
|
||||
data CFGResult = CFGResult {
|
||||
|
|
|
@ -197,12 +197,13 @@ unreachableState = modified newInternalState {
|
|||
}
|
||||
|
||||
-- The default state we assume we get from the environment
|
||||
createEnvironmentState :: InternalState
|
||||
createEnvironmentState = do
|
||||
createEnvironmentState :: CFGParameters -> InternalState
|
||||
createEnvironmentState params = do
|
||||
foldl' (flip ($)) newInternalState $ concat [
|
||||
addVars Data.internalVariables unknownVariableState,
|
||||
addVars Data.variablesWithoutSpaces spacelessVariableState,
|
||||
addVars Data.specialIntegerVariables integerVariableState
|
||||
addVars Data.specialIntegerVariables integerVariableState,
|
||||
addVars (cfAdditionalInitialVariables params) unknownVariableState
|
||||
]
|
||||
where
|
||||
addVars names val = map (\name -> insertGlobal name val) names
|
||||
|
@ -1344,7 +1345,7 @@ analyzeControlFlow params t =
|
|||
runST $ f cfg entry exit
|
||||
where
|
||||
f cfg entry exit = do
|
||||
let env = createEnvironmentState
|
||||
let env = createEnvironmentState params
|
||||
ctx <- newCtx $ cfGraph cfg
|
||||
-- Do a dataflow analysis starting on the root node
|
||||
exitState <- runRoot ctx env entry exit
|
||||
|
|
|
@ -88,11 +88,12 @@ checkScript sys spec = do
|
|||
asTokenPositions = tokenPositions,
|
||||
asOptionalChecks = getEnableDirectives root ++ csOptionalChecks spec
|
||||
} where as = newAnalysisSpec root
|
||||
let analysisMessages =
|
||||
maybe []
|
||||
(arComments . analyzeScript . analysisSpec)
|
||||
$ prRoot result
|
||||
let getAnalysisMessages =
|
||||
case prRoot result of
|
||||
Just root -> arComments <$> (analyzeScript sys $ analysisSpec root)
|
||||
Nothing -> return []
|
||||
let translator = tokenToPosition tokenPositions
|
||||
analysisMessages <- getAnalysisMessages
|
||||
return . nub . sortMessages . filter shouldInclude $
|
||||
(parseMessages ++ map translator analysisMessages)
|
||||
|
||||
|
|
|
@ -117,7 +117,8 @@ dummySystemInterface = mockedSystemInterface [
|
|||
cfgParams :: CFGParameters
|
||||
cfgParams = CFGParameters {
|
||||
cfLastpipe = False,
|
||||
cfPipefail = False
|
||||
cfPipefail = False,
|
||||
cfAdditionalInitialVariables = []
|
||||
}
|
||||
|
||||
-- An example script to play with
|
||||
|
|
|
@ -87,7 +87,9 @@ data SystemInterface m = SystemInterface {
|
|||
-- find the sourced file
|
||||
siFindSource :: String -> Maybe Bool -> [String] -> String -> m FilePath,
|
||||
-- | Get the configuration file (name, contents) for a filename
|
||||
siGetConfig :: String -> m (Maybe (FilePath, String))
|
||||
siGetConfig :: String -> m (Maybe (FilePath, String)),
|
||||
-- | Look up Portage Eclass variables
|
||||
siGetPortageVariables :: m (Map.Map String [String])
|
||||
}
|
||||
|
||||
-- ShellCheck input and output
|
||||
|
@ -141,7 +143,8 @@ newSystemInterface =
|
|||
SystemInterface {
|
||||
siReadFile = \_ _ -> return $ Left "Not implemented",
|
||||
siFindSource = \_ _ _ name -> return name,
|
||||
siGetConfig = \_ -> return Nothing
|
||||
siGetConfig = \_ -> return Nothing,
|
||||
siGetPortageVariables = return Map.empty
|
||||
}
|
||||
|
||||
-- Parser input and output
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue