From 42e8ff6baacdf8253d43b56ebf9ddb8df31bbd9a Mon Sep 17 00:00:00 2001 From: Scott Ming Date: Sun, 13 Aug 2023 11:09:49 +0800 Subject: [PATCH 1/6] Add simple liveview project generated by `mix new live_demo`, then add the `liveview` deps --- .../test/fixtures/live_demo/.formatter.exs | 6 +++++ .../test/fixtures/live_demo/.gitignore | 26 ++++++++++++++++++ .../test/fixtures/live_demo/README.md | 21 +++++++++++++++ .../fixtures/live_demo/lib/core_components.ex | 19 +++++++++++++ .../fixtures/live_demo/lib/simple_live.ex | 5 ++++ .../live_demo/lib/simple_live.html.heex | 3 +++ .../test/fixtures/live_demo/mix.exs | 27 +++++++++++++++++++ .../test/fixtures/live_demo/mix.lock | 14 ++++++++++ 8 files changed, 121 insertions(+) create mode 100644 apps/remote_control/test/fixtures/live_demo/.formatter.exs create mode 100644 apps/remote_control/test/fixtures/live_demo/.gitignore create mode 100644 apps/remote_control/test/fixtures/live_demo/README.md create mode 100644 apps/remote_control/test/fixtures/live_demo/lib/core_components.ex create mode 100644 apps/remote_control/test/fixtures/live_demo/lib/simple_live.ex create mode 100644 apps/remote_control/test/fixtures/live_demo/lib/simple_live.html.heex create mode 100644 apps/remote_control/test/fixtures/live_demo/mix.exs create mode 100644 apps/remote_control/test/fixtures/live_demo/mix.lock diff --git a/apps/remote_control/test/fixtures/live_demo/.formatter.exs b/apps/remote_control/test/fixtures/live_demo/.formatter.exs new file mode 100644 index 000000000..8e65327d1 --- /dev/null +++ b/apps/remote_control/test/fixtures/live_demo/.formatter.exs @@ -0,0 +1,6 @@ +[ + import_deps: [:phoenix], + subdirectories: ["priv/*/migrations"], + plugins: [Phoenix.LiveView.HTMLFormatter], + inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}"] +] diff --git a/apps/remote_control/test/fixtures/live_demo/.gitignore b/apps/remote_control/test/fixtures/live_demo/.gitignore new file mode 100644 index 000000000..1feae788f --- /dev/null +++ b/apps/remote_control/test/fixtures/live_demo/.gitignore @@ -0,0 +1,26 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +live_demo-*.tar + +# Temporary files, for example, from tests. +/tmp/ diff --git a/apps/remote_control/test/fixtures/live_demo/README.md b/apps/remote_control/test/fixtures/live_demo/README.md new file mode 100644 index 000000000..e3285b38f --- /dev/null +++ b/apps/remote_control/test/fixtures/live_demo/README.md @@ -0,0 +1,21 @@ +# LiveDemo + +**TODO: Add description** + +## Installation + +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `live_demo` to your list of dependencies in `mix.exs`: + +```elixir +def deps do + [ + {:live_demo, "~> 0.1.0"} + ] +end +``` + +Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) +and published on [HexDocs](https://hexdocs.pm). Once published, the docs can +be found at . + diff --git a/apps/remote_control/test/fixtures/live_demo/lib/core_components.ex b/apps/remote_control/test/fixtures/live_demo/lib/core_components.ex new file mode 100644 index 000000000..3051dcb3d --- /dev/null +++ b/apps/remote_control/test/fixtures/live_demo/lib/core_components.ex @@ -0,0 +1,19 @@ +defmodule LiveDemo.CoreComponents do + use Phoenix.Component + + def render(assigns) do + ~H""" +
+

CoreComponents

+
+ """ + end + + attr :name, :string, default: "World" + + def greet(assigns) do + ~H""" +

Hello, <%= @name %>!

