Repository: adobe/elixir-styler (elixir)

Issue:
With to case rewrite fails with else stab `(_ -> "")`
## Description
When trying to format code containing the pattern `(_ -> "")`, Styler 1.3.3 crashes with a FunctionClauseError.

## Environment
- Elixir: 1.18.1
- Styler: 1.3.3

## Error Message
```
** (FunctionClauseError) no function clause matching in Enum."-reduce/3-lists^foldl/2-0-"/3

    The following arguments were given to Enum."-reduce/3-lists^foldl/2-0-"/3:

        # 1
        #Function<44.12736194/2 in Code.Formatter.traverse_line>

        # 2
        {1, 456}

        # 3
        {:__block__, [closing: [line: 457], line: 457], [[{:->, [line: 457], [[{:_, [line: 457], nil}], {:__block__, [delimiter: "\"", line: 457], [""]}]}]]}
```

## Minimal Example
```elixir
with :ok <- can(current_user, _, _),
         do: email,
         else: (_ -> "")
```

## Expected Behavior
The formatter should handle this valid Elixir syntax without crashing.

## Actual Behavior
The formatter crashes with a FunctionClauseError when attempting to format this code.


Pull request description:
fix with to case rewrites when with is keyword with an else stab
Closes #219

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9ebaff7..7468937 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,7 +9,8 @@ they can and will change without that change being reflected in Styler's semanti
 
 #### Ex1.17+
 
-Replace `:timer.units(x)` with the new `to_timeout(unit: x)` for `hours|minutes|seconds`
+- Replace `:timer.units(x)` with the new `to_timeout(unit: x)` for `hours|minutes|seconds`
+- Handle `, else: (_ -> x)` bugs introduced by `(_ -> x)` being termed a literal (#219, h/t @iamhassangm)
 
 #### Ex1.18+
 
@@ -48,6 +49,7 @@ This release taught Styler to try just that little bit harder when doing alias l
 ### Fixes
 
 - `pipes`: handle pipifying when the first arg is itself a pipe: `c(a |> b, d)` => `a |> b() |> c(d)` (#214, h/t @kybishop)
+- `with`: correctly handle a stabby `with` `, else: (_ -> :ok)` being rewritten to a case (#219, h/t @iamhassangm)
 
 ## 1.3.3
 
diff --git a/lib/style/blocks.ex b/lib/style/blocks.ex
index ac8e7a5..fd51be1 100644
--- a/lib/style/blocks.ex
+++ b/lib/style/blocks.ex
@@ -53,10 +53,31 @@ defmodule Styler.Style.Blocks do
   # to `case single_statement do success -> body; ...elses end`
   def run({{:with, m, [{:<-, am, [success, single_statement]}, [body, elses]]}, zm}, ctx) do
     {{:__block__, do_meta, [:do]}, body} = body
-    {{:__block__, _else_meta, [:else]}, elses} = elses
+    {{:__block__, _, [:else]}, elses} = elses
+
+    elses =
+      case elses do
+        # unwrap a stab ala `, else: (_ -> :ok)`. these became literals in 1.17
+        {:__block__, _, [[{:->, _, _}] = stab]} -> stab
+        elses -> elses
+      end
+
+    # drops keyword formatting etc
+    do_meta = [line: do_meta[:line]]
     clauses = [{{:__block__, am, [:do]}, [{:->, do_meta, [[success], body]} | elses]}]
+    end_line = Style.max_line(elses) + 1
+
+    # fun fact: i added the detailed meta just because i noticed it was missing while debugging something ...
+    # ... and it fixed the bug 🤷
+    case_meta = [
+      end_of_expression: [newlines: 1, line: end_line],
+      do: do_meta,
+      end: [line: end_line],
+      line: m[:line]
+    ]
+
     # recurse in case this new case should be rewritten to a `if`, etc
-    run({{:case, m, [single_statement, clauses]}, zm}, ctx)
+    run({{:case, case_meta, [single_statement, clauses]}, zm}, ctx)
   end
 
   # `with true <- x, do: bar` =>`if x, do: bar`


Test patch:
diff --git a/test/style/blocks_test.exs b/test/style/blocks_test.exs
index 83cbab4..cf2d164 100644
--- a/test/style/blocks_test.exs
+++ b/test/style/blocks_test.exs
@@ -239,7 +239,7 @@ defmodule Styler.Style.BlocksTest do
     end
   end
 
-  describe "with statements" do
+  describe "with" do
     test "replacement due to no (or all removed) arrows" do
       assert_style(
         """
@@ -787,6 +787,20 @@ defmodule Styler.Style.BlocksTest do
       end
       """
     end
+
+    test "elixir1.17+ stab regressions" do
+      assert_style(
+        """
+        with :ok <- foo, do: :bar, else: (_ -> :baz)
+        """,
+        """
+        case foo do
+          :ok -> :bar
+          _ -> :baz
+        end
+        """
+      )
+    end
   end
 
   test "Credo.Check.Refactor.CondStatements" do