{-
    Copyright 2012-2021 Vidar Holen

    This file is part of ShellCheck.
    https://www.shellcheck.net

    ShellCheck is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    ShellCheck is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TemplateHaskell  #-}
module ShellCheck.AnalyzerLib where

import ShellCheck.AST
import ShellCheck.ASTLib
import ShellCheck.Data
import ShellCheck.Interface
import ShellCheck.Parser
import ShellCheck.Regex

import Control.Arrow (first)
import Control.DeepSeq
import Control.Monad.Identity
import Control.Monad.RWS
import Control.Monad.State
import Control.Monad.Writer
import Data.Char
import Data.List
import Data.Maybe
import Data.Semigroup
import qualified Data.Map as Map

import Test.QuickCheck.All (forAllProperties)
import Test.QuickCheck.Test (maxSuccess, quickCheckWithResult, stdArgs)

type Analysis = AnalyzerM ()
type AnalyzerM a = RWS Parameters [TokenComment] Cache a
nullCheck :: b -> RWST Parameters [TokenComment] Cache Identity ()
nullCheck = RWST Parameters [TokenComment] Cache Identity ()
-> b -> RWST Parameters [TokenComment] Cache Identity ()
forall a b. a -> b -> a
const (RWST Parameters [TokenComment] Cache Identity ()
 -> b -> RWST Parameters [TokenComment] Cache Identity ())
-> RWST Parameters [TokenComment] Cache Identity ()
-> b
-> RWST Parameters [TokenComment] Cache Identity ()
forall a b. (a -> b) -> a -> b
$ () -> RWST Parameters [TokenComment] Cache Identity ()
forall (m :: * -> *) a. Monad m => a -> m a
return ()


data Checker = Checker {
    Checker -> Root -> RWST Parameters [TokenComment] Cache Identity ()
perScript :: Root -> Analysis,
    Checker
-> Token -> RWST Parameters [TokenComment] Cache Identity ()
perToken  :: Token -> Analysis
}

runChecker :: Parameters -> Checker -> [TokenComment]
runChecker :: Parameters -> Checker -> [TokenComment]
runChecker Parameters
params Checker
checker = [TokenComment]
notes
    where
        root :: Token
root = Parameters -> Token
rootNode Parameters
params
        check :: Root -> RWST Parameters [TokenComment] Cache Identity ()
check = Checker -> Root -> RWST Parameters [TokenComment] Cache Identity ()
perScript Checker
checker (Root -> RWST Parameters [TokenComment] Cache Identity ())
-> (Root -> RWST Parameters [TokenComment] Cache Identity ())
-> Root
-> RWST Parameters [TokenComment] Cache Identity ()
forall a.
(a -> RWST Parameters [TokenComment] Cache Identity ())
-> (a -> RWST Parameters [TokenComment] Cache Identity ())
-> a
-> RWST Parameters [TokenComment] Cache Identity ()
`composeAnalyzers` (\(Root Token
x) -> RWST Parameters [TokenComment] Cache Identity Token
-> RWST Parameters [TokenComment] Cache Identity ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (RWST Parameters [TokenComment] Cache Identity Token
 -> RWST Parameters [TokenComment] Cache Identity ())
-> RWST Parameters [TokenComment] Cache Identity Token
-> RWST Parameters [TokenComment] Cache Identity ()
forall a b. (a -> b) -> a -> b
$ (Token -> RWST Parameters [TokenComment] Cache Identity ())
-> Token -> RWST Parameters [TokenComment] Cache Identity Token
forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> Token -> m Token
doAnalysis (Checker
-> Token -> RWST Parameters [TokenComment] Cache Identity ()
perToken Checker
checker) Token
x)
        notes :: [TokenComment]
notes = ((), [TokenComment]) -> [TokenComment]
forall a b. (a, b) -> b
snd (((), [TokenComment]) -> [TokenComment])
-> ((), [TokenComment]) -> [TokenComment]
forall a b. (a -> b) -> a -> b
$ RWST Parameters [TokenComment] Cache Identity ()
-> Parameters -> Cache -> ((), [TokenComment])
forall r w s a. RWS r w s a -> r -> s -> (a, w)
evalRWS (Root -> RWST Parameters [TokenComment] Cache Identity ()
check (Root -> RWST Parameters [TokenComment] Cache Identity ())
-> Root -> RWST Parameters [TokenComment] Cache Identity ()
forall a b. (a -> b) -> a -> b
$ Token -> Root
Root Token
root) Parameters
params Cache
Cache

instance Semigroup Checker where
    <> :: Checker -> Checker -> Checker
(<>) Checker
x Checker
y = Checker :: (Root -> RWST Parameters [TokenComment] Cache Identity ())
-> (Token -> RWST Parameters [TokenComment] Cache Identity ())
-> Checker
Checker {
        perScript :: Root -> RWST Parameters [TokenComment] Cache Identity ()
perScript = Checker -> Root -> RWST Parameters [TokenComment] Cache Identity ()
perScript Checker
x (Root -> RWST Parameters [TokenComment] Cache Identity ())
-> (Root -> RWST Parameters [TokenComment] Cache Identity ())
-> Root
-> RWST Parameters [TokenComment] Cache Identity ()
forall a.
(a -> RWST Parameters [TokenComment] Cache Identity ())
-> (a -> RWST Parameters [TokenComment] Cache Identity ())
-> a
-> RWST Parameters [TokenComment] Cache Identity ()
`composeAnalyzers` Checker -> Root -> RWST Parameters [TokenComment] Cache Identity ()
perScript Checker
y,
        perToken :: Token -> RWST Parameters [TokenComment] Cache Identity ()
perToken = Checker
-> Token -> RWST Parameters [TokenComment] Cache Identity ()
perToken Checker
x (Token -> RWST Parameters [TokenComment] Cache Identity ())
-> (Token -> RWST Parameters [TokenComment] Cache Identity ())
-> Token
-> RWST Parameters [TokenComment] Cache Identity ()
forall a.
(a -> RWST Parameters [TokenComment] Cache Identity ())
-> (a -> RWST Parameters [TokenComment] Cache Identity ())
-> a
-> RWST Parameters [TokenComment] Cache Identity ()
`composeAnalyzers` Checker
-> Token -> RWST Parameters [TokenComment] Cache Identity ()
perToken Checker
y
        }

instance Monoid Checker where
    mempty :: Checker
mempty = Checker :: (Root -> RWST Parameters [TokenComment] Cache Identity ())
-> (Token -> RWST Parameters [TokenComment] Cache Identity ())
-> Checker
Checker {
        perScript :: Root -> RWST Parameters [TokenComment] Cache Identity ()
perScript = Root -> RWST Parameters [TokenComment] Cache Identity ()
forall b. b -> RWST Parameters [TokenComment] Cache Identity ()
nullCheck,
        perToken :: Token -> RWST Parameters [TokenComment] Cache Identity ()
perToken = Token -> RWST Parameters [TokenComment] Cache Identity ()
forall b. b -> RWST Parameters [TokenComment] Cache Identity ()
nullCheck
        }
    mappend :: Checker -> Checker -> Checker
mappend = Checker -> Checker -> Checker
forall a. Semigroup a => a -> a -> a
(Data.Semigroup.<>)

composeAnalyzers :: (a -> Analysis) -> (a -> Analysis) -> a -> Analysis
composeAnalyzers :: (a -> RWST Parameters [TokenComment] Cache Identity ())
-> (a -> RWST Parameters [TokenComment] Cache Identity ())
-> a
-> RWST Parameters [TokenComment] Cache Identity ()
composeAnalyzers a -> RWST Parameters [TokenComment] Cache Identity ()
f a -> RWST Parameters [TokenComment] Cache Identity ()
g a
x = a -> RWST Parameters [TokenComment] Cache Identity ()
f a
x RWST Parameters [TokenComment] Cache Identity ()
-> RWST Parameters [TokenComment] Cache Identity ()
-> RWST Parameters [TokenComment] Cache Identity ()
forall (m :: * -> *) a b. Monad m => m a -> m b -> m b
>> a -> RWST Parameters [TokenComment] Cache Identity ()
g a
x

data Parameters = Parameters {
    -- Whether this script has the 'lastpipe' option set/default.
    Parameters -> Bool
hasLastpipe        :: Bool,
    -- Whether this script has the 'inherit_errexit' option set/default.
    Parameters -> Bool
hasInheritErrexit  :: Bool,
    -- Whether this script has 'set -e' anywhere.
    Parameters -> Bool
hasSetE            :: Bool,
    -- Whether this script has 'set -o pipefail' anywhere.
    Parameters -> Bool
hasPipefail        :: Bool,
    -- A linear (bad) analysis of data flow
    Parameters -> [StackData]
variableFlow       :: [StackData],
    -- A map from Id to parent Token
    Parameters -> Map Id Token
parentMap          :: Map.Map Id Token,
    -- The shell type, such as Bash or Ksh
    Parameters -> Shell
shellType          :: Shell,
    -- True if shell type was forced via flags
    Parameters -> Bool
shellTypeSpecified :: Bool,
    -- The root node of the AST
    Parameters -> Token
rootNode           :: Token,
    -- map from token id to start and end position
    Parameters -> Map Id (Position, Position)
tokenPositions     :: Map.Map Id (Position, Position)
    } deriving (Int -> Parameters -> ShowS
[Parameters] -> ShowS
Parameters -> String
(Int -> Parameters -> ShowS)
-> (Parameters -> String)
-> ([Parameters] -> ShowS)
-> Show Parameters
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Parameters] -> ShowS
$cshowList :: [Parameters] -> ShowS
show :: Parameters -> String
$cshow :: Parameters -> String
showsPrec :: Int -> Parameters -> ShowS
$cshowsPrec :: Int -> Parameters -> ShowS
Show)

-- TODO: Cache results of common AST ops here
data Cache = Cache {}

data Scope = SubshellScope String | NoneScope deriving (Int -> Scope -> ShowS
[Scope] -> ShowS
Scope -> String
(Int -> Scope -> ShowS)
-> (Scope -> String) -> ([Scope] -> ShowS) -> Show Scope
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Scope] -> ShowS
$cshowList :: [Scope] -> ShowS
show :: Scope -> String
$cshow :: Scope -> String
showsPrec :: Int -> Scope -> ShowS
$cshowsPrec :: Int -> Scope -> ShowS
Show, Scope -> Scope -> Bool
(Scope -> Scope -> Bool) -> (Scope -> Scope -> Bool) -> Eq Scope
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Scope -> Scope -> Bool
$c/= :: Scope -> Scope -> Bool
== :: Scope -> Scope -> Bool
$c== :: Scope -> Scope -> Bool
Eq)
data StackData =
    StackScope Scope
    | StackScopeEnd
    -- (Base expression, specific position, var name, assigned values)
    | Assignment (Token, Token, String, DataType)
    | Reference (Token, Token, String)
  deriving (Int -> StackData -> ShowS
[StackData] -> ShowS
StackData -> String
(Int -> StackData -> ShowS)
-> (StackData -> String)
-> ([StackData] -> ShowS)
-> Show StackData
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [StackData] -> ShowS
$cshowList :: [StackData] -> ShowS
show :: StackData -> String
$cshow :: StackData -> String
showsPrec :: Int -> StackData -> ShowS
$cshowsPrec :: Int -> StackData -> ShowS
Show)

data DataType = DataString DataSource | DataArray DataSource
  deriving (Int -> DataType -> ShowS
[DataType] -> ShowS
DataType -> String
(Int -> DataType -> ShowS)
-> (DataType -> String) -> ([DataType] -> ShowS) -> Show DataType
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [DataType] -> ShowS
$cshowList :: [DataType] -> ShowS
show :: DataType -> String
$cshow :: DataType -> String
showsPrec :: Int -> DataType -> ShowS
$cshowsPrec :: Int -> DataType -> ShowS
Show)

data DataSource =
    SourceFrom [Token]
    | SourceExternal
    | SourceDeclaration
    | SourceInteger
    | SourceChecked
  deriving (Int -> DataSource -> ShowS
[DataSource] -> ShowS
DataSource -> String
(Int -> DataSource -> ShowS)
-> (DataSource -> String)
-> ([DataSource] -> ShowS)
-> Show DataSource
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [DataSource] -> ShowS
$cshowList :: [DataSource] -> ShowS
show :: DataSource -> String
$cshow :: DataSource -> String
showsPrec :: Int -> DataSource -> ShowS
$cshowsPrec :: Int -> DataSource -> ShowS
Show)

data VariableState = Dead Token String | Alive deriving (Int -> VariableState -> ShowS
[VariableState] -> ShowS
VariableState -> String
(Int -> VariableState -> ShowS)
-> (VariableState -> String)
-> ([VariableState] -> ShowS)
-> Show VariableState
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [VariableState] -> ShowS
$cshowList :: [VariableState] -> ShowS
show :: VariableState -> String
$cshow :: VariableState -> String
showsPrec :: Int -> VariableState -> ShowS
$cshowsPrec :: Int -> VariableState -> ShowS
Show)

defaultSpec :: ParseResult -> AnalysisSpec
defaultSpec ParseResult
pr = AnalysisSpec
spec {
    asShellType :: Maybe Shell
asShellType = Maybe Shell
forall a. Maybe a
Nothing,
    asCheckSourced :: Bool
asCheckSourced = Bool
False,
    asExecutionMode :: ExecutionMode
asExecutionMode = ExecutionMode
Executed,
    asTokenPositions :: Map Id (Position, Position)
asTokenPositions = ParseResult -> Map Id (Position, Position)
prTokenPositions ParseResult
pr
} where spec :: AnalysisSpec
spec = Token -> AnalysisSpec
newAnalysisSpec (Maybe Token -> Token
forall a. HasCallStack => Maybe a -> a
fromJust (Maybe Token -> Token) -> Maybe Token -> Token
forall a b. (a -> b) -> a -> b
$ ParseResult -> Maybe Token
prRoot ParseResult
pr)

pScript :: String -> ParseResult
pScript String
s =
  let
    pSpec :: ParseSpec
pSpec = ParseSpec
newParseSpec {
        psFilename :: String
psFilename = String
"script",
        psScript :: String
psScript = String
s
    }
  in Identity ParseResult -> ParseResult
forall a. Identity a -> a
runIdentity (Identity ParseResult -> ParseResult)
-> Identity ParseResult -> ParseResult
forall a b. (a -> b) -> a -> b
$ SystemInterface Identity -> ParseSpec -> Identity ParseResult
forall (m :: * -> *).
Monad m =>
SystemInterface m -> ParseSpec -> m ParseResult
parseScript ([(String, String)] -> SystemInterface Identity
mockedSystemInterface []) ParseSpec
pSpec

-- For testing. If parsed, returns whether there are any comments
producesComments :: Checker -> String -> Maybe Bool
producesComments :: Checker -> String -> Maybe Bool
producesComments Checker
c String
s = do
        let pr :: ParseResult
