module Hadolint.Rule.Shellcheck (rule) where

import qualified Data.Set as Set
import qualified Data.Text as Text
import Hadolint.Rule
import qualified Hadolint.Shell
import Hadolint.Pragma (parseShell)
import qualified Hadolint.Shell as Shell
import Language.Docker.Syntax
import qualified ShellCheck.Interface


data Acc
  = Acc
      { Acc -> ShellOpts
opts :: Shell.ShellOpts,
        Acc -> ShellOpts
defaultOpts :: Shell.ShellOpts
      }
  | Empty


rule :: Rule Shell.ParsedShell
rule :: Rule ParsedShell
rule = Rule ParsedShell
scrule forall a. Semigroup a => a -> a -> a
<> forall args. Rule args -> Rule args
onbuild Rule ParsedShell
scrule
{-# INLINEABLE rule #-}

scrule :: Rule Shell.ParsedShell
scrule :: Rule ParsedShell
scrule = forall a args.
(Linenumber -> State a -> Instruction args -> State a)
-> State a -> Rule args
customRule Linenumber -> State Acc -> Instruction ParsedShell -> State Acc
check (forall a. a -> State a
emptyState Acc
Empty)
  where
    check :: Linenumber -> State Acc -> Instruction ParsedShell -> State Acc
check Linenumber
_ State Acc
st (From BaseImage
_) = State Acc
st forall a b. a -> (a -> b) -> b
|> forall a. (a -> a) -> State a -> State a
modify Acc -> Acc
newStage
    check Linenumber
_ State Acc
st (Arg Text
name Maybe Text
_) = State Acc
st forall a b. a -> (a -> b) -> b
|> forall a. (a -> a) -> State a -> State a
modify ([Text] -> Acc -> Acc
addVars [Text
name])
    check Linenumber
_ State Acc
st (Env Pairs
pairs) = State Acc
st forall a b. a -> (a -> b) -> b
|> forall a. (a -> a) -> State a -> State a
modify ([Text] -> Acc -> Acc
addVars (forall a b. (a -> b) -> [a] -> [b]
map forall a b. (a, b) -> a
fst Pairs
pairs))
    check Linenumber
_ State Acc
st (Shell Arguments ParsedShell
args) =
      State Acc
st forall a b. a -> (a -> b) -> b
|> forall a. (a -> a) -> State a -> State a
modify (Text -> Acc -> Acc
setShell (forall a b. (a -> b) -> Arguments a -> b
foldArguments ParsedShell -> Text
Shell.original Arguments ParsedShell
args))
    check Linenumber
_ State Acc
st (Comment Text
com) =
      case Text -> Maybe Text
Hadolint.Pragma.parseShell Text
com of
        Just Text
sh -> State Acc
st forall a b. a -> (a -> b) -> b
|> forall a. (a -> a) -> State a -> State a
modify (Text -> Acc -> Acc
shellPragma Text
sh)
        Maybe Text
_ -> State Acc
st
    check Linenumber
line State Acc
st (Run (RunArgs Arguments ParsedShell
args RunFlags
_)) = Acc -> Set CheckFailure
getFailures (forall a. State a -> a
state State Acc
st) forall a b. a -> (a -> b) -> b
|> forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr forall a. CheckFailure -> State a -> State a
addFail State Acc
st
      where
        getFailures :: Acc -> Set CheckFailure
getFailures Acc
Empty = forall a b. (a -> b) -> Arguments a -> b
foldArguments (ShellOpts -> ParsedShell -> Set CheckFailure
runShellCheck ShellOpts
Shell.defaultShellOpts) Arguments ParsedShell
args
        getFailures Acc
s = forall a b. (a -> b) -> Arguments a -> b
foldArguments (ShellOpts -> ParsedShell -> Set CheckFailure
runShellCheck (Acc -> ShellOpts
opts Acc
s)) Arguments ParsedShell
args
        runShellCheck :: ShellOpts -> ParsedShell -> Set CheckFailure
runShellCheck ShellOpts
options ParsedShell
script =
          forall a. Ord a => [a] -> Set a
Set.fromList
            [ Linenumber -> PositionedComment -> CheckFailure
toFailure Linenumber
line PositionedComment
c
              | PositionedComment
c <- ShellOpts -> ParsedShell -> [PositionedComment]
Shell.shellcheck ShellOpts
options ParsedShell
script
            ]
    check Linenumber
_ State Acc
st Instruction ParsedShell
_ = State Acc
st
{-# INLINEABLE scrule #-}

newStage :: Acc -> Acc
newStage :: Acc -> Acc
newStage Acc
Empty =
  Acc
    { opts :: ShellOpts
opts = ShellOpts
Shell.defaultShellOpts,
      defaultOpts :: ShellOpts
defaultOpts = ShellOpts
Shell.defaultShellOpts
    }
newStage Acc {ShellOpts
defaultOpts :: ShellOpts
opts :: ShellOpts
defaultOpts :: Acc -> ShellOpts
opts :: Acc -> ShellOpts
..} =
  Acc
    { opts :: ShellOpts
opts = ShellOpts
defaultOpts,
      ShellOpts
defaultOpts :: ShellOpts
defaultOpts :: ShellOpts
defaultOpts
    }

addVars :: [Text.Text] -> Acc -> Acc
addVars :: [Text] -> Acc -> Acc
addVars [Text]
vars Acc
Empty =
  Acc
    { opts :: ShellOpts
opts =
        Shell.ShellOpts
          { shellName :: Text
shellName = ShellOpts -> Text
Shell.shellName ShellOpts
Shell.defaultShellOpts,
            envVars :: Set Text
envVars = ShellOpts -> Set Text
Shell.envVars ShellOpts
Shell.defaultShellOpts forall a. Semigroup a => a -> a -> a
<> forall a. Ord a => [a] -> Set a
Set.fromList [Text]
vars
          },
      defaultOpts :: ShellOpts
defaultOpts = ShellOpts
Shell.defaultShellOpts
    }
addVars [Text]
vars Acc {ShellOpts
defaultOpts :: ShellOpts
opts :: ShellOpts
defaultOpts :: Acc -> ShellOpts
opts :: Acc -> ShellOpts
..} =
  Acc
    { opts :: ShellOpts
opts =
        Shell.ShellOpts
          { shellName :: Text
shellName = ShellOpts -> Text
Shell.shellName ShellOpts
opts,
            envVars :: Set Text
envVars = ShellOpts -> Set Text
Shell.envVars ShellOpts
opts forall a. Semigroup a => a -> a -> a
<> forall a. Ord a => [a] -> Set a
Set.fromList [Text]
vars
          },
      ShellOpts
defaultOpts :: ShellOpts
defaultOpts :: ShellOpts
defaultOpts
    }

setShell :: Text.Text -> Acc -> Acc
setShell :: Text -> Acc -> Acc
setShell Text
sh Acc
Empty =
  Acc
    { opts :: ShellOpts
opts =
        Shell.ShellOpts
          { shellName :: Text
shellName = Text
sh,
            envVars :: Set Text
envVars = ShellOpts -> Set Text
Shell.envVars ShellOpts
Shell.defaultShellOpts
          },
      defaultOpts :: ShellOpts
defaultOpts = ShellOpts
Shell.defaultShellOpts
    }
setShell Text
sh Acc {ShellOpts
defaultOpts :: ShellOpts
opts :: ShellOpts
defaultOpts :: Acc -> ShellOpts
opts :: Acc -> ShellOpts
..} =
  Acc
    { opts :: ShellOpts
opts =
        Shell.ShellOpts
          { shellName :: Text
shellName = Text
sh,
            envVars :: Set Text
envVars = ShellOpts -> Set Text
Shell.envVars ShellOpts
opts
          },
      ShellOpts
defaultOpts :: ShellOpts
defaultOpts :: ShellOpts
defaultOpts
    }

shellPragma :: Text.Text -> Acc -> Acc
shellPragma :: Text -> Acc -> Acc
shellPragma Text
sh Acc
Empty =
  Acc
    { opts :: ShellOpts
opts =
        Shell.ShellOpts
          { shellName :: Text
shellName = Text
sh,
            envVars :: Set Text
envVars = ShellOpts -> Set Text
Shell.envVars ShellOpts
Shell.defaultShellOpts
          },
      defaultOpts :: ShellOpts
defaultOpts =
        Shell.ShellOpts
          { shellName :: Text
shellName = Text
sh,
            envVars :: Set Text
envVars = ShellOpts -> Set Text
Shell.envVars ShellOpts
Shell.defaultShellOpts
          }
    }
shellPragma Text
sh Acc {ShellOpts
defaultOpts :: ShellOpts
opts :: ShellOpts
defaultOpts :: Acc -> ShellOpts
opts :: Acc -> ShellOpts
..} =
  Acc
    { opts :: ShellOpts
opts =
        Shell.ShellOpts
          { shellName :: Text
shellName = Text
sh,
            envVars :: Set Text
envVars = ShellOpts -> Set Text
Shell.envVars ShellOpts
opts
          },
      ShellOpts
defaultOpts :: ShellOpts
defaultOpts :: ShellOpts
defaultOpts
    }

-- | Converts ShellCheck errors into our own errors type
toFailure :: Linenumber ->
  ShellCheck.Interface.PositionedComment ->
  CheckFailure
toFailure :: Linenumber -> PositionedComment -> CheckFailure
toFailure Linenumber
line PositionedComment
c =
  CheckFailure
    { code :: RuleCode
code = Text -> RuleCode
RuleCode forall a b. (a -> b) -> a -> b
$ [Char] -> Text
Text.pack ([Char]
"SC" forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> [Char]
show (PositionedComment -> Code
code PositionedComment
c)),
      severity :: DLSeverity
severity = Severity -> DLSeverity
getDLSeverity forall a b. (a -> b) -> a -> b
$ PositionedComment -> Severity
severity PositionedComment
c,
      message :: Text
message = [Char] -> Text
Text.pack (PositionedComment -> [Char]
message PositionedComment
c),
      line :: Linenumber
line = Linenumber
line
    }
  where
    severity :: PositionedComment -> Severity
severity PositionedComment
pc =
      Comment -> Severity
ShellCheck.Interface.cSeverity forall a b. (a -> b) -> a -> b
$ PositionedComment -> Comment
ShellCheck.Interface.pcComment PositionedComment
pc
    code :: PositionedComment -> Code
code PositionedComment
pc = Comment -> Code
ShellCheck.Interface.cCode forall a b. (a -> b) -> a -> b
$ PositionedComment -> Comment
ShellCheck.Interface.pcComment PositionedComment
pc
    message :: PositionedComment -> [Char]
message PositionedComment
pc =
      Comment -> [Char]
ShellCheck.Interface.cMessage forall a b. (a -> b) -> a -> b
$ PositionedComment -> Comment
ShellCheck.Interface.pcComment PositionedComment
pc

getDLSeverity :: ShellCheck.Interface.Severity -> DLSeverity
getDLSeverity :: Severity -> DLSeverity
getDLSeverity Severity
s =
  case Severity
s of
    Severity
ShellCheck.Interface.WarningC -> DLSeverity
DLWarningC
    Severity
ShellCheck.Interface.InfoC -> DLSeverity
DLInfoC
    Severity
ShellCheck.Interface.StyleC -> DLSeverity
DLStyleC
    Severity
_ -> DLSeverity
DLErrorC