+ """ + end +end diff --git a/apps/remote_control/test/fixtures/live_demo/lib/simple_live.ex b/apps/remote_control/test/fixtures/live_demo/lib/simple_live.ex new file mode 100644 index 000000000..fc548d106 --- /dev/null +++ b/apps/remote_control/test/fixtures/live_demo/lib/simple_live.ex @@ -0,0 +1,5 @@ +defmodule LiveDemo.SimpleLive do + import LiveDemo.CoreComponents + + use Phoenix.LiveView +end diff --git a/apps/remote_control/test/fixtures/live_demo/lib/simple_live.html.heex b/apps/remote_control/test/fixtures/live_demo/lib/simple_live.html.heex new file mode 100644 index 000000000..c58604d56 --- /dev/null +++ b/apps/remote_control/test/fixtures/live_demo/lib/simple_live.html.heex @@ -0,0 +1,3 @@ +
simple
+<.greet name={@name} /> +
simple live ended
diff --git a/apps/remote_control/test/fixtures/live_demo/mix.exs b/apps/remote_control/test/fixtures/live_demo/mix.exs new file mode 100644 index 000000000..17b94d0af --- /dev/null +++ b/apps/remote_control/test/fixtures/live_demo/mix.exs @@ -0,0 +1,27 @@ +defmodule LiveDemo.MixProject do + use Mix.Project + + def project do + [ + app: :live_demo, + version: "0.1.0", + elixir: "~> 1.13", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger] + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + {:phoenix_live_view, "~> 0.19.5"} + ] + end +end diff --git a/apps/remote_control/test/fixtures/live_demo/mix.lock b/apps/remote_control/test/fixtures/live_demo/mix.lock new file mode 100644 index 000000000..1e8fe4c98 --- /dev/null +++ b/apps/remote_control/test/fixtures/live_demo/mix.lock @@ -0,0 +1,14 @@ +%{ + "castore": {:hex, :castore, "1.0.3", "7130ba6d24c8424014194676d608cb989f62ef8039efd50ff4b3f33286d06db8", [:mix], [], "hexpm", "680ab01ef5d15b161ed6a95449fac5c6b8f60055677a8e79acf01b27baa4390b"}, + "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, + "phoenix": {:hex, :phoenix, "1.7.7", "4cc501d4d823015007ba3cdd9c41ecaaf2ffb619d6fb283199fa8ddba89191e0", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "8966e15c395e5e37591b6ed0bd2ae7f48e961f0f60ac4c733f9566b519453085"}, + "phoenix_html": {:hex, :phoenix_html, "3.3.2", "d6ce982c6d8247d2fc0defe625255c721fb8d5f1942c5ac051f6177bffa5973f", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "44adaf8e667c1c20fb9d284b6b0fa8dc7946ce29e81ce621860aa7e96de9a11d"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "0.19.5", "6e730595e8e9b8c5da230a814e557768828fd8dfeeb90377d2d8dbb52d4ec00a", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b2eaa0dd3cfb9bd7fb949b88217df9f25aed915e986a28ad5c8a0d054e7ca9d3"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, + "phoenix_template": {:hex, :phoenix_template, "1.0.3", "32de561eefcefa951aead30a1f94f1b5f0379bc9e340bb5c667f65f1edfa4326", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "16f4b6588a4152f3cc057b9d0c0ba7e82ee23afa65543da535313ad8d25d8e2c"}, + "plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"}, + "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"}, + "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, + "websock": {:hex, :websock, "0.5.2", "b3c08511d8d79ed2c2f589ff430bd1fe799bb389686dafce86d28801783d8351", [:mix], [], "hexpm", "925f5de22fca6813dfa980fb62fd542ec43a2d1a1f83d2caec907483fe66ff05"}, + "websock_adapter": {:hex, :websock_adapter, "0.5.3", "4908718e42e4a548fc20e00e70848620a92f11f7a6add8cf0886c4232267498d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "cbe5b814c1f86b6ea002b52dd99f345aeecf1a1a6964e209d208fb404d930d3d"}, +} From fcdd062331e9114e4fedce2884998bc627df6cdf Mon Sep 17 00:00:00 2001 From: Scott Ming Date: Sat, 5 Aug 2023 18:36:31 +0800 Subject: [PATCH 2/6] simple implementation --- .../lexical/remote_control/build/document.ex | 2 +- .../build/document/compilers/heex.ex | 84 +++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 apps/remote_control/lib/lexical/remote_control/build/document/compilers/heex.ex diff --git a/apps/remote_control/lib/lexical/remote_control/build/document.ex b/apps/remote_control/lib/lexical/remote_control/build/document.ex index 354b16569..16d4065e1 100644 --- a/apps/remote_control/lib/lexical/remote_control/build/document.ex +++ b/apps/remote_control/lib/lexical/remote_control/build/document.ex @@ -3,7 +3,7 @@ defmodule Lexical.RemoteControl.Build.Document do alias Lexical.Document alias Lexical.RemoteControl.Build.Document.Compilers - @compilers [Compilers.Config, Compilers.Elixir, Compilers.EEx, Compilers.NoOp] + @compilers [Compilers.Config, Compilers.Elixir, Compilers.EEx, Compilers.HEEx, Compilers.NoOp] def compile(%Document{} = document) do compiler = Enum.find(@compilers, & &1.recognizes?(document)) diff --git a/apps/remote_control/lib/lexical/remote_control/build/document/compilers/heex.ex b/apps/remote_control/lib/lexical/remote_control/build/document/compilers/heex.ex new file mode 100644 index 000000000..399ce26bb --- /dev/null +++ b/apps/remote_control/lib/lexical/remote_control/build/document/compilers/heex.ex @@ -0,0 +1,84 @@ +defmodule Lexical.RemoteControl.Build.Document.Compilers.HEEx do + @moduledoc """ + A compiler for .heex files + """ + alias Lexical.Document + alias Lexical.Plugin.V1.Diagnostic.Result + alias Lexical.RemoteControl.Build.Document.Compiler + alias Lexical.RemoteControl.Build.Error + require Logger + + @behaviour Compiler + + def recognizes?(%Document{} = document) do + Path.extname(document.path) == ".heex" + end + + def enabled? do + true + end + + def compile(%Document{} = document) do + with {:ok, _quoted} <- heex_to_quoted(document), + {:ok, _string} <- eval(document) do + {:ok, []} + end + end + + defp heex_to_quoted(%Document{} = document) do + try do + source = Document.to_string(document) + + opts = + [ + source: source, + file: document.path, + caller: __ENV__, + engine: Phoenix.LiveView.TagEngine, + subengine: Phoenix.LiveView.Engine, + tag_handler: Phoenix.LiveView.HTMLEngine + ] + + quoted = EEx.compile_string(source, opts) + + {:ok, quoted} + rescue + error -> + {:error, [error_to_result(document, error)]} + end + end + + defp eval(%Document{} = document) do + # Evaluating the Html.Engine compiled quoted doesn't report any errors, + # so we need to use the original `EEx` to compile it to quoted and evaluate it. + quoted_ast = + document + |> Document.to_string() + |> EEx.compile_string(file: document.path) + + try do + {result, _} = Code.eval_quoted(quoted_ast, [assigns: %{}], file: document.path) + {:ok, result} + rescue + exception -> + {filled_exception, stack} = Exception.blame(:error, exception, __STACKTRACE__) + {:exception, filled_exception, stack, quoted_ast} + + error = + [Error.error_to_diagnostic(document, exception, stack, quoted_ast)] + |> Error.refine_diagnostics() + + {:error, error} + end + end + + defp error_to_result(document, %error_struct{} = error) + when error_struct in [ + EEx.SystaxError, + TokenMissingError, + Phoenix.LiveView.Tokenizer.ParseError + ] do + position = {error.line, error.column} + Result.new(document.uri, position, error.description, :error, "HEEx") + end +end From 5b5ea3324fe52dc4b98133795870f63db1def8b4 Mon Sep 17 00:00:00 2001 From: Scott Ming Date: Sun, 13 Aug 2023 14:26:20 +0800 Subject: [PATCH 3/6] Compile quoted after eval and add tests --- .../build/document/compilers/heex.ex | 83 +++++++-- .../build/document/compilers/heex_test.exs | 170 ++++++++++++++++++ 2 files changed, 242 insertions(+), 11 deletions(-) create mode 100644 apps/remote_control/test/lexical/remote_control/build/document/compilers/heex_test.exs diff --git a/apps/remote_control/lib/lexical/remote_control/build/document/compilers/heex.ex b/apps/remote_control/lib/lexical/remote_control/build/document/compilers/heex.ex index 399ce26bb..9a6e64c13 100644 --- a/apps/remote_control/lib/lexical/remote_control/build/document/compilers/heex.ex +++ b/apps/remote_control/lib/lexical/remote_control/build/document/compilers/heex.ex @@ -4,8 +4,9 @@ defmodule Lexical.RemoteControl.Build.Document.Compilers.HEEx do """ alias Lexical.Document alias Lexical.Plugin.V1.Diagnostic.Result + alias Lexical.RemoteControl.Build alias Lexical.RemoteControl.Build.Document.Compiler - alias Lexical.RemoteControl.Build.Error + alias Lexical.RemoteControl.Build.Document.Compilers require Logger @behaviour Compiler @@ -20,8 +21,14 @@ defmodule Lexical.RemoteControl.Build.Document.Compilers.HEEx do def compile(%Document{} = document) do with {:ok, _quoted} <- heex_to_quoted(document), - {:ok, _string} <- eval(document) do - {:ok, []} + {:ok, eex_quoted_ast} <- eval(document) do + compile_quoted(document, eex_quoted_ast) + end + end + + defp compile_quoted(%Document{} = document, quoted) do + with {:error, errors} <- Compilers.Quoted.compile(document, quoted, "HEEx") do + {:error, reject_undefined_assigns(errors)} end end @@ -56,25 +63,79 @@ defmodule Lexical.RemoteControl.Build.Document.Compilers.HEEx do |> Document.to_string() |> EEx.compile_string(file: document.path) + result = + if Elixir.Features.with_diagnostics?() do + eval_quoted_with_diagnostics(quoted_ast, document.path) + else + eval_quoted(quoted_ast, document.path) + end + + case result do + {:ok, quoted_ast} -> + {:ok, quoted_ast} + + {:exception, exception, stack} -> + converted = + document + |> Build.Error.error_to_diagnostic(exception, stack, quoted_ast) + |> Map.put(:source, "HEEx") + + {:error, [converted]} + + {{:ok, quoted_ast}, _} -> + # Ignore warnings for now + # because they will be handled by `compile_quoted/2` + # like: `assign @thing not available in EEx template` + {:ok, quoted_ast} + + {{:exception, exception, stack, quoted_ast}, all_errors_and_warnings} -> + converted = Build.Error.error_to_diagnostic(document, exception, stack, quoted_ast) + maybe_diagnostics = Build.Error.diagnostics_from_mix(document, all_errors_and_warnings) + + diagnostics = + [converted | maybe_diagnostics] + |> Enum.reverse() + |> Build.Error.refine_diagnostics() + |> Enum.map(&Map.replace!(&1, :source, "HEEx")) + + {:error, diagnostics} + end + end + + defp eval_quoted_with_diagnostics(quoted_ast, path) do + # Using apply to prevent a compile warning on elixir < 1.15 + # credo:disable-for-next-line + apply(Code, :with_diagnostics, [fn -> eval_quoted(quoted_ast, path) end]) + end + + def eval_quoted(quoted_ast, path) do try do - {result, _} = Code.eval_quoted(quoted_ast, [assigns: %{}], file: document.path) - {:ok, result} + {_, _} = Code.eval_quoted(quoted_ast, [assigns: %{}], file: path) + {:ok, quoted_ast} rescue exception -> {filled_exception, stack} = Exception.blame(:error, exception, __STACKTRACE__) {:exception, filled_exception, stack, quoted_ast} + end + end - error = - [Error.error_to_diagnostic(document, exception, stack, quoted_ast)] - |> Error.refine_diagnostics() + defp reject_undefined_assigns(errors) do + # NOTE: Ignoring error for assigns makes sense, + # because we don't want such a error report, + # for example: `<%= @name %>` + Enum.reject(errors, fn %Result{message: message} -> + message =~ ~s[undefined variable "assigns"] + end) + end - {:error, error} - end + defp error_to_result(%Document{} = document, %EEx.SyntaxError{} = error) do + position = {error.line, error.column} + + Result.new(document.uri, position, error.message, :error, "HEEx") end defp error_to_result(document, %error_struct{} = error) when error_struct in [ - EEx.SystaxError, TokenMissingError, Phoenix.LiveView.Tokenizer.ParseError ] do diff --git a/apps/remote_control/test/lexical/remote_control/build/document/compilers/heex_test.exs b/apps/remote_control/test/lexical/remote_control/build/document/compilers/heex_test.exs new file mode 100644 index 000000000..ba76e8c52 --- /dev/null +++ b/apps/remote_control/test/lexical/remote_control/build/document/compilers/heex_test.exs @@ -0,0 +1,170 @@ +defmodule Lexical.RemoteControl.Build.Document.Compilers.HeexTest do + alias Lexical.Document + alias Lexical.Plugin.V1.Diagnostic.Result + alias Lexical.Project + alias Lexical.RemoteControl + alias Lexical.RemoteControl.Api.Messages + alias Lexical.RemoteControl.Build + alias Lexical.RemoteControl.Build.CaptureServer + alias Lexical.RemoteControl.Build.Document.Compilers + alias Lexical.RemoteControl.ModuleMappings + alias Lexical.RemoteControl.ProjectNodeSupervisor + + import Lexical.Test.Fixtures + import Messages + import Lexical.Test.CodeSigil + + use ExUnit.Case + + def with_capture_server(_) do + start_supervised!(CaptureServer) + start_supervised!(ModuleMappings) + :ok + end + + def with_liveview_project(_) do + fixture_dir = Path.join(fixtures_path(), "live_demo") + project = Project.new("file://#{fixture_dir}") + + {:ok, _} = start_supervised({ProjectNodeSupervisor, project}) + {:ok, _, _} = RemoteControl.start_link(project, self()) + Build.schedule_compile(project, true) + + assert_receive project_compiled(status: :success), 10_000 + + {:ok, %{project: project}} + end + + defp compile(project, document) do + RemoteControl.call(project, Compilers.HEEx, :compile, [document]) + end + + def document_with_content(content) do + Document.new("file:///file.heex", content, 0) + end + + setup_all [:with_liveview_project, :with_capture_server] + + describe "compile/1" do + test "handles valid EEx content", %{project: project} do + document = document_with_content(~q[ + <%= "thing" %> + ]) + + assert {:ok, []} = compile(project, document) + end + + test "handles EEx syntax error", %{project: project} do + document = document_with_content(~q[ + <%= IO. + ]) + assert {:error, [%Result{} = result]} = compile(project, document) + + assert result.message =~ "'%>'" + assert result.source == "HEEx" + end + + test "handles unused error", %{project: project} do + document = document_with_content(~q[ +
+ <%= something = 1 %> +
+ ]) + + assert {:ok, [%Result{} = result]} = compile(project, document) + + assert result.message == "variable \"something\" is unused" + assert result.position == {2, 7} + assert result.severity == :warning + assert result.source == "HEEx" + assert result.uri == "file:///file.heex" + end + + test "handles undefinied function", %{project: project} do + document = document_with_content(~q[ + <%= IO.uts("thing") %> + ]) + + assert {:error, [%Result{} = result]} = compile(project, document) + assert result.message =~ "function IO.uts/1 is undefined or private" + assert result.position == {1, 8} + assert result.severity == :error + assert result.source == "HEEx" + assert result.uri =~ "file:///file.heex" + end + + @tag :with_diagnostics + + test "handles undefinied variable", %{project: project} do + document = document_with_content(~q[ + <%= thing %> + ]) + + assert {:error, [%Result{} = result]} = compile(project, document) + + assert result.message =~ "undefined variable \"thing\"" + assert result.position == {1, 5} + assert result.severity == :error + assert result.source == "HEEx" + assert result.uri =~ "file:///file.heex" + end + + test "ignore undefinied assigns", %{project: project} do + document = document_with_content(~q[ +
<%= @thing %>
+ ]) + + assert {:error, []} = compile(project, document) + end + + test "handles valid HEEx content", %{project: project} do + document = document_with_content(~q[ +
thing
+ ]) + assert {:ok, []} = compile(project, document) + end + + test "handles unclosed tags", %{project: project} do + document = document_with_content(~q[ +
thing + ]) + assert {:error, [%Result{} = result]} = compile(project, document) + + assert result.message =~ + "end of template reached without closing tag for
\n |\n1 |
thing\n | ^" + + assert result.position == {1, 1} + assert result.severity == :error + assert result.source == "HEEx" + assert result.uri =~ "file:///file.heex" + end + + test "handles invalid HEEx syntax", %{project: project} do + document = document_with_content(~q[ + + ]) + + assert {:error, [%Result{} = result]} = compile(project, document) + + assert result.message =~ "invalid attribute value after `=`. " + assert result.position == {1, 10} + assert result.severity == :error + assert result.source == "HEEx" + assert result.uri =~ "file:///file.heex" + end + end + + describe "function components" do + @tag :skip + test "handles undefined function component", %{project: project} do + path = "lib/simple_live.html.heex" + content = ~q[ + <.greets_world name={@name} /> + ] + document = Document.new(path, content, 0) + + assert {:error, [error]} = compile(project, document) + assert error.message =~ "undefined function \"greets_world\"" + end + end +end From cfb8c10ba3fa490a93f295711e1ebccba864b6d4 Mon Sep 17 00:00:00 2001 From: Scott Ming Date: Wed, 30 Aug 2023 21:08:02 +0800 Subject: [PATCH 4/6] Move the logic related to EEX into the EEX file. It's like peeling an onion. When we have an HEEx file, we first use the HTML engine to transform and compile it. Once the part that belongs to HTML is fine, we then use the EEx engine to transform it, evaluate it, and finally compile it. --- .../build/document/compilers/eex.ex | 78 ++++++++++- .../build/document/compilers/heex.ex | 89 +----------- apps/remote_control/mix.exs | 3 +- .../build/document/compilers/eex_test.exs | 47 ++++++- .../build/document/compilers/heex_test.exs | 130 +++--------------- mix.lock | 14 +- 6 files changed, 164 insertions(+), 197 deletions(-) diff --git a/apps/remote_control/lib/lexical/remote_control/build/document/compilers/eex.ex b/apps/remote_control/lib/lexical/remote_control/build/document/compilers/eex.ex index 93ed1a19b..89c2671f7 100644 --- a/apps/remote_control/lib/lexical/remote_control/build/document/compilers/eex.ex +++ b/apps/remote_control/lib/lexical/remote_control/build/document/compilers/eex.ex @@ -4,6 +4,7 @@ defmodule Lexical.RemoteControl.Build.Document.Compilers.EEx do """ alias Lexical.Document alias Lexical.Plugin.V1.Diagnostic.Result + alias Lexical.RemoteControl.Build alias Lexical.RemoteControl.Build.Document.Compiler alias Lexical.RemoteControl.Build.Document.Compilers @@ -18,8 +19,15 @@ defmodule Lexical.RemoteControl.Build.Document.Compilers.EEx do end def compile(%Document{} = document) do - with {:ok, quoted} <- eex_to_quoted(document) do - Compilers.Quoted.compile(document, quoted, "EEx") + with {:ok, quoted} <- eex_to_quoted(document), + :ok <- eval_quoted(document, quoted) do + compile_quoted(document, quoted) + end + end + + defp compile_quoted(%Document{} = document, quoted) do + with {:error, errors} <- Compilers.Quoted.compile(document, quoted, "EEx") do + {:error, reject_undefined_assigns(errors)} end end @@ -37,6 +45,72 @@ defmodule Lexical.RemoteControl.Build.Document.Compilers.EEx do end end + defp eval_quoted(%Document{} = document, quoted_ast) do + result = + if Elixir.Features.with_diagnostics?() do + eval_quoted_with_diagnostics(quoted_ast, document.path) + else + do_eval_quoted(quoted_ast, document.path) + end + + case result do + {:ok, _eval_result} -> + :ok + + {{:ok, _eval_result}, _} -> + # Ignore warnings for now + # because they will be handled by `compile_quoted/2` + # like: `assign @thing not available in EEx template` + :ok + + {:exception, exception, stack, _quoted_ast} -> + converted = + document + |> Build.Error.error_to_diagnostic(exception, stack, quoted_ast) + |> Map.put(:source, "EEx") + + {:error, [converted]} + + {{:exception, exception, stack, _quoted_ast}, all_errors_and_warnings} -> + converted = Build.Error.error_to_diagnostic(document, exception, stack, quoted_ast) + maybe_diagnostics = Build.Error.diagnostics_from_mix(document, all_errors_and_warnings) + + diagnostics = + [converted | maybe_diagnostics] + |> Enum.reverse() + |> Build.Error.refine_diagnostics() + |> Enum.map(&Map.replace!(&1, :source, "EEx")) + + {:error, diagnostics} + end + end + + defp eval_quoted_with_diagnostics(quoted_ast, path) do + # Using apply to prevent a compile warning on elixir < 1.15 + # credo:disable-for-next-line + apply(Code, :with_diagnostics, [fn -> do_eval_quoted(quoted_ast, path) end]) + end + + def do_eval_quoted(quoted_ast, path) do + try do + {result, _} = Code.eval_quoted(quoted_ast, [assigns: %{}], file: path) + {:ok, result} + rescue + exception -> + {filled_exception, stack} = Exception.blame(:error, exception, __STACKTRACE__) + {:exception, filled_exception, stack, quoted_ast} + end + end + + defp reject_undefined_assigns(errors) do + # NOTE: Ignoring error for assigns makes sense, + # because we don't want such a error report, + # for example: `<%= @name %>` + Enum.reject(errors, fn %Result{message: message} -> + message =~ ~s[undefined variable "assigns"] + end) + end + defp error_to_result(%Document{} = document, %EEx.SyntaxError{} = error) do position = {error.line, error.column} diff --git a/apps/remote_control/lib/lexical/remote_control/build/document/compilers/heex.ex b/apps/remote_control/lib/lexical/remote_control/build/document/compilers/heex.ex index 9a6e64c13..a08459782 100644 --- a/apps/remote_control/lib/lexical/remote_control/build/document/compilers/heex.ex +++ b/apps/remote_control/lib/lexical/remote_control/build/document/compilers/heex.ex @@ -4,7 +4,6 @@ defmodule Lexical.RemoteControl.Build.Document.Compilers.HEEx do """ alias Lexical.Document alias Lexical.Plugin.V1.Diagnostic.Result - alias Lexical.RemoteControl.Build alias Lexical.RemoteControl.Build.Document.Compiler alias Lexical.RemoteControl.Build.Document.Compilers require Logger @@ -20,15 +19,12 @@ defmodule Lexical.RemoteControl.Build.Document.Compilers.HEEx do end def compile(%Document{} = document) do - with {:ok, _quoted} <- heex_to_quoted(document), - {:ok, eex_quoted_ast} <- eval(document) do - compile_quoted(document, eex_quoted_ast) - end - end + case heex_to_quoted(document) do + {:ok, _} -> + Compilers.EEx.compile(document) - defp compile_quoted(%Document{} = document, quoted) do - with {:error, errors} <- Compilers.Quoted.compile(document, quoted, "HEEx") do - {:error, reject_undefined_assigns(errors)} + other -> + other end end @@ -55,83 +51,10 @@ defmodule Lexical.RemoteControl.Build.Document.Compilers.HEEx do end end - defp eval(%Document{} = document) do - # Evaluating the Html.Engine compiled quoted doesn't report any errors, - # so we need to use the original `EEx` to compile it to quoted and evaluate it. - quoted_ast = - document - |> Document.to_string() - |> EEx.compile_string(file: document.path) - - result = - if Elixir.Features.with_diagnostics?() do - eval_quoted_with_diagnostics(quoted_ast, document.path) - else - eval_quoted(quoted_ast, document.path) - end - - case result do - {:ok, quoted_ast} -> - {:ok, quoted_ast} - - {:exception, exception, stack} -> - converted = - document - |> Build.Error.error_to_diagnostic(exception, stack, quoted_ast) - |> Map.put(:source, "HEEx") - - {:error, [converted]} - - {{:ok, quoted_ast}, _} -> - # Ignore warnings for now - # because they will be handled by `compile_quoted/2` - # like: `assign @thing not available in EEx template` - {:ok, quoted_ast} - - {{:exception, exception, stack, quoted_ast}, all_errors_and_warnings} -> - converted = Build.Error.error_to_diagnostic(document, exception, stack, quoted_ast) - maybe_diagnostics = Build.Error.diagnostics_from_mix(document, all_errors_and_warnings) - - diagnostics = - [converted | maybe_diagnostics] - |> Enum.reverse() - |> Build.Error.refine_diagnostics() - |> Enum.map(&Map.replace!(&1, :source, "HEEx")) - - {:error, diagnostics} - end - end - - defp eval_quoted_with_diagnostics(quoted_ast, path) do - # Using apply to prevent a compile warning on elixir < 1.15 - # credo:disable-for-next-line - apply(Code, :with_diagnostics, [fn -> eval_quoted(quoted_ast, path) end]) - end - - def eval_quoted(quoted_ast, path) do - try do - {_, _} = Code.eval_quoted(quoted_ast, [assigns: %{}], file: path) - {:ok, quoted_ast} - rescue - exception -> - {filled_exception, stack} = Exception.blame(:error, exception, __STACKTRACE__) - {:exception, filled_exception, stack, quoted_ast} - end - end - - defp reject_undefined_assigns(errors) do - # NOTE: Ignoring error for assigns makes sense, - # because we don't want such a error report, - # for example: `<%= @name %>` - Enum.reject(errors, fn %Result{message: message} -> - message =~ ~s[undefined variable "assigns"] - end) - end - defp error_to_result(%Document{} = document, %EEx.SyntaxError{} = error) do position = {error.line, error.column} - Result.new(document.uri, position, error.message, :error, "HEEx") + Result.new(document.uri, position, error.message, :error, "EEx") end defp error_to_result(document, %error_struct{} = error) diff --git a/apps/remote_control/mix.exs b/apps/remote_control/mix.exs index 8e72f2351..40bc59109 100644 --- a/apps/remote_control/mix.exs +++ b/apps/remote_control/mix.exs @@ -41,7 +41,8 @@ defmodule Lexical.RemoteControl.MixProject do {:lexical_test, path: "../../projects/lexical_test", only: :test}, {:patch, "~> 0.12", only: [:dev, :test], optional: true, runtime: false}, {:path_glob, "~> 0.2", optional: true}, - {:sourceror, "~> 0.14.0"} + {:sourceror, "~> 0.12"}, + {:phoenix_live_view, "~> 0.19.5", only: [:test], optional: true, runtime: false} ] end diff --git a/apps/remote_control/test/lexical/remote_control/build/document/compilers/eex_test.exs b/apps/remote_control/test/lexical/remote_control/build/document/compilers/eex_test.exs index 38b7f8bb6..f53933a0e 100644 --- a/apps/remote_control/test/lexical/remote_control/build/document/compilers/eex_test.exs +++ b/apps/remote_control/test/lexical/remote_control/build/document/compilers/eex_test.exs @@ -45,7 +45,7 @@ defmodule Lexical.RemoteControl.Build.Document.Compilers.EExTest do end end - describe "compile/1" do + describe "eex_to_quoted/1" do setup [:with_capture_server] test "handles syntax errors" do @@ -65,6 +65,14 @@ defmodule Lexical.RemoteControl.Build.Document.Compilers.EExTest do assert result.source == "EEx" assert result.uri end + end + + describe "compile_quoted/2" do + setup [:with_capture_server] + + setup do + Code.compiler_options(parser_options: [columns: true, token_metadata: true]) + end test "handles unused variables" do assert {:ok, [%Result{} = result]} = @@ -75,10 +83,45 @@ defmodule Lexical.RemoteControl.Build.Document.Compilers.EExTest do |> compile() assert result.message =~ ~s["something" is unused] - assert result.position == 1 + assert result.position in [1, {1, 5}] assert result.severity == :warning assert result.source == "EEx" assert result.uri =~ "file:///file.eex" end end + + describe "eval_quoted/2" do + test "handles undefinied function" do + document = document_with_content(~q[ + <%= IO.uts("thing") %> + ]) + + assert {:error, [%Result{} = result]} = compile(document) + assert result.message =~ "function IO.uts/1 is undefined or private" + assert result.position == {1, 8} + assert result.severity == :error + assert result.source == "EEx" + assert result.uri =~ "file:///file.eex" + end + + @tag :with_diagnostics + test "handles undefinied variable" do + document = document_with_content(~q[ + <%= thing %> + ]) + + assert {:error, [%Result{} = result]} = compile(document) + + if Features.with_diagnostics?() do + assert result.message =~ "undefined variable \"thing\"" + else + assert result.message =~ "undefined function thing/0" + end + + assert result.position in [1, {1, 5}] + assert result.severity == :error + assert result.source == "EEx" + assert result.uri =~ "file:///file.eex" + end + end end diff --git a/apps/remote_control/test/lexical/remote_control/build/document/compilers/heex_test.exs b/apps/remote_control/test/lexical/remote_control/build/document/compilers/heex_test.exs index ba76e8c52..29193f910 100644 --- a/apps/remote_control/test/lexical/remote_control/build/document/compilers/heex_test.exs +++ b/apps/remote_control/test/lexical/remote_control/build/document/compilers/heex_test.exs @@ -1,18 +1,12 @@ defmodule Lexical.RemoteControl.Build.Document.Compilers.HeexTest do alias Lexical.Document alias Lexical.Plugin.V1.Diagnostic.Result - alias Lexical.Project - alias Lexical.RemoteControl - alias Lexical.RemoteControl.Api.Messages - alias Lexical.RemoteControl.Build alias Lexical.RemoteControl.Build.CaptureServer alias Lexical.RemoteControl.Build.Document.Compilers alias Lexical.RemoteControl.ModuleMappings - alias Lexical.RemoteControl.ProjectNodeSupervisor - import Lexical.Test.Fixtures - import Messages import Lexical.Test.CodeSigil + import Compilers.HEEx, only: [compile: 1] use ExUnit.Case @@ -22,113 +16,37 @@ defmodule Lexical.RemoteControl.Build.Document.Compilers.HeexTest do :ok end - def with_liveview_project(_) do - fixture_dir = Path.join(fixtures_path(), "live_demo") - project = Project.new("file://#{fixture_dir}") - - {:ok, _} = start_supervised({ProjectNodeSupervisor, project}) - {:ok, _, _} = RemoteControl.start_link(project, self()) - Build.schedule_compile(project, true) - - assert_receive project_compiled(status: :success), 10_000 - - {:ok, %{project: project}} - end - - defp compile(project, document) do - RemoteControl.call(project, Compilers.HEEx, :compile, [document]) - end - def document_with_content(content) do Document.new("file:///file.heex", content, 0) end - setup_all [:with_liveview_project, :with_capture_server] + setup do + Code.compiler_options(parser_options: [columns: true, token_metadata: true]) + end describe "compile/1" do - test "handles valid EEx content", %{project: project} do - document = document_with_content(~q[ - <%= "thing" %> - ]) - - assert {:ok, []} = compile(project, document) - end - - test "handles EEx syntax error", %{project: project} do - document = document_with_content(~q[ - <%= IO. - ]) - assert {:error, [%Result{} = result]} = compile(project, document) - - assert result.message =~ "'%>'" - assert result.source == "HEEx" - end - - test "handles unused error", %{project: project} do - document = document_with_content(~q[ -
- <%= something = 1 %> -
- ]) - - assert {:ok, [%Result{} = result]} = compile(project, document) - - assert result.message == "variable \"something\" is unused" - assert result.position == {2, 7} - assert result.severity == :warning - assert result.source == "HEEx" - assert result.uri == "file:///file.heex" - end + setup [:with_capture_server] - test "handles undefinied function", %{project: project} do + test "handles valid HEEx content" do document = document_with_content(~q[ - <%= IO.uts("thing") %> - ]) - - assert {:error, [%Result{} = result]} = compile(project, document) - assert result.message =~ "function IO.uts/1 is undefined or private" - assert result.position == {1, 8} - assert result.severity == :error - assert result.source == "HEEx" - assert result.uri =~ "file:///file.heex" - end - - @tag :with_diagnostics - - test "handles undefinied variable", %{project: project} do - document = document_with_content(~q[ - <%= thing %> +
thing
]) - - assert {:error, [%Result{} = result]} = compile(project, document) - - assert result.message =~ "undefined variable \"thing\"" - assert result.position == {1, 5} - assert result.severity == :error - assert result.source == "HEEx" - assert result.uri =~ "file:///file.heex" + assert {:ok, []} = compile(document) end - test "ignore undefinied assigns", %{project: project} do + test "ignore undefinied assigns" do document = document_with_content(~q[
<%= @thing %>
]) - assert {:error, []} = compile(project, document) - end - - test "handles valid HEEx content", %{project: project} do - document = document_with_content(~q[ -
thing
- ]) - assert {:ok, []} = compile(project, document) + assert {:error, []} = compile(document) end - test "handles unclosed tags", %{project: project} do + test "returns error when there are unclosed tags" do document = document_with_content(~q[
thing ]) - assert {:error, [%Result{} = result]} = compile(project, document) + assert {:error, [%Result{} = result]} = compile(document) assert result.message =~ "end of template reached without closing tag for
\n |\n1 |
thing\n | ^" @@ -139,12 +57,12 @@ defmodule Lexical.RemoteControl.Build.Document.Compilers.HeexTest do assert result.uri =~ "file:///file.heex" end - test "handles invalid HEEx syntax", %{project: project} do + test "returns error when HEEx syntax is invalid" do document = document_with_content(~q[ ]) - assert {:error, [%Result{} = result]} = compile(project, document) + assert {:error, [%Result{} = result]} = compile(document) assert result.message =~ "invalid attribute value after `=`. " assert result.position == {1, 10} @@ -152,19 +70,15 @@ defmodule Lexical.RemoteControl.Build.Document.Compilers.HeexTest do assert result.source == "HEEx" assert result.uri =~ "file:///file.heex" end - end - describe "function components" do - @tag :skip - test "handles undefined function component", %{project: project} do - path = "lib/simple_live.html.heex" - content = ~q[ - <.greets_world name={@name} /> - ] - document = Document.new(path, content, 0) - - assert {:error, [error]} = compile(project, document) - assert error.message =~ "undefined function \"greets_world\"" + test "handles EEx syntax error" do + document = document_with_content(~q[ + <%= IO. + ]) + assert {:error, [%Result{} = result]} = compile(document) + + assert result.message =~ "'%>'" + assert result.source == "EEx" end end end diff --git a/mix.lock b/mix.lock index e070de734..beeaec8cd 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,6 @@ %{ "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, + "castore": {:hex, :castore, "1.0.3", "7130ba6d24c8424014194676d608cb989f62ef8039efd50ff4b3f33286d06db8", [:mix], [], "hexpm", "680ab01ef5d15b161ed6a95449fac5c6b8f60055677a8e79acf01b27baa4390b"}, "credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"}, "dialyxir": {:hex, :dialyxir, "1.3.0", "fd1672f0922b7648ff9ce7b1b26fcf0ef56dda964a459892ad15f6b4410b5284", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "00b2a4bcd6aa8db9dcb0b38c1225b7277dca9bc370b6438715667071a304696f"}, "earmark_parser": {:hex, :earmark_parser, "1.4.32", "fa739a0ecfa34493de19426681b23f6814573faee95dfd4b4aafe15a7b5b32c6", [:mix], [], "hexpm", "b8b0dd77d60373e77a3d7e8afa598f325e49e8663a51bcc2b88ef41838cca755"}, @@ -12,9 +13,20 @@ "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, + "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, "patch": {:hex, :patch, "0.12.0", "2da8967d382bade20344a3e89d618bfba563b12d4ac93955468e830777f816b0", [:mix], [], "hexpm", "ffd0e9a7f2ad5054f37af84067ee88b1ad337308a1cb227e181e3967127b0235"}, "path_glob": {:hex, :path_glob, "0.2.0", "b9e34b5045cac5ecb76ef1aa55281a52bf603bf7009002085de40958064ca312", [:mix], [{:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "be2594cb4553169a1a189f95193d910115f64f15f0d689454bb4e8cfae2e7ebc"}, - "sourceror": {:hex, :sourceror, "0.14.0", "b6b8552d0240400d66b6f107c1bab7ac1726e998efc797f178b7b517e928e314", [:mix], [], "hexpm", "809c71270ad48092d40bbe251a133e49ae229433ce103f762a2373b7a10a8d8b"}, + "phoenix": {:hex, :phoenix, "1.7.7", "4cc501d4d823015007ba3cdd9c41ecaaf2ffb619d6fb283199fa8ddba89191e0", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "8966e15c395e5e37591b6ed0bd2ae7f48e961f0f60ac4c733f9566b519453085"}, + "phoenix_html": {:hex, :phoenix_html, "3.3.2", "d6ce982c6d8247d2fc0defe625255c721fb8d5f1942c5ac051f6177bffa5973f", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "44adaf8e667c1c20fb9d284b6b0fa8dc7946ce29e81ce621860aa7e96de9a11d"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "0.19.5", "6e730595e8e9b8c5da230a814e557768828fd8dfeeb90377d2d8dbb52d4ec00a", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b2eaa0dd3cfb9bd7fb949b88217df9f25aed915e986a28ad5c8a0d054e7ca9d3"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, + "phoenix_template": {:hex, :phoenix_template, "1.0.3", "32de561eefcefa951aead30a1f94f1b5f0379bc9e340bb5c667f65f1edfa4326", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "16f4b6588a4152f3cc057b9d0c0ba7e82ee23afa65543da535313ad8d25d8e2c"}, + "plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"}, + "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"}, + "sourceror": {:hex, :sourceror, "0.12.3", "a2ad3a1a4554b486d8a113ae7adad5646f938cad99bf8bfcef26dc0c88e8fade", [:mix], [], "hexpm", "4d4e78010ca046524e8194ffc4683422f34a96f6b82901abbb45acc79ace0316"}, "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, + "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, + "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, + "websock_adapter": {:hex, :websock_adapter, "0.5.4", "7af8408e7ed9d56578539594d1ee7d8461e2dd5c3f57b0f2a5352d610ddde757", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "d2c238c79c52cbe223fcdae22ca0bb5007a735b9e933870e241fce66afb4f4ab"}, } From e9b98a0f6a1d33b8d24d5a8cbfeb543dc6f5320e Mon Sep 17 00:00:00 2001 From: Scott Ming Date: Fri, 1 Sep 2023 08:37:53 +0800 Subject: [PATCH 5/6] Revert "Add simple liveview project" This reverts commit 570097fdb0f6b9245aef906dbb31975189dbfb62. --- .../test/fixtures/live_demo/.formatter.exs | 6 ----- .../test/fixtures/live_demo/.gitignore | 26 ------------------ .../test/fixtures/live_demo/README.md | 21 --------------- .../fixtures/live_demo/lib/core_components.ex | 19 ------------- .../fixtures/live_demo/lib/simple_live.ex | 5 ---- .../live_demo/lib/simple_live.html.heex | 3 --- .../test/fixtures/live_demo/mix.exs | 27 ------------------- .../test/fixtures/live_demo/mix.lock | 14 ---------- 8 files changed, 121 deletions(-) delete mode 100644 apps/remote_control/test/fixtures/live_demo/.formatter.exs delete mode 100644 apps/remote_control/test/fixtures/live_demo/.gitignore delete mode 100644 apps/remote_control/test/fixtures/live_demo/README.md delete mode 100644 apps/remote_control/test/fixtures/live_demo/lib/core_components.ex delete mode 100644 apps/remote_control/test/fixtures/live_demo/lib/simple_live.ex delete mode 100644 apps/remote_control/test/fixtures/live_demo/lib/simple_live.html.heex delete mode 100644 apps/remote_control/test/fixtures/live_demo/mix.exs delete mode 100644 apps/remote_control/test/fixtures/live_demo/mix.lock diff --git a/apps/remote_control/test/fixtures/live_demo/.formatter.exs b/apps/remote_control/test/fixtures/live_demo/.formatter.exs deleted file mode 100644 index 8e65327d1..000000000 --- a/apps/remote_control/test/fixtures/live_demo/.formatter.exs +++ /dev/null @@ -1,6 +0,0 @@ -[ - import_deps: [:phoenix], - subdirectories: ["priv/*/migrations"], - plugins: [Phoenix.LiveView.HTMLFormatter], - inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}"] -] diff --git a/apps/remote_control/test/fixtures/live_demo/.gitignore b/apps/remote_control/test/fixtures/live_demo/.gitignore deleted file mode 100644 index 1feae788f..000000000 --- a/apps/remote_control/test/fixtures/live_demo/.gitignore +++ /dev/null @@ -1,26 +0,0 @@ -# The directory Mix will write compiled artifacts to. -/_build/ - -# If you run "mix test --cover", coverage assets end up here. -/cover/ - -# The directory Mix downloads your dependencies sources to. -/deps/ - -# Where third-party dependencies like ExDoc output generated docs. -/doc/ - -# Ignore .fetch files in case you like to edit your project deps locally. -/.fetch - -# If the VM crashes, it generates a dump, let's ignore it too. -erl_crash.dump - -# Also ignore archive artifacts (built via "mix archive.build"). -*.ez - -# Ignore package tarball (built via "mix hex.build"). -live_demo-*.tar - -# Temporary files, for example, from tests. -/tmp/ diff --git a/apps/remote_control/test/fixtures/live_demo/README.md b/apps/remote_control/test/fixtures/live_demo/README.md deleted file mode 100644 index e3285b38f..000000000 --- a/apps/remote_control/test/fixtures/live_demo/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# LiveDemo - -**TODO: Add description** - -## Installation - -If [available in Hex](https://hex.pm/docs/publish), the package can be installed -by adding `live_demo` to your list of dependencies in `mix.exs`: - -```elixir -def deps do - [ - {:live_demo, "~> 0.1.0"} - ] -end -``` - -Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) -and published on [HexDocs](https://hexdocs.pm). Once published, the docs can -be found at . - diff --git a/apps/remote_control/test/fixtures/live_demo/lib/core_components.ex b/apps/remote_control/test/fixtures/live_demo/lib/core_components.ex deleted file mode 100644 index 3051dcb3d..000000000 --- a/apps/remote_control/test/fixtures/live_demo/lib/core_components.ex +++ /dev/null @@ -1,19 +0,0 @@ -defmodule LiveDemo.CoreComponents do - use Phoenix.Component - - def render(assigns) do - ~H""" -
-

