Current section
Files
Jump to
Current section
Files
lib/mix/tasks/guardrails.ex
defmodule Mix.Tasks.Guardrails do
use Mix.Task
require Logger
@shortdoc "Scan for disallowed patterns (IO.puts/IO.inspect, legacy JSON module)"
@moduledoc """
Scans source files for disallowed patterns and fails if any are found.
Disallowed patterns:
- IO.puts
- IO.inspect
- MikrotikApi.JSON (legacy proprietary JSON module)
Usage:
mix guardrails
"""
@impl true
def run(_args) do
files =
globs()
|> Enum.flat_map(&Path.wildcard/1)
|> Enum.reject(&skip?/1)
patterns = patterns()
findings =
files
|> Enum.flat_map(fn file ->
case File.read(file) do
{:ok, bin} ->
for {regex, label} <- patterns, Regex.match?(regex, bin), do: {file, label}
{:error, _} ->
[]
end
end)
if findings == [] do
Logger.info("guardrails: OK (no disallowed patterns found)")
else
Enum.each(findings, fn {file, label} ->
Logger.error("guardrails: disallowed pattern #{label} in #{file}")
end)
Mix.raise("guardrails failed: disallowed patterns present")
end
end
defp globs do
["lib/**/*.ex", "test/**/*.exs", "config/**/*.exs", "mix.exs"]
end
defp patterns do
[
{Regex.compile!("\\bIO\\.puts\\b"), "IO.puts"},
{Regex.compile!("\\bIO\\.inspect\\b"), "IO.inspect"},
{Regex.compile!("\\bMikrotikApi\\.JSON\\b"), "MikrotikApi.JSON"}
]
end
defp skip?(file) do
String.ends_with?(file, "lib/mix/tasks/guardrails.ex")
end
end