Repository: adobe/elixir-styler (elixir)

Issue:
new style: rewrite single-key `Map.merge` to `Map.put`
## Example Test

Should be implemented in SingleNode style (even the pipes bit)

```elixir
for module <- ~w(Map Keyword) do
  assert_style "#{module}.merge(foo, %{one_key: :bar})", "#{module}.put(foo, :one_key, :bar)"
  assert_style "foo |> #{module}.merge(%{one_key: :bar}) |> bop()", "foo |> #{module}.put(:one_key, :bar) |> bop()"
  # doesn't rewrite if there's a custom merge strategy
  assert_style "#{module}.merge(foo, %{one_key: :bar}, custom_merge_strategy)"
  # doesn't rewrite if > 1 key
  assert_style "#{module}.merge(foo, %{a: :b, c: :d})"
end
```


Pull request description:
single node & pipe merge/2 to put/3 rewrites
Closes #90

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a62ddd2..e88d6b5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
 
 ## main
 
+### Improvements
+
+* Rewrite `{Map|Keyword}.merge(single_key: value)` to use `put/3` instead
+
 ## v0.10.5
 
 After being bitten by two of them in a row, Styler's test suite now makes sure that there are no
diff --git a/lib/style/pipes.ex b/lib/style/pipes.ex
index 35701a9..0711684 100644
--- a/lib/style/pipes.ex
+++ b/lib/style/pipes.ex
@@ -209,6 +209,16 @@ defmodule Styler.Style.Pipes do
       else: node
   end
 
+  for mod <- [:Map, :Keyword] do
+    # lhs |> Map.merge(%{key: value}) => lhs |> Map.put(key, value)
+    defp fix_pipe({:|>, pm, [lhs, {{:., dm, [{_, _, [unquote(mod)]} = module, :merge]}, m, [{:%{}, _, [{key, value}]}]}]}),
+      do: {:|>, pm, [lhs, {{:., dm, [module, :put]}, m, [key, value]}]}
+
+    # lhs |> Map.merge(key: value) => lhs |> Map.put(:key, value)
+    defp fix_pipe({:|>, pm, [lhs, {{:., dm, [{_, _, [unquote(mod)]} = module, :merge]}, m, [[{key, value}]]}]}),
+      do: {:|>, pm, [lhs, {{:., dm, [module, :put]}, m, [key, value]}]}
+  end
+
   defp fix_pipe(node), do: node
 
   # most of these values were lifted directly from credo's pipe_chain_start.ex
diff --git a/lib/style/single_node.ex b/lib/style/single_node.ex
index 75c3ba2..3071b4b 100644
--- a/lib/style/single_node.ex
+++ b/lib/style/single_node.ex
@@ -82,6 +82,16 @@ defmodule Styler.Style.SingleNode do
     if Style.empty_map?(collectable), do: {{:., dm, [{:__aliases__, am, [:Map]}, :new]}, funm, [enum | rest]}, else: node
   end
 
+  for mod <- [:Map, :Keyword] do
+    # Map.merge(foo, %{one_key: :bar}) => Map.put(foo, :one_key, :bar)
+    defp style({{:., dm, [{_, _, [unquote(mod)]} = module, :merge]}, m, [lhs, {:%{}, _, [{key, value}]}]}),
+      do: {{:., dm, [module, :put]}, m, [lhs, key, value]}
+
+    # Map.merge(foo, one_key: :bar) => Map.put(foo, :one_key, :bar)
+    defp style({{:., dm, [{_, _, [unquote(mod)]} = module, :merge]}, m, [lhs, [{key, value}]]}),
+      do: {{:., dm, [module, :put]}, m, [lhs, key, value]}
+  end
+
   # Logger.warn => Logger.warning
   defp style({{:., dm, [{:__aliases__, am, [:Logger]}, :warn]}, funm, args}),
     do: {{:., dm, [{:__aliases__, am, [:Logger]}, :warning]}, funm, args}
diff --git a/mix.exs b/mix.exs
index 645eb2c..71b5673 100644
--- a/mix.exs
+++ b/mix.exs
@@ -23,7 +23,6 @@ defmodule Styler.MixProject do
       start_permanent: Mix.env() == :prod,
       elixirc_paths: elixirc_paths(Mix.env()),
       deps: deps(),
-      aliases: [test: ["test", "format --check-formatted"]],
 
       ## Hex
       package: package(),


Test patch:
diff --git a/test/style/pipes_test.exs b/test/style/pipes_test.exs
index cc47008..6b99dc2 100644
--- a/test/style/pipes_test.exs
+++ b/test/style/pipes_test.exs
@@ -468,6 +468,12 @@ defmodule Styler.Style.PipesTest do
   end
 
   describe "simple rewrites" do
+    test "{Keyword/Map}.merge/2 of a single key => *.put/3" do
+      for module <- ~w(Map Keyword) do
+        assert_style "foo |> #{module}.merge(%{one_key: :bar}) |> bop()", "foo |> #{module}.put(:one_key, :bar) |> bop()"
+      end
+    end
+
     test "rewrites anon fun def ahd invoke to use then" do
       assert_style("a |> (& &1).()", "then(a, & &1)")
       assert_style("a |> (& {&1, &2}).(b)", "(&{&1, &2}).(a, b)")
diff --git a/test/style/single_node_test.exs b/test/style/single_node_test.exs
index 6b125a2..fdc970d 100644
--- a/test/style/single_node_test.exs
+++ b/test/style/single_node_test.exs
@@ -19,6 +19,17 @@ defmodule Styler.Style.SingleNodeTest do
     end
   end
 
+  test "{Map/Keyword}.merge with a single static key" do
+    for module <- ~w(Map Keyword) do
+      assert_style("#{module}.merge(foo, %{one_key: :bar})", "#{module}.put(foo, :one_key, :bar)")
+      assert_style("#{module}.merge(foo, one_key: :bar)", "#{module}.put(foo, :one_key, :bar)")
+      # # doesn't rewrite if there's a custom merge strategy
+      assert_style("#{module}.merge(foo, %{one_key: :bar}, custom_merge_strategy)")
+      # # doesn't rewrite if > 1 key
+      assert_style("#{module}.merge(foo, %{a: :b, c: :d})")
+    end
+  end
+
   test "Logger.warn to Logger.warning" do
     assert_style("Logger.warn(foo)", "Logger.warning(foo)")
     assert_style("Logger.warn(foo, bar)", "Logger.warning(foo, bar)")