From e9885158d238b3d95412a7ab021edbc85e9d1ed0 Mon Sep 17 00:00:00 2001 From: Scott Ming Date: Wed, 30 Aug 2023 21:08:02 +0800 Subject: [PATCH] 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 | 12 ++ 6 files changed, 163 insertions(+), 196 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..7c3cb0673 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, _quoted_ast} -> + :ok + + {{: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 + + {: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 + {_, _} = 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} 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 c3d62e7dc..574edfc53 100644 --- a/apps/remote_control/mix.exs +++ b/apps/remote_control/mix.exs @@ -41,7 +41,8 @@ defmodule Lexical.RemoteControl.MixProject do {:elixir_sense, git: "https://github.com/elixir-lsp/elixir_sense.git"}, {:patch, "~> 0.12", only: [:dev, :test], optional: true, runtime: false}, {:path_glob, "~> 0.2", optional: true}, - {:sourceror, "~> 0.12"} + {:sourceror, "~> 0.12"}, + {:phoenix_live_view, "~> 0.19.5", only: [:dev, :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 ffb7f6a03..20cdd596b 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 @@ -43,7 +43,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 @@ -63,6 +63,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]} = @@ -73,10 +81,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 8fb876199..9d2523ca9 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"}, + "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.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"}, }