Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compatibility with LV v0.20 #725

Merged
merged 7 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 27 additions & 9 deletions lib/surface.ex
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ defmodule Surface do
indentation = meta[:indentation] || 0
column = meta[:column] || 1

debug_annotations? = Module.get_attribute(__CALLER__.module, :__debug_annotations__)
component_type = Module.get_attribute(__CALLER__.module, :component_type)

string
Expand All @@ -120,7 +121,9 @@ defmodule Surface do
|> Surface.Compiler.to_live_struct(
debug: Enum.member?(opts, ?d),
file: __CALLER__.file,
line: line
line: line,
caller: __CALLER__,
annotate_content: debug_annotations? && (&Phoenix.LiveView.HTMLEngine.annotate_tagged_content/1)
)
end

Expand Down Expand Up @@ -148,15 +151,12 @@ defmodule Surface do
if File.exists?(file) do
name = file |> Path.rootname() |> Path.basename()

body =
file
|> File.read!()
|> Surface.Compiler.compile(1, __CALLER__, file)
|> Surface.Compiler.to_live_struct()
quote bind_quoted: [file: file, name: name] do
@external_resource file
@file file

body = Surface.__compile_sface__(name, file, __ENV__)

quote do
@external_resource unquote(file)
@file unquote(file)
def unquote(String.to_atom(name))(var!(assigns)) do
_ = var!(assigns)
unquote(body)
Expand All @@ -172,6 +172,24 @@ defmodule Surface do
end
end

@doc false
def __compile_sface__(name, file, env) do
debug_annotations? =
Module.get_attribute(
env.module,
:__debug_annotations__,
Application.get_env(:phoenix_live_view, :debug_heex_annotations, false)
)

file
|> File.read!()
|> Surface.Compiler.compile(1, env, file)
|> Surface.Compiler.to_live_struct(
caller: %Macro.Env{env | file: file, line: 1, function: {String.to_atom(name), 1}},
annotate_content: debug_annotations? && (&Phoenix.LiveView.HTMLEngine.annotate_tagged_content/1)
)
end

@doc """
Converts the given code into Surface's AST.

Expand Down
32 changes: 30 additions & 2 deletions lib/surface/compiler/eex_engine.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ defmodule Surface.Compiler.EExEngine do
for information on this). Finally, it passes these tokens into the engine sequentially in the same
manner as EEx.Compiler.compile/2
"""
alias Surface.Compiler.Helpers
alias Surface.AST
alias Surface.IOHelper
alias Surface.Components.Context
Expand All @@ -27,10 +28,12 @@ defmodule Surface.Compiler.EExEngine do
engine: opts[:engine] || @default_engine,
depth: 0,
context_vars: %{count: 0, changed: []},
scope: []
scope: [],
root_tag?: root_tag?(nodes)
}

nodes
|> maybe_annotate_content(opts[:annotate_content], opts[:caller])
|> to_token_sequence()
|> generate_buffer(state.engine.init(opts), state)
|> maybe_print_expression(
Expand All @@ -40,6 +43,31 @@ defmodule Surface.Compiler.EExEngine do
)
end

defp maybe_annotate_content(nodes, annotate_content, caller) do
if annotate_content do
{before_comment, after_comment} = annotate_content.(caller)
[%AST.Literal{value: before_comment}] ++ nodes ++ [%AST.Literal{value: after_comment}]
else
nodes
end
end

defp root_tag?(nodes) do
Enum.reduce_while(nodes, false, fn
%AST.Tag{}, false ->
{:cont, true}

%AST.Tag{}, true ->
{:halt, false}

%AST.Literal{value: value}, acc ->
if Helpers.blank?(value), do: {:cont, acc}, else: {:halt, false}

_node, _acc ->
{:halt, false}
end)
end

defp to_token_sequence(nodes) do
nodes
|> to_dynamic_nested_html()
Expand All @@ -48,7 +76,7 @@ defmodule Surface.Compiler.EExEngine do
end

defp generate_buffer([], buffer, state) do
ast = state.engine.handle_body(buffer, root: true)
ast = state.engine.handle_body(buffer, root: state.root_tag?)

quote do
require Phoenix.LiveView.TagEngine
Expand Down
2 changes: 1 addition & 1 deletion lib/surface/component.ex
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ defmodule Surface.Component do
@before_compile Surface.Renderer
@before_compile unquote(__MODULE__)

use Phoenix.Component
use Phoenix.Component, unquote(Keyword.drop(opts, [:slot]))
import Phoenix.Component, except: [slot: 1, slot: 2]

