Repository: adobe/elixir-styler
Pull request #74 (2023-09-12 20:48:57)

if/unless & negation
* if/unless rewrites:
  - `Credo.Check.Refactor.NegatedConditionsWithElse`
  - `Credo.Check.Refactor.NegatedConditionsInUnless`
  - `Credo.Check.Refactor.UnlessWithElse`


debating rewriting `if !! expr, do: ...` to be `if expr, do: ...` as well...


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3d2851e..f2706c8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,10 @@
 ### Improvements
 
 * auto-fix `Credo.Check.Refactor.CondStatements`, detects any truthy atom, not just `true`
+* if/unless rewrites:
+  - `Credo.Check.Refactor.NegatedConditionsWithElse`
+  - `Credo.Check.Refactor.NegatedConditionsInUnless`
+  - `Credo.Check.Refactor.UnlessWithElse`
 
 ## v0.9.0
 
diff --git a/README.md b/README.md
index 367e0d4..86eec93 100644
--- a/README.md
+++ b/README.md
@@ -68,8 +68,11 @@ Some of the rules have `priority: :high`, meaning Credo runs them unless you exp
 | `Credo.Check.Refactor.FilterCount` | in pipes only |
 | `Credo.Check.Refactor.MapInto` | in pipes only |
 | `Credo.Check.Refactor.MapJoin` | in pipes only |
+| `Credo.Check.Refactor.NegatedConditionsInUnless` | |
+| `Credo.Check.Refactor.NegatedConditionsWithElse` | |
 | `Credo.Check.Refactor.PipeChainStart` | allows ecto's `from`|
 | `Credo.Check.Refactor.RedundantWithClauseResult` | |
+| `Credo.Check.Refactor.UnlessWithElse` | |
 | `Credo.Check.Refactor.WithClauses` | |
 
 ## Your first Styling
diff --git a/lib/style/single_node.ex b/lib/style/single_node.ex
index 997eac0..ef4ec76 100644
--- a/lib/style/single_node.ex
+++ b/lib/style/single_node.ex
@@ -212,6 +212,19 @@ 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)}
 
+  # IF / UNLESS & NEGATION REWRITES
+  # Credo.Check.Refactor.UnlessWithElse
+  defp style({:unless, m, [{_, hm, _} = head, [{do_, do_body}, {else_, else_body}]]}),
+    do: style({:if, m, [{:!, hm, [head]}, [{do_, else_body}, {else_, do_body}]]})
+
+  # Credo.Check.Refactor.NegatedConditionsInUnless
+  defp style({:unless, m, [{negator, _, [expr]}, [{do_, do_body}]]}) when negator in [:!, :not],
+    do: style({:if, m, [expr, [{do_, do_body}]]})
+
+  # Credo.Check.Refactor.NegatedConditionsWithElse
+  defp style({:if, m, [{negator, _, [expr]}, [{do_, do_body}, {else_, else_body}]]}) when negator in [:!, :not],
+    do: style({:if, m, [expr, [{do_, else_body}, {else_, do_body}]]})
+
   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 01b11b7..e9d60b7 100644
--- a/test/style/single_node_test.exs
+++ b/test/style/single_node_test.exs
@@ -519,4 +519,129 @@ defmodule Styler.Style.SingleNodeTest do
       """)
     end
   end
+
+  describe "if/else" do
+    test "Credo.Check.Refactor.UnlessWithElse" do
+      for negator <- ["!", "not "] do
+        assert_style(
+          """
+          unless #{negator} a do
+            b
+          else
+            c
+          end
+          """,
+          """
+          if a do
+            c
+          else
+            b
+          end
+          """
+        )
+      end
+
+      assert_style(
+        """
+        unless a do
+          b
+        else
+          c
+        end
+        """,
+        """
+        if a do
+          b
+        else
+          c
+        end
+        """
+      )
+    end
+
+    test "Credo.Check.Refactor.NegatedConditionsInUnless" do
+      for negator <- ["!", "not "] do
+        assert_style("unless #{negator} foo, do: :bar", "if foo, do: :bar")
+
+        assert_style(
+          """
+          unless #{negator} foo do
+            bar
+          end
+          """,
+          """
+          if foo do
+            bar
+          end
+          """
+        )
+      end
+    end
+
+    test "Credo.Check.Refactor.NegatedConditionsWithElse" do
+      for negator <- ["!", "not "] do
+        assert_style("if #{negator}foo, do: :bar")
+        assert_style("if #{negator}foo, do: :bar, else: :baz", "if foo, do: :baz, else: :bar")
+
+        assert_style("""
+        if #{negator}foo do
+          bar
+        end
+        """)
+
+        assert_style(
+          """
+          if #{negator}foo do
+            bar
+          else
+            baz
+          end
+          """,
+          """
+          if foo do
+            baz
+          else
+            bar
+          end
+          """
+        )
+      end
+    end
+
+    test "recurses" do
+      assert_style(
+        """
+        if !!val do
+          a
+        else
+          b
+        end
+        """,
+        """
+        if val do
+          a
+        else
+          b
+        end
+        """
+      )
+
+      assert_style(
+        """
+        unless !! not true do
+          a
+        else
+          b
+        end
+        """,
+        """
+        if true do
+          b
+        else
+          a
+        end
+        """
+      )
+    end
+  end
 end