pr = String -> ParseResult
pScript String
s
        ParseResult -> Maybe Token
prRoot ParseResult
pr
        let spec :: AnalysisSpec
spec = ParseResult -> AnalysisSpec
defaultSpec ParseResult
pr
        let params :: Parameters
params = AnalysisSpec -> Parameters
makeParameters AnalysisSpec
spec
        Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> Maybe Bool)
-> ([TokenComment] -> Bool) -> [TokenComment] -> Maybe Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not (Bool -> Bool)
-> ([TokenComment] -> Bool) -> [TokenComment] -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [TokenComment] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null ([TokenComment] -> Maybe Bool) -> [TokenComment] -> Maybe Bool
forall a b. (a -> b) -> a -> b
$ AnalysisSpec -> Parameters -> [TokenComment] -> [TokenComment]
filterByAnnotation AnalysisSpec
spec Parameters
params ([TokenComment] -> [TokenComment])
-> [TokenComment] -> [TokenComment]
forall a b. (a -> b) -> a -> b
$ Parameters -> Checker -> [TokenComment]
runChecker Parameters
params Checker
c

makeComment :: Severity -> Id -> Code -> String -> TokenComment
makeComment :: Severity -> Id -> Code -> String -> TokenComment
makeComment Severity
severity Id
id Code
code String
note =
    TokenComment
newTokenComment {
        tcId :: Id
tcId = Id
id,
        tcComment :: Comment
tcComment = Comment
newComment {
            cSeverity :: Severity
cSeverity = Severity
severity,
            cCode :: Code
cCode = Code
code,
            cMessage :: String
cMessage = String
note
        }
    }

addComment :: a -> m ()
addComment a
note = a
note a -> m () -> m ()
forall a b. NFData a => a -> b -> b
`deepseq` [a] -> m ()
forall w (m :: * -> *). MonadWriter w m => w -> m ()
tell [a
note]

warn :: MonadWriter [TokenComment] m => Id -> Code -> String -> m ()
warn :: Id -> Code -> String -> m ()
warn  Id
id Code
code String
str = TokenComment -> m ()
forall a (m :: * -> *). (NFData a, MonadWriter [a] m) => a -> m ()
addComment (TokenComment -> m ()) -> TokenComment -> m ()
forall a b. (a -> b) -> a -> b
$ Severity -> Id -> Code -> String -> TokenComment
makeComment Severity
WarningC Id
id Code
code String
str
err :: Id -> Code -> String -> m ()
err   Id
id Code
code String
str = TokenComment -> m ()
forall a (m :: * -> *). (NFData a, MonadWriter [a] m) => a -> m ()
addComment (TokenComment -> m ()) -> TokenComment -> m ()
forall a b. (a -> b) -> a -> b
$ Severity -> Id -> Code -> String -> TokenComment
makeComment Severity
ErrorC Id
id Code
code String
str
info :: Id -> Code -> String -> m ()
info  Id
id Code
code String
str = TokenComment -> m ()
forall a (m :: * -> *). (NFData a, MonadWriter [a] m) => a -> m ()
addComment (TokenComment -> m ()) -> TokenComment -> m ()
forall a b. (a -> b) -> a -> b
$ Severity -> Id -> Code -> String -> TokenComment
makeComment Severity
InfoC Id
id Code
code String
str
style :: Id -> Code -> String -> m ()
style Id
id Code
code String
str = TokenComment -> m ()
forall a (m :: * -> *). (NFData a, MonadWriter [a] m) => a -> m ()
addComment (TokenComment -> m ()) -> TokenComment -> m ()
forall a b. (a -> b) -> a -> b
$ Severity -> Id -> Code -> String -> TokenComment
makeComment Severity
StyleC Id
id Code
code String
str

errWithFix :: MonadWriter [TokenComment] m => Id -> Code -> String -> Fix -> m ()
errWithFix :: Id -> Code -> String -> Fix -> m ()
errWithFix  = Severity -> Id -> Code -> String -> Fix -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Severity -> Id -> Code -> String -> Fix -> m ()
addCommentWithFix Severity
ErrorC
warnWithFix :: MonadWriter [TokenComment] m => Id -> Code -> String -> Fix -> m ()
warnWithFix :: Id -> Code -> String -> Fix -> m ()
warnWithFix  = Severity -> Id -> Code -> String -> Fix -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Severity -> Id -> Code -> String -> Fix -> m ()
addCommentWithFix Severity
WarningC
infoWithFix :: MonadWriter [TokenComment] m => Id -> Code -> String -> Fix -> m ()
infoWithFix :: Id -> Code -> String -> Fix -> m ()
infoWithFix = Severity -> Id -> Code -> String -> Fix -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Severity -> Id -> Code -> String -> Fix -> m ()
addCommentWithFix Severity
InfoC
styleWithFix :: MonadWriter [TokenComment] m => Id -> Code -> String -> Fix -> m ()
styleWithFix :: Id -> Code -> String -> Fix -> m ()
styleWithFix = Severity -> Id -> Code -> String -> Fix -> m ()
forall (m :: * -> *).
MonadWriter [TokenComment] m =>
Severity -> Id -> Code -> String -> Fix -> m ()
addCommentWithFix Severity
StyleC

addCommentWithFix :: MonadWriter [TokenComment] m => Severity -> Id -> Code -> String -> Fix -> m ()
addCommentWithFix :: Severity -> Id -> Code -> String -> Fix -> m ()
addCommentWithFix Severity
severity Id
id Code
code String
str Fix
fix =
    TokenComment -> m ()
forall a (m :: * -> *). (NFData a, MonadWriter [a] m) => a -> m ()
addComment (TokenComment -> m ()) -> TokenComment -> m ()
forall a b. (a -> b) -> a -> b
$ Severity -> Id -> Code -> String -> Fix -> TokenComment
makeCommentWithFix Severity
severity Id
id Code
code String
str Fix
fix

makeCommentWithFix :: Severity -> Id -> Code -> String -> Fix -> TokenComment
makeCommentWithFix :: Severity -> Id -> Code -> String -> Fix -> TokenComment
makeCommentWithFix Severity
severity Id
id Code
code String
str Fix
fix =
    let comment :: TokenComment
comment = Severity -> Id -> Code -> String -> TokenComment
makeComment Severity
severity Id
id Code
code String
str
        withFix :: TokenComment
withFix = TokenComment
comment {
            -- If fix is empty, pretend it wasn't there.
            tcFix :: Maybe Fix
tcFix = if [Replacement] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null (Fix -> [Replacement]
fixReplacements Fix
fix) then Maybe Fix
forall a. Maybe a
Nothing else Fix -> Maybe Fix
forall a. a -> Maybe a
Just Fix
fix
        }
    in TokenComment -> TokenComment
forall a. NFData a => a -> a
force TokenComment
withFix

makeParameters :: AnalysisSpec -> Parameters
makeParameters AnalysisSpec
spec =
    let params :: Parameters
params = Parameters :: Bool
-> Bool
-> Bool
-> Bool
-> [StackData]
-> Map Id Token
-> Shell
-> Bool
-> Token
-> Map Id (Position, Position)
-> Parameters
Parameters {
        rootNode :: Token
rootNode = Token
root,
        shellType :: Shell
shellType = Shell -> Maybe Shell -> Shell
forall a. a -> Maybe a -> a
fromMaybe (Maybe Shell -> Token -> Shell
determineShell (AnalysisSpec -> Maybe Shell
asFallbackShell AnalysisSpec
spec) Token
root) (Maybe Shell -> Shell) -> Maybe Shell -> Shell
forall a b. (a -> b) -> a -> b
$ AnalysisSpec -> Maybe Shell
asShellType AnalysisSpec
spec,
        hasSetE :: Bool
hasSetE = Token -> Bool
containsSetE Token
root,
        hasLastpipe :: Bool
hasLastpipe =
            case Parameters -> Shell
shellType Parameters
params of
                Shell
Bash -> Token -> Bool
containsLastpipe Token
root
                Shell
Dash -> Bool
False
                Shell
Sh   -> Bool
False
                Shell
Ksh  -> Bool
True,
        hasInheritErrexit :: Bool
hasInheritErrexit =
            case Parameters -> Shell
shellType Parameters
params of
                Shell
Bash -> Token -> Bool
containsInheritErrexit Token
root
                Shell
Dash -> Bool
True
                Shell
Sh   -> Bool
True
                Shell
Ksh  -> Bool
False,
        hasPipefail :: Bool
hasPipefail =
            case Parameters -> Shell
shellType Parameters
params of
                Shell
Bash -> Token -> Bool
containsPipefail Token
root
                Shell
Dash -> Bool
True
                Shell
Sh   -> Bool
True
                Shell
Ksh  -> Token -> Bool
containsPipefail Token
root,
        shellTypeSpecified :: Bool
shellTypeSpecified = Maybe Shell -> Bool
forall a. Maybe a -> Bool
isJust (AnalysisSpec -> Maybe Shell
asShellType AnalysisSpec
spec) Bool -> Bool -> Bool
|| Maybe Shell -> Bool
forall a. Maybe a -> Bool
isJust (AnalysisSpec -> Maybe Shell
asFallbackShell AnalysisSpec
spec),
        parentMap :: Map Id Token
parentMap = Token -> Map Id Token
getParentTree Token
root,
        variableFlow :: [StackData]
variableFlow = Parameters -> Token -> [StackData]
getVariableFlow Parameters
params Token
root,
        tokenPositions :: Map Id (Position, Position)
tokenPositions = AnalysisSpec -> Map Id (Position, Position)
asTokenPositions AnalysisSpec
spec
    } in Parameters
params
  where root :: Token
root = AnalysisSpec -> Token
asScript AnalysisSpec
spec


-- Does this script mention 'set -e' anywhere?
-- Used as a hack to disable certain warnings.
containsSetE :: Token -> Bool
containsSetE Token
root = Maybe Token -> Bool
forall a. Maybe a -> Bool
isNothing (Maybe Token -> Bool) -> Maybe Token -> Bool
forall a b. (a -> b) -> a -> b
$ (Token -> Maybe ()) -> Token -> Maybe Token
forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> Token -> m Token
doAnalysis (Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> (Token -> Bool) -> Token -> Maybe ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not (Bool -> Bool) -> (Token -> Bool) -> Token -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> Bool
isSetE) Token
root
  where
    isSetE :: Token -> Bool
isSetE Token
t =
        case Token
t of
            T_Script Id
_ (T_Literal Id
_ String
str) [Token]
_ -> String
str String -> Regex -> Bool
`matches` Regex
re
            T_SimpleCommand {}  ->
                Token
t Token -> String -> Bool
`isUnqualifiedCommand` String
"set" Bool -> Bool -> Bool
&&
                    (String
"errexit" String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` Token -> [String]
oversimplify Token
t Bool -> Bool -> Bool
||
                        String
"e" String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` ((Token, String) -> String) -> [(Token, String)] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (Token, String) -> String
forall a b. (a, b) -> b
snd (Token -> [(Token, String)]
getAllFlags Token
t))
            Token
_ -> Bool
False
    re :: Regex
re = String -> Regex
mkRegex String
"[[:space:]]-[^-]*e"

containsPipefail :: Token -> Bool
containsPipefail Token
root = Maybe Token -> Bool
forall a. Maybe a -> Bool
isNothing (Maybe Token -> Bool) -> Maybe Token -> Bool
forall a b. (a -> b) -> a -> b
$ (Token -> Maybe ()) -> Token -> Maybe Token
forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> Token -> m Token
doAnalysis (Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> (Token -> Bool) -> Token -> Maybe ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not (Bool -> Bool) -> (Token -> Bool) -> Token -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> Bool
isPipefail) Token
root
  where
    isPipefail :: Token -> Bool
isPipefail Token
t =
        case Token
t of
            T_SimpleCommand {}  ->
                Token
t Token -> String -> Bool
`isUnqualifiedCommand` String
"set" Bool -> Bool -> Bool
&&
                    (String
"pipefail" String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` Token -> [String]
oversimplify Token
t Bool -> Bool -> Bool
||
                        String
"o" String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` ((Token, String) -> String) -> [(Token, String)] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (Token, String) -> String
forall a b. (a, b) -> b
snd (Token -> [(Token, String)]
getAllFlags Token
t))
            Token
_ -> Bool
False

containsShopt :: String -> Token -> Bool
containsShopt String
shopt Token
root =
        Maybe Token -> Bool
forall a. Maybe a -> Bool
isNothing (Maybe Token -> Bool) -> Maybe Token -> Bool
forall a b. (a -> b) -> a -> b
$ (Token -> Maybe ()) -> Token -> Maybe Token
forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> Token -> m Token
doAnalysis (Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> (Token -> Bool) -> Token -> Maybe ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not (Bool -> Bool) -> (Token -> Bool) -> Token -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Token -> Bool
isShoptLastPipe) Token
root
    where
        isShoptLastPipe :: Token -> Bool
isShoptLastPipe Token
t =
            case Token
t of
                T_SimpleCommand {}  ->
                    Token
