Repository: adobe/elixir-styler
Pull request #111 (2023-12-14 22:41:53)

Add support for negated Enum.any? -> Enum.empty?
- [x] Please [sign Adobe's CLA](http://opensource.adobe.com/cla.html) if this is your first time contributing to an Adobe open source repo. Thanks!


mgieger: > would you move is_negator into Styler.Style? that's the place i've been dumping the shared library sort of functions

Done! Thanks for the call out on this. As soon as I made the guard public I thought it should be moved, but then I didn't move it. 😆 

Patch:
diff --git a/lib/style.ex b/lib/style.ex
index 2eba371..c9f1c88 100644
--- a/lib/style.ex
+++ b/lib/style.ex
@@ -31,6 +31,8 @@ defmodule Styler.Style do
   """
   @callback run(Zipper.zipper(), context()) :: {Zipper.command(), Zipper.zipper(), context()}
 
+  defguard is_negator(n) when n in [:!, :not]
+
   @doc "Recursively sets `:line` meta to `line`. Deletes `:newlines` unless `delete_lines: false` is passed"
   def set_line(ast_node, line, opts \\ []) do
     set_line = fn _ -> line end
diff --git a/lib/style/blocks.ex b/lib/style/blocks.ex
index 8c49973..42dc581 100644
--- a/lib/style/blocks.ex
+++ b/lib/style/blocks.ex
@@ -27,11 +27,11 @@ defmodule Styler.Style.Blocks do
 
   @behaviour Styler.Style
 
+  import Styler.Style, only: [is_negator: 1]
+
   alias Styler.Style
   alias Styler.Zipper
 
-  defguardp is_negator(n) when n in [:!, :not]
-
   # case statement with exactly 2 `->` cases
   # rewrite to `if` if it's any of 3 trivial cases
   def run({{:case, _, [head, [{_, [{:->, _, [[lhs_a], a]}, {:->, _, [[lhs_b], b]}]}]]}, _} = zipper, ctx) do
diff --git a/lib/style/single_node.ex b/lib/style/single_node.ex
index 3071b4b..8a7a1ee 100644
--- a/lib/style/single_node.ex
+++ b/lib/style/single_node.ex
@@ -27,6 +27,8 @@ defmodule Styler.Style.SingleNode do
 
   @behaviour Styler.Style
 
+  import Styler.Style, only: [is_negator: 1]
+
   alias Styler.Style
   alias Styler.Zipper
 
@@ -139,6 +141,23 @@ defmodule Styler.Style.SingleNode do
   defp style({:case, cm, [head, [{do_, arrows}]]}), do: {:case, cm, [head, [{do_, rewrite_arrows(arrows)}]]}
   defp style({:fn, m, arrows}), do: {:fn, m, rewrite_arrows(arrows)}
 
+  # Negation of Enum.any? => Enum.empty? and Negation of Enum.empty? => Enum.any?
+  defp style({negator, _, [{{:., dm, [{:__aliases__, am, [:Enum]}, :any?]}, funm, [arg]}]}) when is_negator(negator) do
+    {{:., dm, [{:__aliases__, am, [:Enum]}, :empty?]}, funm, [arg]}
+  end
+
+  defp style({negator, _, [{{:., dm, [{:__aliases__, am, [:Enum]}, :empty?]}, funm, [arg]}]}) when is_negator(negator) do
+    {{:., dm, [{:__aliases__, am, [:Enum]}, :any?]}, funm, [arg]}
+  end
+
+  defp style({:unless, m, [{{:., dm, [{:__aliases__, am, [:Enum]}, :any?]}, funm, [arg]}, block]}) do
+    {:if, m, [{{:., dm, [{:__aliases__, am, [:Enum]}, :empty?]}, funm, [arg]}, block]}
+  end
+
+  defp style({:unless, m, [{{:., dm, [{:__aliases__, am, [:Enum]}, :empty?]}, funm, [arg]}, block]}) do
+    {:if, m, [{{:., dm, [{:__aliases__, am, [:Enum]}, :any?]}, funm, [arg]}, block]}
+  end
+
   defp style(node), do: node
 
   defp rewrite_arrows(arrows) when is_list(arrows),


Test patch:
diff --git a/test/style/single_node_test.exs b/test/style/single_node_test.exs
index fdc970d..0714169 100644
--- a/test/style/single_node_test.exs
+++ b/test/style/single_node_test.exs
@@ -304,4 +304,42 @@ defmodule Styler.Style.SingleNodeTest do
       assert_style("Enum.reverse(foo, bar) ++ bar")
     end
   end
+
+  describe "Enum.any?/1 => Enum.empty?/1" do
+    test "transforms correctly" do
+      for negator <- ["!", "not "] do
+        assert_style("#{negator}Enum.empty?(foo)", "Enum.any?(foo)")
+        assert_style("#{negator}Enum.any?(foo)", "Enum.empty?(foo)")
+      end
+    end
+
+    test "within unless clause without else block" do
+      # When there is an Unless with an `else` block, the Credo.Check.Refactor.UnlessWithElse rule will be applied
+      assert_style(
+        """
+        unless Enum.any?([]) do
+          a
+        end
+        """,
+        """
+        if Enum.empty?([]) do
+          a
+        end
+        """
+      )
+
+      assert_style(
+        """
+        unless Enum.empty?([]) do
+          a
+        end
+        """,
+        """
+        if Enum.any?([]) do
+          a
+        end
+        """
+      )
+    end
+  end
 end