CoreComponents

-
- """ - end - - attr :name, :string, default: "World" - - def greet(assigns) do - ~H""" -

Hello, <%= @name %>!

- """ - end -end diff --git a/apps/remote_control/test/fixtures/live_demo/lib/simple_live.ex b/apps/remote_control/test/fixtures/live_demo/lib/simple_live.ex deleted file mode 100644 index fc548d106..000000000 --- a/apps/remote_control/test/fixtures/live_demo/lib/simple_live.ex +++ /dev/null @@ -1,5 +0,0 @@ -defmodule LiveDemo.SimpleLive do - import LiveDemo.CoreComponents - - use Phoenix.LiveView -end diff --git a/apps/remote_control/test/fixtures/live_demo/lib/simple_live.html.heex b/apps/remote_control/test/fixtures/live_demo/lib/simple_live.html.heex deleted file mode 100644 index c58604d56..000000000 --- a/apps/remote_control/test/fixtures/live_demo/lib/simple_live.html.heex +++ /dev/null @@ -1,3 +0,0 @@ -
simple
-<.greet name={@name} /> -
simple live ended
diff --git a/apps/remote_control/test/fixtures/live_demo/mix.exs b/apps/remote_control/test/fixtures/live_demo/mix.exs deleted file mode 100644 index 17b94d0af..000000000 --- a/apps/remote_control/test/fixtures/live_demo/mix.exs +++ /dev/null @@ -1,27 +0,0 @@ -defmodule LiveDemo.MixProject do - use Mix.Project - - def project do - [ - app: :live_demo, - version: "0.1.0", - elixir: "~> 1.13", - start_permanent: Mix.env() == :prod, - deps: deps() - ] - end - - # Run "mix help compile.app" to learn about applications. - def application do - [ - extra_applications: [:logger] - ] - end - - # Run "mix help deps" to learn about dependencies. - defp deps do - [ - {:phoenix_live_view, "~> 0.19.5"} - ] - end -end diff --git a/apps/remote_control/test/fixtures/live_demo/mix.lock b/apps/remote_control/test/fixtures/live_demo/mix.lock deleted file mode 100644 index 1e8fe4c98..000000000 --- a/apps/remote_control/test/fixtures/live_demo/mix.lock +++ /dev/null @@ -1,14 +0,0 @@ -%{ - "castore": {:hex, :castore, "1.0.3", "7130ba6d24c8424014194676d608cb989f62ef8039efd50ff4b3f33286d06db8", [:mix], [], "hexpm", "680ab01ef5d15b161ed6a95449fac5c6b8f60055677a8e79acf01b27baa4390b"}, - "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, - "phoenix": {:hex, :phoenix, "1.7.7", "4cc501d4d823015007ba3cdd9c41ecaaf2ffb619d6fb283199fa8ddba89191e0", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "8966e15c395e5e37591b6ed0bd2ae7f48e961f0f60ac4c733f9566b519453085"}, - "phoenix_html": {:hex, :phoenix_html, "3.3.2", "d6ce982c6d8247d2fc0defe625255c721fb8d5f1942c5ac051f6177bffa5973f", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "44adaf8e667c1c20fb9d284b6b0fa8dc7946ce29e81ce621860aa7e96de9a11d"}, - "phoenix_live_view": {:hex, :phoenix_live_view, "0.19.5", "6e730595e8e9b8c5da230a814e557768828fd8dfeeb90377d2d8dbb52d4ec00a", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b2eaa0dd3cfb9bd7fb949b88217df9f25aed915e986a28ad5c8a0d054e7ca9d3"}, - "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, - "phoenix_template": {:hex, :phoenix_template, "1.0.3", "32de561eefcefa951aead30a1f94f1b5f0379bc9e340bb5c667f65f1edfa4326", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "16f4b6588a4152f3cc057b9d0c0ba7e82ee23afa65543da535313ad8d25d8e2c"}, - "plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"}, - "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"}, - "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, - "websock": {:hex, :websock, "0.5.2", "b3c08511d8d79ed2c2f589ff430bd1fe799bb389686dafce86d28801783d8351", [:mix], [], "hexpm", "925f5de22fca6813dfa980fb62fd542ec43a2d1a1f83d2caec907483fe66ff05"}, - "websock_adapter": {:hex, :websock_adapter, "0.5.3", "4908718e42e4a548fc20e00e70848620a92f11f7a6add8cf0886c4232267498d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "cbe5b814c1f86b6ea002b52dd99f345aeecf1a1a6964e209d208fb404d930d3d"}, -} From 472e0728832910baed9ac16ff1eff2fecb69b30b Mon Sep 17 00:00:00 2001 From: Scott Ming Date: Wed, 27 Sep 2023 11:39:20 +0800 Subject: [PATCH 6/6] Resolve conflicts --- apps/remote_control/mix.exs | 2 +- .../remote_control/build/document/compilers/heex_test.exs | 2 ++ mix.lock | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/remote_control/mix.exs b/apps/remote_control/mix.exs index 40bc59109..86094c6a1 100644 --- a/apps/remote_control/mix.exs +++ b/apps/remote_control/mix.exs @@ -41,7 +41,7 @@ defmodule Lexical.RemoteControl.MixProject do {:lexical_test, path: "../../projects/lexical_test", only: :test}, {:patch, "~> 0.12", only: [:dev, :test], optional: true, runtime: false}, {:path_glob, "~> 0.2", optional: true}, - {:sourceror, "~> 0.12"}, + {:sourceror, "~> 0.14.0"}, {:phoenix_live_view, "~> 0.19.5", only: [:test], optional: true, runtime: false} ] end diff --git a/apps/remote_control/test/lexical/remote_control/build/document/compilers/heex_test.exs b/apps/remote_control/test/lexical/remote_control/build/document/compilers/heex_test.exs index 29193f910..a8e232749 100644 --- a/apps/remote_control/test/lexical/remote_control/build/document/compilers/heex_test.exs +++ b/apps/remote_control/test/lexical/remote_control/build/document/compilers/heex_test.exs @@ -3,6 +3,7 @@ defmodule Lexical.RemoteControl.Build.Document.Compilers.HeexTest do alias Lexical.Plugin.V1.Diagnostic.Result alias Lexical.RemoteControl.Build.CaptureServer alias Lexical.RemoteControl.Build.Document.Compilers + alias Lexical.RemoteControl.Dispatch alias Lexical.RemoteControl.ModuleMappings import Lexical.Test.CodeSigil @@ -12,6 +13,7 @@ defmodule Lexical.RemoteControl.Build.Document.Compilers.HeexTest do def with_capture_server(_) do start_supervised!(CaptureServer) + start_supervised!(Dispatch) start_supervised!(ModuleMappings) :ok end diff --git a/mix.lock b/mix.lock index beeaec8cd..eabf41fb8 100644 --- a/mix.lock +++ b/mix.lock @@ -24,7 +24,7 @@ "phoenix_template": {:hex, :phoenix_template, "1.0.3", "32de561eefcefa951aead30a1f94f1b5f0379bc9e340bb5c667f65f1edfa4326", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "16f4b6588a4152f3cc057b9d0c0ba7e82ee23afa65543da535313ad8d25d8e2c"}, "plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"}, "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"}, - "sourceror": {:hex, :sourceror, "0.12.3", "a2ad3a1a4554b486d8a113ae7adad5646f938cad99bf8bfcef26dc0c88e8fade", [:mix], [], "hexpm", "4d4e78010ca046524e8194ffc4683422f34a96f6b82901abbb45acc79ace0316"}, + "sourceror": {:hex, :sourceror, "0.14.0", "b6b8552d0240400d66b6f107c1bab7ac1726e998efc797f178b7b517e928e314", [:mix], [], "hexpm", "809c71270ad48092d40bbe251a133e49ae229433ce103f762a2373b7a10a8d8b"}, "stream_data": {:hex, :stream_data, "0.6.0", "e87a9a79d7ec23d10ff83eb025141ef4915eeb09d4491f79e52f2562b73e5f47", [:mix], [], "hexpm", "b92b5031b650ca480ced047578f1d57ea6dd563f5b57464ad274718c9c29501c"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},