t Token -> String -> Bool
`isUnqualifiedCommand` String
"shopt" Bool -> Bool -> Bool
&&
                        (String
shopt String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` Token -> [String]
oversimplify Token
t)
                Token
_ -> Bool
False

-- Does this script mention 'shopt -s inherit_errexit' anywhere?
containsInheritErrexit :: Token -> Bool
containsInheritErrexit = String -> Token -> Bool
containsShopt String
"inherit_errexit"

-- Does this script mention 'shopt -s lastpipe' anywhere?
-- Also used as a hack.
containsLastpipe :: Token -> Bool
containsLastpipe = String -> Token -> Bool
containsShopt String
"lastpipe"


prop_determineShell0 :: Bool
prop_determineShell0 = String -> Shell
determineShellTest String
"#!/bin/sh" Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
== Shell
Sh
prop_determineShell1 :: Bool
prop_determineShell1 = String -> Shell
determineShellTest String
"#!/usr/bin/env ksh" Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
== Shell
Ksh
prop_determineShell2 :: Bool
prop_determineShell2 = String -> Shell
determineShellTest String
"" Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
== Shell
Bash
prop_determineShell3 :: Bool
prop_determineShell3 = String -> Shell
determineShellTest String
"#!/bin/sh -e" Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
== Shell
Sh
prop_determineShell4 :: Bool
prop_determineShell4 = String -> Shell
determineShellTest String
"#!/bin/ksh\n#shellcheck shell=sh\nfoo" Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
== Shell
Sh
prop_determineShell5 :: Bool
prop_determineShell5 = String -> Shell
determineShellTest String
"#shellcheck shell=sh\nfoo" Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
== Shell
Sh
prop_determineShell6 :: Bool
prop_determineShell6 = String -> Shell
determineShellTest String
"#! /bin/sh" Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
== Shell
Sh
prop_determineShell7 :: Bool
prop_determineShell7 = String -> Shell
determineShellTest String
"#! /bin/ash" Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
== Shell
Dash
prop_determineShell8 :: Bool
prop_determineShell8 = Maybe Shell -> String -> Shell
determineShellTest' (Shell -> Maybe Shell
forall a. a -> Maybe a
Just Shell
Ksh) String
"#!/bin/sh" Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
== Shell
Sh
prop_determineShell9 :: Bool
prop_determineShell9 = String -> Shell
determineShellTest String
"#!/bin/env -S dash -x" Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
== Shell
Dash
prop_determineShell10 :: Bool
prop_determineShell10 = String -> Shell
determineShellTest String
"#!/bin/env --split-string= dash -x" Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
== Shell
Dash
prop_determineShell11 :: Bool
prop_determineShell11 = String -> Shell
determineShellTest String
"#!/bin/busybox sh" Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
== Shell
Dash -- busybox sh is a specific shell, not posix sh
prop_determineShell12 :: Bool
prop_determineShell12 = String -> Shell
determineShellTest String
"#!/bin/busybox ash" Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
== Shell
Dash

determineShellTest :: String -> Shell
determineShellTest = Maybe Shell -> String -> Shell
determineShellTest' Maybe Shell
forall a. Maybe a
Nothing
determineShellTest' :: Maybe Shell -> String -> Shell
determineShellTest' Maybe Shell
fallbackShell = Maybe Shell -> Token -> Shell
determineShell Maybe Shell
fallbackShell (Token -> Shell) -> (String -> Token) -> String -> Shell
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Maybe Token -> Token
forall a. HasCallStack => Maybe a -> a
fromJust (Maybe Token -> Token)
-> (String -> Maybe Token) -> String -> Token
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ParseResult -> Maybe Token
prRoot (ParseResult -> Maybe Token)
-> (String -> ParseResult) -> String -> Maybe Token
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> ParseResult
pScript
determineShell :: Maybe Shell -> Token -> Shell
determineShell Maybe Shell
fallbackShell Token
t = Shell -> Maybe Shell -> Shell
forall a. a -> Maybe a -> a
fromMaybe Shell
Bash (Maybe Shell -> Shell) -> Maybe Shell -> Shell
forall a b. (a -> b) -> a -> b
$
    String -> Maybe Shell
shellForExecutable String
shellString Maybe Shell -> Maybe Shell -> Maybe Shell
forall (m :: * -> *) a. MonadPlus m => m a -> m a -> m a
`mplus` Maybe Shell
fallbackShell
  where
    shellString :: String
shellString = Token -> String
getCandidate Token
t
    getCandidate :: Token -> String
    getCandidate :: Token -> String
getCandidate t :: Token
t@T_Script {} = Token -> String
fromShebang Token
t
    getCandidate (T_Annotation Id
_ [Annotation]
annotations Token
s) =
        String -> [String] -> String
forall p. p -> [p] -> p
headOrDefault (Token -> String
fromShebang Token
s) [String
s | ShellOverride String
s <- [Annotation]
annotations]
    fromShebang :: Token -> String
fromShebang (T_Script Id
_ (T_Literal Id
_ String
s) [Token]
_) = ShowS
executableFromShebang String
s

-- Given a root node, make a map from Id to parent Token.
-- This is used to populate parentMap in Parameters
getParentTree :: Token -> Map.Map Id Token
getParentTree :: Token -> Map Id Token
getParentTree Token
t =
    ([Token], Map Id Token) -> Map Id Token
forall a b. (a, b) -> b
snd (([Token], Map Id Token) -> Map Id Token)
-> ([Token], Map Id Token) -> Map Id Token
forall a b. (a -> b) -> a -> b
$ State ([Token], Map Id Token) Token
-> ([Token], Map Id Token) -> ([Token], Map Id Token)
forall s a. State s a -> s -> s
execState ((Token -> StateT ([Token], Map Id Token) Identity ())
-> (Token -> StateT ([Token], Map Id Token) Identity ())
-> Token
-> State ([Token], Map Id Token) Token
forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> (Token -> m ()) -> Token -> m Token
doStackAnalysis Token -> StateT ([Token], Map Id Token) Identity ()
forall a d (m :: * -> *). MonadState ([a], d) m => a -> m ()
pre Token -> StateT ([Token], Map Id Token) Identity ()
forall (m :: * -> *) a.
MonadState ([a], Map Id a) m =>
Token -> m ()
post Token
t) ([], Map Id Token
forall k a. Map k a
Map.empty)
  where
    pre :: a -> m ()
pre a
t = (([a], d) -> ([a], d)) -> m ()
forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify (([a] -> [a]) -> ([a], d) -> ([a], d)
forall (a :: * -> * -> *) b c d.
Arrow a =>
a b c -> a (b, d) (c, d)
first ((:) a
t))
    post :: Token -> m ()
post Token
t = do
        ([a]
x, Map Id a
map) <- m ([a], Map Id a)
forall s (m :: * -> *). MonadState s m => m s
get
        case [a]
x of
          a
_:[a]
rest -> case [a]
rest of []    -> ([a], Map Id a) -> m ()
forall s (m :: * -> *). MonadState s m => s -> m ()
put ([a]
rest, Map Id a
map)
                                 (a
x:[a]
_) -> ([a], Map Id a) -> m ()
forall s (m :: * -> *). MonadState s m => s -> m ()
put ([a]
rest, Id -> a -> Map Id a -> Map Id a
forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert (Token -> Id
getId Token
t) a
x Map Id a
map)

-- Given a root node, make a map from Id to Token
getTokenMap :: Token -> Map.Map Id Token
getTokenMap :: Token -> Map Id Token
getTokenMap Token
t =
    State (Map Id Token) Token -> Map Id Token -> Map Id Token
forall s a. State s a -> s -> s
execState ((Token -> StateT (Map Id Token) Identity ())
-> Token -> State (Map Id Token) Token
forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> Token -> m Token
doAnalysis Token -> StateT (Map Id Token) Identity ()
forall (m :: * -> *). MonadState (Map Id Token) m => Token -> m ()
f Token
t) Map Id Token
forall k a. Map k a
Map.empty
  where
    f :: Token -> m ()
f Token
t = (Map Id Token -> Map Id Token) -> m ()
forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify (Id -> Token -> Map Id Token -> Map Id Token
forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert (Token -> Id
getId Token
t) Token
t)


-- Is this token in a quoting free context? (i.e. would variable expansion split)
-- True:  Assignments, [[ .. ]], here docs, already in double quotes
-- False: Regular words
isStrictlyQuoteFree :: Shell -> Map Id Token -> Token -> Bool
isStrictlyQuoteFree = Bool -> Shell -> Map Id Token -> Token -> Bool
isQuoteFreeNode Bool
True

-- Like above, but also allow some cases where splitting may be desired.
-- True:  Like above + for loops
-- False: Like above
isQuoteFree :: Shell -> Map Id Token -> Token -> Bool
isQuoteFree = Bool -> Shell -> Map Id Token -> Token -> Bool
isQuoteFreeNode Bool
False


isQuoteFreeNode :: Bool -> Shell -> Map Id Token -> Token -> Bool
isQuoteFreeNode Bool
strict Shell
shell Map Id Token
tree Token
t =
    Token -> Bool
isQuoteFreeElement Token
t Bool -> Bool -> Bool
||
        (Bool -> Maybe Bool -> Bool
forall a. a -> Maybe a -> a
fromMaybe Bool
False (Maybe Bool -> Bool) -> Maybe Bool -> Bool
forall a b. (a -> b) -> a -> b
$ [Maybe Bool] -> Maybe Bool
forall (t :: * -> *) (m :: * -> *) a.
(Foldable t, MonadPlus m) =>
t (m a) -> m a
msum ([Maybe Bool] -> Maybe Bool) -> [Maybe Bool] -> Maybe Bool
forall a b. (a -> b) -> a -> b
$ (Token -> Maybe Bool) -> [Token] -> [Maybe Bool]
forall a b. (a -> b) -> [a] -> [b]
map Token -> Maybe Bool
isQuoteFreeContext ([Token] -> [Maybe Bool]) -> [Token] -> [Maybe Bool]
forall a b. (a -> b) -> a -> b
$ Int -> [Token] -> [Token]
forall a. Int -> [a] -> [a]
drop Int
1 ([Token] -> [Token]) -> [Token] -> [Token]
forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> [Token]
getPath Map Id Token
tree Token
t)
  where
    -- Is this node self-quoting in itself?
    isQuoteFreeElement :: Token -> Bool
isQuoteFreeElement Token
t =
        case Token
t of
            T_Assignment {} -> Token -> Bool
assignmentIsQuoting Token
t
            T_FdRedirect {} -> Bool
True
            Token
_               -> Bool
False

    -- Are any subnodes inherently self-quoting?
    isQuoteFreeContext :: Token -> Maybe Bool
isQuoteFreeContext Token
t =
        case Token
t of
            TC_Nullary Id
_ ConditionType
DoubleBracket Token
_    -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
            TC_Unary Id
_ ConditionType
DoubleBracket String
_ Token
_    -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
            TC_Binary Id
_ ConditionType
DoubleBracket String
_ Token
_ Token
_ -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
            TA_Sequence {}                  -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
            T_Arithmetic {}                 -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
            T_Assignment {}                 -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> Maybe Bool) -> Bool -> Maybe Bool
forall a b. (a -> b) -> a -> b
$ Token -> Bool
assignmentIsQuoting Token
t
            T_Redirecting {}                -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
False
            T_DoubleQuoted Id
_ [Token]
_              -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
            T_DollarDoubleQuoted Id
_ [Token]
_        -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
            T_CaseExpression {}             -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
            T_HereDoc {}                    -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
            T_DollarBraced {}               -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
            -- When non-strict, pragmatically assume it's desirable to split here
            T_ForIn {}                      -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> Bool
not Bool
strict)
            T_SelectIn {}                   -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> Bool
not Bool
strict)
            Token
_                               -> Maybe Bool
forall a. Maybe a
Nothing

    -- Check whether this assigment is self-quoting due to being a recognized
    -- assignment passed to a Declaration Utility. This will soon be required
    -- by POSIX: https://austingroupbugs.net/view.php?id=351
    assignmentIsQuoting :: Token -> Bool
assignmentIsQuoting Token
t = Bool
shellParsesParamsAsAssignments Bool -> Bool -> Bool
|| Bool -> Bool
not (Token -> Bool
isAssignmentParamToCommand Token
t)
    shellParsesParamsAsAssignments :: Bool
shellParsesParamsAsAssignments = Shell
shell Shell -> Shell -> Bool
forall a. Eq a => a -> a -> Bool
/= Shell
Sh

    -- Is this assignment a parameter to a command like export/typeset/etc?
    isAssignmentParamToCommand :: Token -> Bool
isAssignmentParamToCommand (T_Assignment Id
id AssignmentMode
_ String
_ [Token]
_ Token
_) =
        case Id -> Map Id Token -> Maybe Token
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup Id
id Map Id Token
tree of
            Just (T_SimpleCommand Id
_ [Token]
_ (Token
_:[Token]
args)) -> Id
id Id -> [Id] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` ((Token -> Id) -> [Token] -> [Id]
forall a b. (a -> b) -> [a] -> [b]
map Token -> Id
getId [Token]
args)
            Maybe Token
_ -> Bool
False

-- Check if a token is a parameter to a certain command by name:
-- Example: isParamTo (parentMap params) "sed" t
isParamTo :: Map.Map Id Token -> String -> Token -> Bool
isParamTo :: Map Id Token -> String -> Token -> Bool
isParamTo Map Id Token
tree String
cmd =
    Token -> Bool
go
  where
    go :: Token -> Bool
go Token
x = case Id -> Map Id Token -> Maybe Token
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup (Token -> Id
getId Token
x) Map Id Token
tree of
                Maybe Token
Nothing     -> Bool
False
                Just Token
parent -> Token -> Bool
check Token
parent
    check :: Token -> Bool
check Token
t =
        case Token
t of
            T_SingleQuoted Id
_ String
_ -> Token -> Bool
go Token
t
            T_DoubleQuoted Id
_ [Token]
_ -> Token -> Bool
go Token
t
            T_NormalWord Id
_ [Token]
_   -> Token -> Bool
go Token
t
            T_SimpleCommand {} -> Token -> String -> Bool
isCommand Token
t String
cmd
            T_Redirecting {}   -> Token -> String -> Bool
isCommand Token
t String
cmd
            Token
_                  -> Bool
False

-- Get the parent command (T_Redirecting) of a Token, if any.
getClosestCommand :: Map.Map Id Token -> Token -> Maybe Token
getClosestCommand :: Map Id Token -> Token -> Maybe Token
getClosestCommand Map Id Token
tree Token
t =
    (Token -> Maybe Bool) -> [Token] -> Maybe Token
forall a. (a -> Maybe Bool) -> [a] -> Maybe a
findFirst Token -> Maybe Bool
findCommand ([Token] -> Maybe Token) -> [Token] -> Maybe Token
forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> [Token]
getPath Map Id Token
tree Token
t
  where
    findCommand :: Token -> Maybe Bool
findCommand Token
t =
        case Token
t of
            T_Redirecting {} -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
            T_Script {}      -> Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
False
            Token
_                -> Maybe Bool
forall a. Maybe a
Nothing

-- Like above, if koala_man knew Haskell when starting this project.
getClosestCommandM :: Token -> m (Maybe Token)
getClosestCommandM Token
t = do
    Parameters
params <- m Parameters
forall r (m :: * -> *). MonadReader r m => m r
ask
    Maybe Token -> m (Maybe Token)
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe Token -> m (Maybe Token)) -> Maybe Token -> m (Maybe Token)
forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> Maybe Token
getClosestCommand (Parameters -> Map Id Token
parentMap Parameters
params) Token
t

-- Is the token used as a command name (the first word in a T_SimpleCommand)?
usedAsCommandName :: Map Id Token -> Token -> Bool
usedAsCommandName Map Id Token
tree Token
token = Id -> [Token] -> Bool
go (Token -> Id
getId Token
token) ([Token] -> [Token]
forall a. [a] -> [a]
tail ([Token] -> [Token]) -> [Token] -> [Token]
forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> [Token]
getPath Map Id Token
tree Token
token)
  where
    go :: Id -> [Token] -> Bool
go Id
currentId (T_NormalWord Id
id [Token
word]:[Token]
rest)
        | Id
currentId Id -> Id -> Bool
forall a. Eq a => a -> a -> Bool
== Token -> Id
getId Token
word = Id -> [Token] -> Bool
go Id
id [Token]
rest
    go Id
currentId (T_DoubleQuoted Id
id [Token
word]:[Token]
rest)
        | Id
