module Hadolint.Rule.DL3044 (rule) where
import Data.List.Index (indexed)
import qualified Data.Set as Set
import qualified Data.Text as Text
import Hadolint.Rule
import Language.Docker.Syntax
rule :: Rule args
rule :: forall args. Rule args
rule = forall a args.
(Int -> State a -> Instruction args -> State a)
-> State a -> Rule args
customRule forall {args}.
Int -> State (Set Text) -> Instruction args -> State (Set Text)
check (forall a. a -> State a
emptyState forall a. Set a
Set.empty)
where
code :: RuleCode
code = RuleCode
"DL3044"
severity :: DLSeverity
severity = DLSeverity
DLErrorC
message :: Text
message = Text
"Do not refer to an environment variable within the same `ENV` statement where it is defined."
check :: Int -> State (Set Text) -> Instruction args -> State (Set Text)
check Int
line State (Set Text)
st (Env Pairs
pairs) =
let newState :: State (Set Text)
newState = State (Set Text)
st forall a b. a -> (a -> b) -> b
|> forall a. (a -> a) -> State a -> State a
modify (forall a. Ord a => Set a -> Set a -> Set a
Set.union (forall a. Ord a => [a] -> Set a
Set.fromList (forall a b. (a -> b) -> [a] -> [b]
map forall a b. (a, b) -> a
fst Pairs
pairs)))
in if forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Text
env | Text
env <- Pairs -> [Text]
listOfReferences Pairs
pairs, Text
env forall a. Ord a => a -> Set a -> Bool
`Set.notMember` forall a. State a -> a
state State (Set Text)
st]
then State (Set Text)
newState
else State (Set Text)
newState 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 (Set Text)
st (Arg Text
arg Maybe Text
_) = State (Set Text)
st forall a b. a -> (a -> b) -> b
|> forall a. (a -> a) -> State a -> State a
modify (forall a. Ord a => a -> Set a -> Set a
Set.insert Text
arg)
check Int
_ State (Set Text)
st Instruction args
_ = State (Set Text)
st
{-# INLINEABLE rule #-}
listOfReferences :: Pairs -> [Text.Text]
listOfReferences :: Pairs -> [Text]
listOfReferences Pairs
prs =
[ Text
var
| (Int
idx, (Text
var, Text
_)) <- forall a. [a] -> [(Int, a)]
indexed Pairs
prs,
Text
var Text -> [Text] -> Bool
`isSubstringOfAny` forall a b. (a -> b) -> [a] -> [b]
map (forall a b. (a, b) -> b
snd forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a, b) -> b
snd) (forall a. (a -> Bool) -> [a] -> [a]
filter ((forall a. Eq a => a -> a -> Bool
/= Int
idx) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a, b) -> a
fst) (forall a. [a] -> [(Int, a)]
indexed Pairs
prs))
]
isSubstringOfAny :: Text.Text -> [Text.Text] -> Bool
isSubstringOfAny :: Text -> [Text] -> Bool
isSubstringOfAny Text
t [Text]
l =
Bool -> Bool
not forall a b. (a -> b) -> a -> b
$
forall (t :: * -> *) a. Foldable t => t a -> Bool
null
[ Text
v
| Text
v <- [Text]
l,
(String -> Text
Text.pack String
"${" forall a. Semigroup a => a -> a -> a
<> Text
t forall a. Semigroup a => a -> a -> a
<> String -> Text
Text.pack String
"}") Text -> Text -> Bool
`Text.isInfixOf` Text
v
Bool -> Bool -> Bool
|| (Text
t Text -> Text -> Bool
`bareVariableInText` Text
v)
]
bareVariableInText :: Text.Text -> Text.Text -> Bool
bareVariableInText :: Text -> Text -> Bool
bareVariableInText Text
v Text
t =
let var :: Text
var = Text
"$" forall a. Semigroup a => a -> a -> a
<> Text
v
rest :: [Text]
rest = forall a. Int -> [a] -> [a]
drop Int
1 forall a b. (a -> b) -> a -> b
$ Text -> Text -> [Text]
Text.splitOn Text
var Text
t
in Text
var Text -> Text -> Bool
`Text.isInfixOf` Text
t Bool -> Bool -> Bool
&& forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Text -> Bool
terminatesVarName [Text]
rest
where
terminatesVarName :: Text.Text -> Bool
terminatesVarName :: Text -> Bool
terminatesVarName Text
x = Text -> Bool
Text.null Text
x Bool -> Bool -> Bool
|| Bool -> Bool
not (Text -> Set Char -> Bool
beginsWithAnyOf Text
x Set Char
varChar)
beginsWithAnyOf :: Text.Text -> Set.Set Char -> Bool
beginsWithAnyOf :: Text -> Set Char -> Bool
beginsWithAnyOf Text
txt Set Char
str = forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Text -> Text -> Bool
`Text.isPrefixOf` Text
txt) (forall b a. Ord b => (a -> b) -> Set a -> Set b
Set.map Char -> Text
Text.singleton Set Char
str)
varChar :: Set.Set Char
varChar :: Set Char
varChar = forall a. Ord a => [a] -> Set a
Set.fromList ([Char
'0' .. Char
'9'] forall a. [a] -> [a] -> [a]
++ [Char
'a' .. Char
'z'] forall a. [a] -> [a] -> [a]
++ [Char
'A' .. Char
'Z'] forall a. [a] -> [a] -> [a]
++ [Char
'_'])