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
(Acc -> Acc -> Bool) -> (Acc -> Acc -> Bool) -> Eq Acc
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 = (Int -> State Acc -> Instruction ParsedShell -> State Acc)
-> State Acc -> Rule ParsedShell
forall a args.
(Int -> State a -> Instruction args -> State a)
-> State a -> Rule args
customRule Int -> State Acc -> Instruction ParsedShell -> State Acc
check (Acc -> State Acc
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))
      | State Acc -> Acc
forall a. State a -> a
state State Acc
st Acc -> Acc -> Bool
forall a. Eq a => a -> a -> Bool
== Acc
Empty =
          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 (RunFlags -> Int -> Acc -> Acc
remember RunFlags
fl ((ParsedShell -> Int) -> Arguments ParsedShell -> Int
forall a b. (a -> b) -> Arguments a -> b
foldArguments ParsedShell -> Int
countCommands Arguments ParsedShell
ar))
      | Acc -> RunFlags
flags (State Acc -> Acc
forall a. State a -> a
state State Acc
st) RunFlags -> RunFlags -> Bool
forall a. Eq a => a -> a -> Bool
/= RunFlags
fl =
          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 (RunFlags -> Int -> Acc -> Acc
remember RunFlags
fl ((ParsedShell -> Int) -> Arguments ParsedShell -> Int
forall a b. (a -> b) -> Arguments a -> b
foldArguments ParsedShell -> Int
countCommands Arguments ParsedShell
ar))
      | (ParsedShell -> Int) -> Arguments ParsedShell -> Int
forall a b. (a -> b) -> Arguments a -> b
foldArguments ParsedShell -> Int
countCommands Arguments ParsedShell
ar Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
2 Bool -> Bool -> Bool
|| Acc -> Int
count (State Acc -> Acc
forall a. State a -> a
state State Acc
st) Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
2 =
          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 (RunFlags -> Int -> Acc -> Acc
remember RunFlags
fl ((ParsedShell -> Int) -> Arguments ParsedShell -> Int
forall a b. (a -> b) -> Arguments a -> b
foldArguments ParsedShell -> Int
countCommands Arguments ParsedShell
ar))
      | Bool
otherwise = State Acc
st State Acc -> (State Acc -> State Acc) -> State Acc
forall a b. a -> (a -> b) -> b
|> CheckFailure -> State Acc -> State Acc
forall a. CheckFailure -> State a -> State a
addFail CheckFailure :: RuleCode -> DLSeverity -> Text -> Int -> CheckFailure
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 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
reset
{-# INLINEABLE rule #-}

remember :: RunFlags -> Int -> Acc -> Acc
remember :: RunFlags -> Int -> Acc -> Acc
remember RunFlags
fl Int
cn Acc
_ = Acc :: RunFlags -> Int -> 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 = [Command] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length ([Command] -> Int) -> [Command] -> Int
forall a b. (a -> b) -> a -> b
$ ParsedShell -> [Command]
Shell.presentCommands ParsedShell
script