Current section
Files
Jump to
Current section
Files
lib/pattern/no_explicit_min_reduce.ex
defmodule Credence.Pattern.NoExplicitMinReduce do
@moduledoc "Flags explicit min-reduction patterns inside Enum.reduce/3."
use Credence.Pattern.Rule
alias Credence.Issue
@impl true
def fixable?, do: true
@impl true
def check(ast, _opts) do
{_ast, issues} =
Macro.prewalk(ast, [], fn
{{:., _, _}, meta, args} = node, issues ->
if reduce_call?(node) and min_reduce_body?(args) do
issue = %Issue{
rule: :no_explicit_min_reduce,
message: "Explicit min-reduction detected. Prefer Enum.min/1 or Enum.min_by/2.",
meta: %{line: Keyword.get(meta, :line)}
}
{node, [issue | issues]}
else
{node, issues}
end
node, issues ->
{node, issues}
end)
Enum.reverse(issues)
end
@impl true
def fix(source, _opts) do
source
|> Sourceror.parse_string!()
|> Macro.postwalk(fn
{{:., _, _}, _, args} = node ->
if reduce_call?(node) and min_reduce_body?(args) do
[enum | _] = args
enum_min_call(enum)
else
node
end
node ->
node
end)
|> Sourceror.to_string()
end
defp enum_min_call(enum) do
{{:., [], [{:__aliases__, [], [:Enum]}, :min]}, [], [enum]}
end
defp reduce_call?({{:., _, [{:__aliases__, _, [:Enum]}, :reduce]}, _, _}), do: true
defp reduce_call?({{:., _, [:Enum, :reduce]}, _, _}), do: true
defp reduce_call?(_), do: false
defp min_reduce_body?([_enum, _acc, {:fn, _, [{:->, _, [_args, body]}]}]) do
explicit_min?(body)
end
defp min_reduce_body?(_), do: false
defp explicit_min?({:__block__, _, [body]}), do: explicit_min?(body)
defp explicit_min?({:min, _, [_, _]}), do: true
defp explicit_min?({:if, _, [{:<, _, [_, _]}, _opts]}), do: true
defp explicit_min?({:if, _, [{:<=, _, [_, _]}, _opts]}), do: true
defp explicit_min?(_), do: false
end