Repository: adobe/elixir-styler (elixir)

Issue:
`with` handling seems to be broken in 0.11.0
## Versions

* Elixir: 1.15.7
* Styler: 0.11.0

## Example Input

```elixir
defmodule Foo do
  @moduledoc false
  def run(arg) do
    with value <- arg do
      value
    end
  end
end
```

## Stacktrace / Current Behaviour

```
foo$ mix format
** (Styler.StyleError) Error running style Blocks on lib/foo.ex
   Please consider opening an issue at: https://github.com/adobe/elixir-styler/issues/new
** (RuntimeError) unexpected `with` parent ast: {{:__block__, [line: 3], [:do]}, {:with, [do: [line: 4], end: [line: 6], line: 4], [{:<-, [line: 4], [{:value, [line: 4], nil}, {:arg, [line: 4], nil}]}, [{{:__block__, [line: 4], [:do]}, {:value, [line: 5], nil}}]]}}

    (styler 0.11.0) lib/style/blocks.ex:220: Styler.Style.Blocks.replace_with_statement/2
    (styler 0.11.0) lib/style/blocks.ex:79: Styler.Style.Blocks.run/2
    (styler 0.11.0) lib/zipper.ex:367: Styler.Zipper.do_traverse_while/3
    (styler 0.11.0) lib/styler.ex:37: anonymous fn/4 in Styler.style/3
    (elixir 1.15.7) lib/enum.ex:2510: Enum."-reduce/3-lists^foldl/2-0-"/3
    (styler 0.11.0) lib/styler.ex:35: Styler.style/3
    (styler 0.11.0) lib/styler.ex:65: Styler.format/3
    (elixir 1.15.7) lib/enum.ex:2510: Enum."-reduce/3-lists^foldl/2-0-"/3

Skipping style and continuing on
```

Pull request description:
fix: with replacement edge cases
Closes #107

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2e786b1..845eeb8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,10 @@
 
 ## main
 
+### Fixes
+
+* `with`: fix `with` replacement when it's the only child of a `do` or `->` block (Closes #107, h/t @kerryb -- turns out those edge cases _did_ exist in the wild!)
+
 ## v0.11.0
 
 ### Improvements
diff --git a/lib/style/blocks.ex b/lib/style/blocks.ex
index 37ef7fe..8c49973 100644
--- a/lib/style/blocks.ex
+++ b/lib/style/blocks.ex
@@ -202,22 +202,47 @@ defmodule Styler.Style.Blocks do
   # `a = b(); c = d(); :ok`
   defp replace_with_statement(zipper, preroll) do
     [[{_do, do_body} | _elses] | preroll] = Enum.reverse(preroll)
-    block = Enum.reverse(preroll, [do_body])
 
-    case Zipper.up(zipper) do
-      nil ->
-        Zipper.zip({:__block__, [], block})
+    # RedundantWithClauseResult except we rewrote the `<-` to an `=`
+    # with a, b, x <- y(), do: x
+    # =>
+    # a; b; y
+    block =
+      case preroll do
+        [{:=, _, [lhs, rhs]} | rest] ->
+          if nodes_equivalent?(lhs, do_body),
+            do: [rhs | rest],
+            else: [do_body | preroll]
+
+        _ ->
+          [do_body | preroll]
+      end
+
+    block = Enum.reverse(block)
 
+    case Zipper.up(zipper) do
+      # x = with a, b, c, do: d
+      # =>
+      # x =
+      #   (
+      #     a
+      #     b
+      #     c
+      #     d
+      #   )
+      # @TODO would be nice to changeto
+      # a
+      # b
+      # c
+      # x = d
       {{:=, _, _}, _} ->
         Zipper.update(zipper, fn {:with, meta, _} -> {:__block__, Keyword.take(meta, [:line]), block} end)
 
-      {{:__block__, _, _}, _} ->
+      _ ->
         zipper
+        |> Style.ensure_block_parent()
         |> Zipper.prepend_siblings(block)
         |> Zipper.remove()
-
-      {ast, _} ->
-        raise "unexpected `with` parent ast: #{inspect(ast)}"
     end
   end
 


Test patch:
diff --git a/test/style/blocks_test.exs b/test/style/blocks_test.exs
index 75d966f..d337ad9 100644
--- a/test/style/blocks_test.exs
+++ b/test/style/blocks_test.exs
@@ -307,6 +307,36 @@ defmodule Styler.Style.BlocksTest do
         y()
         """
       )
+
+      assert_style(
+        """
+        def run() do
+          with value <- arg do
+            value
+          end
+        end
+        """,
+        """
+        def run do
+          arg
+        end
+        """
+      )
+
+      assert_style(
+        """
+        fn ->
+          with value <- arg do
+            value
+          end
+        end
+        """,
+        """
+        fn ->
+          arg
+        end
+        """
+      )
     end
 
     test "doesn't false positive with vars" do