Class: Lilac::CLI::CrossRefLinter

Inherits:
Object
  • Object
show all
Defined in:
lib/lilac/cli/lint/cross_ref_linter.rb

Overview

Build-time cross-reference linter. Compares identifiers used in template directives (ivars in data-text, methods in data-on-X, etc.) against the declarations extracted from <script type="text/ruby"> by ScriptAnalyzer (AST-based). Undeclared references emit a diagnostic to stderr (or any caller-provided IO).

Pure scan: never raises. Returns a Result(warnings:, errors:) struct — the caller fails the build when errors > 0. All current checks are warning-level; errors is retained for the Result contract so future fatal checks can fail the build without reshaping the API.

Class-shaped (instead of a module of self.lint_* methods) so the per-call state — analysis, directives, refs_map, component name, file, IO — lives in ivars rather than being threaded through every helper as 5 positional arguments.

Defined Under Namespace

Classes: Result

Constant Summary collapse

SUGGESTION_MAX_DISTANCE =

Tunable: maximum Levenshtein distance for the "Did you mean?" suggestion to be offered. 2 keeps it useful for typos without surfacing unrelated names.

2
LIFECYCLE_METHODS =

Methods the framework calls without an explicit def namedata-on-X wiring; never flag them as dead even if no template directive references them.

%w[
  setup initialize bind_template_hook prepare_setup
].freeze
RESERVED_REF_NAMES =

data-ref names that collide with Object / Kernel methods — refs.X then dispatches to the Ruby method instead of Refs#method_missing, producing a confusing NoMethodError or silent wrong behaviour.

%w[
  p puts print pp format sprintf printf
  gets getc
  raise throw catch fail exit abort
  lambda proc method methods
  caller inspect class send public_send tap then itself
  nil? frozen? is_a? kind_of? respond_to? object_id hash eql? to_s freeze
].freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(script_text:, directives:, component_name:, file:, refs_map: {}, out: $stderr) ⇒ CrossRefLinter

Returns a new instance of CrossRefLinter.



78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/lilac/cli/lint/cross_ref_linter.rb', line 78

def initialize(script_text:, directives:, component_name:, file:, refs_map: {}, out: $stderr)
  # Narrow the AST walk to the named class so a multi-class
  # script (e.g. a page-inline page with sibling components, or
  # a `.lil` that declares helper classes alongside the
  # component) doesn't bleed sibling-class declarations into
  # this component's dead-signal / dead-method checks.
  @analysis = ScriptAnalyzer.analyze(script_text, class_name: component_name)
  @directives = directives
  @component_name = component_name
  @file = file
  @refs_map = refs_map
  @out = out
end

Class Method Details

.lint(script_text:, directives:, component_name:, file:, refs_map: {}, out: $stderr) ⇒ Object

Public entry. Keeps the kwargs-only call shape from before the class refactor so existing callers (PageCompiler's assembler, tests) are unchanged.



70
71
72
73
74
75
76
# File 'lib/lilac/cli/lint/cross_ref_linter.rb', line 70

def self.lint(script_text:, directives:, component_name:, file:, refs_map: {}, out: $stderr)
  new(
    script_text: script_text, directives: directives,
    component_name: component_name, file: file,
    refs_map: refs_map, out: out
  ).run
end

Instance Method Details

#runObject



92
93
94
95
96
97
98
99
100
101
# File 'lib/lilac/cli/lint/cross_ref_linter.rb', line 92

def run
  warnings = 0
  warnings += lint_undeclared_signals
  warnings += lint_undefined_methods
  warnings += lint_each_without_key
  warnings += lint_reserved_ref_names
  warnings += lint_dead_signals
  warnings += lint_dead_methods
  Result.new(warnings: warnings, errors: 0)
end