Compile-time validation of template-level references against the DSL.
Where Lavash.Transformers.ValidateDsl checks entity-internal
references (rx body deps, action reads: lists, set targets, calc
deps), this transformer checks template references — things the
user writes in HEEx that resolve to DSL-declared names at runtime.
What's validated
phx-click="action_name"(andphx-submit,phx-change,phx-blur,phx-focus,phx-keydown,phx-keyup,phx-window-keydown,phx-window-keyup) on plain HTML tags must refer to a declaredaction :nameon the module (or an auto-generated setter fromstate :name, setter: true/optimistic: true).phx-value-<key>="..."attributes on the same node as a phx-event must correspond to a declaredparams [...]entry on the action. So<button phx-click="bump_by" phx-value-amount="5">is valid only if:bump_bywas declared asaction :bump_by, [:amount] do ... end. Catches typo'd value keys whose runtime symptom is "the action body receives a nil param and crashes insideString.to_integer(nil)."@nameassign references anywhere an Elixir expression appears in HEEx ({@field},:if={@open},class={@theme},<%= @count %>, etc.). Every reference must resolve to one of:- a declared
statefield (includingfrom: :assignsones) - a
Phoenix.Componentattr or slot declared on the module - a Phoenix-injected assign (
@flash,@socket,@live_action,@uploads,@streams,@myself,@inner_block,@__changed__,@__given__)
Catches typos like
{@flsh[:info]}whose runtime symptom is silent nil — or aKeyErrordeep inside a render that doesn't point at the template. Bare variable references (e.g.iteminside:for={item <- @items}) are ignored: they're Elixir locals, not assigns, and the compiler catches undefined ones itself.- a declared
What's not validated
Dynamic event values like
phx-click={@some_var}orphx-click={JS.dispatch(...)}— the AST is an expression, not a static string. We can't know at compile time whether the runtime value resolves to a server event name or a JS command. When the phx-event itself is dynamic, the phx-value-* check is also skipped (we don't know which action to check against).Events on component nodes like
<.my_component phx-click="...">. The attribute binds to a prop, not an event handler on the host module. Component-level events are validated when the component itself compiles.Modules using
def render(assigns)withouttemplate do. No token pipeline runs, no extraction, no validation.
Ordering
Runs after AnalyzeTemplate (which collects template event
references into :lavash_template_phx_events) and before the
Compile{Component,LiveView} transformers.
Summary
Functions
Callback implementation for Spark.Dsl.Transformer.after?/1.
Callback implementation for Spark.Dsl.Transformer.after_compile?/0.
Callback implementation for Spark.Dsl.Transformer.before?/1.
Callback implementation for Spark.Dsl.Transformer.transform/1.
Functions
Callback implementation for Spark.Dsl.Transformer.after?/1.
Callback implementation for Spark.Dsl.Transformer.after_compile?/0.
Callback implementation for Spark.Dsl.Transformer.before?/1.
Callback implementation for Spark.Dsl.Transformer.transform/1.