Repository: surface-ui/surface_formatter (elixir)

Issue:
FunctionClauseError in Surface.Formatter.Phases.Render.quoted_strings_with_newlines/1    
The problem is new in `0.7.3`, it worked in `0.7.2`.

Update PR in our project: https://github.com/jshmrtn/hygeia/pull/1036
Failed CI (detailed error): https://github.com/jshmrtn/hygeia/runs/4584434083?check_suite_focus=true
File with Problems: https://github.com/jshmrtn/hygeia/blob/master/lib/hygeia_web/live/organisation_live/merge.sface

Detailed Error:

```
mix format --check-formatted
mix format failed for file: lib/hygeia_web/live/organisation_live/merge.sface
** (FunctionClauseError) no function clause matching in Surface.Formatter.Phases.Render.quoted_strings_with_newlines/1    
    
    The following arguments were given to Surface.Formatter.Phases.Render.quoted_strings_with_newlines/1:
    
        # 1
        {:unless, [line: 1], [{:is_nil, [line: 1], [{:@, [line: 1], [{:delete, [line: 1], nil}]}]}, [do: {{:., [line: 1], [{:@, [line: 1], [{:delete, [line: 1], nil}]}, :uuid]}, [no_parens: true, line: 1], []}]]}
    
    Attempted function clauses (showing 2 out of 2):
    
        defp quoted_strings_with_newlines(+attribute_expression+) when -is_binary(attribute_expression)-
        defp quoted_strings_with_newlines(+nodes+) when -is_list(nodes)-
    
    lib/surface/formatter/phases/render.ex:377: Surface.Formatter.Phases.Render.quoted_strings_with_newlines/1
Warning:     lib/surface/formatter/phases/render.ex:445: Surface.Formatter.Phases.Render.format_attribute_expression/2
    lib/surface/formatter/phases/render.ex:371: Surface.Formatter.Phases.Render.render_attribute/2
Warning:     (elixir 1.13.0) lib/enum.ex:1593: Enum."-map/2-lists^map/1-0-"/2
    (elixir 1.13.0) lib/enum.ex:1593: Enum."-map/2-lists^map/1-0-"/2
Warning:     lib/surface/formatter/phases/render.ex:203: Surface.Formatter.Phases.Render.render_opening_tag/3
    (elixir 1.13.0) lib/enum.ex:1593: Enum."-map/2-lists^map/1-0-"/2
Warning:     (elixir 1.13.0) lib/enum.ex:1593: Enum."-map/2-lists^map/1-0-"/2
Error: Process completed with exit code 1.
```



Pull request description:
Handle arbitrary expressions when checking for newlines in strings
Fixes #55

Patch:
diff --git a/lib/surface/formatter/phases/render.ex b/lib/surface/formatter/phases/render.ex
index 7be2195..5d1fb51 100644
--- a/lib/surface/formatter/phases/render.ex
+++ b/lib/surface/formatter/phases/render.ex
@@ -389,7 +389,20 @@ defmodule Surface.Formatter.Phases.Render do
           []
         end
 
-      {:<<>>, _, nodes} when is_list(nodes) ->
+      [{:do, string}] when is_binary(string) ->
+        if String.contains?(string, "\n") do
+          [string]
+        else
+          []
+        end
+
+      {operation, _, nodes} when is_atom(operation) and is_list(nodes) ->
+        quoted_strings_with_newlines(nodes)
+
+      [{:do, node_or_nodes}] ->
+        quoted_strings_with_newlines(node_or_nodes)
+
+      nodes when is_list(nodes) ->
         quoted_strings_with_newlines(nodes)
 
       _ ->
@@ -398,6 +411,10 @@ defmodule Surface.Formatter.Phases.Render do
     |> List.flatten()
   end
 
+  defp quoted_strings_with_newlines(node) do
+    quoted_strings_with_newlines([node])
+  end
+
   defp is_keyword_item_with_interpolated_key?(item) do
     case item do
       {{{:., _, [:erlang, :binary_to_atom]}, _, [_, :utf8]}, _} -> true


Test patch:
diff --git a/test/surface/formatter_test.exs b/test/surface/formatter_test.exs
index 8b3194f..bfb30b0 100644
--- a/test/surface/formatter_test.exs
+++ b/test/surface/formatter_test.exs
@@ -755,7 +755,7 @@ defmodule Surface.FormatterTest do
       )
     end
 
-    test "attribute expressions that are a list merged with a keyword list are formatted" do
+    test "attribute expressions that are a list merged with a keyword list" do
       assert_formatter_outputs(
         """
         <span class={ "container", "container--dark": @dark_mode } />
@@ -766,7 +766,7 @@ defmodule Surface.FormatterTest do
       )
     end
 
-    test "attribute expressions with a function call that omits parentheses are formatted" do
+    test "attribute expressions with a function call that omits parentheses" do
       assert_formatter_outputs(
         """
         <Component items={ Enum.map @items, & &1.foo }/>
@@ -796,7 +796,7 @@ defmodule Surface.FormatterTest do
       )
     end
 
-    test "(bugfix) attribute expresssions that are keyword lists without brackets and with interpolated string keys are formatted and don't crash" do
+    test "(bugfix) attribute expresssions that are keyword lists without brackets, with interpolated string keys" do
       assert_formatter_outputs(
         ~S"""
         <Component attr={"a-#{@b}": c} />
@@ -829,7 +829,7 @@ defmodule Surface.FormatterTest do
       )
     end
 
-    test "dynamic attributes are formatted" do
+    test "dynamic attributes" do
       assert_formatter_outputs(
         """
         <Foo { ... @bar } />
@@ -892,7 +892,7 @@ defmodule Surface.FormatterTest do
       """)
     end
 
-    test "shorthand assigns passthrough attributes are formatted" do
+    test "shorthand assigns passthrough attributes" do
       assert_formatter_outputs(
         """
         <Foo {= @bar} />
@@ -923,7 +923,7 @@ defmodule Surface.FormatterTest do
       )
     end
 
-    test "root prop is formatted" do
+    test "root prop" do
       assert_formatter_outputs(
         """
         <MyIf { @var  >  10 } />
@@ -986,6 +986,60 @@ defmodule Surface.FormatterTest do
       <div :if={true} />
       """)
     end
+
+    test "strings in attribute expressions with keyword shorthand aren't modified" do
+      # By putting at least 2 attributes in the following examples,
+      # we make sure to hit `Surface.Formatter.Phases.Render.quoted_strings_with_newlines/1`
+      # which is only used with multiple attributes.
+      #
+      # Rendering a multi-attribute node involves extra specialized logic
+      # for dealing with newlines in strings properly.
+
+      assert_formatter_doesnt_change("""
+      <Component
+        id="foo"
+        value={if @bar, do: "baz
+
+        qux"}
+      />
+      """)
+
+      # deeply nested in blocks
+      assert_formatter_doesnt_change("""
+      <Component
+        id="foo"
+        value={if @bar do
+          if @baz do
+            unless @qux do
+              "result
+              with
+              newlines"
+            end
+          end
+        end}
+      />
+      """)
+
+      assert_formatter_doesnt_change("""
+      <Component
+        id="1"
+        value={case @foo do
+          "bar" ->
+            with "qux" <- @baz,
+                 :ok <- value?() do
+              cond do
+                @test != "newliney
+                " ->
+                  "pass"
+              end
+            end
+
+          :baz ->
+            nil
+        end}
+      />
+      """)
+    end
   end
 
   describe "[blocks]" do