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_contextblock 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_diagnosticscall. - 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
rootonce, 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.
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
49 50 51 |
# File 'lib/rigor/plugin/node_rule_walk.rb', line 49 def empty? @entries.empty? end |