Repository: surface-ui/surface_formatter
Pull request #51 (2021-11-29 22:28:57)

Render blank lines in expressions without adding trailing whitespace


Patch:
diff --git a/lib/surface/formatter/phases/render.ex b/lib/surface/formatter/phases/render.ex
index 05b1b0c..6ddbd5a 100644
--- a/lib/surface/formatter/phases/render.ex
+++ b/lib/surface/formatter/phases/render.ex
@@ -20,6 +20,8 @@ defmodule Surface.Formatter.Phases.Render do
   # Line length of opening tags before splitting attributes onto their own line
   @default_line_length 98
 
+  @newline_not_followed_by_newline ~r/\n(?!\n)/
+
   @doc """
   Given a `t:Surface.Formatter.formatter_node/0` node, render it to a string
   for writing back into a file.
@@ -37,7 +39,7 @@ defmodule Surface.Formatter.Phases.Render do
 
         String.replace(
           "{#{formatted}}",
-          "\n",
+          @newline_not_followed_by_newline,
           "\n#{String.duplicate(@tab, opts[:indent])}"
         )
 
@@ -132,7 +134,10 @@ defmodule Surface.Formatter.Phases.Render do
 
     opening =
       "{##{name}#{if expr, do: " "}#{expr}}"
-      |> String.replace("\n", "\n" <> String.duplicate(@tab, opts[:indent] + 1))
+      |> String.replace(
+        @newline_not_followed_by_newline,
+        "\n" <> String.duplicate(@tab, opts[:indent] + 1)
+      )
 
     next_indent =
       case children do
@@ -245,7 +250,9 @@ defmodule Surface.Formatter.Phases.Render do
             #     baz: true
             #   }
             # to look right
-            with_newlines_indented = String.replace(attr, "\n", "\n#{attr_indentation}")
+            with_newlines_indented =
+              String.replace(attr, @newline_not_followed_by_newline, "\n#{attr_indentation}")
+
             "#{attr_indentation}#{with_newlines_indented}"
         end)
 
@@ -275,8 +282,11 @@ defmodule Surface.Formatter.Phases.Render do
             joined_attributes =
               rendered_attributes
               |> Enum.map(fn
-                {:do_not_indent_newlines, attr} -> attr
-                attr -> String.replace(attr, "\n", "\n#{attr_indentation}")
+                {:do_not_indent_newlines, attr} ->
+                  attr
+
+                attr ->
+                  String.replace(attr, @newline_not_followed_by_newline, "\n#{attr_indentation}")
               end)
               |> Enum.join(" ")
 


Test patch:
diff --git a/test/surface/formatter_test.exs b/test/surface/formatter_test.exs
index 489915e..43c9f83 100644
--- a/test/surface/formatter_test.exs
+++ b/test/surface/formatter_test.exs
@@ -4,7 +4,7 @@ defmodule Surface.FormatterTest do
   alias Surface.Formatter
 
   def assert_formatter_outputs(input_code, expected_formatted_result, opts \\ []) do
-    assert expected_formatted_result == Formatter.format_string!(input_code, opts)
+    assert Formatter.format_string!(input_code, opts) == expected_formatted_result
 
     # demonstrate that the output can be parsed by the Surface parser
     Surface.Compiler.Parser.parse!(expected_formatted_result)
@@ -1606,4 +1606,31 @@ defmodule Surface.FormatterTest do
     } />
     """)
   end
+
+  test "newlines without trailing whitespace in formatted attribute expressions" do
+    # This demonstrates a bugfix where the empty newline in between case clauses
+    # would be indented with spaces even though there were no contents on the line.
+    assert_formatter_outputs(
+      ~S"""
+      <Component bool_prop attribute={case @foo do
+        "bar" ->
+          true
+        "baz" ->
+          false
+      end} />
+      """,
+      ~S"""
+      <Component
+        bool_prop
+        attribute={case @foo do
+          "bar" ->
+            true
+
+          "baz" ->
+            false
+        end}
+      />
+      """
+    )
+  end
 end