Credence.Pattern.PreferMapNewWithTransform (credence v0.8.0)

Copy Markdown

Detects Enum.map/2 followed by Map.new/0 and suggests combining them into a single Map.new/2 call.

Why this matters

Enum.map(coll, fn ...) |> Map.new() allocates an intermediate list of 2-tuples only to immediately convert them into a map. Map.new/2 combines both steps into a single pass without the intermediate allocation, and is clearer about intent.

Bad

Enum.map(1..5, fn i -> {i, i * i} end) |> Map.new()

Map.new(Enum.map(1..5, fn i -> {i, i * i} end))

1..5
|> Enum.map(fn i -> {i, i * i} end)
|> Map.new()

Good

Map.new(1..5, fn i -> {i, i * i} end)

When the Enum.map is fed by an upstream pipeline, that pipeline is kept and the map step folds into a piped Map.new/2 (the collection stays in the pipe):

# Bad
list
|> filter_keys()
|> Enum.map(fn {k, v} -> {k, f(v)} end)
|> Map.new()

# Good
list
|> filter_keys()
|> Map.new(fn {k, v} -> {k, f(v)} end)

Scope

Flags when ALL of these hold:

  • Enum.map/2 is called with two arguments (enumerable + function).
  • Its result is passed to Map.new/0 (via pipe or nesting).
  • The Map.new call has zero arguments (i.e., it's Map.new/0, not Map.new/1).

Does NOT flag:

  • Enum.map/2 alone (no following Map.new).
  • Map.new/1 (with an explicit enumerable arg).
  • Enum.map/2 with a function that doesn't return 2-tuples (we don't check this — the pattern match is syntactic only).

Auto-fix

Replaces the Enum.map |> Map.new pair with Map.new/2, moving the enumerable and function into the Map.new call.