HEEX formatter adds a line break in generated html?

HEEX formatter (Phoenix.LiveView.HTMLFormatter / mix format) seems to introduce an unexpected line break in the generated html. User error? A bug? ; )

This looks super-related to Formatter adds space/newline to inline elements · Issue #2237 · phoenixframework/phoenix_live_view · GitHub, but, AFAICT, the fix got merged in LiveView 0.18.2, and I am seeing the problem in 0.18.3.

Details:

Here is my repro (happy to provide a more streamline repro gist, if useful):

I want to add a comma after some text

        <%= link to: ... do %>
          <%= @item.content %><% end %>,

and this works great. Here is the web page (loaded in Safari, on a Mac):
image

Running mix format, moves the <% end %> element (and, therefore, the comma that follows) to its own line:

        <%= link to: ... do %>
          <%= @item.content %>
        <% end %>,

which introduces a space before the comma:
image

I am not expecting a code formatter to change what the code does, only what the code looks like. Am I doing something obviously silly here, or does this look like a bug? ; )

Thank you!

PS A bit more data:

$ cat .formatter.exs
[
  line_length: 120,
  import_deps: [:ecto, :phoenix],
  plugins: [Phoenix.LiveView.HTMLFormatter],
  inputs: ["*.{heex,ex,exs}", "priv/*/seeds.exs", "{config,lib,test}/**/*.{heex,ex,exs}"],
  subdirectories: ["priv/*/migrations"]
]
$ cat mix.exs | grep phoenix
      {:phoenix, "~> 1.6"},
      {:phoenix_ecto, "~> 4.4"},
      {:phoenix_html, "~> 3.2"},
      {:phoenix_live_dashboard, "~> 0.7"},
      {:phoenix_live_reload, "~> 1.4", only: :dev},
      {:phoenix_live_view, "~> 0.18.3"},
$ elixir --version
Erlang/OTP 25 [erts-13.1.1] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] [dtrace]

Elixir 1.14.1 (compiled with Erlang/OTP 25)
1 Like

I guess this has to do with how how HTML is specced. Browsers condense multiple whitespace characters (including newlines) to a single space for most scripts (Latin for example).

Can you show us the generated HTML source? Won’t be surprised when it looks like

<a href=“http://foo.com”>content</a>
,

or

<a href=“http://foo.com”>
   content
</a>
,

W3C about whitespace

However…as this is code and not HTML I guess the formatter should recognize such and this is a bug.

1 Like

In the meantime, you could do:

<%= link to: ... do %>
  <%= "#{@item.content}," %>
<% end %>
1 Like

Sure, here is what the html looks like in both cases:

$ elixir heexformat.exs
next_line: "<a href=\"/i\">\n  next line\n</a>,\n" <<<<< notice \n just before the closing </a> and ","
same_line: "<a href=\"/i\">\n  same line</a>,\n"

, where heexformat.exs is

Mix.install(
  [
    {:jason, "~> 1.4"},
    {:phoenix, "~> 1.6"},
    {:phoenix_live_view, "~> 0.18.3"}
  ],
  config: [
    phoenix: [json_library: Jason]
  ]
)

defmodule HeexFormatTest do
  use Phoenix.HTML
  import Phoenix.Component, only: [sigil_H: 2]

  def to_text(h) do
    h
    |> Phoenix.HTML.Safe.to_iodata()
    |> List.to_string()
  end

  def generate_html_next_line() do
    assigns = %{}

    ~H"""
    <%= link to: "/i", method: :get do %>
      <%= "next line" %>
    <% end %>,
    """
  end

  def generate_html_same_line() do
    assigns = %{}

    ~H"""
    <%= link to: "/i", method: :get do %>
      <%= "same line" %><% end %>,
    """
  end
end

HeexFormatTest.generate_html_next_line() |> HeexFormatTest.to_text() |> IO.inspect(label: :next_line)
HeexFormatTest.generate_html_same_line() |> HeexFormatTest.to_text() |> IO.inspect(label: :same_line)

Update: this is by design:

Thank you, José!

2 Likes

Does anyone know how to deal with this?

For example, if I write

<a href="/foo">Foo</a>

Formatting it can result in

<a href="/foo">Foo </a>

Adding a space and changing the appearance.

This happens whether I use <a> or <.link>.

Your example formats just fine for me. But you can also use this: Phoenix.LiveView.HTMLFormatter — Phoenix LiveView v1.0.9

Sorry, I simplied it. I’ll check out the LiveView formatter, thanks.

Here’s a reproducible example:

<div>
  <div>
    <div>
      <div>
        <div>
          <div>
            <div>
              <a
                class="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
                href="/foo"
              >Foo</a>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

Did you look at the link he posted? The documentation describes a phx-no-format attribute you can add to any element to disable formatting for that element. So in your case:

<a
  phx-no-format
  class="..."

Yes I did, and that avoids the problem. But I agree with @markmark206 - it’s surprising that the formatter adds whitespace to visible text where there was previously none. So it’s really a workaround, rather than a solution.

Out of curiosity, I checked what Prettier does, and it formats it as the following, which preserves the visible whitespace.

<a
  class="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
  href="/foo"
  >Foo</a
>
1 Like