Class: Lilac::CLI::TemplateAST

Inherits:
Object
  • Object
show all
Defined in:
lib/lilac/cli/build/template_ast.rb

Overview

Parses a template body HTML fragment with Nokogiri, walks the element tree, and:

1. Collects every `data-*` directive into a flat list of
 `Directive` records (in document order) for the lint layer.
2. Assigns a synthetic `data-ref="lilN"` to any element bearing
 a binding directive but no explicit `data-ref`, so the
 runtime scanner can resolve it positionally at mount.
3. Builds `refs_map` keyed by ref name, recording each ref's
 source line for duplicate / reserved-name lint checks.

SFC's regex-based template extraction (SFC.parse) preserves the template body byte-for-byte. TemplateAST does NOT preserve byte equality on the way out — Nokogiri normalizes quoting and boolean attribute serialization. The HTML returned by parse is round-tripped through Nokogiri::HTML5.

Directive values are NOT validated against the value grammar here — directive attributes are left in the output HTML so the runtime scanner sees data-component / data-ref and the directives it binds at mount time.

Defined Under Namespace

Classes: Error, Result, WalkScopes

Constant Summary collapse

DIRECTIVE_PATTERNS =

Maps an attribute-name regex to its directive kind. Order matters: the more specific X-family patterns (data-on-X etc.) must be checked before the simpler ones so that data-on-click is classified as :on not as an unknown attribute.

class_ trailing underscore avoids shadowing Ruby's reserved class keyword in pattern-match contexts; symbol-side only.

[
  [/\Adata-text\z/,        :text,        false],
  [/\Adata-unsafe-html\z/, :unsafe_html, false],
  # data-bind is the form-independent two-way binding directive
  # (Phase E revival/unification of data-value / data-checked).
  # Property auto-selected from element type:
  # <input type=checkbox> → :checked, others → :value.
  # See directive-spec §6.2 for the full grammar; runtime parity
  # lives in runtime/mruby-lilac-directives/.
  [/\Adata-bind\z/,        :bind,        false],
  [/\Adata-show\z/,        :show,        false],
  [/\Adata-hide\z/,        :hide,        false],
  [/\Adata-each\z/,        :each,        false],
  [/\Adata-key\z/,         :key,         false],
  [/\Adata-class\z/,       :class_,      false],
  # data-component is detected only so collision checks can see
  # it; it has no emit_* of its own (the runtime autoregister
  # reads the HTML attribute directly) and `walk` skips Directive
  # records when it appears alone, to keep the dist HTML and the
  # linter input unchanged for the common case.
  [/\Adata-component\z/,   :component,   false],
  # Form directives (mruby-lilac-form gem). Collected so scope
  # validation / lint can see them; the runtime form scanner wires
  # the actual behavior at mount (resolves scope via ancestor walk).
  [/\Adata-form\z/,        :form,        false],
  [/\Adata-field\z/,       :field,       false],
  [/\Adata-button\z/,      :button,      false],
  [/\Adata-on-(.+)\z/,     :on,          true],
  [/\Adata-attr-(.+)\z/,   :attr,        true],
  [/\Adata-arg-(.+)\z/,    :arg,         true],
  [/\Adata-css-(.+)\z/,    :css,         true],
].freeze

Instance Method Summary collapse

Constructor Details

#initialize(body_html, source_path: nil) ⇒ TemplateAST

Returns a new instance of TemplateAST.



78
79
80
81
# File 'lib/lilac/cli/build/template_ast.rb', line 78

def initialize(body_html, source_path: nil)
  @body_html = body_html
  @source_path = source_path
end

Instance Method Details

#parseObject



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/lilac/cli/build/template_ast.rb', line 118

def parse
  fragment = Nokogiri::HTML5.fragment(@body_html)
  directives = []
  refs_map = {}
  # `at_root: true` for the outermost walk — when the first
  # element child of the fragment carries `data-component`, it's
  # the component's own root (this AST run IS that component),
  # so its directives + body belong to this component. Any deeper
  # `data-component` element is a different component; we treat
  # those subtrees opaquely so their directives don't leak into
  # the current component's scope.
  walk(fragment, directives, refs_map, WalkScopes.new, at_root: true)

  Result.new(
    html: fragment.to_html,
    directives: directives,
    refs_map: refs_map,
  )
end