module Hadolint.Rule.DL3059 (rule) where

import Hadolint.Rule
import qualified Hadolint.Shell as Shell
import Language.Docker.Syntax


data Acc
  = Acc { Acc -> RunFlags
flags :: RunFlags, Acc -> Int
count :: Int }
  | Empty
  deriving (Acc -> Acc -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Acc -> Acc -> Bool
$c/= :: Acc -> Acc -> Bool
== :: Acc -> Acc -> Bool
$c== :: Acc -> Acc -> Bool
Eq)

-- | This Rule catches multiple consecutive `RUN` instructions.
-- It ignores the case where multiple commands are chained together (e.g. with
-- `&&`) because in that case the programmer most likely has deliberately
-- chosen to use multiuple `RUN` instructions. Cases where --mount=xxx flags
-- differ are excluded as well.
rule :: Rule Shell.ParsedShell
rule :: Rule ParsedShell
rule = forall a args.
(Int -> State a -> Instruction args -> State a)
-> State a -> Rule args
customRule Int -> State Acc -> Instruction ParsedShell -> State Acc
check (forall a. a -> State a
emptyState Acc
Empty)
  where
    code :: RuleCode
code = RuleCode
"DL3059"
    severity :: DLSeverity
severity = DLSeverity
DLInfoC
    message :: Text
message = Text
"Multiple consecutive `RUN` instructions. Consider consolidation."

    check :: Int -> State Acc -> Instruction ParsedShell -> State Acc
check Int
line State Acc
st (Run (RunArgs Arguments ParsedShell
ar RunFlags
fl))
      | forall a. State a -> a
state State Acc
st forall a. Eq a => a -> a -> Bool
== Acc
Empty =
          State Acc
st forall a b. a -> (a -> b) -> b
|> forall a. (a -> a) -> State a -> State a
modify (RunFlags -> Int -> Acc -> Acc
remember RunFlags
fl (forall a b. (a -> b) -> Arguments a -> b
foldArguments ParsedShell -> Int
countCommands Arguments ParsedShell
ar))
      | Acc -> RunFlags
flags (forall a. State a -> a
state State Acc
st) forall a. Eq a => a -> a -> Bool
/= RunFlags
fl =
          State Acc
st forall a b. a -> (a -> b) -> b
|> forall a. (a -> a) -> State a -> State a
modify (RunFlags -> Int -> Acc -> Acc
remember RunFlags
fl (forall a b. (a -> b) -> Arguments a -> b
foldArguments ParsedShell -> Int
countCommands Arguments ParsedShell
ar))
      | forall a b. (a -> b) -> Arguments a -> b
foldArguments ParsedShell -> Int
countCommands Arguments ParsedShell
ar forall a. Ord a => a -> a -> Bool
> Int
2 Bool -> Bool -> Bool
|| Acc -> Int
count (forall a. State a -> a
state State Acc
st) forall a. Ord a => a -> a -> Bool
> Int
2 =
          State Acc
st forall a b. a -> (a -> b) -> b
|> forall a. (a -> a) -> State a -> State a
modify (RunFlags -> Int -> Acc -> Acc
remember RunFlags
fl (forall a b. (a -> b) -> Arguments a -> b
foldArguments ParsedShell -> Int
countCommands Arguments ParsedShell
ar))
      | Bool
otherwise = State Acc
st forall a b. a -> (a -> b) -> b
|> forall a. CheckFailure -> State a -> State a
addFail CheckFailure {Int
Text
RuleCode
DLSeverity
line :: Int
message :: Text
severity :: DLSeverity
code :: RuleCode
line :: Int
message :: Text
severity :: DLSeverity
code :: RuleCode
..}
    check Int
_ State Acc
st (Comment Text
_) = State Acc
st
    check Int
_ State Acc
st Instruction ParsedShell
_ = State Acc
st forall a b. a -> (a -> b) -> b
|> forall a. (a -> a) -> State a -> State a
modify Acc -> Acc
reset
{-# INLINEABLE rule #-}

remember :: RunFlags -> Int -> Acc -> Acc
remember :: RunFlags -> Int -> Acc -> Acc
remember RunFlags
fl Int
cn Acc
_ = Acc { flags :: RunFlags
flags = RunFlags
fl, count :: Int
count = Int
cn }

reset :: Acc -> Acc
reset :: Acc -> Acc
reset Acc
_ = Acc
Empty

countCommands :: Shell.ParsedShell -> Int
countCommands :: ParsedShell -> Int
countCommands ParsedShell
script = forall (t :: * -> *) a. Foldable t => t a -> Int
length forall a b. (a -> b) -> a -> b
$ ParsedShell -> [Command]
Shell.presentCommands ParsedShell
script