phoenixframework / phoenix_live_view

Rich, real-time user experiences with server-rendered HTML
https://hex.pm/packages/phoenix_live_view
MIT License
6.12k stars 917 forks source link

(FunctionClauseError) no function clause matching in Phoenix.LiveView.HTMLTokenizer.handle_tag_name/3 #1608

Closed dabaer closed 3 years ago

dabaer commented 3 years ago

Environment

Actual behavior

Certain invalid template sequences completely break the tokenizer. BASICALLY, my template voodoo interacted with your tokenizer voodoo and exploded.

For example, line 2 of this admittedly horrible no good template code (albeit perfectly valid in EEx)

<%= for {cust, orders} <- @orders_cust do %>
  <tr<%= if cust == @cust do %> class="current"<% end %>>
    <td><%= Enum.count(orders) %></td>
    <td><%= link cust, to: Routes.invoice_path(@conn, :auditable, cust: cust) %></td>
  </tr>
<% end %>

Results in:

** (FunctionClauseError) no function clause matching in Phoenix.LiveView.HTMLTokenizer.handle_tag_name/3

    The following arguments were given to Phoenix.LiveView.HTMLTokenizer.handle_tag_name/3:

        # 1
        ""

        # 2
        12

        # 3
        ["r", "t"]

    Attempted function clauses (showing 3 out of 3):

        defp handle_tag_name(<<c::utf8(), _rest::binary()>>, _column, _buffer = []) when c === 32 or c === 9 or c === 12 or c === 62 or c === 47 or c === 61 or c === 13 or c === 10
        defp handle_tag_name(<<c::utf8(), _rest::binary()>> = text, column, buffer) when c === 32 or c === 9 or c === 12 or c === 62 or c === 47 or c === 61 or c === 13 or c === 10
        defp handle_tag_name(<<c::utf8(), rest::binary()>>, column, buffer)

    (phoenix_live_view 0.16.3) lib/phoenix_live_view/html_tokenizer.ex:170: Phoenix.LiveView.HTMLTokenizer.handle_tag_name/3
    (phoenix_live_view 0.16.3) lib/phoenix_live_view/html_tokenizer.ex:126: Phoenix.LiveView.HTMLTokenizer.handle_tag_open/5
    (phoenix_live_view 0.16.3) lib/phoenix_live_view/html_engine.ex:89: Phoenix.LiveView.HTMLEngine.handle_text/3
    (eex 1.12.2) lib/eex/compiler.ex:48: EEx.Compiler.generate_buffer/4
    (eex 1.12.2) lib/eex/compiler.ex:80: EEx.Compiler.generate_buffer/4
    (phoenix_view 1.0.0) lib/phoenix/template.ex:371: Phoenix.Template.compile/3
    (phoenix_view 1.0.0) lib/phoenix/template.ex:165: anonymous fn/4 in Phoenix.Template."MACRO-__before_compile__"/2
    (elixir 1.12.2) lib/enum.ex:2385: Enum."-reduce/3-lists^foldl/2-0-"/3
    (phoenix_view 1.0.0) expanding macro: Phoenix.Template.__before_compile__/1
    lib/ship_web/views/invoice_view.ex:1: ShipWeb.InvoiceView (module)
    (elixir 1.12.2) lib/kernel/parallel_compiler.ex:319: anonymous fn/4 in Kernel.ParallelCompiler.spawn_workers/7

which only points to the parent view file.

Expected behavior

A helpful error message such as happens with old style EEx tags inside of HTML tags (<%= @blah %>):

** (Phoenix.LiveView.HTMLTokenizer.ParseError) lib/proj/templates/invoice/to_audit.html.heex:24:12: expected closing `"` for attribute value

This may happen if there is an EEx interpolation inside a tag,
which is not supported. Instead of

    <div <%= @some_attributes %>>
    </div>

do

    <div {@some_attributes}>
    </div>

Where @some_attributes must be a keyword list or a map.

as happens with other similar template areas.

This appears to happen because rather than interpolating the variable directly into an attribute, i'm interpolating the entire attribute expression.

I completely understand this is a problem with my code, and know how to fix it on my side, but perhaps the tokenizer could be improved to be more helpful in cases like this. I suppose valid EEx syntax should also not break HEEx's tokenizer.

josevalim commented 3 years ago

HEEx is by definition a restricted version of EEx, so not all EEx works, but you are definitely correct the error message has to be better. :)

dabaer commented 3 years ago

A gentleman and a scholar. Thank you for fixing it so fast!