Repository: surface-ui/surface_formatter (elixir)

Issue:
moves next element onto end of previous line
Hi,

I thought I'd try with v0.6.0 but it is still moving stuff up onto the previous line.  I feel like it worked when I first tried it but lately it is shifting comments and other tags up onto the end of the line above it.  As you can see in the diff below, the commented out `div`, `GotoTime` and the start of the surface comment `{!--` all disappeared at the end of the `AlarmDivisionFilterDropdown`.  And for some reason it put the `<button ...` lines onto the one line.

![image](https://user-images.githubusercontent.com/22566656/138456069-8b452f70-779c-461d-926e-0a72b0019d59.png)

I'm on Elixir 1.12.3, OTP 24, surface & formatter 0.6 and Windows 10, though I use `LF` line endings.


Pull request description:
Handle multiline strings in expressions regardless of invisible brackets
Fixes #49

Patch:
diff --git a/lib/surface/formatter/phases/render.ex b/lib/surface/formatter/phases/render.ex
index 6ddbd5a..7be2195 100644
--- a/lib/surface/formatter/phases/render.ex
+++ b/lib/surface/formatter/phases/render.ex
@@ -423,39 +423,39 @@ defmodule Surface.Formatter.Phases.Render do
 
   @spec format_attribute_expression(String.t(), [render_attribute_option]) :: String.t()
   defp format_attribute_expression(expression, opts) when is_binary(expression) do
-    if has_invisible_brackets?(expression) do
-      # handle keyword lists, which will be stripped of the outer brackets per surface syntax sugar
-      formatted =
+    formatted =
+      if has_invisible_brackets?(expression) do
+        # handle keyword lists, which will be stripped of the outer brackets per surface syntax sugar
         "[#{expression}]"
         |> Code.format_string!(locals_without_parens: [...: 1])
         |> Enum.slice(1..-2)
         |> to_string()
-
-      if length(Keyword.get(opts, :attributes, [])) > 1 do
-        # handle scenario where list contains string(s) with newlines;
-        # in order to ensure the formatter is idempotent (always emits
-        # the same output when run more than once), we dedent newlines
-        # in strings because multi-line attributes are later indented
-        expression
-        |> quoted_strings_with_newlines()
-        |> Enum.uniq()
-        |> Enum.reduce(formatted, fn string_with_newlines, formatted ->
-          dedented =
-            String.replace(
-              string_with_newlines,
-              "\n#{String.duplicate(@tab, opts[:indent] + 1)}",
-              "\n"
-            )
-
-          String.replace(formatted, string_with_newlines, dedented)
-        end)
       else
-        formatted
+        expression
+        |> Code.format_string!(locals_without_parens: [...: 1])
+        |> to_string()
       end
-    else
+
+    if length(Keyword.get(opts, :attributes, [])) > 1 do
+      # handle scenario where list contains string(s) with newlines;
+      # in order to ensure the formatter is idempotent (always emits
+      # the same output when run more than once), we dedent newlines
+      # in strings because multi-line attributes are later indented
       expression
-      |> Code.format_string!(locals_without_parens: [...: 1])
-      |> to_string()
+      |> quoted_strings_with_newlines()
+      |> Enum.uniq()
+      |> Enum.reduce(formatted, fn string_with_newlines, formatted ->
+        dedented =
+          String.replace(
+            string_with_newlines,
+            "\n#{String.duplicate(@tab, opts[:indent] + 1)}",
+            "\n"
+          )
+
+        String.replace(formatted, string_with_newlines, dedented)
+      end)
+    else
+      formatted
     end
   end
 


Test patch:
diff --git a/test/surface/formatter_test.exs b/test/surface/formatter_test.exs
index 43c9f83..8b3194f 100644
--- a/test/surface/formatter_test.exs
+++ b/test/surface/formatter_test.exs
@@ -1523,10 +1523,43 @@ defmodule Surface.FormatterTest do
     )
   end
 
-  test "Multi-line strings in lists in attributes aren't indented every time the formatter is ran" do
+  test "Multi-line strings in attributes aren't indented every time the formatter is ran" do
+    # multiple attributes
+    assert_formatter_doesnt_change(~S"""
+    <div
+      class="test"
+      x-data={"{
+        foo: '#{@bar}',
+        baz: '#{@qux}'
+      }"}
+    />
+    """)
+
+    # single attribute
+    assert_formatter_doesnt_change(~S"""
+    <div x-data={"{
+      foo: '#{@bar}',
+      baz: '#{@qux}'
+    }"} />
+    """)
+
+    # nested multiline strings
+    assert_formatter_doesnt_change(~S"""
+    <div x-data={"{
+      foo: '#{@bar}',
+      baz: '#{"[
+        nested
+        multiline
+        string
+      ]"}'
+    }"} />
+    """)
+
+    # lists in attributes
     assert_formatter_doesnt_change(~S"""
     <Wrapper>
       <Wrapper>
+        {!-- indenting this component allowed us to reproduce a bug --}
         <First
           class={
             "w-full h-12 max-w-full px-4 bg-black-100 hover:bg-black-120 text-base leading-normal