defmodule Mix.Tasks.Compile.ParserPathGuard do
@moduledoc false
use Mix.Task.Compiler
@recursive true
@shortdoc "Fails compile when parser usage escapes the XML seam"
@parser_patterns [~r/\bSaxy\b/, ~r/\bSweetXml\b/, ~r/\bxmerl\b/]
@allowed_roots [
"lib/relyra/security/xml.ex",
"lib/relyra/security/xml/",
"lib/mix/tasks/compile/parser_path_guard.ex"
]
@impl true
def run(_args) do
violations =
"lib/**/*.ex"
|> Path.wildcard()
|> Enum.reject(&allowed_file?/1)
|> Enum.flat_map(&scan_file/1)
if violations == [] do
{:ok, []}
else
Mix.shell().error(
"ParserPathGuard blocked parser references outside Relyra.Security.XML seam:"
)
Enum.each(violations, fn {path, line, text} ->
Mix.shell().error(" #{path}:#{line} -> #{text}")
end)
{:error, []}
end
end
defp allowed_file?(path) do
relative_path = Path.relative_to_cwd(path)
Enum.any?(@allowed_roots, fn root ->
String.starts_with?(relative_path, root)
end)
end
defp scan_file(path) do
path
|> File.read!()
|> String.split("\n")
|> Enum.with_index(1)
|> Enum.reduce([], fn {line, line_no}, acc ->
if Enum.any?(@parser_patterns, &Regex.match?(&1, line)) do
[{path, line_no, String.trim(line)} | acc]
else
acc
end
end)
|> Enum.reverse()
end
end