module Hadolint.Rule.Shellcheck (rule) where

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

rule :: Rule ParsedShell
rule :: Rule ParsedShell
rule = (Linenumber
 -> State ShellOpts -> Instruction ParsedShell -> State ShellOpts)
-> State ShellOpts -> Rule ParsedShell
forall a args.
(Linenumber -> State a -> Instruction args -> State a)
-> State a -> Rule args
customRule Linenumber
-> State ShellOpts -> Instruction ParsedShell -> State ShellOpts
check (ShellOpts -> State ShellOpts
forall a. a -> State a
emptyState ShellOpts
Shell.defaultShellOpts)
  where
    check :: Linenumber
-> State ShellOpts -> Instruction ParsedShell -> State ShellOpts
check Linenumber
_ State ShellOpts
st (From BaseImage
_) = State ShellOpts
st State ShellOpts
-> (State ShellOpts -> State ShellOpts) -> State ShellOpts
forall a b. a -> (a -> b) -> b
|> ShellOpts -> State ShellOpts -> State ShellOpts
forall a. a -> State a -> State a
replaceWith ShellOpts
Shell.defaultShellOpts -- Reset the state
    check Linenumber
_ State ShellOpts
st (Arg Text
name Maybe Text
_) = State ShellOpts
st State ShellOpts
-> (State ShellOpts -> State ShellOpts) -> State ShellOpts
forall a b. a -> (a -> b) -> b
|> (ShellOpts -> ShellOpts) -> State ShellOpts -> State ShellOpts
forall a. (a -> a) -> State a -> State a
modify ([Text] -> ShellOpts -> ShellOpts
Shell.addVars [Text
name])
    check Linenumber
_ State ShellOpts
st (Env Pairs
pairs) = State ShellOpts
st State ShellOpts
-> (State ShellOpts -> State ShellOpts) -> State ShellOpts
forall a b. a -> (a -> b) -> b
|> (ShellOpts -> ShellOpts) -> State ShellOpts -> State ShellOpts
forall a. (a -> a) -> State a -> State a
modify ([Text] -> ShellOpts -> ShellOpts
Shell.addVars (((Text, Text) -> Text) -> Pairs -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map (Text, Text) -> Text
forall a b. (a, b) -> a
fst Pairs
pairs))
    check Linenumber
_ State ShellOpts
st (Shell Arguments ParsedShell
args) = State ShellOpts
st State ShellOpts
-> (State ShellOpts -> State ShellOpts) -> State ShellOpts
forall a b. a -> (a -> b) -> b
|> (ShellOpts -> ShellOpts) -> State ShellOpts -> State ShellOpts
forall a. (a -> a) -> State a -> State a
modify (Text -> ShellOpts -> ShellOpts
Shell.setShell ((ParsedShell -> Text) -> Arguments ParsedShell -> Text
forall a b. (a -> b) -> Arguments a -> b
foldArguments ParsedShell -> Text
Shell.original Arguments ParsedShell
args))
    check Linenumber
line State ShellOpts
st (Run (RunArgs Arguments ParsedShell
args RunFlags
_)) = Set CheckFailure
getFailures Set CheckFailure
-> (Set CheckFailure -> State ShellOpts) -> State ShellOpts
forall a b. a -> (a -> b) -> b
|> (CheckFailure -> State ShellOpts -> State ShellOpts)
-> State ShellOpts -> Set CheckFailure -> State ShellOpts
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr CheckFailure -> State ShellOpts -> State ShellOpts
forall a. CheckFailure -> State a -> State a
addFail State ShellOpts
st
      where
        getFailures :: Set CheckFailure
getFailures = (ParsedShell -> Set CheckFailure)
-> Arguments ParsedShell -> Set CheckFailure
forall a b. (a -> b) -> Arguments a -> b
foldArguments (ShellOpts -> ParsedShell -> Set CheckFailure
runShellCheck (State ShellOpts -> ShellOpts
forall a. State a -> a
state State ShellOpts
st)) Arguments ParsedShell
args
        runShellCheck :: ShellOpts -> ParsedShell -> Set CheckFailure
runShellCheck ShellOpts
opts ParsedShell
script = [CheckFailure] -> Set CheckFailure
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
opts ParsedShell
script]
    check Linenumber
_ State ShellOpts
st Instruction ParsedShell
_ = State ShellOpts
st
{-# INLINEABLE rule #-}

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