Module: Shirobai::NodeLocator
- Defined in:
- lib/shirobai/node_locator.rb
Overview
Locates parser-gem AST nodes by their source range.
Rust reports offsets on the prism AST; some Architecture-B cops need the
actual Parser::AST::Node at a reported range so they can run stock's
detection + autocorrect (which reason over the parser AST) VERBATIM. Given
a ProcessedSource and a list of [begin_char, end_char] ranges
(CHARACTER offsets — convert byte offsets with SourceOffsets first),
locate returns a Hash mapping each requested [begin, end] to the node
whose expression range matches it exactly.
The SHALLOWEST node wins an exact-range tie (a wrapper node sharing its single child's range resolves to the wrapper). Ranges with no matching node are simply absent from the result. The result is identical to a plain full pre-order descent — the two phases below are only an optimization and never change which node a range resolves to.
Two phases, because two forces pull against each other:
-
Pruned descent (fast, the common case). Pre-order, but skip any child subtree whose expression range contains no remaining target: a target lives inside its ancestors' ranges, so such a subtree cannot hold one. The walk cost is then proportional to the paths down to the targets, not the whole AST. Record exact-range matches shallowest-wins and early-exit once every target is found. A wrapper sharing its child's range still has that range CONTAIN the target range, so the prune never skips it — shallowest-wins survives pruning.
-
Full-descent fallback (slow, rare). A heredoc body sits OUTSIDE the expression range of every ancestor between the heredoc node and the root (their
loc.expressionstops at the<<~marker), so a target nested in heredoc interpolation ("#{user.errors.to_h}") is pruned away in phase 1. ONLY when targets remain unfound do we pay for a full pre-order descent (no pruning) to locate the stragglers, again with the early-exit. Prune alone is unsound (drops heredoc-interior targets); full descent alone is slow on large spec files (walks the whole AST even when targets sit late); the fallback pays the full walk only when a target actually hides in a heredoc.
(This is the plugin-neutral twin of Shirobai::RSpec::NodeLocator; the
rspec gem keeps its own copy so it needs no dependency on this file, and
the rails gem uses this one.)
Class Method Summary collapse
Class Method Details
.locate(processed_source, ranges) ⇒ Hash{Array(Integer, Integer) => Parser::AST::Node}
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/shirobai/node_locator.rb', line 51 def locate(processed_source, ranges) result = {} ast = processed_source.ast return result if ast.nil? || ranges.empty? targets = {} ranges.each { |range| targets[range] = true } # Phase 1: pruned descent handles every non-heredoc target quickly. descend_pruned(ast, targets, result) # Phase 2: only if a target escaped phase 1 (heredoc interior), pay for a # full descent to find whatever is still missing. descend_full(ast, targets, result) if result.size < targets.size result end |