Class: Rigor::Plugin::NodeRuleWalk

Inherits:
Object
  • Object
show all
Defined in:
lib/rigor/plugin/node_rule_walk.rb

Overview

ADR-52 WD4 — one engine-owned AST walk per file for node rules.

Before this, every plugin that declared a Base.node_rule walked the file's AST itself (Base#node_rule_diagnosticsSource::NodeWalker.each_with_ancestors), so a project with N node-rule plugins paid N walks per file. This folds them into a single walk that dispatches each visited node to every matching (plugin, rule) pair.

Behaviour is preserved exactly so the diagnostics stay byte-identical (the WD6 gate):

  • Each plugin's node_file_context block runs once per file, before any of its rules fire, instance_exec'd on that plugin — same as the per-plugin walk.
  • One frozen NodeContext is built per node, lazily, only when at least one rule matches it. Because it wraps only the ancestors it is safe to share across plugins for the same node.
  • Each rule block is instance_exec'd on its own plugin instance with the same five arguments (node, scope, path, file_context, context).
  • A plugin whose context block or any rule block raises has its whole node-rule contribution isolated — the walk records the error against that plugin and continues, matching the runner's per-plugin rescue around the old #node_rule_diagnostics call.
  • Diagnostics are bucketed per plugin and returned in the registry order the runner already iterates, so emission order is unchanged (plugin-major, not node-major) — order preservation is what keeps the gate byte-identical in this slice.

The result is an ordered Array of Result, one per node-rule plugin (registry order). Result#error is non-nil iff that plugin's context or a rule block raised, in which case #diagnostics is empty; the runner turns the error into the same per-plugin runtime-error envelope it produced before.

Defined Under Namespace

Classes: Result

Instance Method Summary collapse

Constructor Details

#initialize(plugins) ⇒ NodeRuleWalk

Plugins that declare at least one node_rule, paired with their frozen rule list, in registry order. Built once per run and reused for every file.



41
42
43
44
45
46
47
# File 'lib/rigor/plugin/node_rule_walk.rb', line 41

def initialize(plugins)
  @entries = plugins.filter_map do |plugin|
    rules = plugin.class.node_rules
    rules.empty? ? nil : [plugin, rules]
  end.freeze
  freeze
end

Instance Method Details

#diagnostics_for_file(path:, scope:, root:, collector_driver: nil) ⇒ Object

Walk root once, dispatching every node to each matching (plugin, rule). Returns an Array of Result in plugin (registry) order. root nil yields one empty Result per plugin.

ADR-53 B4 — when collector_driver is given (an Analysis::CheckRules::RuleWalk::CollectorDriver), the SAME single traversal also drives the built-in CheckRules node collectors: each visited node is dispatched both to the plugin rules (this walk's original job) and to the built-in collectors (the CollectorDriver), so a file is walked once for both instead of once each. The two dispatch models coexist: plugin rules keep is_a? matching via the per-class memo and receive a lazily-built Rigor::Plugin::NodeContext (ancestors); built-in collectors keep exact-node-class dispatch and receive the immutable RuleWalk::Context threaded through the descent. Order is preserved because each side accumulates into its own bucket (per-plugin Results / per-collector `results`) and the two are assembled separately by their respective diagnostic builders. A raising plugin rule isolates only that plugin (per-State rescue) and never aborts built-in collection, nor vice versa (the collectors' visit is the verbatim legacy gather logic, which does not raise on the corpora).



67
68
69
70
71
72
73
# File 'lib/rigor/plugin/node_rule_walk.rb', line 67

def diagnostics_for_file(path:, scope:, root:, collector_driver: nil)
  return @entries.map { |plugin, _| Result.new(plugin, [], nil) } if root.nil?

  states = @entries.map { |plugin, rules| State.new(plugin, rules, scope, root) }
  walk(path, scope, root, states, collector_driver)
  states.map(&:result)
end

#empty?Boolean

Returns:

  • (Boolean)


49
50
51
# File 'lib/rigor/plugin/node_rule_walk.rb', line 49

def empty?
  @entries.empty?
end