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 :: Rule args
rule = (Linenumber
-> State (Set Text) -> Instruction args -> State (Set Text))
-> State (Set Text) -> Rule args
forall a args.
(Linenumber -> State a -> Instruction args -> State a)
-> State a -> Rule args
customRule Linenumber
-> State (Set Text) -> Instruction args -> State (Set Text)
forall args.
Linenumber
-> State (Set Text) -> Instruction args -> State (Set Text)
check (Set Text -> State (Set Text)
forall a. a -> State a
emptyState Set Text
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 :: Linenumber
-> State (Set Text) -> Instruction args -> State (Set Text)
check Linenumber
line State (Set Text)
st (Env Pairs
pairs) =
let newState :: State (Set Text)
newState = State (Set Text)
st State (Set Text)
-> (State (Set Text) -> State (Set Text)) -> State (Set Text)
forall a b. a -> (a -> b) -> b
|> (Set Text -> Set Text) -> State (Set Text) -> State (Set Text)
forall a. (a -> a) -> State a -> State a
modify (Set Text -> Set Text -> Set Text
forall a. Ord a => Set a -> Set a -> Set a
Set.union ([Text] -> Set Text
forall a. Ord a => [a] -> Set a
Set.fromList (((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)))
in if [Text] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [Text
env | Text
env <- Pairs -> [Text]
listOfReferences Pairs
pairs, Text
env Text -> Set Text -> Bool
forall a. Ord a => a -> Set a -> Bool
`Set.notMember` State (Set Text) -> Set Text
forall a. State a -> a
state State (Set Text)
st]
then State (Set Text)
newState
else State (Set Text)
newState State (Set Text)
-> (State (Set Text) -> State (Set Text)) -> State (Set Text)
forall a b. a -> (a -> b) -> b
|> CheckFailure -> State (Set Text) -> State (Set Text)
forall a. CheckFailure -> State a -> State a
addFail CheckFailure :: RuleCode -> DLSeverity -> Text -> Linenumber -> CheckFailure
CheckFailure {Linenumber
Text
RuleCode
DLSeverity
line :: Linenumber
message :: Text
severity :: DLSeverity
code :: RuleCode
line :: Linenumber
message :: Text
severity :: DLSeverity
code :: RuleCode
..}
check Linenumber
_ State (Set Text)
st (Arg Text
arg Maybe Text
_) = State (Set Text)
st State (Set Text)
-> (State (Set Text) -> State (Set Text)) -> State (Set Text)
forall a b. a -> (a -> b) -> b
|> (Set Text -> Set Text) -> State (Set Text) -> State (Set Text)
forall a. (a -> a) -> State a -> State a
modify (Text -> Set Text -> Set Text
forall a. Ord a => a -> Set a -> Set a
Set.insert Text
arg)
check Linenumber
_ State (Set Text)
st Instruction args
_ = State (Set Text)
st
{-# INLINEABLE rule #-}
listOfReferences :: Pairs -> [Text.Text]
listOfReferences :: Pairs -> [Text]
listOfReferences Pairs
prs =
[ Text
var
| (Linenumber
idx, (Text
var, Text
_)) <- Pairs -> [(Linenumber, (Text, Text))]
forall a. [a] -> [(Linenumber, a)]
indexed Pairs
prs,
Text
var Text -> [Text] -> Bool
`isSubstringOfAny` ((Linenumber, (Text, Text)) -> Text)
-> [(Linenumber, (Text, Text))] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map ((Text, Text) -> Text
forall a b. (a, b) -> b
snd ((Text, Text) -> Text)
-> ((Linenumber, (Text, Text)) -> (Text, Text))
-> (Linenumber, (Text, Text))
-> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Linenumber, (Text, Text)) -> (Text, Text)
forall a b. (a, b) -> b
snd) (((Linenumber, (Text, Text)) -> Bool)
-> [(Linenumber, (Text, Text))] -> [(Linenumber, (Text, Text))]
forall a. (a -> Bool) -> [a] -> [a]
filter ((Linenumber -> Linenumber -> Bool
forall a. Eq a => a -> a -> Bool
/= Linenumber
idx) (Linenumber -> Bool)
-> ((Linenumber, (Text, Text)) -> Linenumber)
-> (Linenumber, (Text, Text))
-> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Linenumber, (Text, Text)) -> Linenumber
forall a b. (a, b) -> a
fst) (Pairs -> [(Linenumber, (Text, Text))]
forall a. [a] -> [(Linenumber, a)]
indexed Pairs
prs))
]
isSubstringOfAny :: Text.Text -> [Text.Text] -> Bool
isSubstringOfAny :: Text -> [Text] -> Bool
isSubstringOfAny Text
t [Text]
l =
Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$
[Text] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null
[ Text
v
| Text
v <- [Text]
l,
(String -> Text
Text.pack String
"${" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
t Text -> Text -> Text
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
"$" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
v
rest :: [Text]
rest = Linenumber -> [Text] -> [Text]
forall a. Linenumber -> [a] -> [a]
drop Linenumber
1 ([Text] -> [Text]) -> [Text] -> [Text]
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
&& (Text -> Bool) -> [Text] -> 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 = (Text -> Bool) -> Set Text -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Text -> Text -> Bool
`Text.isPrefixOf` Text
txt) ((Char -> Text) -> Set Char -> Set Text
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 = String -> Set Char
forall a. Ord a => [a] -> Set a
Set.fromList ([Char
'0' .. Char
'9'] String -> String -> String
forall a. [a] -> [a] -> [a]
++ [Char
'a' .. Char
'z'] String -> String -> String
forall a. [a] -> [a] -> [a]
++ [Char
'A' .. Char
'Z'] String -> String -> String
forall a. [a] -> [a] -> [a]
++ [Char
'_'])