currentId Id -> Id -> Bool
forall a. Eq a => a -> a -> Bool
== Token -> Id
getId Token
word = Id -> [Token] -> Bool
go Id
id [Token]
rest
    go Id
currentId (t :: Token
t@(T_SimpleCommand Id
_ [Token]
_ (Token
word:[Token]
_)):[Token]
_) =
        Token -> Id
getId Token
word Id -> Id -> Bool
forall a. Eq a => a -> a -> Bool
== Id
currentId Bool -> Bool -> Bool
|| Token -> Id
getId (Token -> Token
getCommandTokenOrThis Token
t) Id -> Id -> Bool
forall a. Eq a => a -> a -> Bool
== Id
currentId
    go Id
_ [Token]
_ = Bool
False

-- A list of the element and all its parents up to the root node.
getPath :: Map Id Token -> Token -> [Token]
getPath Map Id Token
tree Token
t = Token
t Token -> [Token] -> [Token]
forall a. a -> [a] -> [a]
:
    case Id -> Map Id Token -> Maybe Token
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup (Token -> Id
getId Token
t) Map Id Token
tree of
        Maybe Token
Nothing     -> []
        Just Token
parent -> Map Id Token -> Token -> [Token]
getPath Map Id Token
tree Token
parent

-- Version of the above taking the map from the current context
-- Todo: give this the name "getPath"
getPathM :: Token -> m [Token]
getPathM Token
t = do
    Parameters
params <- m Parameters
forall r (m :: * -> *). MonadReader r m => m r
ask
    [Token] -> m [Token]
forall (m :: * -> *) a. Monad m => a -> m a
return ([Token] -> m [Token]) -> [Token] -> m [Token]
forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) Token
t

isParentOf :: Map Id Token -> Token -> Token -> Bool
isParentOf Map Id Token
tree Token
parent Token
child =
    Id -> [Id] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
elem (Token -> Id
getId Token
parent) ([Id] -> Bool) -> ([Token] -> [Id]) -> [Token] -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Token -> Id) -> [Token] -> [Id]
forall a b. (a -> b) -> [a] -> [b]
map Token -> Id
getId ([Token] -> Bool) -> [Token] -> Bool
forall a b. (a -> b) -> a -> b
$ Map Id Token -> Token -> [Token]
getPath Map Id Token
tree Token
child

parents :: Parameters -> Token -> [Token]
parents Parameters
params = Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params)

-- Find the first match in a list where the predicate is Just True.
-- Stops if it's Just False and ignores Nothing.
findFirst :: (a -> Maybe Bool) -> [a] -> Maybe a
findFirst :: (a -> Maybe Bool) -> [a] -> Maybe a
findFirst a -> Maybe Bool
p = (a -> Maybe a -> Maybe a) -> Maybe a -> [a] -> Maybe a
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr a -> Maybe a -> Maybe a
go Maybe a
forall a. Maybe a
Nothing
  where
    go :: a -> Maybe a -> Maybe a
go a
x Maybe a
acc =
      case a -> Maybe Bool
p a
x of
        Just Bool
True  -> a -> Maybe a
forall (m :: * -> *) a. Monad m => a -> m a
return a
x
        Just Bool
False -> Maybe a
forall a. Maybe a
Nothing
        Maybe Bool
Nothing    -> Maybe a
acc

-- Check whether a word is entirely output from a single command
tokenIsJustCommandOutput :: Token -> Bool
tokenIsJustCommandOutput Token
t = case Token
t of
    T_NormalWord Id
id [T_DollarExpansion Id
_ [Token]
cmds] -> [Token] -> Bool
check [Token]
cmds
    T_NormalWord Id
id [T_DoubleQuoted Id
_ [T_DollarExpansion Id
_ [Token]
cmds]] -> [Token] -> Bool
check [Token]
cmds
    T_NormalWord Id
id [T_Backticked Id
_ [Token]
cmds] -> [Token] -> Bool
check [Token]
cmds
    T_NormalWord Id
id [T_DoubleQuoted Id
_ [T_Backticked Id
_ [Token]
cmds]] -> [Token] -> Bool
check [Token]
cmds
    Token
_ -> Bool
False
  where
    check :: [Token] -> Bool
check [Token
x] = Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ Token -> Bool
isOnlyRedirection Token
x
    check [Token]
_   = Bool
False

-- TODO: Replace this with a proper Control Flow Graph
getVariableFlow :: Parameters -> Token -> [StackData]
getVariableFlow Parameters
params Token
t =
    [StackData] -> [StackData]
forall a. [a] -> [a]
reverse ([StackData] -> [StackData]) -> [StackData] -> [StackData]
forall a b. (a -> b) -> a -> b
$ State [StackData] Token -> [StackData] -> [StackData]
forall s a. State s a -> s -> s
execState ((Token -> StateT [StackData] Identity ())
-> (Token -> StateT [StackData] Identity ())
-> Token
-> State [StackData] Token
forall (m :: * -> *).
Monad m =>
(Token -> m ()) -> (Token -> m ()) -> Token -> m Token
doStackAnalysis Token -> StateT [StackData] Identity ()
forall (m :: * -> *). MonadState [StackData] m => Token -> m ()
startScope Token -> StateT [StackData] Identity ()
forall (m :: * -> *). MonadState [StackData] m => Token -> m ()
endScope Token
t) []
  where
    startScope :: Token -> m ()
startScope Token
t =
        let scopeType :: Scope
scopeType = Parameters -> Token -> Scope
leadType Parameters
params Token
t
        in do
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Scope
scopeType Scope -> Scope -> Bool
forall a. Eq a => a -> a -> Bool
/= Scope
NoneScope) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ ([StackData] -> [StackData]) -> m ()
forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify (Scope -> StackData
StackScope Scope
scopeTypeStackData -> [StackData] -> [StackData]
forall a. a -> [a] -> [a]
:)
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Token -> Bool
assignFirst Token
t) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ Token -> m ()
forall (m :: * -> *). MonadState [StackData] m => Token -> m ()
setWritten Token
t

    endScope :: Token -> m ()
endScope Token
t =
        let scopeType :: Scope
scopeType = Parameters -> Token -> Scope
leadType Parameters
params Token
t
        in do
            Token -> m ()
forall (m :: * -> *). MonadState [StackData] m => Token -> m ()
setRead Token
t
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (Token -> Bool
assignFirst Token
t) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ Token -> m ()
forall (m :: * -> *). MonadState [StackData] m => Token -> m ()
setWritten Token
t
            Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Scope
scopeType Scope -> Scope -> Bool
forall a. Eq a => a -> a -> Bool
/= Scope
NoneScope) (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$ ([StackData] -> [StackData]) -> m ()
forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify (StackData
StackScopeEndStackData -> [StackData] -> [StackData]
forall a. a -> [a] -> [a]
:)

    assignFirst :: Token -> Bool
assignFirst T_ForIn {}    = Bool
True
    assignFirst T_SelectIn {} = Bool
True
    assignFirst (T_BatsTest {}) = Bool
True
    assignFirst Token
_             = Bool
False

    setRead :: Token -> m ()
setRead Token
t =
        let read :: [(Token, Token, String)]
read    = Map Id Token -> Token -> [(Token, Token, String)]
getReferencedVariables (Parameters -> Map Id Token
parentMap Parameters
params) Token
t
        in ((Token, Token, String) -> m ())
-> [(Token, Token, String)] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (\(Token, Token, String)
v -> ([StackData] -> [StackData]) -> m ()
forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify ((Token, Token, String) -> StackData
Reference (Token, Token, String)
vStackData -> [StackData] -> [StackData]
forall a. a -> [a] -> [a]
:)) [(Token, Token, String)]
read

    setWritten :: Token -> m ()
setWritten Token
t =
        let written :: [(Token, Token, String, DataType)]
written = Token -> [(Token, Token, String, DataType)]
getModifiedVariables Token
t
        in ((Token, Token, String, DataType) -> m ())
-> [(Token, Token, String, DataType)] -> m ()
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
(a -> m b) -> t a -> m ()
mapM_ (\(Token, Token, String, DataType)
v -> ([StackData] -> [StackData]) -> m ()
forall s (m :: * -> *). MonadState s m => (s -> s) -> m ()
modify ((Token, Token, String, DataType) -> StackData
Assignment (Token, Token, String, DataType)
vStackData -> [StackData] -> [StackData]
forall a. a -> [a] -> [a]
:)) [(Token, Token, String, DataType)]
written


leadType :: Parameters -> Token -> Scope
leadType Parameters
params Token
t =
    case Token
t of
        T_DollarExpansion Id
_ [Token]
_  -> String -> Scope
SubshellScope String
"$(..) expansion"
        T_Backticked Id
_ [Token]
_  -> String -> Scope
SubshellScope String
"`..` expansion"
        T_Backgrounded Id
_ Token
_  -> String -> Scope
SubshellScope String
"backgrounding &"
        T_Subshell Id
_ [Token]
_  -> String -> Scope
SubshellScope String
"(..) group"
        T_BatsTest {} -> String -> Scope
SubshellScope String
"@bats test"
        T_CoProcBody Id
_ Token
_  -> String -> Scope
SubshellScope String
"coproc"
        T_Redirecting {}  ->
            if Maybe Bool
causesSubshell Maybe Bool -> Maybe Bool -> Bool
forall a. Eq a => a -> a -> Bool
== Bool -> Maybe Bool
forall a. a -> Maybe a
Just Bool
True
            then String -> Scope
SubshellScope String
"pipeline"
            else Scope
NoneScope
        Token
_ -> Scope
NoneScope
  where
    parentPipeline :: Maybe Token
parentPipeline = do
        Token
parent <- Id -> Map Id Token -> Maybe Token
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup (Token -> Id
getId Token
t) (Parameters -> Map Id Token
parentMap Parameters
params)
        case Token
parent of
            T_Pipeline {} -> Token -> Maybe Token
forall (m :: * -> *) a. Monad m => a -> m a
return Token
parent
            Token
_             -> Maybe Token
forall a. Maybe a
Nothing

    causesSubshell :: Maybe Bool
causesSubshell = do
        (T_Pipeline Id
_ [Token]
_ [Token]
list) <- Maybe Token
parentPipeline
        Bool -> Maybe Bool
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> Maybe Bool) -> Bool -> Maybe Bool
forall a b. (a -> b) -> a -> b
$ case [Token]
list of
            Token
_:Token
_:[Token]
_ -> Bool -> Bool
not (Parameters -> Bool
hasLastpipe Parameters
params) Bool -> Bool -> Bool
|| Token -> Id
getId ([Token] -> Token
forall a. [a] -> a
last [Token]
list) Id -> Id -> Bool
forall a. Eq a => a -> a -> Bool
/= Token -> Id
getId Token
t
            [Token]
_ -> Bool
False

getModifiedVariables :: Token -> [(Token, Token, String, DataType)]
getModifiedVariables Token
t =
    case Token
t of
        T_SimpleCommand Id
_ [Token]
vars [] ->
            [(Token
x, Token
x, String
name, (DataSource -> DataType) -> Token -> DataType
dataTypeFrom DataSource -> DataType
DataString Token
w) | x :: Token
x@(T_Assignment Id
id AssignmentMode
_ String
name [Token]
_ Token
w) <- [Token]
vars]
        T_SimpleCommand {} ->
            Token -> [(Token, Token, String, DataType)]
getModifiedVariableCommand Token
t

        TA_Unary Id
_ String
"++|" v :: Token
v@(TA_Variable Id
_ String
name [Token]
_)  ->
            [(Token
t, Token
v, String
name, DataSource -> DataType
DataString (DataSource -> DataType) -> DataSource -> DataType
forall a b. (a -> b) -> a -> b
$ [Token] -> DataSource
SourceFrom [Token
v])]
        TA_Unary Id
_ String
"|++" v :: Token
v@(TA_Variable Id
_ String
name [Token]
_)  ->
            [(Token
t, Token
v, String
name, DataSource -> DataType
DataString (DataSource -> DataType) -> DataSource -> DataType
forall a b. (a -> b) -> a -> b
$ [Token] -> DataSource
SourceFrom [Token
v])]
        TA_Assignment Id