@behaviour unquote(__MODULE__)
Expand Down
1 change: 1 addition & 0 deletions lib/surface/components/form.ex
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ defmodule Surface.Components.Form do
assigns = assign(assigns, opts: opts)

~F"""
<!-- XXX -->
<.form :let={form} for={@for} action={@action} {...@opts}>
<#slot {@default, form: form} context_put={__MODULE__, form: form}/>
</.form>
Expand Down
11 changes: 7 additions & 4 deletions lib/surface/components/form/error_tag.ex
Original file line number Diff line number Diff line change
Expand Up @@ -103,18 +103,21 @@ defmodule Surface.Components.Form.ErrorTag do
prop feedback_for, :string

def render(assigns) do
translate_error = assigns.translator || translator_from_config() || (&translate_error/1)
class = assigns.class || get_config(:default_class)
assigns = assign(assigns, :class, assigns.class || get_config(:default_class))

~F"""
<span
:for={error <- Keyword.get_values(@form.errors || [], @field)}
class={class}
class={@class}
phx-feedback-for={@feedback_for || input_name(@form, @field)}
>{translate_error.(error)}</span>
>{translator(assigns.translator).(error)}</span>
"""
end

defp translator(translator) do
translator || translator_from_config() || (&translate_error/1)
end

@doc """
Translates an error message.

Expand Down
12 changes: 10 additions & 2 deletions lib/surface/renderer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,20 @@

template_ast =
if File.exists?(template) do
env = Map.put(env, :function, {:render, 1})
env =
env
|> Map.put(:function, {:render, 1})
|> Map.put(:file, template)

debug_annotations? = Module.get_attribute(__CALLER__.module, :__debug_annotations__)

template
|> File.read!()
|> Surface.Compiler.compile(1, env, template)
|> Surface.Compiler.to_live_struct()
|> Surface.Compiler.to_live_struct(
caller: env,
annotate_content: debug_annotations? && (&Phoenix.LiveView.HTMLEngine.annotate_tagged_content/1)

Check warning on line 25 in lib/surface/renderer.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.4 | Erlang/OTP 24.0)

Phoenix.LiveView.HTMLEngine.annotate_tagged_content/1 is undefined or private
)
else
nil
end
Expand Down
7 changes: 6 additions & 1 deletion lib/surface/type_handler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,12 @@ defmodule Surface.TypeHandler do
{:ok, [~S( ), to_string(name)]}

{:ok, val} ->
{:ok, Phoenix.HTML.attributes_escape([{name, val}])}
attr_value =
[{name, val}]
|> Phoenix.HTML.attributes_escape()
|> Phoenix.HTML.safe_to_string()

{:ok, attr_value}

{:error, message} ->
{:error, message}
Expand Down
11 changes: 9 additions & 2 deletions test/mix/tasks/compile/surface/validate_components_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -222,11 +222,18 @@ defmodule Mix.Tasks.Compile.Surface.ValidateComponentsTest do
use Surface.Component

prop list, :list, required: true
data item, :any
data rest, :list

def render(%{list: [item | rest]} = assigns) do
assigns =
assigns
|> assign(:item, item)
|> assign(:rest, rest)

~F"""
{item}
<Recursive list={rest} />
{@item}
<Recursive list={@rest} />
"""
end

Expand Down
25 changes: 25 additions & 0 deletions test/support/debug_annotations.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
defmodule Surface.CompilerTest.DebugAnnotations do
import Surface.CompilerTest.DebugAnnotationsUtil

use_component()

def func_with_tag(assigns) do
~F[<div>func_with_tag</div>]
end

def func_with_only_text(assigns) do
~F[only_text]
end

def func_with_text_and_tag(assigns) do
~F"""
text_before<br>text_after
"""
end

def func_with_multiple_root_tags(assigns) do
~F[<div>text 1</div><div>text 2</div>]
end

embed_sface "debug_annotations.sface"
end
1 change: 1 addition & 0 deletions test/support/debug_annotations.sface
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
render
20 changes: 20 additions & 0 deletions test/support/debug_annotations_util.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
defmodule Surface.CompilerTest.DebugAnnotationsUtil do
def debug_heex_annotations_supported? do
Application.spec(:phoenix_live_view, :vsn)
|> to_string()
|> Version.parse!()
|> Version.compare("0.20.0") != :lt
end

defmacro use_component() do
if __MODULE__.debug_heex_annotations_supported?() do
quote do
use Surface.Component, debug_heex_annotations: true
end
else
quote do
use Surface.Component
end
end
end
end
Loading
Loading