Current section
Files
Jump to
Current section
Files
lib/ex_slop/check/refactor/reduce_map_put.ex
defmodule ExSlop.Check.Refactor.ReduceMapPut do
use Credo.Check,
id: "EXS4013",
base_priority: :normal,
category: :refactor,
tags: [:ex_slop],
explanations: [
check: """
`Enum.reduce(%{}, fn x, acc -> Map.put(acc, key, value) end)` is
`Map.new/2` (or a `for` comprehension).
# bad — verbose reduce to build a map
Enum.reduce(batch, %{}, fn event, acc ->
Map.put(acc, event.id, event)
end)
# good — use Map.new
Map.new(batch, fn event -> {event.id, event} end)
# good — for comprehension
for event <- batch, into: %{}, do: {event.id, event}
"""
]
@doc false
@impl true
def run(%SourceFile{} = source_file, params) do
ctx = Context.build(source_file, params, __MODULE__)
result = Credo.Code.prewalk(source_file, &walk/2, ctx)
result.issues
end
# Enum.reduce(list, %{}, fn x, acc -> Map.put(acc, k, v) end)
defp walk(
{{:., meta, [{:__aliases__, _, [:Enum]}, :reduce]}, _, [_, {:%{}, _, []}, fun]} = ast,
ctx
) do
if fn_body_is_only_map_put?(fun) do
{ast, put_issue(ctx, issue_for(ctx, meta))}
else
{ast, ctx}
end
end
# |> Enum.reduce(%{}, fn x, acc -> Map.put(acc, k, v) end)
defp walk(
{:|>, meta,
[
_,
{{:., _, [{:__aliases__, _, [:Enum]}, :reduce]}, _, [{:%{}, _, []}, fun]}
]} = ast,
ctx
) do
if fn_body_is_only_map_put?(fun) do
{ast, put_issue(ctx, issue_for(ctx, meta))}
else
{ast, ctx}
end
end
defp walk(ast, ctx), do: {ast, ctx}
defp fn_body_is_only_map_put?({:fn, _, [{:->, _, [[_arg, _acc], body]}]}) do
body_is_map_put?(body)
end
defp fn_body_is_only_map_put?({:fn, _, [{:->, _, [[_arg, _acc], [body]]}]}) do
body_is_map_put?(body)
end
defp fn_body_is_only_map_put?(_), do: false
defp body_is_map_put?({{:., _, [{:__aliases__, _, [:Map]}, :put]}, _, [acc_name, _, _]})
when is_atom(acc_name),
do: true
defp body_is_map_put?({{:., _, [{:__aliases__, _, [:Map]}, :put]}, _, [{acc_name, _, _} | _]})
when is_atom(acc_name),
do: true
defp body_is_map_put?({:__block__, _, [expr]}), do: body_is_map_put?(expr)
defp body_is_map_put?(_), do: false
defp issue_for(ctx, meta) do
format_issue(ctx,
message:
"Use `Map.new/2` or `for ... into: %{}` instead of `Enum.reduce(%{}, ..., Map.put/3)`.",
trigger: "Enum.reduce",
line_no: meta[:line]
)
end
end