_ String
op (TA_Variable Id
_ String
name [Token]
_) Token
rhs -> do
            Bool -> [()]
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> [()]) -> Bool -> [()]
forall a b. (a -> b) -> a -> b
$ String
op String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"=", String
"*=", String
"/=", String
"%=", String
"+=", String
"-=", String
"<<=", String
">>=", String
"&=", String
"^=", String
"|="]
            (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall (m :: * -> *) a. Monad m => a -> m a
return (Token
t, Token
t, String
name, DataSource -> DataType
DataString (DataSource -> DataType) -> DataSource -> DataType
forall a b. (a -> b) -> a -> b
$ [Token] -> DataSource
SourceFrom [Token
rhs])

        T_BatsTest {} -> [
            (Token
t, Token
t, String
"lines", DataSource -> DataType
DataArray DataSource
SourceExternal),
            (Token
t, Token
t, String
"status", DataSource -> DataType
DataString DataSource
SourceInteger),
            (Token
t, Token
t, String
"output", DataSource -> DataType
DataString DataSource
SourceExternal)
            ]

        -- Count [[ -v foo ]] as an "assignment".
        -- This is to prevent [ -v foo ] being unassigned or unused.
        TC_Unary Id
id ConditionType
_ String
"-v" Token
token -> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a. Maybe a -> [a]
maybeToList (Maybe (Token, Token, String, DataType)
 -> [(Token, Token, String, DataType)])
-> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ do
            String
str <- Token -> Maybe String
getVariableForTestDashV Token
token
            (Token, Token, String, DataType)
-> Maybe (Token, Token, String, DataType)
forall (m :: * -> *) a. Monad m => a -> m a
return (Token
t, Token
token, String
str, DataSource -> DataType
DataString DataSource
SourceChecked)

        TC_Unary Id
_ ConditionType
_ String
"-n" Token
token -> Token -> Token -> [(Token, Token, String, DataType)]
forall a. a -> Token -> [(a, Token, String, DataType)]
markAsChecked Token
t Token
token
        TC_Unary Id
_ ConditionType
_ String
"-z" Token
token -> Token -> Token -> [(Token, Token, String, DataType)]
forall a. a -> Token -> [(a, Token, String, DataType)]
markAsChecked Token
t Token
token
        TC_Nullary Id
_ ConditionType
_ Token
token -> Token -> Token -> [(Token, Token, String, DataType)]
forall a. a -> Token -> [(a, Token, String, DataType)]
markAsChecked Token
t Token
token

        T_DollarBraced Id
_ Bool
_ Token
l -> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a. Maybe a -> [a]
maybeToList (Maybe (Token, Token, String, DataType)
 -> [(Token, Token, String, DataType)])
-> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ do
            let string :: String
string = [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
l
            let modifier :: String
modifier = ShowS
getBracedModifier String
string
            Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ (String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
modifier) [String
"=", String
":="]
            (Token, Token, String, DataType)
-> Maybe (Token, Token, String, DataType)
forall (m :: * -> *) a. Monad m => a -> m a
return (Token
t, Token
t, ShowS
getBracedReference String
string, DataSource -> DataType
DataString (DataSource -> DataType) -> DataSource -> DataType
forall a b. (a -> b) -> a -> b
$ [Token] -> DataSource
SourceFrom [Token
l])

        T_FdRedirect Id
_ (Char
'{':String
var) Token
op -> -- {foo}>&2 modifies foo
            [(Token
t, Token
t, (Char -> Bool) -> ShowS
forall a. (a -> Bool) -> [a] -> [a]
takeWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
'}') String
var, DataSource -> DataType
DataString DataSource
SourceInteger) | Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ Token -> Bool
isClosingFileOp Token
op]

        T_CoProc Id
_ Maybe String
name Token
_ ->
            [(Token
t, Token
t, String -> Maybe String -> String
forall a. a -> Maybe a -> a
fromMaybe String
"COPROC" Maybe String
name, DataSource -> DataType
DataArray DataSource
SourceInteger)]

        --Points to 'for' rather than variable
        T_ForIn Id
id String
str [] [Token]
_ -> [(Token
t, Token
t, String
str, DataSource -> DataType
DataString DataSource
SourceExternal)]
        T_ForIn Id
id String
str [Token]
words [Token]
_ -> [(Token
t, Token
t, String
str, DataSource -> DataType
DataString (DataSource -> DataType) -> DataSource -> DataType
forall a b. (a -> b) -> a -> b
$ [Token] -> DataSource
SourceFrom [Token]
words)]
        T_SelectIn Id
id String
str [Token]
words [Token]
_ -> [(Token
t, Token
t, String
str, DataSource -> DataType
DataString (DataSource -> DataType) -> DataSource -> DataType
forall a b. (a -> b) -> a -> b
$ [Token] -> DataSource
SourceFrom [Token]
words)]
        Token
_ -> []
  where
    markAsChecked :: a -> Token -> [(a, Token, String, DataType)]
markAsChecked a
place Token
token = (Token -> Maybe (a, Token, String, DataType))
-> [Token] -> [(a, Token, String, DataType)]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (a -> Token -> Maybe (a, Token, String, DataType)
forall a. a -> Token -> Maybe (a, Token, String, DataType)
f a
place) ([Token] -> [(a, Token, String, DataType)])
-> [Token] -> [(a, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ Token -> [Token]
getWordParts Token
token
    f :: a -> Token -> Maybe (a, Token, String, DataType)
f a
place Token
t = case Token
t of
            T_DollarBraced Id
_ Bool
_ Token
l ->
                let str :: String
str = ShowS
getBracedReference ShowS -> ShowS
forall a b. (a -> b) -> a -> b
$ [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
l in do
                    Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String -> Bool
isVariableName String
str
                    (a, Token, String, DataType) -> Maybe (a, Token, String, DataType)
forall (m :: * -> *) a. Monad m => a -> m a
return (a
place, Token
t, String
str, DataSource -> DataType
DataString DataSource
SourceChecked)
            Token
_ -> Maybe (a, Token, String, DataType)
forall a. Maybe a
Nothing

isClosingFileOp :: Token -> Bool
isClosingFileOp Token
op =
    case Token
op of
        T_IoDuplicate Id
_ (T_GREATAND Id
_) String
"-" -> Bool
True
        T_IoDuplicate Id
_ (T_LESSAND  Id
_) String
"-" -> Bool
True
        Token
_                                  -> Bool
False


-- Consider 'export/declare -x' a reference, since it makes the var available
getReferencedVariableCommand :: Token -> [(Token, Token, String)]
getReferencedVariableCommand base :: Token
base@(T_SimpleCommand Id
_ [Token]
_ (T_NormalWord Id
_ (T_Literal Id
_ String
x:[Token]
_):[Token]
rest)) =
    case String
x of
        String
"declare" -> [(Token, Token, String)]
forDeclare
        String
"typeset" -> [(Token, Token, String)]
forDeclare

        String
"export" -> if String
"f" String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
flags
            then []
            else (Token -> [(Token, Token, String)])
-> [Token] -> [(Token, Token, String)]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Token -> [(Token, Token, String)]
getReference [Token]
rest
        String
"local" -> if String
"x" String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
flags
            then (Token -> [(Token, Token, String)])
-> [Token] -> [(Token, Token, String)]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Token -> [(Token, Token, String)]
getReference [Token]
rest
            else []
        String
"trap" ->
            case [Token]
rest of
                Token
head:[Token]
_ -> (String -> (Token, Token, String))
-> [String] -> [(Token, Token, String)]
forall a b. (a -> b) -> [a] -> [b]
map (\String
x -> (Token
base, Token
head, String
x)) ([String] -> [(Token, Token, String)])
-> [String] -> [(Token, Token, String)]
forall a b. (a -> b) -> a -> b
$ Token -> [String]
getVariablesFromLiteralToken Token
head
                [Token]
_ -> []
        String
"alias" -> [(Token
base, Token
token, String
name) | Token
token <- [Token]
rest, String
name <- Token -> [String]
getVariablesFromLiteralToken Token
token]
        String
_ -> []
  where
    forDeclare :: [(Token, Token, String)]
forDeclare =
            if
                (String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
flags) [String
"x", String
"p"] Bool -> Bool -> Bool
&&
                    (Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ (String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
flags) [String
"f", String
"F"])
            then (Token -> [(Token, Token, String)])
-> [Token] -> [(Token, Token, String)]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Token -> [(Token, Token, String)]
getReference [Token]
rest
            else []

    getReference :: Token -> [(Token, Token, String)]
getReference t :: Token
t@(T_Assignment Id
_ AssignmentMode
_ String
name [Token]
_ Token
value) = [(Token
t, Token
t, String
name)]
    getReference t :: Token
t@(T_NormalWord Id
_ [T_Literal Id
_ String
name]) | Bool -> Bool
not (String
"-" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
name) = [(Token
t, Token
t, String
name)]
    getReference Token
_ = []
    flags :: [String]
flags = ((Token, String) -> String) -> [(Token, String)] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (Token, String) -> String
forall a b. (a, b) -> b
snd ([(Token, String)] -> [String]) -> [(Token, String)] -> [String]
forall a b. (a -> b) -> a -> b
$ Token -> [(Token, String)]
getAllFlags Token
base

getReferencedVariableCommand Token
_ = []

-- The function returns a tuple consisting of four items describing an assignment.
-- Given e.g. declare foo=bar
-- (
--   BaseCommand :: Token,     -- The command/structure assigning the variable, i.e. declare foo=bar
--   AssignmentToken :: Token, -- The specific part that assigns this variable, i.e. foo=bar
--   VariableName :: String,   -- The variable name, i.e. foo
--   VariableValue :: DataType -- A description of the value being assigned, i.e. "Literal string with value foo"
-- )
getModifiedVariableCommand :: Token -> [(Token, Token, String, DataType)]
getModifiedVariableCommand base :: Token
base@(T_SimpleCommand Id
id [Token]
cmdPrefix (T_NormalWord Id
_ (T_Literal Id
_ String
x:[Token]
_):[Token]
rest)) =
   ((Token, Token, String, DataType) -> Bool)
-> [(Token, Token, String, DataType)]
-> [(Token, Token, String, DataType)]
forall a. (a -> Bool) -> [a] -> [a]
filter (\(Token
_,Token
_,String
s,DataType
_) -> Bool -> Bool
not (String
"-" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
s)) ([(Token, Token, String, DataType)]
 -> [(Token, Token, String, DataType)])
-> [(Token, Token, String, DataType)]
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$
    case String
x of
        String
"builtin" ->
            Token -> [(Token, Token, String, DataType)]
getModifiedVariableCommand (Token -> [(Token, Token, String, DataType)])
-> Token -> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ Id -> [Token] -> [Token] -> Token
T_SimpleCommand Id
id [Token]
cmdPrefix [Token]
rest
        String
"read" ->
            let fallback :: [(Token, Token, String, DataType)]
fallback = [Maybe (Token, Token, String, DataType)]
-> [(Token, Token, String, DataType)]
forall a. [Maybe a] -> [a]
catMaybes ([Maybe (Token, Token, String, DataType)]
 -> [(Token, Token, String, DataType)])
-> [Maybe (Token, Token, String, DataType)]
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ (Maybe (Token, Token, String, DataType) -> Bool)
-> [Maybe (Token, Token, String, DataType)]
-> [Maybe (Token, Token, String, DataType)]
forall a. (a -> Bool) -> [a] -> [a]
takeWhile Maybe (Token, Token, String, DataType) -> Bool
forall a. Maybe a -> Bool
isJust ([Maybe (Token, Token, String, DataType)]
-> [Maybe (Token, Token, String, DataType)]
forall a. [a] -> [a]
reverse ([Maybe (Token, Token, String, DataType)]
 -> [Maybe (Token, Token, String, DataType)])
-> [Maybe (Token, Token, String, DataType)]
-> [Maybe (Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ (Token -> Maybe (Token, Token, String, DataType))
-> [Token] -> [Maybe (Token, Token, String, DataType)]
forall a b. (a -> b) -> [a] -> [b]
map Token -> Maybe (Token, Token, String, DataType)
getLiteral [Token]
rest)
            in [(Token, Token, String, DataType)]
-> Maybe [(Token, Token, String, DataType)]
-> [(Token, Token, String, DataType)]
forall a. a -> Maybe a -> a
fromMaybe [(Token, Token, String, DataType)]
fallback (Maybe [(Token, Token, String, DataType)]
 -> [(Token, Token, String, DataType)])
-> Maybe [(Token, Token, String, DataType)]
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ do
                [(String, (Token, Token))]
parsed <- String -> [Token] -> Maybe [(String, (Token, Token))]
getGnuOpts String
flagsForRead [Token]
rest
                case String -> [(String, (Token, Token))] -> Maybe (Token, Token)
forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup String
"a" [(String, (Token, Token))]
parsed of
                    Just (Token
_, Token
var) -> ((Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
-> [(Token, Token, String, DataType)]
forall a. a -> [a] -> [a]
:[]) ((Token, Token, String, DataType)
 -> [(Token, Token, String, DataType)])
-> Maybe (Token, Token, String, DataType)
-> Maybe [(Token, Token, String, DataType)]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Token -> Maybe (Token, Token, String, DataType)
getLiteralArray Token
var
                    Maybe (Token, Token)
Nothing -> [(Token, Token, String, DataType)]
-> Maybe [(Token, Token, String, DataType)]
forall (m :: * -> *) a. Monad m => a -> m a
return ([(Token, Token, String, DataType)]
 -> Maybe [(Token, Token, String, DataType)])
-> [(Token, Token, String, DataType)]
-> Maybe [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ [Maybe (Token, Token, String, DataType)]
-> [(Token, Token, String, DataType)]
forall a. [Maybe a] -> [a]
catMaybes ([Maybe (Token, Token, String, DataType)]
 -> [(Token, Token, String, DataType)])
-> [Maybe (Token, Token, String, DataType)]
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$
                        ((String, (Token, Token))
 -> Maybe (Token, Token, String, DataType))
-> [(String, (Token, Token))]
-> [Maybe (Token, Token, String, DataType)]
forall a b. (a -> b) -> [a] -> [b]
map (Token -> Maybe (Token, Token, String, DataType)
getLiteral (Token -> Maybe (Token, Token, String, DataType))
-> ((String, (Token, Token)) -> Token)
-> (String, (Token, Token))
-> Maybe (Token, Token, String, DataType)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Token, Token) -> Token
forall a b. (a, b) -> b
snd ((Token, Token) -> Token)
-> ((String, (Token, Token)) -> (Token, Token))
-> (String, (Token, Token))
-> Token
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String, (Token, Token)) -> (Token, Token)
forall a b. (a, b) -> b
snd) ([(String, (Token, Token))]
 -> [Maybe (Token, Token, String, DataType)])
-> [(String, (Token, Token))]
-> [Maybe (Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ ((String, (Token, Token)) -> Bool)
-> [(String, (Token, Token))] -> [(String, (Token, Token))]
forall a. (a -> Bool) -> [a] -> [a]
filter (String -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null (String -> Bool)
-> ((String, (Token, Token)) -> String)
-> (String, (Token, Token))
-> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (String, (Token, Token)) -> String
forall a b. (a, b) -> a
fst) [(String, (Token, Token))]
parsed

        String
"getopts" ->
            case [Token]
rest of
                Token
opts:Token
var:[Token]
_ -> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a. Maybe a -> [a]
maybeToList (Maybe (Token, Token, String, DataType)
 -> [(Token, Token, String, DataType)])
-> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ Token -> Maybe (Token, Token, String, DataType)
getLiteral Token
var
                [Token]
_          -> []

        String
"let" -> (Token -> [(Token, Token, String, DataType)])
-> [Token] -> [(Token, Token, String, DataType)]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Token -> [(Token, Token, String, DataType)]
letParamToLiteral [Token]
rest

        String
"export" ->
            if String
"f" String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
flags then [] else (Token -> [(Token, Token, String, DataType)])
-> [Token] -> [(Token, Token, String, DataType)]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Token -> [(Token, Token, String, DataType)]
getModifierParamString [Token]
rest

        String
"declare" -> [(Token, Token, String, DataType)]
forDeclare
        String
"typeset" -> [(Token, Token, String, DataType)]
forDeclare

        String
"local" -> (Token -> [(Token, Token, String, DataType)])
-> [Token] -> [(Token, Token, String, DataType)]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Token -> [(Token, Token, String, DataType)]
getModifierParamString [Token]
rest
        String
"readonly" ->
            if (String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
flags) [String
"f", String
"p"]
            then []
            else (Token -> [(Token, Token, String, DataType)])
-> [Token] -> [(Token, Token, String, DataType)]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Token -> [(Token, Token, String, DataType)]
getModifierParamString [Token]
rest
        String
"set" -> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a. Maybe a -> [a]
maybeToList (Maybe (Token, Token, String, DataType)
 -> [(Token, Token, String, DataType)])
-> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ do
            [Token]
params <- [Token] -> Maybe [Token]
getSetParams [Token]
rest
            (Token, Token, String, DataType)
-> Maybe (Token, Token, String, DataType)
forall (m :: * -> *) a. Monad m => a -> m a
return (Token
base, Token
base, String
"@", DataSource -> DataType
DataString (DataSource -> DataType) -> DataSource -> DataType
forall a b. (a -> b) -> a -> b
$ [Token] -> DataSource
SourceFrom [Token]
params)

        String
"printf" -> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a. Maybe a -> [a]
maybeToList (Maybe (Token, Token, String, DataType)
 -> [(Token, Token, String, DataType)])
-> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ [Token] -> Maybe (Token, Token, String, DataType)
getPrintfVariable [Token]
rest
        String
"wait" ->   Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a. Maybe a -> [a]
maybeToList (Maybe (Token, Token, String, DataType)
 -> [(Token, Token, String, DataType)])
-> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ [Token] -> Maybe (Token, Token, String, DataType)
getWaitVariable [Token]
rest

        String
"mapfile" -> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a. Maybe a -> [a]
maybeToList (Maybe (Token, Token, String, DataType)
 -> [(Token, Token, String, DataType)])
-> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ Token -> [Token] -> Maybe (Token, Token, String, DataType)
getMapfileArray Token
base [Token]
rest
        String
"readarray" -> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a. Maybe a -> [a]
maybeToList (Maybe (Token, Token, String, DataType)
 -> [(Token, Token, String, DataType)])
-> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ Token -> [Token] -> Maybe (Token, Token, String, DataType)
getMapfileArray Token
base [Token]
rest

        String
"DEFINE_boolean" -> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a. Maybe a -> [a]
maybeToList (Maybe (Token, Token, String, DataType)
 -> [(Token, Token, String, DataType)])
-> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ [Token] -> Maybe (Token, Token, String, DataType)
getFlagVariable [Token]
rest
        String
"DEFINE_float" -> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a. Maybe a -> [a]
maybeToList (Maybe (Token, Token, String, DataType)
 -> [(Token, Token, String, DataType)])
-> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ [Token] -> Maybe (Token, Token, String, DataType)
getFlagVariable [Token]
rest
        String
"DEFINE_integer" -> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a. Maybe a -> [a]
maybeToList (Maybe (Token, Token, String, DataType)
 -> [(Token, Token, String, DataType)])
-> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ [Token] -> Maybe (Token, Token, String, DataType)
getFlagVariable [Token]
rest
        String
"DEFINE_string" -> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a. Maybe a -> [a]
maybeToList (Maybe (Token, Token, String, DataType)
 -> [(Token, Token, String, DataType)])
-> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ [Token] -> Maybe (Token, Token, String, DataType)
getFlagVariable [Token]
rest

        String
_ -> []
  where
    flags :: [String]
flags = ((Token, String) -> String) -> [(Token, String)] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (Token, String) -> String
forall a b. (a, b) -> b
snd ([(Token, String)] -> [String]) -> [(Token, String)] -> [String]
forall a b. (a -> b) -> a -> b
$ Token -> [(Token, String)]
getAllFlags Token
base
    stripEquals :: ShowS
stripEquals String
s = Int -> ShowS
forall a. Int -> [a] -> [a]
drop Int
1 ShowS -> ShowS
forall a b. (a -> b) -> a -> b
$ (Char -> Bool) -> ShowS
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
'=') String
s
    stripEqualsFrom :: Token -> Token
stripEqualsFrom (T_NormalWord Id
id1 (T_Literal Id
id2 String
s:[Token]
rs)) =
        Id -> [Token] -> Token
T_NormalWord Id
id1 (Id -> String -> Token
T_Literal Id
id2 (ShowS
stripEquals String
s)Token -> [Token] -> [Token]
forall a. a -> [a] -> [a]
:[Token]
rs)
    stripEqualsFrom (T_NormalWord Id
id1 [T_DoubleQuoted Id
id2 [T_Literal Id
id3 String
s]]) =
        Id -> [Token] -> Token
T_NormalWord Id
id1 [Id -> [Token] -> Token
T_DoubleQuoted Id
id2 [Id -> String -> Token
T_Literal Id
id3 (ShowS
stripEquals String
s)]]
    stripEqualsFrom Token
t = Token
t

    forDeclare :: [(Token, Token, String, DataType)]
forDeclare = if (String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
flags) [String
"F", String
"f", String
"p"] then [] else [(Token, Token, String, DataType)]
declaredVars

    declaredVars :: [(Token, Token, String, DataType)]
declaredVars = (Token -> [(Token, Token, String, DataType)])
-> [Token] -> [(Token, Token, String, DataType)]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap ((DataSource -> DataType)
-> Token -> [(Token, Token, String, DataType)]
getModifierParam DataSource -> DataType
defaultType) [Token]
rest
      where
        defaultType :: DataSource -> DataType
defaultType = if (String -> Bool) -> [String] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String]
flags) [String
"a", String
"A"] then DataSource -> DataType
DataArray else DataSource -> DataType
DataString

    getLiteralOfDataType :: Token -> d -> Maybe (Token, Token, String, d)
getLiteralOfDataType Token
t d
d = do
        String
s <- Token -> Maybe String
getLiteralString Token
t
        Bool -> Maybe () -> Maybe ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (String
"-" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
s) (Maybe () -> Maybe ()) -> Maybe () -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String -> Maybe ()
forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
"argument"
        (Token, Token, String, d) -> Maybe (Token, Token, String, d)
forall (m :: * -> *) a. Monad m => a -> m a
return (Token
base, Token
t, String
s, d
d)

    getLiteral :: Token -> Maybe (Token, Token, String, DataType)
getLiteral Token
t = Token -> DataType -> Maybe (Token, Token, String, DataType)
forall d. Token -> d -> Maybe (Token, Token, String, d)
getLiteralOfDataType Token
t (DataSource -> DataType
DataString DataSource
SourceExternal)

    getLiteralArray :: Token -> Maybe (Token, Token, String, DataType)
getLiteralArray Token
t = Token -> DataType -> Maybe (Token, Token, String, DataType)
forall d. Token -> d -> Maybe (Token, Token, String, d)
getLiteralOfDataType Token
t (DataSource -> DataType
DataArray DataSource
SourceExternal)

    getModifierParamString :: Token -> [(Token, Token, String, DataType)]
getModifierParamString = (DataSource -> DataType)
-> Token -> [(Token, Token, String, DataType)]
getModifierParam DataSource -> DataType
DataString

    getModifierParam :: (DataSource -> DataType)
-> Token -> [(Token, Token, String, DataType)]
getModifierParam DataSource -> DataType
def t :: Token
t@(T_Assignment Id
_ AssignmentMode
_ String
name [Token]
_ Token
value) =
        [(Token
base, Token
t, String
name, (DataSource -> DataType) -> Token -> DataType
dataTypeFrom DataSource -> DataType
def Token
value)]
    getModifierParam DataSource -> DataType
def t :: Token
t@T_NormalWord {} = Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a. Maybe a -> [a]
maybeToList (Maybe (Token, Token, String, DataType)
 -> [(Token, Token, String, DataType)])
-> Maybe (Token, Token, String, DataType)
-> [(Token, Token, String, DataType)]
forall a b. (a -> b) -> a -> b
$ do
        String
name <- Token -> Maybe String
getLiteralString Token
t
        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String -> Bool
isVariableName String
name
        (Token, Token, String, DataType)
-> Maybe (Token, Token, String, DataType)
forall (m :: * -> *) a. Monad m => a -> m a
return (Token
base, Token
t, String
name, DataSource -> DataType
def DataSource
SourceDeclaration)
    getModifierParam DataSource -> DataType
_ Token
_ = []

    letParamToLiteral :: Token -> [(Token, Token, String, DataType)]
letParamToLiteral Token
token =
          if String -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
var
            then []
            else [(Token
base, Token
token, String
var, DataSource -> DataType
DataString (DataSource -> DataType) -> DataSource -> DataType
forall a b. (a -> b) -> a -> b
$ [Token] -> DataSource
SourceFrom [Token -> Token
stripEqualsFrom Token
token])]
        where var :: String
var = (Char -> Bool) -> ShowS
forall a. (a -> Bool) -> [a] -> [a]
takeWhile Char -> Bool
isVariableChar ShowS -> ShowS
forall a b. (a -> b) -> a -> b
$ (Char -> Bool) -> ShowS
forall a. (a -> Bool) -> [a] -> [a]
dropWhile (Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
"+-") ShowS -> ShowS
forall a b. (a -> b) -> a -> b
$ [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
token

    getSetParams :: [Token] -> Maybe [Token]
getSetParams (Token
t:Token
_:[Token]
rest) | Token -> Maybe String
getLiteralString Token
t Maybe String -> Maybe String -> Bool
forall a. Eq a => a -> a -> Bool
== String -> Maybe String
forall a. a -> Maybe a
Just String
"-o" = [Token] -> Maybe [Token]
getSetParams [Token]
rest
    getSetParams (Token
t:[Token]
rest) =
        let s :: Maybe String
s = Token -> Maybe String
getLiteralString Token
t in
            case Maybe String
s of
                Just String
"--"    -> [Token] -> Maybe [Token]
forall (m :: * -> *) a. Monad m => a -> m a
return [Token]
rest
                Just (Char
'-':String
_) -> [Token] -> Maybe [Token]
getSetParams [Token]
rest
                Maybe String
_            -> [Token] -> Maybe [Token]
forall (m :: * -> *) a. Monad m => a -> m a
return (Token
tToken -> [Token] -> [Token]
forall a. a -> [a] -> [a]
:[Token] -> Maybe [Token] -> [Token]
forall a. a -> Maybe a -> a
fromMaybe [] ([Token] -> Maybe [Token]
getSetParams [Token]
rest))
    getSetParams [] = Maybe [Token]
forall a. Maybe a
Nothing

    getPrintfVariable :: [Token] -> Maybe (Token, Token, String, DataType)
getPrintfVariable [Token]
list = String
-> DataSource
-> Maybe [(String, (Token, Token))]
-> Maybe (Token, Token, String, DataType)
forall (t :: * -> *) a a.
(Foldable t, Eq a) =>
a
-> DataSource
-> Maybe (t (a, (a, Token)))
-> Maybe (Token, Token, String, DataType)
getFlagAssignedVariable String
"v" ([Token] -> DataSource
SourceFrom [Token]
list) (Maybe [(String, (Token, Token))]
 -> Maybe (Token, Token, String, DataType))
-> Maybe [(String, (Token, Token))]
-> Maybe (Token, Token, String, DataType)
forall a b. (a -> b) -> a -> b
$ String -> [Token] -> Maybe [(String, (Token, Token))]
getBsdOpts String
"v:" [Token]
list
    getWaitVariable :: [Token] -> Maybe (Token, Token, String, DataType)
getWaitVariable   [Token]
list = String
-> DataSource
-> Maybe [(String, (Token, Token))]
-> Maybe (Token, Token, String, DataType)
forall (t :: * -> *) a a.
(Foldable t, Eq a) =>
a
-> DataSource
-> Maybe (t (a, (a, Token)))
-> Maybe (Token, Token, String, DataType)
getFlagAssignedVariable String
"p" DataSource
SourceInteger     (Maybe [(String, (Token, Token))]
 -> Maybe (Token, Token, String, DataType))
-> Maybe [(String, (Token, Token))]
-> Maybe (Token, Token, String, DataType)
forall a b. (a -> b) -> a -> b
$ [(String, (Token, Token))] -> Maybe [(String, (Token, Token))]
forall (m :: * -> *) a. Monad m => a -> m a
return ([(String, (Token, Token))] -> Maybe [(String, (Token, Token))])
-> [(String, (Token, Token))] -> Maybe [(String, (Token, Token))]
forall a b. (a -> b) -> a -> b
$ [Token] -> [(String, (Token, Token))]
getGenericOpts [Token]
list

    getFlagAssignedVariable :: a
-> DataSource
-> Maybe (t (a, (a, Token)))
-> Maybe (Token, Token, String, DataType)
getFlagAssignedVariable a
str DataSource
dataSource Maybe (t (a, (a, Token)))
maybeFlags = do
        t (a, (a, Token))
flags <- Maybe (t (a, (a, Token)))
maybeFlags
        (a
_, (a
flag, Token
value)) <- ((a, (a, Token)) -> Bool)
-> t (a, (a, Token)) -> Maybe (a, (a, Token))
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find ((a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
str) (a -> Bool) -> ((a, (a, Token)) -> a) -> (a, (a, Token)) -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (a, (a, Token)) -> a
forall a b. (a, b) -> a
fst) t (a, (a, Token))
flags
        String
variableName <- (Token -> Maybe String) -> Token -> Maybe String
forall (m :: * -> *).
Monad m =>
(Token -> m String) -> Token -> m String
getLiteralStringExt (Maybe String -> Token -> Maybe String
forall a b. a -> b -> a
const (Maybe String -> Token -> Maybe String)
-> Maybe String -> Token -> Maybe String
forall a b. (a -> b) -> a -> b
$ String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return String
"!") Token
value
        let (String
baseName, String
index) = (Char -> Bool) -> String -> (String, String)
forall a. (a -> Bool) -> [a] -> ([a], [a])
span (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
'[') String
variableName
        (Token, Token, String, DataType)
-> Maybe (Token, Token, String, DataType)
forall (m :: * -> *) a. Monad m => a -> m a
return (Token
base, Token
value, String
baseName, (if String -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
index then DataSource -> DataType
DataString else DataSource -> DataType
DataArray) DataSource
dataSource)

    -- mapfile has some curious syntax allowing flags plus 0..n variable names
    -- where only the first non-option one is used if any.
    getMapfileArray :: Token -> [Token] -> Maybe (Token, Token, String, DataType)
getMapfileArray Token
base [Token]
rest = Maybe (Token, Token, String, DataType)
parseArgs Maybe (Token, Token, String, DataType)
-> Maybe (Token, Token, String, DataType)
-> Maybe (Token, Token, String, DataType)
forall (m :: * -> *) a. MonadPlus m => m a -> m a -> m a
`mplus` Maybe (Token, Token, String, DataType)
fallback
      where
        parseArgs :: Maybe (Token, Token, String, DataType)
        parseArgs :: Maybe (Token, Token, String, DataType)
parseArgs = do
            [(String, (Token, Token))]
args <- String -> [Token] -> Maybe [(String, (Token, Token))]
getGnuOpts String
"d:n:O:s:u:C:c:t" [Token]
rest
            case [Token
y | (String
"",(Token
_,Token
y)) <- [(String, (Token, Token))]
args] of
                [] ->
                    (Token, Token, String, DataType)
-> Maybe (Token, Token, String, DataType)
forall (m :: * -> *) a. Monad m => a -> m a
return (Token
base, Token
base, String
"MAPFILE", DataSource -> DataType
DataArray DataSource
SourceExternal)
                Token
first:[Token]
_ -> do
                    String
name <- Token -> Maybe String
getLiteralString Token
first
                    Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String -> Bool
isVariableName String
name
                    (Token, Token, String, DataType)
-> Maybe (Token, Token, String, DataType)
forall (m :: * -> *) a. Monad m => a -> m a
return (Token
base, Token
first, String
name, DataSource -> DataType
DataArray DataSource
SourceExternal)
        -- If arg parsing fails (due to bad or new flags), get the last variable name
        fallback :: Maybe (Token, Token, String, DataType)
        fallback :: Maybe (Token, Token, String, DataType)
fallback = do
            (String
name, Token
token) <- [(String, Token)] -> Maybe (String, Token)
forall a. [a] -> Maybe a
listToMaybe ([(String, Token)] -> Maybe (String, Token))
-> ([Token] -> [(String, Token)])
-> [Token]
-> Maybe (String, Token)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Token -> Maybe (String, Token)) -> [Token] -> [(String, Token)]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe Token -> Maybe (String, Token)
f ([Token] -> Maybe (String, Token))
-> [Token] -> Maybe (String, Token)
forall a b. (a -> b) -> a -> b
$ [Token] -> [Token]
forall a. [a] -> [a]
reverse [Token]
rest
            (Token, Token, String, DataType)
-> Maybe (Token, Token, String, DataType)
forall (m :: * -> *) a. Monad m => a -> m a
return (Token
base, Token
token, String
name, DataSource -> DataType
DataArray DataSource
SourceExternal)
        f :: Token -> Maybe (String, Token)
f Token
arg = do
            String
name <- Token -> Maybe String
getLiteralString Token
arg
            Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String -> Bool
isVariableName String
name
            (String, Token) -> Maybe (String, Token)
forall (m :: * -> *) a. Monad m => a -> m a
return (String
name, Token
arg)

    -- get the FLAGS_ variable created by a shflags DEFINE_ call
    getFlagVariable :: [Token] -> Maybe (Token, Token, String, DataType)
getFlagVariable (Token
n:Token
v:[Token]
_) = do
        String
name <- Token -> Maybe String
getLiteralString Token
n
        (Token, Token, String, DataType)
-> Maybe (Token, Token, String, DataType)
forall (m :: * -> *) a. Monad m => a -> m a
return (Token
base, Token
n, String
"FLAGS_" String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
name, DataSource -> DataType
DataString (DataSource -> DataType) -> DataSource -> DataType
forall a b. (a -> b) -> a -> b
$ DataSource
SourceExternal)
    getFlagVariable [Token]
_ = Maybe (Token, Token, String, DataType)
forall a. Maybe a
Nothing

getModifiedVariableCommand Token
_ = []

getIndexReferences :: String -> [String]
getIndexReferences String
s = [String] -> Maybe [String] -> [String]
forall a. a -> Maybe a -> a
fromMaybe [] (Maybe [String] -> [String]) -> Maybe [String] -> [String]
forall a b. (a -> b) -> a -> b
$ do
    [String]
match <- Regex -> String -> Maybe [String]
matchRegex Regex
re String
s
    String
index <- [String]
match [String] -> Int -> Maybe String
forall a. [a] -> Int -> Maybe a
!!! Int
0
    [String] -> Maybe [String]
forall (m :: * -> *) a. Monad m => a -> m a
return ([String] -> Maybe [String]) -> [String] -> Maybe [String]
forall a b. (a -> b) -> a -> b
$ Regex -> String -> [String]
matchAllStrings Regex
variableNameRegex String
index
  where
    re :: Regex
re = String -> Regex
mkRegex String
"(\\[.*\\])"

-- Given a NormalWord like foo or foo[$bar], get foo.
-- Primarily used to get references for [[ -v foo[bar] ]]
getVariableForTestDashV :: Token -> Maybe String
getVariableForTestDashV :: Token -> Maybe String
getVariableForTestDashV Token
t = do
    String
str <- (Char -> Bool) -> ShowS
forall a. (a -> Bool) -> [a] -> [a]
takeWhile (Char
'[' Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/=) ShowS -> Maybe String -> Maybe String
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (Token -> Maybe String) -> Token -> Maybe String
forall (m :: * -> *).
Monad m =>
(Token -> m String) -> Token -> m String
getLiteralStringExt Token -> Maybe String
forall (m :: * -> *). Monad m => Token -> m String
toStr Token
t
    Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ String -> Bool
isVariableName String
str
    String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return String
str
  where
    -- foo[bar] gets parsed with [bar] as a glob, so undo that
    toStr :: Token -> m String
toStr (T_Glob Id
_ String
s) = String -> m String
forall (m :: * -> *) a. Monad m => a -> m a
return String
s
    -- 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])
    toStr Token
_ = String -> m String
forall (m :: * -> *) a. Monad m => a -> m a
return String
"\0"

prop_getOffsetReferences1 :: Bool
prop_getOffsetReferences1 = String -> [String]
getOffsetReferences String
":bar" [String] -> [String] -> Bool
forall a. Eq a => a -> a -> Bool
== [String
"bar"]
prop_getOffsetReferences2 :: Bool
prop_getOffsetReferences2 = String -> [String]
getOffsetReferences String
":bar:baz" [String] -> [String] -> Bool
forall a. Eq a => a -> a -> Bool
== [String
"bar", String
"baz"]
prop_getOffsetReferences3 :: Bool
prop_getOffsetReferences3 = String -> [String]
getOffsetReferences String
"[foo]:bar" [String] -> [String] -> Bool
forall a. Eq a => a -> a -> Bool
== [String
"bar"]
prop_getOffsetReferences4 :: Bool
prop_getOffsetReferences4 = String -> [String]
getOffsetReferences String
"[foo]:bar:baz" [String] -> [String] -> Bool
forall a. Eq a => a -> a -> Bool
== [String
"bar", String
"baz"]
getOffsetReferences :: String -> [String]
getOffsetReferences String
mods = [String] -> Maybe [String] -> [String]
forall a. a -> Maybe a -> a
fromMaybe [] (Maybe [String] -> [String]) -> Maybe [String] -> [String]
forall a b. (a -> b) -> a -> b
$ do
-- if mods start with [, then drop until ]
    [String]
match <- Regex -> String -> Maybe [String]
matchRegex Regex
re String
mods
    String
offsets <- [String]
match [String] -> Int -> Maybe String
forall a. [a] -> Int -> Maybe a
!!! Int
1
    [String] -> Maybe [String]
forall (m :: * -> *) a. Monad m => a -> m a
return ([String] -> Maybe [String]) -> [String] -> Maybe [String]
forall a b. (a -> b) -> a -> b
$ Regex -> String -> [String]
matchAllStrings Regex
variableNameRegex String
offsets
  where
    re :: Regex
re = String -> Regex
mkRegex String
"^(\\[.+\\])? *:([^-=?+].*)"

getReferencedVariables :: Map Id Token -> Token -> [(Token, Token, String)]
getReferencedVariables Map Id Token
parents Token
t =
    case Token
t of
        T_DollarBraced Id
id Bool
_ Token
l -> let str :: String
str = [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
l in
            (Token
t, Token
t, ShowS
getBracedReference String
str) (Token, Token, String)
-> [(Token, Token, String)] -> [(Token, Token, String)]
forall a. a -> [a] -> [a]
:
                (String -> (Token, Token, String))
-> [String] -> [(Token, Token, String)]
forall a b. (a -> b) -> [a] -> [b]
map (\String
x -> (Token
l, Token
l, String
x)) (
                    String -> [String]
getIndexReferences String
str
                    [String] -> [String] -> [String]
forall a. [a] -> [a] -> [a]
++ String -> [String]
getOffsetReferences (ShowS
getBracedModifier String
str))
        TA_Variable Id
id String
name [Token]
_ ->
            if Token -> Bool
isArithmeticAssignment Token
t
            then []
            else [(Token
t, Token
t, String
name)]
        T_Assignment Id
id AssignmentMode
mode String
str [Token]
_ Token
word ->
            [(Token
t, Token
t, String
str) | AssignmentMode
mode AssignmentMode -> AssignmentMode -> Bool
forall a. Eq a => a -> a -> Bool
== AssignmentMode
Append] [(Token, Token, String)]
-> [(Token, Token, String)] -> [(Token, Token, String)]
forall a. [a] -> [a] -> [a]
++ String -> Token -> Token -> [(Token, Token, String)]
forall b. String -> b -> Token -> [(b, b, String)]
specialReferences String
str Token
t Token
word

        TC_Unary Id
id ConditionType
_ String
"-v" Token
token -> Token -> Token -> [(Token, Token, String)]
forall a. a -> Token -> [(a, Token, String)]
getIfReference Token
t Token
token
        TC_Unary Id
id ConditionType
_ String
"-R" Token
token -> Token -> Token -> [(Token, Token, String)]
forall a. a -> Token -> [(a, Token, String)]
getIfReference Token
t Token
token
        TC_Binary Id
id ConditionType
DoubleBracket String
op Token
lhs Token
rhs ->
            if String -> Bool
isDereferencingBinaryOp String
op
            then (Token -> [(Token, Token, String)])
-> [Token] -> [(Token, Token, String)]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (Token -> Token -> [(Token, Token, String)]
forall a. a -> Token -> [(a, Token, String)]
getIfReference Token
t) [Token
lhs, Token
rhs]
            else []

        T_BatsTest {} -> [ -- pretend @test references vars to avoid warnings
            (Token
t, Token
t, String
"lines"),
            (Token
t, Token
t, String
"status"),
            (Token
t, Token
t, String
"output")
            ]

        T_FdRedirect Id
_ (Char
'{':String
var) Token
op -> -- {foo}>&- references and closes foo
            [(Token
t, Token
t, (Char -> Bool) -> ShowS
forall a. (a -> Bool) -> [a] -> [a]
takeWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/= Char
'}') String
var) | Token -> Bool
isClosingFileOp Token
op]
        Token
x -> Token -> [(Token, Token, String)]
getReferencedVariableCommand Token
x
  where
    -- Try to reduce false positives for unused vars only referenced from evaluated vars
    specialReferences :: String -> b -> Token -> [(b, b, String)]
specialReferences String
name b
base Token
word =
        if String
name String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [
            String
"PS1", String
"PS2", String
"PS3", String
"PS4",
            String
"PROMPT_COMMAND"
          ]
        then
            (String -> (b, b, String)) -> [String] -> [(b, b, String)]
forall a b. (a -> b) -> [a] -> [b]
map (\String
x -> (b
base, b
base, String
x)) ([String] -> [(b, b, String)]) -> [String] -> [(b, b, String)]
forall a b. (a -> b) -> a -> b
$
                Token -> [String]
getVariablesFromLiteralToken Token
word
        else []

    literalizer :: Token -> [String]
literalizer Token
t = case Token
t of
        T_Glob Id
_ String
s -> String -> [String]
forall (m :: * -> *) a. Monad m => a -> m a
return String
s    -- Also when parsed as globs
        Token
_          -> []

    getIfReference :: a -> Token -> [(a, Token, String)]
getIfReference a
context Token
token = Maybe (a, Token, String) -> [(a, Token, String)]
forall a. Maybe a -> [a]
maybeToList (Maybe (a, Token, String) -> [(a, Token, String)])
-> Maybe (a, Token, String) -> [(a, Token, String)]
forall a b. (a -> b) -> a -> b
$ do
            String
str <- Token -> Maybe String
getVariableForTestDashV Token
token
            (a, Token, String) -> Maybe (a, Token, String)
forall (m :: * -> *) a. Monad m => a -> m a
return (a
context, Token
token, ShowS
getBracedReference String
str)

    isArithmeticAssignment :: Token -> Bool
isArithmeticAssignment Token
t = case Map Id Token -> Token -> [Token]
getPath Map Id Token
parents Token
t of
        Token
this: TA_Assignment Id
_ String
"=" Token
lhs Token
_ :[Token]
_ -> Token
lhs Token -> Token -> Bool
forall a. Eq a => a -> a -> Bool
== Token
t
        [Token]
_                                  -> Bool
False

isDereferencingBinaryOp :: String -> Bool
isDereferencingBinaryOp = (String -> [String] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [String
"-eq", String
"-ne", String
"-lt", String
"-le", String
"-gt", String
"-ge"])

dataTypeFrom :: (DataSource -> DataType) -> Token -> DataType
dataTypeFrom DataSource -> DataType
defaultType Token
v = (case Token
v of T_Array {} -> DataSource -> DataType
DataArray; Token
_ -> DataSource -> DataType
defaultType) (DataSource -> DataType) -> DataSource -> DataType
forall a b. (a -> b) -> a -> b
$ [Token] -> DataSource
SourceFrom [Token
v]


--- Command specific checks

-- Compare a command to a string: t `isCommand` "sed" (also matches /usr/bin/sed)
isCommand :: Token -> String -> Bool
isCommand Token
token String
str = Token -> (String -> Bool) -> Bool
isCommandMatch Token
token (\String
cmd -> String
cmd  String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
str Bool -> Bool -> Bool
|| (Char
'/' Char -> ShowS
forall a. a -> [a] -> [a]
: String
str) String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isSuffixOf` String
cmd)

-- Compare a command to a literal. Like above, but checks full path.
isUnqualifiedCommand :: Token -> String -> Bool
isUnqualifiedCommand Token
token String
str = Token -> (String -> Bool) -> Bool
isCommandMatch Token
token (String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
str)

isCommandMatch :: Token -> (String -> Bool) -> Bool
isCommandMatch Token
token String -> Bool
matcher = Bool -> (String -> Bool) -> Maybe String -> Bool
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
False
    String -> Bool
matcher (Token -> Maybe String
getCommandName Token
token)

-- Does this regex look like it was intended as a glob?
-- True:  *foo*
-- False: .*foo.*
isConfusedGlobRegex :: String -> Bool
isConfusedGlobRegex :: String -> Bool
isConfusedGlobRegex (Char
'*':String
_) = Bool
True
isConfusedGlobRegex [Char
x,Char
'*'] | Char
x Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` String
"\\." = Bool
True
isConfusedGlobRegex String
_       = Bool
False

isVariableStartChar :: Char -> Bool
isVariableStartChar Char
x = Char
x Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'_' Bool -> Bool -> Bool
|| Char -> Bool
isAsciiLower Char
x Bool -> Bool -> Bool
|| Char -> Bool
isAsciiUpper Char
x
isVariableChar :: Char -> Bool
isVariableChar Char
x = Char -> Bool
isVariableStartChar Char
x Bool -> Bool -> Bool
|| Char -> Bool
isDigit Char
x
isSpecialVariableChar :: Char -> Bool
isSpecialVariableChar = (Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
"*@#?-$!")
variableNameRegex :: Regex
variableNameRegex = String -> Regex
mkRegex String
"[_a-zA-Z][_a-zA-Z0-9]*"

prop_isVariableName1 :: Bool
prop_isVariableName1 = String -> Bool
isVariableName String
"_fo123"
prop_isVariableName2 :: Bool
prop_isVariableName2 = Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ String -> Bool
isVariableName String
"4"
prop_isVariableName3 :: Bool
prop_isVariableName3 = Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ String -> Bool
isVariableName String
"test: "
isVariableName :: String -> Bool
isVariableName (Char
x:String
r) = Char -> Bool
isVariableStartChar Char
x Bool -> Bool -> Bool
&& (Char -> Bool) -> String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isVariableChar String
r
isVariableName String
_     = Bool
False

getVariablesFromLiteralToken :: Token -> [String]
getVariablesFromLiteralToken Token
token =
    String -> [String]
getVariablesFromLiteral (String -> Token -> String
getLiteralStringDef String
" " Token
token)

-- Try to get referenced variables from a literal string like "$foo"
-- Ignores tons of cases like arithmetic evaluation and array indices.
prop_getVariablesFromLiteral1 :: Bool
prop_getVariablesFromLiteral1 =
    String -> [String]
getVariablesFromLiteral String
"$foo${bar//a/b}$BAZ" [String] -> [String] -> Bool
forall a. Eq a => a -> a -> Bool
== [String
"foo", String
"bar", String
"BAZ"]
getVariablesFromLiteral :: String -> [String]
getVariablesFromLiteral String
string =
    ([String] -> String) -> [[String]] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map [String] -> String
forall a. [a] -> a
head ([[String]] -> [String]) -> [[String]] -> [String]
forall a b. (a -> b) -> a -> b
$ Regex -> String -> [[String]]
matchAllSubgroups Regex
variableRegex String
string
  where
    variableRegex :: Regex
variableRegex = String -> Regex
mkRegex String
"\\$\\{?([A-Za-z0-9_]+)"

-- Get the variable name from an expansion like ${var:-foo}
prop_getBracedReference1 :: Bool
prop_getBracedReference1 = ShowS
getBracedReference String
"foo" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"foo"
prop_getBracedReference2 :: Bool
prop_getBracedReference2 = ShowS
getBracedReference String
"#foo" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"foo"
prop_getBracedReference3 :: Bool
prop_getBracedReference3 = ShowS
getBracedReference String
"#" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"#"
prop_getBracedReference4 :: Bool
prop_getBracedReference4 = ShowS
getBracedReference String
"##" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"#"
prop_getBracedReference5 :: Bool
prop_getBracedReference5 = ShowS
getBracedReference String
"#!" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"!"
prop_getBracedReference6 :: Bool
prop_getBracedReference6 = ShowS
getBracedReference String
"!#" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"#"
prop_getBracedReference7 :: Bool
prop_getBracedReference7 = ShowS
getBracedReference String
"!foo#?" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"foo"
prop_getBracedReference8 :: Bool
prop_getBracedReference8 = ShowS
getBracedReference String
"foo-bar" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"foo"
prop_getBracedReference9 :: Bool
prop_getBracedReference9 = ShowS
getBracedReference String
"foo:-bar" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"foo"
prop_getBracedReference10 :: Bool
prop_getBracedReference10= ShowS
getBracedReference String
"foo: -1" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"foo"
prop_getBracedReference11 :: Bool
prop_getBracedReference11= ShowS
getBracedReference String
"!os*" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
""
prop_getBracedReference11b :: Bool
prop_getBracedReference11b= ShowS
getBracedReference String
"!os@" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
""
prop_getBracedReference12 :: Bool
prop_getBracedReference12= ShowS
getBracedReference String
"!os?bar**" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
""
prop_getBracedReference13 :: Bool
prop_getBracedReference13= ShowS
getBracedReference String
"foo[bar]" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"foo"
getBracedReference :: ShowS
getBracedReference String
s = String -> Maybe String -> String
forall a. a -> Maybe a -> a
fromMaybe String
s (Maybe String -> String) -> Maybe String -> String
forall a b. (a -> b) -> a -> b
$
    String -> Maybe String
nameExpansion String
s Maybe String -> Maybe String -> Maybe String
forall (m :: * -> *) a. MonadPlus m => m a -> m a -> m a
`mplus` String -> Maybe String
forall (m :: * -> *).
(Monad m, Alternative m) =>
String -> m String
takeName String
noPrefix Maybe String -> Maybe String -> Maybe String
forall (m :: * -> *) a. MonadPlus m => m a -> m a -> m a
`mplus` String -> Maybe String
forall (m :: * -> *). MonadFail m => String -> m String
getSpecial String
noPrefix Maybe String -> Maybe String -> Maybe String
forall (m :: * -> *) a. MonadPlus m => m a -> m a -> m a
`mplus` String -> Maybe String
forall (m :: * -> *). MonadFail m => String -> m String
getSpecial String
s
  where
    noPrefix :: String
noPrefix = ShowS
dropPrefix String
s
    dropPrefix :: ShowS
dropPrefix (Char
c:String
rest) | Char
c Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
"!#" = String
rest
    dropPrefix String
cs = String
cs
    takeName :: String -> m String
takeName String
s = do
        let name :: String
name = (Char -> Bool) -> ShowS
forall a. (a -> Bool) -> [a] -> [a]
takeWhile Char -> Bool
isVariableChar String
s
        Bool -> m ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> m ()) -> (Bool -> Bool) -> Bool -> m ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bool -> Bool
not (Bool -> m ()) -> Bool -> m ()
forall a b. (a -> b) -> a -> b
$ String -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
name
        String -> m String
forall (m :: * -> *) a. Monad m => a -> m a
return String
name
    getSpecial :: String -> m String
getSpecial (Char
c:String
_) | Char -> Bool
isSpecialVariableChar Char
c = String -> m String
forall (m :: * -> *) a. Monad m => a -> m a
return [Char
c]
    getSpecial String
_ = String -> m String
forall (m :: * -> *) a. MonadFail m => String -> m a
fail String
"empty or not special"

    nameExpansion :: String -> Maybe String
nameExpansion (Char
'!':Char
next:String
rest) = do -- e.g. ${!foo*bar*}
        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ Char -> Bool
isVariableChar Char
next -- e.g. ${!@}
        Char
first <- (Char -> Bool) -> String -> Maybe Char
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find (Bool -> Bool
not (Bool -> Bool) -> (Char -> Bool) -> Char -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Char -> Bool
isVariableChar) String
rest
        Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ Char
first Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
"*?@"
        String -> Maybe String
forall (m :: * -> *) a. Monad m => a -> m a
return String
""
    nameExpansion String
_ = Maybe String
forall a. Maybe a
Nothing

prop_getBracedModifier1 :: Bool
prop_getBracedModifier1 = ShowS
getBracedModifier String
"foo:bar:baz" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
":bar:baz"
prop_getBracedModifier2 :: Bool
prop_getBracedModifier2 = ShowS
getBracedModifier String
"!var:-foo" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
":-foo"
prop_getBracedModifier3 :: Bool
prop_getBracedModifier3 = ShowS
getBracedModifier String
"foo[bar]" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"[bar]"
prop_getBracedModifier4 :: Bool
prop_getBracedModifier4 = ShowS
getBracedModifier String
"foo[@]@Q" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"[@]@Q"
prop_getBracedModifier5 :: Bool
prop_getBracedModifier5 = ShowS
getBracedModifier String
"@@Q" String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
"@Q"
getBracedModifier :: ShowS
getBracedModifier String
s = String -> [String] -> String
forall p. p -> [p] -> p
headOrDefault String
"" ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ do
    let var :: String
var = ShowS
getBracedReference String
s
    String
a <- String -> [String]
dropModifier String
s
    String -> String -> [String]
forall a. Eq a => [a] -> [a] -> [[a]]
dropPrefix String
var String
a
  where
    dropPrefix :: [a] -> [a] -> [[a]]
dropPrefix [] [a]
t        = [a] -> [[a]]
forall (m :: * -> *) a. Monad m => a -> m a
return [a]
t
    dropPrefix (a
a:[a]
b) (a
c:[a]
d) | a
a a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
c = [a] -> [a] -> [[a]]
dropPrefix [a]
b [a]
d
    dropPrefix [a]
_ [a]
_         = []

    dropModifier :: String -> [String]
dropModifier (Char
c:String
rest) | Char
c Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` String
"#!" = [String
rest, Char
cChar -> ShowS
forall a. a -> [a] -> [a]
:String
rest]
    dropModifier String
x        = [String
x]

-- Useful generic functions.

-- Get element 0 or a default. Like `head` but safe.
headOrDefault :: p -> [p] -> p
headOrDefault p
_ (p
a:[p]
_) = p
a
headOrDefault p
def [p]
_   = p
def

-- Get the last element or a default. Like `last` but safe.
lastOrDefault :: p -> [p] -> p
lastOrDefault p
def [] = p
def
lastOrDefault p
_ [p]
list = [p] -> p
forall a. [a] -> a
last [p]
list

--- Get element n of a list, or Nothing. Like `!!` but safe.
!!! :: [a] -> Int -> Maybe a
(!!!) [a]
list Int
i =
    case Int -> [a] -> [a]
forall a. Int -> [a] -> [a]
drop Int
i [a]
list of
        []    -> Maybe a
forall a. Maybe a
Nothing
        (a
r:[a]
_) -> a -> Maybe a
forall a. a -> Maybe a
Just a
r

-- Run a command if the shell is in the given list
whenShell :: t Shell -> m () -> m ()
whenShell t Shell
l m ()
c = do
    Parameters
params <- m Parameters
forall r (m :: * -> *). MonadReader r m => m r
ask
    Bool -> m () -> m ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Parameters -> Shell
shellType Parameters
params Shell -> t Shell -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` t Shell
l ) m ()
c


filterByAnnotation :: AnalysisSpec -> Parameters -> [TokenComment] -> [TokenComment]
filterByAnnotation AnalysisSpec
asSpec Parameters
params =
    (TokenComment -> Bool) -> [TokenComment] -> [TokenComment]
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool) -> (TokenComment -> Bool) -> TokenComment -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. TokenComment -> Bool
shouldIgnore)
  where
    token :: Token
