Class: Rigor::Plugin::NodeRuleWalk
- Inherits:
-
Object
- Object
- Rigor::Plugin::NodeRuleWalk
- 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_diagnostics` →`Source::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
-
#diagnostics_for_file(path:, scope:, root:, collector_driver: nil) ⇒ Object
Walk ‘root` once, dispatching every node to each matching `(plugin, rule)`.
- #empty? ⇒ Boolean
-
#initialize(plugins) ⇒ NodeRuleWalk
constructor
Plugins that declare at least one ‘node_rule`, paired with their frozen rule list, in registry order.
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.
53 54 55 56 57 58 59 |
# File 'lib/rigor/plugin/node_rule_walk.rb', line 53 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).
87 88 89 90 91 92 93 |
# File 'lib/rigor/plugin/node_rule_walk.rb', line 87 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
61 62 63 |
# File 'lib/rigor/plugin/node_rule_walk.rb', line 61 def empty? @entries.empty? end |