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 Rule ParsedShell -> Rule ParsedShell -> Rule ParsedShell
forall a. Semigroup a => a -> a -> a
<> Rule ParsedShell -> Rule ParsedShell
forall args. Rule args -> Rule args
onbuild Rule ParsedShell
scrule
{-# INLINEABLE rule #-}

scrule :: Rule Shell.ParsedShell
scrule :: Rule ParsedShell
scrule = (Linenumber -> State Acc -> Instruction ParsedShell -> State Acc)
-> State Acc -> Rule ParsedShell
forall a args.
(Linenumber -> State a -> Instruction args -> State a)
-> State a -> Rule args
customRule Linenumber -> State Acc -> Instruction ParsedShell -> State Acc
check (Acc -> State Acc
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 State Acc -> (State Acc -> State Acc) -> State Acc
forall a b. a -> (a -> b) -> b
|> (Acc -> Acc) -> State Acc -> State Acc
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 State Acc -> (State Acc -> State Acc) -> State Acc
forall a b. a -> (a -> b) -> b
|> (Acc -> Acc) -> State Acc -> State Acc
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 State Acc -> (State Acc -> State Acc) -> State Acc
forall a b. a -> (a -> b) -> b
|> (Acc -> Acc) -> State Acc -> State Acc
forall a. (a -> a) -> State a -> State a
modify ([Text] -> Acc -> Acc
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 Acc
st (Shell Arguments ParsedShell
args) =
      State Acc
st State Acc -> (State Acc -> State Acc) -> State Acc
forall a b. a -> (a -> b) -> b
|> (Acc -> Acc) -> State Acc -> State Acc
forall a. (a -> a) -> State a -> State a
modify (Text -> Acc -> Acc
setShell ((ParsedShell -> Text) -> Arguments ParsedShell -> Text
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 State Acc -> (State Acc -> State Acc) -> State Acc
forall a b. a -> (a -> b) -> b
|> (Acc -> Acc) -> State Acc -> State Acc
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 (State Acc -> Acc
forall a. State a -> a
state State Acc
st) Set CheckFailure -> (Set CheckFailure -> State Acc) -> State Acc
forall a b. a -> (a -> b) -> b
|> (CheckFailure -> State Acc -> State Acc)
-> State Acc -> Set CheckFailure -> State Acc
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr CheckFailure -> State Acc -> State Acc
forall a. CheckFailure -> State a -> State a
addFail State Acc
st
      where
        getFailures :: Acc -> Set CheckFailure
getFailures Acc
Empty = (ParsedShell -> Set CheckFailure)
-> Arguments ParsedShell -> Set CheckFailure
forall a b. (a -> b) -> Arguments a -> b
foldArguments (ShellOpts -> ParsedShell -> Set CheckFailure
runShellCheck ShellOpts
Shell.defaultShellOpts) Arguments ParsedShell
args
        getFailures Acc
s = (ParsedShell -> Set CheckFailure)
-> Arguments ParsedShell -> Set CheckFailure
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 =
          [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
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 :: ShellOpts -> ShellOpts -> Acc
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 :: ShellOpts -> ShellOpts -> Acc
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 :: ShellOpts -> ShellOpts -> Acc
Acc
    { opts :: ShellOpts
opts =
        ShellOpts :: Text -> Set Text -> ShellOpts
Shell.ShellOpts
          { shellName :: Text
shellName = ShellOpts -> Text
Shell.shellName ShellOpts
Shell.defaultShellOpts,
            envVars :: Set Text
envVars = ShellOpts -> Set Text
Shell.envVars ShellOpts
Shell.defaultShellOpts Set Text -> Set Text -> Set Text
forall a. Semigroup a => a -> a -> a
<> [Text] -> Set Text
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 :: ShellOpts -> ShellOpts -> Acc
Acc
    { opts :: ShellOpts
opts =
        ShellOpts :: Text -> Set Text -> ShellOpts
Shell.ShellOpts
          { shellName :: Text
shellName = ShellOpts -> Text
Shell.shellName ShellOpts
opts,
            envVars :: Set Text
envVars = ShellOpts -> Set Text
Shell.envVars ShellOpts
opts Set Text -> Set Text -> Set Text
forall a. Semigroup a => a -> a -> a
<> [Text] -> Set Text
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 :: ShellOpts -> ShellOpts -> Acc
Acc
    { opts :: ShellOpts
opts =
        ShellOpts :: Text -> Set Text -> ShellOpts
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 :: ShellOpts -> ShellOpts -> Acc
Acc
    { opts :: ShellOpts
opts =
        ShellOpts :: Text -> Set Text -> ShellOpts
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 :: ShellOpts -> ShellOpts -> Acc
Acc
    { opts :: ShellOpts
opts =
        ShellOpts :: Text -> Set Text -> ShellOpts
Shell.ShellOpts
          { shellName :: Text
shellName = Text
sh,
            envVars :: Set Text
envVars = ShellOpts -> Set Text
Shell.envVars ShellOpts
Shell.defaultShellOpts
          },
      defaultOpts :: ShellOpts
defaultOpts =
        ShellOpts :: Text -> Set Text -> ShellOpts
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 :: ShellOpts -> ShellOpts -> Acc
Acc
    { opts :: ShellOpts
opts =
        ShellOpts :: Text -> Set Text -> ShellOpts
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 :: 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
    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

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