token = AnalysisSpec -> Token
asScript AnalysisSpec
asSpec
    shouldIgnore :: TokenComment -> Bool
shouldIgnore TokenComment
note =
        (Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Code -> Token -> Bool
shouldIgnoreFor (TokenComment -> Code
getCode TokenComment
note)) ([Token] -> Bool) -> [Token] -> Bool
forall a b. (a -> b) -> a -> b
$
            Map Id Token -> Token -> [Token]
getPath Map Id Token
parents (Id -> Token
T_Bang (Id -> Token) -> Id -> Token
forall a b. (a -> b) -> a -> b
$ TokenComment -> Id
tcId TokenComment
note)
    shouldIgnoreFor :: Code -> Token -> Bool
shouldIgnoreFor Code
_ T_Include {} = Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ AnalysisSpec -> Bool
asCheckSourced AnalysisSpec
asSpec
    shouldIgnoreFor Code
code Token
t = Code -> Token -> Bool
isAnnotationIgnoringCode Code
code Token
t
    parents :: Map Id Token
parents = Parameters -> Map Id Token
parentMap Parameters
params
    getCode :: TokenComment -> Code
getCode = Comment -> Code
cCode (Comment -> Code)
-> (TokenComment -> Comment) -> TokenComment -> Code
forall b c a. (b -> c) -> (a -> b) -> a -> c
. TokenComment -> Comment
tcComment

