"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "src/ShellCheck/AnalyzerLib.hs" between
shellcheck-0.8.0.tar.gz and shellcheck-0.9.0.tar.gz

About: ShellCheck is a static analysis and linting tool for sh/bash scripts (written in Haskell).

AnalyzerLib.hs  (shellcheck-0.8.0):AnalyzerLib.hs  (shellcheck-0.9.0)
{- {-
Copyright 2012-2021 Vidar Holen Copyright 2012-2022 Vidar Holen
This file is part of ShellCheck. This file is part of ShellCheck.
https://www.shellcheck.net https://www.shellcheck.net
ShellCheck is free software: you can redistribute it and/or modify ShellCheck is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
ShellCheck is distributed in the hope that it will be useful, ShellCheck is distributed in the hope that it will be useful,
skipping to change at line 26 skipping to change at line 26
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
-} -}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
module ShellCheck.AnalyzerLib where module ShellCheck.AnalyzerLib where
import ShellCheck.AST import ShellCheck.AST
import ShellCheck.ASTLib import ShellCheck.ASTLib
import qualified ShellCheck.CFGAnalysis as CF
import ShellCheck.Data import ShellCheck.Data
import ShellCheck.Interface import ShellCheck.Interface
import ShellCheck.Parser import ShellCheck.Parser
import ShellCheck.Prelude
import ShellCheck.Regex import ShellCheck.Regex
import Control.Arrow (first) import Control.Arrow (first)
import Control.DeepSeq import Control.DeepSeq
import Control.Monad
import Control.Monad.Identity import Control.Monad.Identity
import Control.Monad.RWS import Control.Monad.RWS
import Control.Monad.State import Control.Monad.State
import Control.Monad.Writer import Control.Monad.Writer
import Data.Char import Data.Char
import Data.List import Data.List
import Data.Maybe import Data.Maybe
import Data.Semigroup import Data.Semigroup
import qualified Data.Map as Map import qualified Data.Map as Map
skipping to change at line 89 skipping to change at line 92
-- Whether this script has the 'lastpipe' option set/default. -- Whether this script has the 'lastpipe' option set/default.
hasLastpipe :: Bool, hasLastpipe :: Bool,
-- Whether this script has the 'inherit_errexit' option set/default. -- Whether this script has the 'inherit_errexit' option set/default.
hasInheritErrexit :: Bool, hasInheritErrexit :: Bool,
-- Whether this script has 'set -e' anywhere. -- Whether this script has 'set -e' anywhere.
hasSetE :: Bool, hasSetE :: Bool,
-- Whether this script has 'set -o pipefail' anywhere. -- Whether this script has 'set -o pipefail' anywhere.
hasPipefail :: Bool, hasPipefail :: Bool,
-- A linear (bad) analysis of data flow -- A linear (bad) analysis of data flow
variableFlow :: [StackData], variableFlow :: [StackData],
-- A map from Id to Token
idMap :: Map.Map Id Token,
-- A map from Id to parent Token -- A map from Id to parent Token
parentMap :: Map.Map Id Token, parentMap :: Map.Map Id Token,
-- The shell type, such as Bash or Ksh -- The shell type, such as Bash or Ksh
shellType :: Shell, shellType :: Shell,
-- True if shell type was forced via flags -- True if shell type was forced via flags
shellTypeSpecified :: Bool, shellTypeSpecified :: Bool,
-- The root node of the AST -- The root node of the AST
rootNode :: Token, rootNode :: Token,
-- map from token id to start and end position -- map from token id to start and end position
tokenPositions :: Map.Map Id (Position, Position) tokenPositions :: Map.Map Id (Position, Position),
-- Result from Control Flow Graph analysis (including data flow analysis)
cfgAnalysis :: CF.CFGAnalysis
} deriving (Show) } deriving (Show)
-- TODO: Cache results of common AST ops here -- TODO: Cache results of common AST ops here
data Cache = Cache {} data Cache = Cache {}
data Scope = SubshellScope String | NoneScope deriving (Show, Eq) data Scope = SubshellScope String | NoneScope deriving (Show, Eq)
data StackData = data StackData =
StackScope Scope StackScope Scope
| StackScopeEnd | StackScopeEnd
-- (Base expression, specific position, var name, assigned values) -- (Base expression, specific position, var name, assigned values)
skipping to change at line 191 skipping to change at line 198
makeCommentWithFix :: Severity -> Id -> Code -> String -> Fix -> TokenComment makeCommentWithFix :: Severity -> Id -> Code -> String -> Fix -> TokenComment
makeCommentWithFix severity id code str fix = makeCommentWithFix severity id code str fix =
let comment = makeComment severity id code str let comment = makeComment severity id code str
withFix = comment { withFix = comment {
-- If fix is empty, pretend it wasn't there. -- If fix is empty, pretend it wasn't there.
tcFix = if null (fixReplacements fix) then Nothing else Just fix tcFix = if null (fixReplacements fix) then Nothing else Just fix
} }
in force withFix in force withFix
makeParameters spec = makeParameters spec = params
let params = Parameters { where
params = Parameters {
rootNode = root, rootNode = root,
shellType = fromMaybe (determineShell (asFallbackShell spec) root) $ asS hellType spec, shellType = fromMaybe (determineShell (asFallbackShell spec) root) $ asS hellType spec,
hasSetE = containsSetE root, hasSetE = containsSetE root,
hasLastpipe = hasLastpipe =
case shellType params of case shellType params of
Bash -> containsLastpipe root Bash -> isOptionSet "lastpipe" root
Dash -> False Dash -> False
Sh -> False Sh -> False
Ksh -> True, Ksh -> True,
hasInheritErrexit = hasInheritErrexit =
case shellType params of case shellType params of
Bash -> containsInheritErrexit root Bash -> isOptionSet "inherit_errexit" root
Dash -> True Dash -> True
Sh -> True Sh -> True
Ksh -> False, Ksh -> False,
hasPipefail = hasPipefail =
case shellType params of case shellType params of
Bash -> containsPipefail root Bash -> isOptionSet "pipefail" root
Dash -> True Dash -> True
Sh -> True Sh -> True
Ksh -> containsPipefail root, Ksh -> isOptionSet "pipefail" root,
shellTypeSpecified = isJust (asShellType spec) || isJust (asFallbackShel l spec), shellTypeSpecified = isJust (asShellType spec) || isJust (asFallbackShel l spec),
idMap = getTokenMap root,
parentMap = getParentTree root, parentMap = getParentTree root,
variableFlow = getVariableFlow params root, variableFlow = getVariableFlow params root,
tokenPositions = asTokenPositions spec tokenPositions = asTokenPositions spec,
} in params cfgAnalysis = CF.analyzeControlFlow cfParams root
where root = asScript spec }
cfParams = CF.CFGParameters {
CF.cfLastpipe = hasLastpipe params,
CF.cfPipefail = hasPipefail params
}
root = asScript spec
-- Does this script mention 'set -e' anywhere? -- Does this script mention 'set -e' anywhere?
-- Used as a hack to disable certain warnings. -- Used as a hack to disable certain warnings.
containsSetE root = isNothing $ doAnalysis (guard . not . isSetE) root containsSetE root = isNothing $ doAnalysis (guard . not . isSetE) root
where where
isSetE t = isSetE t =
case t of case t of
T_Script _ (T_Literal _ str) _ -> str `matches` re T_Script _ (T_Literal _ str) _ -> str `matches` re
T_SimpleCommand {} -> T_SimpleCommand {} ->
t `isUnqualifiedCommand` "set" && t `isUnqualifiedCommand` "set" &&
("errexit" `elem` oversimplify t || ("errexit" `elem` oversimplify t ||
"e" `elem` map snd (getAllFlags t)) "e" `elem` map snd (getAllFlags t))
_ -> False _ -> False
re = mkRegex "[[:space:]]-[^-]*e" re = mkRegex "[[:space:]]-[^-]*e"
containsPipefail root = isNothing $ doAnalysis (guard . not . isPipefail) root containsSetOption opt root = isNothing $ doAnalysis (guard . not . isPipefail) r oot
where where
isPipefail t = isPipefail t =
case t of case t of
T_SimpleCommand {} -> T_SimpleCommand {} ->
t `isUnqualifiedCommand` "set" && t `isUnqualifiedCommand` "set" &&
("pipefail" `elem` oversimplify t || (opt `elem` oversimplify t ||
"o" `elem` map snd (getAllFlags t)) "o" `elem` map snd (getAllFlags t))
_ -> False _ -> False
containsShopt shopt root = containsShopt shopt root =
isNothing $ doAnalysis (guard . not . isShoptLastPipe) root isNothing $ doAnalysis (guard . not . isShoptLastPipe) root
where where
isShoptLastPipe t = isShoptLastPipe t =
case t of case t of
T_SimpleCommand {} -> T_SimpleCommand {} ->
t `isUnqualifiedCommand` "shopt" && t `isUnqualifiedCommand` "shopt" &&
(shopt `elem` oversimplify t) (shopt `elem` oversimplify t)
_ -> False _ -> False
containsInheritErrexit = containsShopt "inherit_errexit" -- Does this script mention 'shopt -s $opt' or 'set -o $opt' anywhere?
isOptionSet opt root = containsShopt opt root || containsSetOption opt root
containsLastpipe = containsShopt "lastpipe"
prop_determineShell0 = determineShellTest "#!/bin/sh" == Sh prop_determineShell0 = determineShellTest "#!/bin/sh" == Sh
prop_determineShell1 = determineShellTest "#!/usr/bin/env ksh" == Ksh prop_determineShell1 = determineShellTest "#!/usr/bin/env ksh" == Ksh
prop_determineShell2 = determineShellTest "" == Bash prop_determineShell2 = determineShellTest "" == Bash
prop_determineShell3 = determineShellTest "#!/bin/sh -e" == Sh prop_determineShell3 = determineShellTest "#!/bin/sh -e" == Sh
prop_determineShell4 = determineShellTest "#!/bin/ksh\n#shellcheck shell=sh\nfoo " == Sh prop_determineShell4 = determineShellTest "#!/bin/ksh\n#shellcheck shell=sh\nfoo " == Sh
prop_determineShell5 = determineShellTest "#shellcheck shell=sh\nfoo" == Sh prop_determineShell5 = determineShellTest "#shellcheck shell=sh\nfoo" == Sh
prop_determineShell6 = determineShellTest "#! /bin/sh" == Sh prop_determineShell6 = determineShellTest "#! /bin/sh" == Sh
prop_determineShell7 = determineShellTest "#! /bin/ash" == Dash prop_determineShell7 = determineShellTest "#! /bin/ash" == Dash
prop_determineShell8 = determineShellTest' (Just Ksh) "#!/bin/sh" == Sh prop_determineShell8 = determineShellTest' (Just Ksh) "#!/bin/sh" == Sh
skipping to change at line 349 skipping to change at line 359
T_DoubleQuoted _ _ -> return True T_DoubleQuoted _ _ -> return True
T_DollarDoubleQuoted _ _ -> return True T_DollarDoubleQuoted _ _ -> return True
T_CaseExpression {} -> return True T_CaseExpression {} -> return True
T_HereDoc {} -> return True T_HereDoc {} -> return True
T_DollarBraced {} -> return True T_DollarBraced {} -> return True
-- When non-strict, pragmatically assume it's desirable to split her e -- When non-strict, pragmatically assume it's desirable to split her e
T_ForIn {} -> return (not strict) T_ForIn {} -> return (not strict)
T_SelectIn {} -> return (not strict) T_SelectIn {} -> return (not strict)
_ -> Nothing _ -> Nothing
-- Check whether this assigment is self-quoting due to being a recognized -- Check whether this assignment is self-quoting due to being a recognized
-- assignment passed to a Declaration Utility. This will soon be required -- assignment passed to a Declaration Utility. This will soon be required
-- by POSIX: https://austingroupbugs.net/view.php?id=351 -- by POSIX: https://austingroupbugs.net/view.php?id=351
assignmentIsQuoting t = shellParsesParamsAsAssignments || not (isAssignmentP aramToCommand t) assignmentIsQuoting t = shellParsesParamsAsAssignments || not (isAssignmentP aramToCommand t)
shellParsesParamsAsAssignments = shell /= Sh shellParsesParamsAsAssignments = shell /= Sh
-- Is this assignment a parameter to a command like export/typeset/etc? -- Is this assignment a parameter to a command like export/typeset/etc?
isAssignmentParamToCommand (T_Assignment id _ _ _ _) = isAssignmentParamToCommand (T_Assignment id _ _ _ _) =
case Map.lookup id tree of case Map.lookup id tree of
Just (T_SimpleCommand _ _ (_:args)) -> id `elem` (map getId args) Just (T_SimpleCommand _ _ (_:args)) -> id `elem` (map getId args)
_ -> False _ -> False
skipping to change at line 406 skipping to change at line 416
usedAsCommandName tree token = go (getId token) (tail $ getPath tree token) usedAsCommandName tree token = go (getId token) (tail $ getPath tree token)
where where
go currentId (T_NormalWord id [word]:rest) go currentId (T_NormalWord id [word]:rest)
| currentId == getId word = go id rest | currentId == getId word = go id rest
go currentId (T_DoubleQuoted id [word]:rest) go currentId (T_DoubleQuoted id [word]:rest)
| currentId == getId word = go id rest | currentId == getId word = go id rest
go currentId (t@(T_SimpleCommand _ _ (word:_)):_) = go currentId (t@(T_SimpleCommand _ _ (word:_)):_) =
getId word == currentId || getId (getCommandTokenOrThis t) == currentId getId word == currentId || getId (getCommandTokenOrThis t) == currentId
go _ _ = False go _ _ = False
getPath tree t = t :
case Map.lookup (getId t) tree of
Nothing -> []
Just parent -> getPath tree parent
-- Version of the above taking the map from the current context -- Version of the above taking the map from the current context
-- Todo: give this the name "getPath" -- Todo: give this the name "getPath"
getPathM t = do getPathM t = do
params <- ask params <- ask
return $ getPath (parentMap params) t return $ getPath (parentMap params) t
isParentOf tree parent child = isParentOf tree parent child =
elem (getId parent) . map getId $ getPath tree child elem (getId parent) . map getId $ getPath tree child
parents params = getPath (parentMap params) parents params = getPath (parentMap params)
skipping to change at line 508 skipping to change at line 512
_:_:_ -> not (hasLastpipe params) || getId (last list) /= getId t _:_:_ -> not (hasLastpipe params) || getId (last list) /= getId t
_ -> False _ -> False
getModifiedVariables t = getModifiedVariables t =
case t of case t of
T_SimpleCommand _ vars [] -> T_SimpleCommand _ vars [] ->
[(x, x, name, dataTypeFrom DataString w) | x@(T_Assignment id _ name _ w) <- vars] [(x, x, name, dataTypeFrom DataString w) | x@(T_Assignment id _ name _ w) <- vars]
T_SimpleCommand {} -> T_SimpleCommand {} ->
getModifiedVariableCommand t getModifiedVariableCommand t
TA_Unary _ "++|" v@(TA_Variable _ name _) -> TA_Unary _ op v@(TA_Variable _ name _) | "--" `isInfixOf` op || "++" `is
[(t, v, name, DataString $ SourceFrom [v])] InfixOf` op ->
TA_Unary _ "|++" v@(TA_Variable _ name _) -> [(t, v, name, DataString SourceInteger)]
[(t, v, name, DataString $ SourceFrom [v])]
TA_Assignment _ op (TA_Variable _ name _) rhs -> do TA_Assignment _ op (TA_Variable _ name _) rhs -> do
guard $ op `elem` ["=", "*=", "/=", "%=", "+=", "-=", "<<=", ">>=", "&=", "^=", "|="] guard $ op `elem` ["=", "*=", "/=", "%=", "+=", "-=", "<<=", ">>=", "&=", "^=", "|="]
return (t, t, name, DataString $ SourceFrom [rhs]) return (t, t, name, DataString SourceInteger)
T_BatsTest {} -> [ T_BatsTest {} -> [
(t, t, "lines", DataArray SourceExternal), (t, t, "lines", DataArray SourceExternal),
(t, t, "status", DataString SourceInteger), (t, t, "status", DataString SourceInteger),
(t, t, "output", DataString SourceExternal) (t, t, "output", DataString SourceExternal)
] ]
-- Count [[ -v foo ]] as an "assignment". -- Count [[ -v foo ]] as an "assignment".
-- This is to prevent [ -v foo ] being unassigned or unused. -- This is to prevent [ -v foo ] being unassigned or unused.
TC_Unary id _ "-v" token -> maybeToList $ do TC_Unary id _ "-v" token -> maybeToList $ do
skipping to change at line 558 skipping to change at line 560
_ -> [] _ -> []
where where
markAsChecked place token = mapMaybe (f place) $ getWordParts token markAsChecked place token = mapMaybe (f place) $ getWordParts token
f place t = case t of f place t = case t of
T_DollarBraced _ _ l -> T_DollarBraced _ _ l ->
let str = getBracedReference $ concat $ oversimplify l in do let str = getBracedReference $ concat $ oversimplify l in do
guard $ isVariableName str guard $ isVariableName str
return (place, t, str, DataString SourceChecked) return (place, t, str, DataString SourceChecked)
_ -> Nothing _ -> Nothing
isClosingFileOp op =
case op of
T_IoDuplicate _ (T_GREATAND _) "-" -> True
T_IoDuplicate _ (T_LESSAND _) "-" -> True
_ -> False
-- Consider 'export/declare -x' a reference, since it makes the var available -- Consider 'export/declare -x' a reference, since it makes the var available
getReferencedVariableCommand base@(T_SimpleCommand _ _ (T_NormalWord _ (T_Litera l _ x:_):rest)) = getReferencedVariableCommand base@(T_SimpleCommand _ _ (T_NormalWord _ (T_Litera l _ x:_):rest)) =
case x of case x of
"declare" -> forDeclare "declare" -> forDeclare
"typeset" -> forDeclare "typeset" -> forDeclare
"export" -> if "f" `elem` flags "export" -> if "f" `elem` flags
then [] then []
else concatMap getReference rest else concatMap getReference rest
"local" -> if "x" `elem` flags "local" -> if "x" `elem` flags
skipping to change at line 744 skipping to change at line 740
return (name, arg) return (name, arg)
-- get the FLAGS_ variable created by a shflags DEFINE_ call -- get the FLAGS_ variable created by a shflags DEFINE_ call
getFlagVariable (n:v:_) = do getFlagVariable (n:v:_) = do
name <- getLiteralString n name <- getLiteralString n
return (base, n, "FLAGS_" ++ name, DataString $ SourceExternal) return (base, n, "FLAGS_" ++ name, DataString $ SourceExternal)
getFlagVariable _ = Nothing getFlagVariable _ = Nothing
getModifiedVariableCommand _ = [] getModifiedVariableCommand _ = []
getIndexReferences s = fromMaybe [] $ do
match <- matchRegex re s
index <- match !!! 0
return $ matchAllStrings variableNameRegex index
where
re = mkRegex "(\\[.*\\])"
-- Given a NormalWord like foo or foo[$bar], get foo. -- Given a NormalWord like foo or foo[$bar], get foo.
-- Primarily used to get references for [[ -v foo[bar] ]] -- Primarily used to get references for [[ -v foo[bar] ]]
getVariableForTestDashV :: Token -> Maybe String getVariableForTestDashV :: Token -> Maybe String
getVariableForTestDashV t = do getVariableForTestDashV t = do
str <- takeWhile ('[' /=) <$> getLiteralStringExt toStr t str <- takeWhile ('[' /=) <$> getLiteralStringExt toStr t
guard $ isVariableName str guard $ isVariableName str
return str return str
where where
-- foo[bar] gets parsed with [bar] as a glob, so undo that -- foo[bar] gets parsed with [bar] as a glob, so undo that
toStr (T_Glob _ s) = return s toStr (T_Glob _ s) = return s
-- Turn foo[$x] into foo[\0] so that we can get the constant array name -- Turn foo[$x] into foo[\0] so that we can get the constant array name
-- in a non-constant expression (while filtering out foo$x[$y]) -- in a non-constant expression (while filtering out foo$x[$y])
toStr _ = return "\0" toStr _ = return "\0"
prop_getOffsetReferences1 = getOffsetReferences ":bar" == ["bar"]
prop_getOffsetReferences2 = getOffsetReferences ":bar:baz" == ["bar", "baz"]
prop_getOffsetReferences3 = getOffsetReferences "[foo]:bar" == ["bar"]
prop_getOffsetReferences4 = getOffsetReferences "[foo]:bar:baz" == ["bar", "baz"
]
getOffsetReferences mods = fromMaybe [] $ do
match <- matchRegex re mods
offsets <- match !!! 1
return $ matchAllStrings variableNameRegex offsets
where
re = mkRegex "^(\\[.+\\])? *:([^-=?+].*)"
getReferencedVariables parents t = getReferencedVariables parents t =
case t of case t of
T_DollarBraced id _ l -> let str = concat $ oversimplify l in T_DollarBraced id _ l -> let str = concat $ oversimplify l in
(t, t, getBracedReference str) : (t, t, getBracedReference str) :
map (\x -> (l, l, x)) ( map (\x -> (l, l, x)) (
getIndexReferences str getIndexReferences str
++ getOffsetReferences (getBracedModifier str)) ++ getOffsetReferences (getBracedModifier str))
TA_Variable id name _ -> TA_Variable id name _ ->
if isArithmeticAssignment t if isArithmeticAssignment t
then [] then []
skipping to change at line 854 skipping to change at line 831
matcher (getCommandName token) matcher (getCommandName token)
-- Does this regex look like it was intended as a glob? -- Does this regex look like it was intended as a glob?
-- True: *foo* -- True: *foo*
-- False: .*foo.* -- False: .*foo.*
isConfusedGlobRegex :: String -> Bool isConfusedGlobRegex :: String -> Bool
isConfusedGlobRegex ('*':_) = True isConfusedGlobRegex ('*':_) = True
isConfusedGlobRegex [x,'*'] | x `notElem` "\\." = True isConfusedGlobRegex [x,'*'] | x `notElem` "\\." = True
isConfusedGlobRegex _ = False isConfusedGlobRegex _ = False
isVariableStartChar x = x == '_' || isAsciiLower x || isAsciiUpper x
isVariableChar x = isVariableStartChar x || isDigit x
isSpecialVariableChar = (`elem` "*@#?-$!")
variableNameRegex = mkRegex "[_a-zA-Z][_a-zA-Z0-9]*"
prop_isVariableName1 = isVariableName "_fo123"
prop_isVariableName2 = not $ isVariableName "4"
prop_isVariableName3 = not $ isVariableName "test: "
isVariableName (x:r) = isVariableStartChar x && all isVariableChar r
isVariableName _ = False
getVariablesFromLiteralToken token = getVariablesFromLiteralToken token =
getVariablesFromLiteral (getLiteralStringDef " " token) getVariablesFromLiteral (getLiteralStringDef " " token)
-- Try to get referenced variables from a literal string like "$foo" -- Try to get referenced variables from a literal string like "$foo"
-- Ignores tons of cases like arithmetic evaluation and array indices. -- Ignores tons of cases like arithmetic evaluation and array indices.
prop_getVariablesFromLiteral1 = prop_getVariablesFromLiteral1 =
getVariablesFromLiteral "$foo${bar//a/b}$BAZ" == ["foo", "bar", "BAZ"] getVariablesFromLiteral "$foo${bar//a/b}$BAZ" == ["foo", "bar", "BAZ"]
getVariablesFromLiteral string = getVariablesFromLiteral string =
map head $ matchAllSubgroups variableRegex string map head $ matchAllSubgroups variableRegex string
where where
variableRegex = mkRegex "\\$\\{?([A-Za-z0-9_]+)" variableRegex = mkRegex "\\$\\{?([A-Za-z0-9_]+)"
prop_getBracedReference1 = getBracedReference "foo" == "foo"
prop_getBracedReference2 = getBracedReference "#foo" == "foo"
prop_getBracedReference3 = getBracedReference "#" == "#"
prop_getBracedReference4 = getBracedReference "##" == "#"
prop_getBracedReference5 = getBracedReference "#!" == "!"
prop_getBracedReference6 = getBracedReference "!#" == "#"
prop_getBracedReference7 = getBracedReference "!foo#?" == "foo"
prop_getBracedReference8 = getBracedReference "foo-bar" == "foo"
prop_getBracedReference9 = getBracedReference "foo:-bar" == "foo"
prop_getBracedReference10= getBracedReference "foo: -1" == "foo"
prop_getBracedReference11= getBracedReference "!os*" == ""
prop_getBracedReference11b= getBracedReference "!os@" == ""
prop_getBracedReference12= getBracedReference "!os?bar**" == ""
prop_getBracedReference13= getBracedReference "foo[bar]" == "foo"
getBracedReference s = fromMaybe s $
nameExpansion s `mplus` takeName noPrefix `mplus` getSpecial noPrefix `mplus
` getSpecial s
where
noPrefix = dropPrefix s
dropPrefix (c:rest) | c `elem` "!#" = rest
dropPrefix cs = cs
takeName s = do
let name = takeWhile isVariableChar s
guard . not $ null name
return name
getSpecial (c:_) | isSpecialVariableChar c = return [c]
getSpecial _ = fail "empty or not special"
nameExpansion ('!':next:rest) = do -- e.g. ${!foo*bar*}
guard $ isVariableChar next -- e.g. ${!@}
first <- find (not . isVariableChar) rest
guard $ first `elem` "*?@"
return ""
nameExpansion _ = Nothing
prop_getBracedModifier1 = getBracedModifier "foo:bar:baz" == ":bar:baz"
prop_getBracedModifier2 = getBracedModifier "!var:-foo" == ":-foo"
prop_getBracedModifier3 = getBracedModifier "foo[bar]" == "[bar]"
prop_getBracedModifier4 = getBracedModifier "foo[@]@Q" == "[@]@Q"
prop_getBracedModifier5 = getBracedModifier "@@Q" == "@Q"
getBracedModifier s = headOrDefault "" $ do
let var = getBracedReference s
a <- dropModifier s
dropPrefix var a
where
dropPrefix [] t = return t
dropPrefix (a:b) (c:d) | a == c = dropPrefix b d
dropPrefix _ _ = []
dropModifier (c:rest) | c `elem` "#!" = [rest, c:rest]
dropModifier x = [x]
headOrDefault _ (a:_) = a
headOrDefault def _ = def
lastOrDefault def [] = def
lastOrDefault _ list = last list
(!!!) list i =
case drop i list of
[] -> Nothing
(r:_) -> Just r
-- Run a command if the shell is in the given list -- Run a command if the shell is in the given list
whenShell l c = do whenShell l c = do
params <- ask params <- ask
when (shellType params `elem` l ) c when (shellType params `elem` l ) c
filterByAnnotation asSpec params = filterByAnnotation asSpec params =
filter (not . shouldIgnore) filter (not . shouldIgnore)
where where
token = asScript asSpec token = asScript asSpec
shouldIgnore note = shouldIgnore note =
skipping to change at line 995 skipping to change at line 893
-- Returns true if the shell is Bash or Ksh (sorry for the name, Ksh) -- Returns true if the shell is Bash or Ksh (sorry for the name, Ksh)
isBashLike :: Parameters -> Bool isBashLike :: Parameters -> Bool
isBashLike params = isBashLike params =
case shellType params of case shellType params of
Bash -> True Bash -> True
Ksh -> True Ksh -> True
Dash -> False Dash -> False
Sh -> False Sh -> False
isUnmodifiedParameterExpansion t =
case t of
T_DollarBraced _ False _ -> True
T_DollarBraced _ _ list ->
let str = concat $ oversimplify list
in getBracedReference str == str
_ -> False
isTrueAssignmentSource c = isTrueAssignmentSource c =
case c of case c of
DataString SourceChecked -> False DataString SourceChecked -> False
DataString SourceDeclaration -> False DataString SourceDeclaration -> False
DataArray SourceChecked -> False DataArray SourceChecked -> False
DataArray SourceDeclaration -> False DataArray SourceDeclaration -> False
_ -> True _ -> True
modifiesVariable params token name = modifiesVariable params token name =
or $ map check flow or $ map check flow
 End of changes. 26 change blocks. 
135 lines changed or deleted 34 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)