shouldIgnoreCode :: Parameters -> Code -> Token -> Bool
shouldIgnoreCode Parameters
params Code
code Token
t =
    (Token -> Bool) -> [Token] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Code -> Token -> Bool
isAnnotationIgnoringCode Code
code) ([Token] -> Bool) -> [Token] -> Bool
forall a b. (a -> b) -> a -> b
$
        Map Id Token -> Token -> [Token]
getPath (Parameters -> Map Id Token
parentMap Parameters
params) Token
t

-- Is this a ${#anything}, to get string length or array count?
isCountingReference :: Token -> Bool
isCountingReference (T_DollarBraced Id
id Bool
_ Token
token) =
    case [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
token of
        Char
'#':String
_ -> Bool
True
        String
_     -> Bool
False
isCountingReference Token
_ = Bool
False

-- FIXME: doesn't handle ${a:+$var} vs ${a:+"$var"}
isQuotedAlternativeReference :: Token -> Bool
isQuotedAlternativeReference Token
t =
    case Token
t of
        T_DollarBraced Id
_ Bool
_ Token
l ->
            ShowS
getBracedModifier ([String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
l) String -> Regex -> Bool
`matches` Regex
re
        Token
_ -> Bool
False
  where
    re :: Regex
re = String -> Regex
mkRegex String
"(^|\\]):?\\+"

supportsArrays :: Shell -> Bool
supportsArrays Shell
Bash = Bool
True
supportsArrays Shell
Ksh = Bool
True
supportsArrays Shell
_ = Bool
False

-- Returns true if the shell is Bash or Ksh (sorry for the name, Ksh)
isBashLike :: Parameters -> Bool
isBashLike :: Parameters -> Bool
isBashLike Parameters
params =
    case Parameters -> Shell
shellType Parameters
params of
        Shell
Bash -> Bool
True
        Shell
Ksh -> Bool
True
        Shell
Dash -> Bool
False
        Shell
Sh -> Bool
False

-- Returns whether a token is a parameter expansion without any modifiers.
-- True for $var ${var} $1 $#
-- False for ${#var} ${var[x]} ${var:-0}
isUnmodifiedParameterExpansion :: Token -> Bool
isUnmodifiedParameterExpansion Token
t =
    case Token
t of
        T_DollarBraced Id
_ Bool
False Token
_ -> Bool
True
        T_DollarBraced Id
_ Bool
_ Token
list ->
            let str :: String
str = [String] -> String
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ Token -> [String]
oversimplify Token
list
            in ShowS
getBracedReference String
str String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
str
        Token
_ -> Bool
False

isTrueAssignmentSource :: DataType -> Bool
isTrueAssignmentSource DataType
c =
    case DataType
c of
        DataString DataSource
SourceChecked -> Bool
False
        DataString DataSource
SourceDeclaration -> Bool
False
        DataArray DataSource
SourceChecked -> Bool
False
        DataArray DataSource
SourceDeclaration -> Bool
False
        DataType
_ -> Bool
True

modifiesVariable :: Parameters -> Token -> String -> Bool
modifiesVariable Parameters
params Token
token String
name =
    [Bool] -> Bool
forall (t :: * -> *). Foldable t => t Bool -> Bool
or ([Bool] -> Bool) -> [Bool] -> Bool
forall a b. (a -> b) -> a -> b
$ (StackData -> Bool) -> [StackData] -> [Bool]
forall a b. (a -> b) -> [a] -> [b]
map StackData -> Bool
check [StackData]
flow
  where
    flow :: [StackData]
flow = Parameters -> Token -> [StackData]
getVariableFlow Parameters
params Token
token
    check :: StackData -> Bool
check StackData
t =
        case StackData
t of
            Assignment (Token
_, Token
_, String
n, DataType
source) -> DataType -> Bool
isTrueAssignmentSource DataType
source Bool -> Bool -> Bool
&& String
n String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
name
            StackData
_ -> Bool
False


return []
runTests :: IO Bool
runTests =  $( [| $(forAllProperties) (quickCheckWithResult (stdArgs { maxSuccess = 1 }) ) |])