Skip to main content

big_code_analysis/
node.rs

1// Metric counts (token, function, branch, argument, etc.) are stored as
2// `usize` and crossed with `f64` averages, ratios, and Halstead scores
3// across the cyclomatic / MI / Halstead computations. The `usize as f64`
4// and `f64 as usize` casts are intentional and snapshot-anchored — every
5// site is bounded by the count it came from. Allowing the lints at the
6// module level keeps the metric arithmetic legible.
7#![allow(
8    clippy::cast_precision_loss,
9    clippy::cast_possible_truncation,
10    clippy::cast_sign_loss
11)]
12
13mod parser_cache;
14
15use tree_sitter::Node as OtherNode;
16use tree_sitter::Tree as OtherTree;
17use tree_sitter::TreeCursor;
18
19use crate::checker::Checker;
20use crate::traits::{LanguageInfo, Search};
21
22use parser_cache::parse_on_scratch_parser;
23
24// Sibling lookups that resolved the parent from the node, on this
25// thread.
26//
27// [`Node::previous_sibling`] answers exactly what the chain-based
28// [`Ancestors::previous_sibling`] does, so no assertion on a metric
29// value can tell a walk that uses one from a walk that uses the
30// other — only the cost differs, and only on a deep tree. #1096 took
31// the last of these out of the metric bodies and #1100 out of the
32// `exclude_tests` prune; the counter is what makes putting one back a
33// test failure rather than a silent quadratic.
34crate::observation::counter!(node_resolved_sibling_lookups);
35
36// Child scans that built their own `TreeCursor`, on this thread.
37//
38// [`Node::children_with`] yields exactly what [`Node::children`] yields
39// — it exists to reuse one cursor across a traversal instead of
40// heap-allocating and freeing one per visited node, and no assertion on
41// a metric value can tell the two apart. #1112 moved the per-node
42// traversals that could hoist a cursor onto it; the counter is what
43// makes moving one back a test failure rather than a silent allocation
44// per node.
45//
46// All five consumers are guarded: `preorder` and `act_on_node` here,
47// `metrics::npa::python` and the suppression DFS through a `metrics()`
48// / `suppression_markers` call, and `output::dump`'s renderer from that
49// module's own tests. The accessor is `pub(crate)` (not `pub(super)`)
50// precisely so the last one can be asserted from where it lives — see
51// `crate::observation`.
52crate::observation::counter!(child_scan_cursors);
53
54/// A parsed source tree wrapping a [`tree_sitter::Tree`].
55///
56/// The "open parse seam" (see issue #251) is reached by external
57/// callers through [`crate::Ast::from_tree_sitter`], which accepts a
58/// caller-built `tree_sitter::Tree` directly; this wrapper stays
59/// internal so the metric walker is the only thing that observes it.
60#[derive(Clone, Debug)]
61pub(crate) struct Tree(OtherTree);
62
63impl Tree {
64    pub(crate) fn new<T: LanguageInfo>(code: &[u8]) -> Self {
65        // `Tree::new::<T>` is only reachable from the `mk_action!`
66        // dispatchers, which themselves cfg-gate each `LANG::*` arm
67        // behind the matching per-language feature (see #252). When
68        // the feature is off the dispatcher returns
69        // `Err(LanguageDisabled)` before we get here, so
70        // `get_ts_language` is provably `Ok` at this call site.
71        let language = T::lang().get_ts_language().expect(
72            "invariant: dispatcher cfg-gates this call behind the per-language Cargo feature",
73        );
74        Self(parse_on_scratch_parser(&language, code))
75    }
76
77    pub(crate) fn from_ts_tree(tree: OtherTree) -> Self {
78        Self(tree)
79    }
80
81    pub(crate) fn get_root(&self) -> Node<'_> {
82        Node(self.0.root_node())
83    }
84
85    pub(crate) fn as_ts_tree(&self) -> &OtherTree {
86        &self.0
87    }
88}
89
90/// An `AST` node.
91///
92/// The inner [`tree_sitter::Node`] is reached through
93/// [`Node::as_tree_sitter`] for advanced use cases that need direct
94/// access to the underlying tree-sitter API; the field itself is
95/// private so a `tree-sitter` version bump cannot silently reshape
96/// this struct's public layout.
97#[derive(Clone, Copy, Debug)]
98pub struct Node<'a>(OtherNode<'a>);
99
100impl<'a> Node<'a> {
101    /// Returns the underlying [`tree_sitter::Node`] for callers that
102    /// want to drive their own traversal alongside the metric walker.
103    ///
104    /// `tree_sitter::Node` is [`Copy`], so the node is returned by
105    /// value. The returned node borrows the same source-tree lifetime
106    /// as `self`.
107    ///
108    /// The `tree-sitter` re-export this exposes is *value-not-stable*:
109    /// the underlying pin may bump in any minor release, so node shape
110    /// and node-kind ids are not part of this crate's stability
111    /// contract (see the [`tree_sitter`](crate::tree_sitter) re-export
112    /// note in the crate root).
113    #[must_use]
114    #[inline]
115    pub fn as_tree_sitter(&self) -> OtherNode<'a> {
116        self.0
117    }
118
119    /// Checks if a node represents a syntax error or contains any syntax errors
120    /// anywhere within it.
121    #[must_use]
122    pub fn has_error(&self) -> bool {
123        self.0.has_error()
124    }
125
126    pub(crate) fn id(&self) -> usize {
127        self.0.id()
128    }
129
130    pub(crate) fn kind(&self) -> &'static str {
131        self.0.kind()
132    }
133
134    pub(crate) fn kind_id(&self) -> u16 {
135        self.0.kind_id()
136    }
137
138    pub(crate) fn utf8_text(&self, data: &'a [u8]) -> Option<&'a str> {
139        self.0.utf8_text(data).ok()
140    }
141
142    pub(crate) fn start_byte(&self) -> usize {
143        self.0.start_byte()
144    }
145
146    pub(crate) fn end_byte(&self) -> usize {
147        self.0.end_byte()
148    }
149
150    pub(crate) fn start_position(&self) -> (usize, usize) {
151        let temp = self.0.start_position();
152        (temp.row, temp.column)
153    }
154
155    pub(crate) fn end_position(&self) -> (usize, usize) {
156        let temp = self.0.end_position();
157        (temp.row, temp.column)
158    }
159
160    pub(crate) fn start_row(&self) -> usize {
161        self.0.start_position().row
162    }
163
164    pub(crate) fn end_row(&self) -> usize {
165        self.0.end_position().row
166    }
167
168    /// The 1-based **last line this node occupies** — the inclusive end
169    /// of its line span.
170    ///
171    /// Converting tree-sitter's 0-based end row needs a `+ 1` only when
172    /// the node actually occupies the row it ends on. A node whose end
173    /// column is 0 finished at the *start* of the row below its last
174    /// content row, having absorbed the preceding newline, so that row
175    /// is not part of its span.
176    ///
177    /// Getting this wrong is invisible in almost every grammar, because
178    /// almost every function node ends just past its closing delimiter
179    /// and therefore at a column above 0. Perl's trailing `sub` does
180    /// not: it ends at column 0 of the row below its `}`, exactly where
181    /// the file root ends, and a blanket `+ 1` then reported it ending a
182    /// line past both its enclosing space and EOF (#1163) — the shape
183    /// behind the release `usize` underflow in #1051.
184    pub(crate) fn end_line(&self) -> usize {
185        let end = self.0.end_position();
186        if end.column == 0 {
187            end.row
188        } else {
189            end.row + 1
190        }
191    }
192
193    /// Returns this node's parent.
194    ///
195    /// **`O(depth)`, not `O(1)`.** tree-sitter stores no parent pointer:
196    /// `ts_node_parent` restarts at the tree root and descends. A single
197    /// call in a per-node metric therefore costs `O(nodes × depth)` over a
198    /// walk, and an ancestor *loop* built on it costs `O(depth²)` per call.
199    ///
200    /// This has bitten the analyzer for real: #1052 was a per-leaf
201    /// `successors(node, Node::parent)` walk in `Tokens` that made the
202    /// metric `O(leaves × depth²)`, so a 2 KB file of nested parentheses
203    /// took ~19 s. Prefer inheriting state downward through the
204    /// traversal (see `Walk` in `spaces::compute`) over rediscovering it
205    /// upward, and where a predicate genuinely needs an ancestor, take
206    /// an [`Ancestors`] rather than calling this (#1084).
207    ///
208    /// As of #1096 no code the metric, `ops`, `bca function`, or
209    /// comment-removal walks reach calls this; the remaining callers are
210    /// `Ancestors` itself (the no-chain fallback), the one-off start node
211    /// of a `dump`, and tests. `rg '\.parent\(\)' src/` re-checks that.
212    pub(crate) fn parent(&self) -> Option<Node<'a>> {
213        self.0.parent().map(Node)
214    }
215
216    /// Returns `true` if this node's parent has any direct child with
217    /// the given grammar `kind_id` (the parent's children include this
218    /// node itself, so a self-match counts). Delegates to [`wraps_any`]
219    /// on the parent. This sits on the JS/TS arrow-function
220    /// closure-classification hot path (`check_if_arrow_func!`); see
221    /// #521.
222    ///
223    /// `ancestors` supplies the parent, for [`Node::parent`]'s
224    /// `O(depth)` reason (#1088).
225    ///
226    /// [`wraps_any`]: Self::wraps_any
227    #[inline]
228    pub(crate) fn has_sibling(&self, ancestors: Ancestors<'a, '_>, id: u16) -> bool {
229        ancestors
230            .parent(self)
231            .is_some_and(|parent| parent.is_child(id))
232    }
233
234    /// The sibling immediately before this node.
235    ///
236    /// **`O(depth)`, not `O(1)`**, for [`Node::parent`]'s reason:
237    /// `ts_node__prev_sibling` opens with `ts_node_parent`. Callers on a
238    /// walk should use [`Ancestors::previous_sibling`] instead (#1096).
239    pub(crate) fn previous_sibling(&self) -> Option<Node<'a>> {
240        node_resolved_sibling_lookups::record();
241        self.0.prev_sibling().map(Node)
242    }
243
244    /// Returns `true` if any direct child has the given grammar
245    /// `kind_id`. See #217 for the motivating perf finding from the
246    /// JS/TS template-literal hot path.
247    #[inline]
248    pub(crate) fn is_child(&self, id: u16) -> bool {
249        self.wraps_any(&[id])
250    }
251
252    /// Returns `true` if any direct child matches one of the given
253    /// grammar `kind_id`s. The single-id [`is_child`] delegates here, so
254    /// both share one child scan (the `#[inline]` makes the
255    /// single-element `contains` collapse to an equality check).
256    /// Generalizing the check to a set lets the shared
257    /// string-interpolation operand skip declare its rule once (issue
258    /// #420).
259    ///
260    /// # Why a cursor rather than `child(0)` + `next_sibling()`
261    ///
262    /// #217 replaced the cursor walk with a `next_sibling()` chain to
263    /// dodge the `TreeCursor` heap allocation, on the premise that a
264    /// sibling step is `O(1)`. It is not: `ts_node_next_sibling`
265    /// resolves the parent first, and `tree_sitter` stores no parent
266    /// pointer — it descends from the root — so each step cost
267    /// `O(depth)` and the scan `O(children × depth)`. That made every
268    /// caller of this method `O(depth)` per node, which is the same
269    /// defect #1084 removed from the predicates that ask for an
270    /// ancestor outright. The cursor iterator is `O(children)` after one
271    /// allocation, and measured faster on real input as well as on the
272    /// pathological one: the `nom/nested-arrow` probe went from
273    /// quadratic (17.6 s at depth 4000) to linear (6.3 ms), and a walk
274    /// over the 384-file `pdf.js` corpus dropped from ~443 ms to
275    /// ~370 ms (#1088).
276    ///
277    /// [`is_child`]: Self::is_child
278    #[inline]
279    pub(crate) fn wraps_any(&self, ids: &[u16]) -> bool {
280        self.children().any(|c| ids.contains(&c.kind_id()))
281    }
282
283    pub(crate) fn child_count(&self) -> usize {
284        self.0.child_count()
285    }
286
287    /// Number of nodes in this node's subtree, counting the node itself.
288    ///
289    /// `O(1)`: `tree_sitter` stores the visible-descendant count on each
290    /// subtree, so this is a field read rather than a walk. It counts the
291    /// same nodes the metric walk visits — visible children, named and
292    /// anonymous alike — which is what makes it usable as an exact
293    /// capacity for a per-node map (see `spaces::compute::metrics_inner`).
294    pub(crate) fn descendant_count(&self) -> usize {
295        self.0.descendant_count()
296    }
297
298    // Returns `true` if this node is a named grammar production
299    // (as opposed to an anonymous token such as a punctuation or
300    // keyword literal). Used to skip anonymous tokens like the
301    // leading `|` in an or-pattern.
302    pub(crate) fn is_named(&self) -> bool {
303        self.0.is_named()
304    }
305
306    /// Returns the direct child reached through the grammar `field_name`,
307    /// if any. The child carries the underlying tree lifetime `'a` (the
308    /// `tree_sitter::Node` it wraps is [`Copy`] and valid for the whole
309    /// tree), so callers may hold it past the borrow of `&self` — matching
310    /// the sibling accessors ([`child`], [`parent`], [`children`], …) rather
311    /// than over-narrowing to the method-call borrow (see issue #786).
312    ///
313    /// [`child`]: Self::child
314    /// [`parent`]: Self::parent
315    /// [`children`]: Self::children
316    pub(crate) fn child_by_field_name(&self, name: &str) -> Option<Node<'a>> {
317        self.0.child_by_field_name(name).map(Node)
318    }
319
320    pub(crate) fn child(&self, pos: usize) -> Option<Node<'a>> {
321        self.0.child(pos as u32).map(Node)
322    }
323
324    /// Returns the tree-sitter grammar field name through which this
325    /// node reaches the child at `child_index`, if any. Used by the
326    /// AST builder to thread the parent's `field_name` into each child
327    /// without a parallel cursor walk.
328    pub(crate) fn field_name_for_child(&self, child_index: u32) -> Option<&'static str> {
329        self.0.field_name_for_child(child_index)
330    }
331
332    /// Iterator over this node's direct children.
333    ///
334    /// Builds a [`Cursor`], which heap-allocates. A loop that visits
335    /// many nodes should hoist one cursor and use [`children_with`]
336    /// instead; see its note for when the difference is worth the
337    /// plumbing.
338    ///
339    /// [`children_with`]: Self::children_with
340    pub(crate) fn children(&self) -> Children<'a> {
341        child_scan_cursors::record();
342        // `descend`, not `seed`: `ts_node_walk` already ran the
343        // `ts_tree_cursor_init` that `Cursor::reset` would run again.
344        let mut cursor = self.cursor();
345        let scan = ChildScan::descend(self, &mut cursor);
346        Children { cursor, scan }
347    }
348
349    /// [`children`], over a cursor the caller owns.
350    ///
351    /// `tree_sitter::TreeCursor` heap-allocates its stack when built and
352    /// frees it when dropped, so a traversal that calls [`children`]
353    /// once per visited node pays a `malloc`/`free` pair per node.
354    /// `ts_tree_cursor_reset` keeps the allocation, so a loop that can
355    /// hoist one cursor pays for it once however many nodes it visits
356    /// (#1112).
357    ///
358    /// Worth the plumbing only where a loop visits many nodes. Measured
359    /// on the corpus slice, a full metric walk reaches [`children`] on
360    /// 3-6 % of nodes in C++, Rust, JavaScript, and Java, 16 % in C#,
361    /// and 60 % in Python, where one scan — the instance-attribute walk
362    /// in `metrics::npa::python` — was 92 % of the total, and
363    /// [`preorder`] plus the suppression DFS are the rest. Crate-wide
364    /// there is one more per-node consumer, `output::dump`'s renderer,
365    /// which no metric walk runs and so is absent from that total.
366    /// Predicates
367    /// that hold a bare `&Node` and scan one node's children keep
368    /// [`children`]: threading a cursor to them would cross the
369    /// `Checker` / `Getter` trait surface for one allocation per call.
370    ///
371    /// [`preorder`]: Self::preorder
372    ///
373    /// [`children`]: Self::children
374    pub(crate) fn children_with<'c>(&self, cursor: &'c mut Cursor<'a>) -> ChildrenWith<'c, 'a> {
375        let scan = ChildScan::seed(self, cursor);
376        ChildrenWith { cursor, scan }
377    }
378
379    pub(crate) fn cursor(&self) -> Cursor<'a> {
380        Cursor(self.0.walk())
381    }
382
383    /// Counts this node's ancestors satisfying `check`, walking upward
384    /// from the parent and stopping at (and excluding) the first
385    /// ancestor satisfying `stop`. An ancestor that is the `if` of an
386    /// `else if` chain never counts — it is a continuation of the
387    /// branch above it, not a new enclosing one.
388    ///
389    /// `ancestors` is the chain the caller descended through. Passing
390    /// [`Ancestors::unknown`] is always correct and answers identically;
391    /// it just pays `O(depth)` per step instead of `O(1)`.
392    pub(crate) fn count_specific_ancestors<C: Checker>(
393        &self,
394        ancestors: Ancestors<'a, '_>,
395        check: fn(&Node) -> bool,
396        stop: fn(&Node) -> bool,
397    ) -> usize {
398        let mut count = 0;
399        for (parent, above_parent) in ancestors.iter(self) {
400            if stop(&parent) {
401                break;
402            }
403            if check(&parent) && !C::is_else_if(&parent, above_parent) {
404                count += 1;
405            }
406        }
407        count
408    }
409
410    /// Returns `true` iff this node's parent satisfies `parent_pred`
411    /// AND that parent's own parent (this node's grandparent)
412    /// satisfies `grand_pred`. Returns `false` as soon as either link
413    /// is absent or its predicate fails, so a misordered predicate
414    /// cannot silently degrade to a single-predicate check.
415    ///
416    /// `ancestors` is the chain the caller descended through. Passing
417    /// [`Ancestors::unknown`] is always correct and answers
418    /// identically; it just pays [`Node::parent`]'s `O(depth)` for each
419    /// of the two links, which on a per-node metric arm is quadratic in
420    /// nesting depth (#1096).
421    pub(crate) fn parent_grandparent_match(
422        &self,
423        ancestors: Ancestors<'a, '_>,
424        parent_pred: fn(&Node) -> bool,
425        grand_pred: fn(&Node) -> bool,
426    ) -> bool {
427        let mut climb = ancestors.iter(self);
428        let Some((parent, _)) = climb.next() else {
429            return false;
430        };
431        if !parent_pred(&parent) {
432            return false;
433        }
434        let Some((grand, _)) = climb.next() else {
435            return false;
436        };
437        grand_pred(&grand)
438    }
439
440    /// Returns a pre-order iterator over this node and all of its
441    /// descendants (this node first, then each child subtree left to
442    /// right).
443    ///
444    /// The traversal is allocation-light: it reuses one work stack *and
445    /// one cursor*, and visits each node exactly once, so a full walk is
446    /// O(n) in the subtree size and allocates only the stack's growth.
447    /// Building a fresh cursor per visited node instead would cost a
448    /// `malloc`/`free` pair per node (#1112). Every yielded [`Node`]
449    /// carries the underlying tree lifetime `'a`, so callers may collect
450    /// or retain the handles.
451    ///
452    /// This is the Rust counterpart of the Python `Node.walk()` binding
453    /// (issue #728): the binding wraps each yielded node, so Rust and
454    /// Python share one traversal order.
455    #[must_use]
456    pub fn preorder(&self) -> Preorder<'a> {
457        Preorder {
458            stack: vec![*self],
459            cursor: self.cursor(),
460        }
461    }
462
463    /// Collects every node in this subtree (this node included) whose
464    /// [`kind`](tree_sitter::Node::kind) is listed in `kinds`, in
465    /// pre-order.
466    ///
467    /// Membership is an exact match against the raw grammar kind — the
468    /// same unaltered vocabulary [`crate::Ast::root_node`] exposes, not
469    /// the `Alterator`-curated kinds [`crate::Ast::dump`] emits. This is
470    /// the Rust counterpart of the Python `Node.descendants_by_kind()`
471    /// binding (issue #728).
472    #[must_use]
473    pub fn descendants_by_kind(&self, kinds: &[&str]) -> Vec<Node<'a>> {
474        self.preorder()
475            .filter(|node| kinds.contains(&node.kind()))
476            .collect()
477    }
478}
479
480/// The chain of a node's ancestors, root first, as recorded by a walker
481/// that descended to that node.
482///
483/// A predicate that asks a node for its parent pays [`Node::parent`]'s
484/// `O(depth)` once per node, so `O(depth²)` over a deeply nested file,
485/// however few parent steps it takes (#1084). A walker that visits
486/// parents before children already holds the chain, and handing it down
487/// turns each step into a slice index — the upward counterpart of the
488/// downward flag propagation #1052 and #1062 used.
489///
490/// Callers that reached a node some other way pass
491/// [`Ancestors::unknown`], which climbs with [`Node::parent`]: the same
492/// answers at the original cost.
493#[derive(Clone, Copy, Debug)]
494pub(crate) struct Ancestors<'tree, 'chain>(Option<&'chain [Node<'tree>]>);
495
496impl<'tree, 'chain> Ancestors<'tree, 'chain> {
497    /// No chain is available; every query climbs with [`Node::parent`].
498    pub(crate) const fn unknown() -> Self {
499        Self(None)
500    }
501
502    /// `chain` lists every ancestor of the node about to be queried,
503    /// root first — so `chain.last()` is its parent and an empty chain
504    /// means the node is the root.
505    pub(crate) const fn known(chain: &'chain [Node<'tree>]) -> Self {
506        Self(Some(chain))
507    }
508
509    /// [`Ancestors::known`], but first checks that `chain` really is
510    /// `node`'s ancestry.
511    ///
512    /// The parity test below proves the walker's truncate/push rule on a
513    /// *replica* walker, so it cannot see `metrics_inner` itself
514    /// desynchronising — a `chain.push` moved ahead of the per-node
515    /// computes, say, or a `continue` inserted above the truncate.
516    /// [`Ancestors::parent`] trusts `chain.last()` unvalidated, so such a
517    /// drift would feed every predicate a wrong ancestor silently rather
518    /// than fail. Walkers that maintain a chain should construct through
519    /// here; [`Ancestors::known`] stays unchecked for the callers that
520    /// deliberately pair a chain with a foreign node.
521    ///
522    /// # Two checks, because the exact one is not affordable by default
523    ///
524    /// The invariant is "`chain.last()` **is** `node.parent()`", and
525    /// asking that outright costs [`Node::parent`]'s `O(depth)` — the
526    /// very lookup #1084 exists to remove. Per node, on all five walks
527    /// that construct a checked chain, it made every debug-build walk
528    /// `O(nodes × depth)` while the shipped walk is `O(nodes)`: a tax on
529    /// every `cargo test`, worst on the deep-nesting regression tests
530    /// that exist to pin the shipped walk's linearity (#1122). It now
531    /// runs only under `--cfg chain_audit`, which `make chain-audit` and
532    /// the CI lane of the same name set — and there as a plain
533    /// `assert_eq!`, so the audit has teeth in a release profile too.
534    ///
535    /// What stays on in every debug build is an `O(1)` *consequence* of
536    /// that invariant: a parent's byte span contains its child's, and no
537    /// node is its own parent. Strictly weaker — a grandparent contains
538    /// the node as well — but it is shaped to the two ways a walker
539    /// desynchronises. A `push` moved ahead of the per-node computes
540    /// leaves `chain.last() == node`; a dropped `truncate` leaves the
541    /// previous subtree's path, whose last entry is disjoint from the
542    /// node that follows it. Both trip here, on the first node that
543    /// shows them, at four integer comparisons.
544    pub(crate) fn checked(chain: &'chain [Node<'tree>], node: &Node<'tree>) -> Self {
545        // Opt-in only: `Node::parent` restarts at the root, so this is
546        // the `O(nodes × depth)` walk described above.
547        #[cfg(chain_audit)]
548        assert_eq!(
549            chain.last().map(Node::id),
550            node.parent().map(|parent| parent.id()),
551            "ancestor chain desynchronised on a {} node",
552            node.kind()
553        );
554        debug_assert!(
555            chain.last().is_none_or(|parent| {
556                parent.id() != node.id()
557                    && parent.start_byte() <= node.start_byte()
558                    && node.end_byte() <= parent.end_byte()
559            }),
560            "ancestor chain desynchronised on a {} node: chain.last() neither \
561             contains it nor differs from it",
562            node.kind()
563        );
564        Self::known(chain)
565    }
566
567    /// How far the node this chain describes sits from the root, or
568    /// `None` when no chain is known — deriving it then would cost the
569    /// [`Node::parent`] climb the chain exists to remove.
570    pub(crate) fn depth(self) -> Option<usize> {
571        self.0.map(<[Node<'tree>]>::len)
572    }
573
574    /// `node`'s parent.
575    pub(crate) fn parent(self, node: &Node<'tree>) -> Option<Node<'tree>> {
576        match self.0 {
577            Some(chain) => chain.last().copied(),
578            None => node.parent(),
579        }
580    }
581
582    /// Whether `node`'s parent has kind id `kind`.
583    ///
584    /// The compound-leaf guard of `.claude/rules/grammar-dispatch.md`
585    /// section 5 asks this and nothing else: a delimiter or keyword
586    /// token is suppressed *only* directly under the construct that
587    /// owns it, never under an arbitrary ancestor. Eleven dispatch
588    /// arms across `getter`, `checker` and the metric walkers spelled
589    /// it out as `.parent(node).is_some_and(|p| p.kind_id() == X as
590    /// u16)`, which wraps onto four rustfmt lines inside a match guard
591    /// and buries the question under the plumbing; #1314 added six more
592    /// and folded all seventeen onto this. Three sites spelling their
593    /// binding `|parent|` were left alone and would each fit:
594    /// `impl_is_else_if_parent_clause!` (`src/checker.rs`),
595    /// `is_stabby_lambda_body` (`src/checker/ruby.rs`) and
596    /// `is_useful_comment` (`src/checker/rust.rs`, a let-chain).
597    ///
598    /// A `false` when `node` has no parent is the answer every one of
599    /// those call sites wants: a root node's token is not inside the
600    /// construct, so it is not suppressed.
601    pub(crate) fn parent_has_kind(self, node: &Node<'tree>, kind: u16) -> bool {
602        self.parent(node).is_some_and(|p| p.kind_id() == kind)
603    }
604
605    /// The sibling immediately before `node`, or `None` when `node` is
606    /// its parent's first child or has no parent.
607    ///
608    /// `tree_sitter`'s own `prev_sibling` resolves the parent first
609    /// (`ts_node__prev_sibling` opens with `ts_node_parent`), so it
610    /// carries the same `O(depth)` cost [`Node::parent`] does. With a
611    /// known chain the parent is free and what remains is a cursor walk
612    /// over the siblings.
613    pub(crate) fn previous_sibling(self, node: &Node<'tree>) -> Option<Node<'tree>> {
614        let Some(chain) = self.0 else {
615            return node.previous_sibling();
616        };
617        let parent = chain.last()?;
618        let mut previous = None;
619        for child in parent.children() {
620            if child.id() == node.id() {
621                return previous;
622            }
623            previous = Some(child);
624        }
625        // `node` is not among `parent.children()`, so this chain does
626        // not describe `node`. Answering `None` would claim "no
627        // previous sibling", which is a different (and wrong) answer;
628        // fall back to the authoritative lookup instead.
629        node.previous_sibling()
630    }
631
632    /// `node`'s ancestors, nearest first, each paired with *its* own
633    /// ancestry so a predicate applied to an ancestor stays as cheap as
634    /// one applied to `node`.
635    pub(crate) fn iter(self, node: &Node<'tree>) -> AncestorIter<'tree, 'chain> {
636        match self.0 {
637            Some(chain) => AncestorIter::Chain(chain),
638            None => AncestorIter::Climb(node.parent()),
639        }
640    }
641}
642
643/// Ancestor iterator returned by [`Ancestors::iter`], nearest first.
644pub(crate) enum AncestorIter<'tree, 'chain> {
645    /// The not-yet-yielded prefix of a known chain. Its last element is
646    /// the next ancestor, and the prefix before it is that ancestor's
647    /// own chain — so splitting from the back hands out both at once.
648    Chain(&'chain [Node<'tree>]),
649    /// The next ancestor to yield, reached by climbing.
650    Climb(Option<Node<'tree>>),
651}
652
653impl<'tree, 'chain> Iterator for AncestorIter<'tree, 'chain> {
654    type Item = (Node<'tree>, Ancestors<'tree, 'chain>);
655
656    fn next(&mut self) -> Option<Self::Item> {
657        match self {
658            Self::Chain(remaining) => {
659                let (&nearest, above) = remaining.split_last()?;
660                *remaining = above;
661                Some((nearest, Ancestors::known(above)))
662            }
663            Self::Climb(next) => {
664                let nearest = (*next)?;
665                *next = nearest.parent();
666                Some((nearest, Ancestors::unknown()))
667            }
668        }
669    }
670}
671
672/// Pre-order iterator over a node and its descendants, returned by
673/// [`Node::preorder`].
674///
675/// Holds a single work stack of not-yet-visited nodes, and a single
676/// cursor to enumerate each node's children with. Each step pops the
677/// next node, pushes its children so the leftmost is visited first, and
678/// yields the popped node — so the sequence is the node, then each child
679/// subtree in order. Both are reused across steps (children are pushed
680/// then the freshly-pushed slice is reversed in place), so the walk
681/// allocates only the stack's growth: no fresh buffer per node, and no
682/// fresh `TreeCursor` either (#1112).
683pub struct Preorder<'a> {
684    stack: Vec<Node<'a>>,
685    cursor: Cursor<'a>,
686}
687
688impl<'a> Iterator for Preorder<'a> {
689    type Item = Node<'a>;
690
691    fn next(&mut self) -> Option<Self::Item> {
692        let node = self.stack.pop()?;
693        // Push children in document order, then reverse just the slice we
694        // appended so the leftmost child ends up on top of the stack and
695        // is visited next — pre-order without a per-node temporary.
696        let first_child = self.stack.len();
697        // Destructured so the stack and the cursor are borrowed as the
698        // disjoint fields they are.
699        let Self { stack, cursor } = self;
700        stack.extend(node.children_with(cursor));
701        stack[first_child..].reverse();
702        Some(node)
703    }
704}
705
706/// An `AST` cursor.
707#[derive(Clone)]
708pub(crate) struct Cursor<'a>(TreeCursor<'a>);
709
710impl<'a> Cursor<'a> {
711    pub(crate) fn reset(&mut self, node: &Node<'a>) {
712        self.0.reset(node.0);
713    }
714
715    pub(crate) fn goto_next_sibling(&mut self) -> bool {
716        self.0.goto_next_sibling()
717    }
718
719    pub(crate) fn goto_first_child(&mut self) -> bool {
720        self.0.goto_first_child()
721    }
722
723    pub(crate) fn node(&self) -> Node<'a> {
724        Node(self.0.node())
725    }
726}
727
728/// Position of a child scan, independent of who owns the cursor driving
729/// it.
730///
731/// [`Children`] and [`ChildrenWith`] differ only in that — one owns its
732/// cursor, the other borrows the caller's — and `children_with` exists
733/// to save an allocation, not to answer differently. Keeping the
734/// termination rule here rather than in each iterator is what stops the
735/// two from drifting.
736struct ChildScan {
737    done: bool,
738    remaining: usize,
739}
740
741impl ChildScan {
742    /// Seats `cursor` on `node`'s first child.
743    ///
744    /// `goto_first_child` returns false when the node has no children,
745    /// in which case the scan is exhausted from the outset. Termination
746    /// is then driven entirely by the cursor (see [`ChildScan::step`]),
747    /// so the iterator stops exactly when the tree reports no further
748    /// siblings — it can never pad the sequence with duplicate nodes if
749    /// `child_count` and the cursor walk ever disagree.
750    ///
751    /// `child_count` is the authoritative length for the
752    /// `ExactSizeIterator` contract; for well-formed trees it equals the
753    /// cursor sibling walk, so the reported length and the emitted data
754    /// agree. A childless node reports `0` so the empty iterator's
755    /// length matches its (lack of) data.
756    fn seed<'a>(node: &Node<'a>, cursor: &mut Cursor<'a>) -> Self {
757        cursor.reset(node);
758        Self::descend(node, cursor)
759    }
760
761    /// [`ChildScan::seed`], for a cursor already seated on `node` —
762    /// which is what [`Node::cursor`] hands back, and `ts_node_walk` and
763    /// `ts_tree_cursor_reset` run the same `ts_tree_cursor_init`. Only
764    /// [`Node::children`] may skip the reset; every other caller reuses
765    /// a cursor left wherever the previous scan ended.
766    fn descend<'a>(node: &Node<'a>, cursor: &mut Cursor<'a>) -> Self {
767        let done = !cursor.goto_first_child();
768        Self {
769            done,
770            remaining: if done { 0 } else { node.child_count() },
771        }
772    }
773
774    /// Yields the cursor's current child and advances past it.
775    fn step<'a>(&mut self, cursor: &mut Cursor<'a>) -> Option<Node<'a>> {
776        if self.done {
777            return None;
778        }
779        let result = cursor.node();
780        // The cursor is the single source of truth for termination:
781        // once there is no next sibling this yield is the last one.
782        self.done = !cursor.goto_next_sibling();
783        // Keep the advertised length consistent with termination: when
784        // the cursor stops, nothing remains. For well-formed trees this
785        // equals `child_count - emitted`; if the cursor walk and
786        // `child_count` ever disagree, this still honors the
787        // `ExactSizeIterator` contract (`len() == 0` exactly at
788        // exhaustion) rather than reporting a phantom remainder.
789        self.remaining = if self.done {
790            0
791        } else {
792            self.remaining.saturating_sub(1)
793        };
794        Some(result)
795    }
796
797    fn size_hint(&self) -> (usize, Option<usize>) {
798        (self.remaining, Some(self.remaining))
799    }
800}
801
802/// Iterator over a node's direct children, returned by
803/// [`Node::children`]. Owns the cursor it walks with.
804///
805/// Termination is driven by the cursor alone: each step yields the
806/// cursor's current node, then advances with `goto_next_sibling`,
807/// stopping the moment that returns false. This makes the cursor the
808/// single source of truth for both the emitted data and when to stop, so
809/// the sequence can never be padded with duplicates if `child_count` and
810/// the actual sibling walk disagree.
811///
812/// The `ExactSizeIterator` length is reported from `child_count` (tracked
813/// in [`ChildScan`]). For well-formed trees the cursor walk and
814/// `child_count` agree, so the advertised length matches the data.
815pub(crate) struct Children<'a> {
816    cursor: Cursor<'a>,
817    scan: ChildScan,
818}
819
820impl<'a> Iterator for Children<'a> {
821    type Item = Node<'a>;
822
823    fn next(&mut self) -> Option<Self::Item> {
824        self.scan.step(&mut self.cursor)
825    }
826
827    fn size_hint(&self) -> (usize, Option<usize>) {
828        self.scan.size_hint()
829    }
830}
831
832impl ExactSizeIterator for Children<'_> {}
833
834/// Iterator over a node's direct children, returned by
835/// [`Node::children_with`]. Borrows the caller's cursor rather than
836/// building one, which is the whole of the difference: it yields exactly
837/// what [`Children`] yields, through the same [`ChildScan`].
838pub(crate) struct ChildrenWith<'c, 'a> {
839    cursor: &'c mut Cursor<'a>,
840    scan: ChildScan,
841}
842
843impl<'a> Iterator for ChildrenWith<'_, 'a> {
844    type Item = Node<'a>;
845
846    fn next(&mut self) -> Option<Self::Item> {
847        self.scan.step(self.cursor)
848    }
849
850    fn size_hint(&self) -> (usize, Option<usize>) {
851        self.scan.size_hint()
852    }
853}
854
855impl ExactSizeIterator for ChildrenWith<'_, '_> {}
856
857impl<'a> Search<'a> for Node<'a> {
858    fn act_on_node(&self, action: &mut dyn FnMut(&Node<'a>, Ancestors<'a, '_>)) {
859        let mut cursor = self.cursor();
860        let mut stack = Vec::new();
861        // Ancestor chain of the node being visited, root first. Kept by
862        // the same truncate/push rule as the metric walk, so a predicate
863        // the action applies can read an ancestor as a slice index
864        // rather than through the `O(depth)` `Node::parent` (#1088).
865        //
866        // Seeded with this subtree root's own ancestry rather than left
867        // empty: `Ancestors` reads an empty chain as "this node is the
868        // tree root", so on a subtree an empty seed would report no
869        // parent for `*self` — silently costing e.g. the JS getters the
870        // binding a `function_expression` takes its name from. One
871        // climb, and none at all for the tree root this is called on
872        // today.
873        let mut chain: Vec<Node<'a>> = std::iter::successors(self.parent(), Node::parent).collect();
874        chain.reverse();
875        let depth = chain.len();
876
877        stack.push((*self, depth));
878
879        while let Some((node, depth)) = stack.pop() {
880            chain.truncate(depth);
881            action(&node, Ancestors::checked(&chain, &node));
882            chain.push(node);
883            // Source order in, tail reversed in place, so the LIFO
884            // `stack` yields the leftmost child first — pre-order with
885            // no staging buffer.
886            let first_child = stack.len();
887            stack.extend(
888                node.children_with(&mut cursor)
889                    .map(|child| (child, depth + 1)),
890            );
891            stack[first_child..].reverse();
892        }
893    }
894
895    fn first_child(&self, pred: fn(u16) -> bool) -> Option<Node<'a>> {
896        self.children().find(|&child| pred(child.kind_id()))
897    }
898
899    fn act_on_child(&self, action: &mut dyn FnMut(&Node<'a>)) {
900        for child in self.children() {
901            action(&child);
902        }
903    }
904}
905
906#[cfg(test)]
907mod tests {
908    use super::*;
909    use crate::langs::MozjsCode;
910    use crate::test_support::for_each_node_with_chain;
911
912    /// Under a parent narrow enough to read forward, the
913    /// `exclude_tests` prune finds the run of `#[…]` siblings before an
914    /// item through the walker's ancestor chain, never by resolving
915    /// siblings from the node.
916    ///
917    /// Nothing in the output says so: the backward walk this replaced
918    /// returns the same answer, only `O(depth)` per step (#1100), and
919    /// `rust_outer_attr_scans_agree` in `checker.rs` exists precisely
920    /// to prove the two agree. The counter is the sole observable, so a
921    /// revert is a silent quadratic without this.
922    ///
923    /// Every parent in the fixture holds at most five children, which
924    /// keeps it under `MAX_FORWARD_ATTRIBUTE_SCAN_CHILDREN` — the
925    /// backward walk is still the deliberate reading above that width,
926    /// so a wider fixture would assert the opposite of what it looks
927    /// like it asserts.
928    ///
929    /// Seeding a real lookup first is what makes the assertion
930    /// falsifiable: compared against zero it would also pass with
931    /// `record()` never wired up at all.
932    #[cfg(feature = "rust")]
933    #[test]
934    fn the_exclude_tests_prune_resolves_no_sibling_from_a_node() {
935        let source = "#[cfg(test)]\nmod tests {\nfn t() {}\n}\n\
936                      #[inline]\nfn kept() {\n#[allow(dead_code)]\nfn nested() {}\nlet x = 1;\n}\n";
937        let ast = crate::test_support::parse_named(crate::LANG::Rust, "lib.rs", source);
938
939        let root = Node(ast.as_tree_sitter().root_node());
940        let last = root.children().last().expect("the file has items");
941        let _ = last.previous_sibling();
942        let seeded = node_resolved_sibling_lookups::observed();
943        assert!(seeded > 0, "the seed call must be counted");
944
945        ast.metrics(crate::MetricsOptions::default().with_exclude_tests(true))
946            .expect("the walk must yield a top-level space");
947
948        assert_eq!(
949            node_resolved_sibling_lookups::observed(),
950            seeded,
951            "the metric walk resolved a sibling from a node; \
952             read it off the ancestor chain instead (#1096 / #1100)"
953        );
954    }
955
956    /// Which arm the `exclude_tests` attribute-scan dispatch takes, at
957    /// the boundary in both directions and on both of its axes.
958    ///
959    /// `rust_outer_attr_scans_agree` in `checker.rs` proves the two
960    /// readings answer the same thing, which is exactly why it cannot
961    /// see which one ran — it passes at any budget, including one that
962    /// never reads forward. This counter is the only observable that
963    /// tells them apart, and it lives here, so the boundary is pinned
964    /// here too.
965    ///
966    /// The third case is the one #1100 got wrong: dispatching on width
967    /// alone sent any over-wide body to the `O(depth)` walk however deep
968    /// it sat, which on a nested `mod` tree is quadratic (a 3_200-deep
969    /// fixture measured 2.67 s against 0.045 s for the same shape one
970    /// child narrower).
971    #[cfg(feature = "rust")]
972    #[test]
973    fn the_exclude_tests_prune_reads_forward_up_to_its_depth_scaled_budget() {
974        // Three attributed items make a `source_file` exactly six
975        // children wide — the depth-1 budget. A fourth, bare item makes
976        // seven, one over. Wrapping that in a `mod` puts the same seven
977        // between two braces, so its `declaration_list` is nine wide, at
978        // depth 3 — where the budget is also exactly nine.
979        let at_budget = "#[cfg(test)]\nfn a() {}\n#[inline]\nfn b() {}\n#[cfg(test)]\nfn c() {}\n";
980        let past_budget = format!("{at_budget}fn d() {{}}\n");
981        let nested = format!("mod m {{\n{past_budget}}}\n");
982
983        for (shape, source, resolves_siblings) in [
984            ("six children at depth 1", at_budget.to_string(), false),
985            ("seven children at depth 1", past_budget, true),
986            ("nine children at depth 3", nested, false),
987        ] {
988            let before = node_resolved_sibling_lookups::observed();
989            crate::test_support::parse_named(crate::LANG::Rust, "lib.rs", &source)
990                .metrics(crate::MetricsOptions::default().with_exclude_tests(true))
991                .expect("the walk must yield a top-level space");
992            let resolved = node_resolved_sibling_lookups::observed() > before;
993            assert_eq!(
994                resolved, resolves_siblings,
995                "{shape}: the prune took the wrong dispatch arm"
996            );
997        }
998    }
999
1000    /// The `child(0)` + `next_sibling()` chain [`Node::wraps_any`] used
1001    /// between #217 and #1088, kept here as the reference the cursor
1002    /// walk that replaced it is checked against.
1003    ///
1004    /// The swap was made for cost, not for behaviour: a sibling step
1005    /// resolves its parent, and `tree_sitter` resolves a parent by
1006    /// descending from the root, so the chain was `O(children × depth)`
1007    /// where the cursor is `O(children)`. Nothing about the *set* of
1008    /// children was supposed to change, and this is what says so —
1009    /// node-by-node over a real tree, same order and same short-circuit,
1010    /// without hardcoding grammar `kind_id`s.
1011    fn sibling_chain_has_sibling(node: OtherNode, id: u16) -> bool {
1012        node.parent().is_some_and(|parent| {
1013            let mut cur = parent.child(0);
1014            while let Some(c) = cur {
1015                if c.kind_id() == id {
1016                    return true;
1017                }
1018                cur = c.next_sibling();
1019            }
1020            false
1021        })
1022    }
1023
1024    #[test]
1025    fn has_sibling_matches_the_retired_sibling_chain() {
1026        // Arrow functions exercise the `check_if_arrow_func!` call site
1027        // that motivated #521 (PropertyIdentifier siblings on the JS/TS
1028        // closure-classification hot path).
1029        let code = b"const o = { m: (a) => a + 1, n: function () {} }; foo.bar();";
1030        let tree = Tree::new::<MozjsCode>(code);
1031        let ts_tree = tree.as_ts_tree();
1032
1033        // Collect the grammar kinds that actually occur, so the
1034        // equivalence check covers present-sibling (true) cases.
1035        let mut kinds = std::collections::BTreeSet::new();
1036        let mut stack = vec![ts_tree.root_node()];
1037        while let Some(n) = stack.pop() {
1038            kinds.insert(n.kind_id());
1039            let mut child = n.child(0);
1040            while let Some(c) = child {
1041                stack.push(c);
1042                child = c.next_sibling();
1043            }
1044        }
1045        // Include an id that does not occur anywhere for absent-sibling
1046        // (false) coverage.
1047        let absent_id = u16::MAX;
1048
1049        let mut stack = vec![ts_tree.root_node()];
1050        let mut matched = 0;
1051        while let Some(n) = stack.pop() {
1052            let wrapped = Node(n);
1053            for &id in kinds.iter().chain(std::iter::once(&absent_id)) {
1054                let found = wrapped.has_sibling(Ancestors::unknown(), id);
1055                assert_eq!(
1056                    found,
1057                    sibling_chain_has_sibling(n, id),
1058                    "has_sibling diverged from the retired sibling chain at node kind {} for id {id}",
1059                    n.kind(),
1060                );
1061                matched += usize::from(found);
1062            }
1063            let mut child = n.child(0);
1064            while let Some(c) = child {
1065                stack.push(c);
1066                child = c.next_sibling();
1067            }
1068        }
1069        // The comment above claims the collected kinds cover the
1070        // present-sibling case; this enforces it. Both sides answering
1071        // `false` everywhere would agree without either scan ever
1072        // running to a match.
1073        assert!(
1074            matched > 0,
1075            "every answer was `false`, so the sibling scan was never exercised"
1076        );
1077
1078        // No-parent node (root) always reports no sibling.
1079        let root = Node(ts_tree.root_node());
1080        assert!(!root.has_sibling(Ancestors::unknown(), absent_id));
1081        for &id in &kinds {
1082            assert!(
1083                !root.has_sibling(Ancestors::unknown(), id),
1084                "root node has no parent → no sibling"
1085            );
1086        }
1087    }
1088
1089    /// `children()` must yield exactly the node's direct children, in
1090    /// order, for every node in a real tree — including the empty
1091    /// (leaf) and single-child cases. Termination is cursor-driven, so
1092    /// the emitted set is compared node-by-node against the raw
1093    /// tree-sitter `child(i)` walk (the ground truth for both order and
1094    /// count). This pins the no-duplicate-padding property: a desync
1095    /// between `child_count` and the cursor walk would surface here as
1096    /// extra trailing duplicates or a length mismatch.
1097    #[test]
1098    fn children_matches_tree_sitter_child_walk() {
1099        // Mix of leaf nodes (no children), single-child wrappers, and
1100        // multi-child constructs to cover all arities.
1101        let code = b"const o = { m: (a) => a + 1 }; foo(); ;";
1102        let tree = Tree::new::<MozjsCode>(code);
1103        let ts_tree = tree.as_ts_tree();
1104
1105        let mut stack = vec![ts_tree.root_node()];
1106        while let Some(n) = stack.pop() {
1107            let wrapped = Node(n);
1108
1109            // Ground truth: walk children by index off the raw node.
1110            let expected: Vec<_> = (0..n.child_count() as u32)
1111                .filter_map(|i| n.child(i))
1112                .map(|c| (c.id(), c.kind_id()))
1113                .collect();
1114
1115            let actual =
1116                drain_checking_exact_size(wrapped.children(), expected.len(), "children", n.kind());
1117            assert_eq!(
1118                actual,
1119                expected,
1120                "children() diverged from child(i) walk at kind {}",
1121                n.kind(),
1122            );
1123
1124            for i in 0..n.child_count() as u32 {
1125                if let Some(c) = n.child(i) {
1126                    stack.push(c);
1127                }
1128            }
1129        }
1130    }
1131
1132    /// `child_by_field_name` (issue #786) must return the child at the
1133    /// underlying tree lifetime `'a`, not the method-call borrow of
1134    /// `&self`. The proof is a helper whose return type *requires* the
1135    /// child to outlive an intermediate `&Node` borrow: under the old
1136    /// `Option<Node<'_>>` signature the returned node would be tied to
1137    /// `parent`'s borrow and this would fail to compile. Binding the
1138    /// child to a variable that outlives the `&parent` reborrow inside
1139    /// the helper exercises the widened lifetime.
1140    #[test]
1141    fn child_by_field_name_outlives_self_borrow() {
1142        // `find_named_child` takes the parent by value, reborrows it
1143        // through a `&` reference to call `child_by_field_name`, and
1144        // returns the child. The returned `Node<'a>` must survive past
1145        // that inner `&parent` borrow — only possible because the child
1146        // carries the tree lifetime, not the borrow of `&parent`.
1147        fn find_named_child<'a>(parent: Node<'a>) -> Option<Node<'a>> {
1148            let borrowed: &Node<'a> = &parent;
1149            borrowed.child_by_field_name("declarator")
1150        }
1151
1152        let code = b"int answer = 42;";
1153        let tree = Tree::new::<crate::langs::CppCode>(code);
1154        let root = tree.get_root();
1155
1156        // Walk to the `declaration` node, then pull its `declarator`
1157        // child out and hold it after the producing borrow has ended.
1158        let mut held: Option<Node> = None;
1159        let mut stack = vec![root];
1160        while let Some(n) = stack.pop() {
1161            if n.kind() == "declaration" {
1162                // `find_named_child` consumes a copy of `n`; the result
1163                // must remain valid here, well past the inner borrow.
1164                held = find_named_child(n);
1165                break;
1166            }
1167            for child in n.children() {
1168                stack.push(child);
1169            }
1170        }
1171
1172        let declarator = held.expect("C declaration has a `declarator` field");
1173        // The held node is still usable: it kept its tree linkage rather
1174        // than dangling at the end of the producing borrow.
1175        assert_eq!(declarator.kind(), "init_declarator");
1176    }
1177
1178    /// `Node::as_tree_sitter` (issue #556) must hand back the *same*
1179    /// underlying `tree_sitter::Node` the wrapper holds: identical
1180    /// `kind()` / `kind_id()` and a usable tree-sitter API. Obtaining
1181    /// the wrapper through the public `CppParser` + `ParserTrait::root`
1182    /// path (rather than the in-module `Tree::new`) proves the accessor
1183    /// is the public seam that replaced the former `pub` `.0` field.
1184    #[test]
1185    fn as_tree_sitter_round_trips_wrapper_kind() {
1186        use crate::{CppParser, ParserTrait};
1187        use std::path::Path;
1188
1189        let source = b"int main() { return 0; }";
1190        let parser = CppParser::new(source.to_vec(), Path::new("example.cpp"), None);
1191        let root = parser.root();
1192
1193        let ts_root = root.as_tree_sitter();
1194
1195        // A well-formed C++ translation unit roots at `translation_unit`.
1196        assert_eq!(ts_root.kind(), "translation_unit");
1197        // The accessor must agree with the wrapper's own kind views.
1198        assert_eq!(ts_root.kind(), root.kind());
1199        assert_eq!(ts_root.kind_id(), root.kind_id());
1200        // The returned node is usable as a tree-sitter node, not a copy
1201        // that has lost its tree linkage: the parse is error-free and
1202        // the root has children.
1203        assert!(!ts_root.has_error());
1204        assert!(ts_root.child_count() > 0);
1205    }
1206
1207    /// Ground-truth pre-order walk over the raw tree-sitter node, by
1208    /// document order (`child(0..child_count)`). [`Node::preorder`] must
1209    /// emit exactly this sequence of node ids — node first, then each
1210    /// child subtree left to right.
1211    fn ground_truth_preorder(node: OtherNode) -> Vec<usize> {
1212        let mut out = vec![node.id()];
1213        for i in 0..node.child_count() as u32 {
1214            if let Some(child) = node.child(i) {
1215                out.extend(ground_truth_preorder(child));
1216            }
1217        }
1218        out
1219    }
1220
1221    #[test]
1222    fn preorder_matches_recursive_document_order() {
1223        // A nested construct (function holding a declaration and a call)
1224        // gives the walk real depth and sibling fan-out to order.
1225        let code = b"int main() { int x = 1; foo(x); return 0; }";
1226        let tree = Tree::new::<crate::langs::CppCode>(code);
1227        let root = tree.get_root();
1228
1229        let actual: Vec<usize> = root.preorder().map(|n| n.id()).collect();
1230        let expected = ground_truth_preorder(root.as_tree_sitter());
1231
1232        assert_eq!(
1233            actual, expected,
1234            "preorder diverged from recursive child(0..n) document order"
1235        );
1236        // Sanity: a non-trivial tree, and the root is visited first.
1237        assert!(actual.len() > 5, "expected a multi-node tree");
1238        assert_eq!(actual[0], root.id(), "root must be yielded first");
1239    }
1240
1241    #[test]
1242    fn descendants_by_kind_collects_matching_subtree_nodes() {
1243        // `x` is declared once and used twice, so three `identifier`
1244        // nodes exist under the function; `main` is an identifier too.
1245        let code = b"int main() { int x = 1; return x + x; }";
1246        let tree = Tree::new::<crate::langs::CppCode>(code);
1247        let root = tree.get_root();
1248
1249        let found = root.descendants_by_kind(&["identifier"]);
1250        // Cross-check against an independent pre-order count so the helper
1251        // cannot pass by matching everything or nothing.
1252        let expected: Vec<usize> = root
1253            .preorder()
1254            .filter(|n| n.kind() == "identifier")
1255            .map(|n| n.id())
1256            .collect();
1257        let actual: Vec<usize> = found.iter().map(Node::id).collect();
1258        assert_eq!(actual, expected);
1259        assert!(
1260            found.len() >= 3,
1261            "expected at least the `main`, `x` decl, and `x` uses"
1262        );
1263        assert!(
1264            found.iter().all(|n| n.kind() == "identifier"),
1265            "every collected node must match the requested kind"
1266        );
1267
1268        // An absent kind yields nothing; a multi-kind filter unions.
1269        assert!(root.descendants_by_kind(&["no_such_kind"]).is_empty());
1270        assert!(
1271            root.descendants_by_kind(&["identifier", "number_literal"])
1272                .len()
1273                > found.len(),
1274            "adding `number_literal` must widen the match set"
1275        );
1276    }
1277
1278    /// `descendant_count` must count the same nodes the metric walk
1279    /// visits, because `spaces::compute::metrics_inner` uses it as the
1280    /// exact capacity for a map that ends up holding one entry per
1281    /// visited node.
1282    ///
1283    /// The risk it guards is silent: `ts_node_descendant_count` counts
1284    /// *visible* descendants, so were it ever to narrow to named nodes
1285    /// only, the reserve would under-size by the anonymous-token share
1286    /// of the tree — roughly half — and the map would quietly go back
1287    /// to rehashing, with no test failing. The source below is chosen to
1288    /// carry plenty of anonymous tokens (`int`, `(`, `{`, `=`, `;`) so
1289    /// the named-only reading is not accidentally equal.
1290    #[test]
1291    fn descendant_count_matches_the_walked_node_population() {
1292        let code = b"int main() { int x = 1; foo(x); return 0; }";
1293        let tree = Tree::new::<crate::langs::CppCode>(code);
1294        let root = tree.get_root();
1295
1296        // `preorder` yields the node itself and then every descendant,
1297        // enumerating children exactly as the metric walk's
1298        // `push_children` does.
1299        let walked = root.preorder().count();
1300        assert_eq!(
1301            root.descendant_count(),
1302            walked,
1303            "descendant_count must equal the pre-order node count"
1304        );
1305
1306        let named = root.preorder().filter(Node::is_named).count();
1307        assert!(
1308            named < walked,
1309            "fixture must contain anonymous tokens, else the assertion \
1310             above cannot distinguish a named-only count"
1311        );
1312    }
1313
1314    /// Drains `iter`, holding it to the `ExactSizeIterator` contract at
1315    /// every step, and returns the `(id, kind_id)` of each child yielded.
1316    ///
1317    /// `len()` must equal the node's `child_count` before the first step,
1318    /// fall by exactly one per yield, and be zero at exhaustion. Both
1319    /// child iterators are checked against it, so the contract is stated
1320    /// once — `children_with` exists to save an allocation, and a
1321    /// separate copy of this is how the two would come to disagree.
1322    fn drain_checking_exact_size<'a>(
1323        mut iter: impl ExactSizeIterator<Item = Node<'a>>,
1324        child_count: usize,
1325        what: &str,
1326        kind: &str,
1327    ) -> Vec<(usize, u16)> {
1328        assert_eq!(
1329            iter.len(),
1330            child_count,
1331            "{what}().len() disagreed with child_count at kind {kind}"
1332        );
1333        let mut remaining = child_count;
1334        let mut drained = Vec::with_capacity(remaining);
1335        while let Some(child) = iter.next() {
1336            remaining -= 1;
1337            assert_eq!(
1338                iter.len(),
1339                remaining,
1340                "{what}() size_hint drifted mid-iteration at kind {kind}"
1341            );
1342            drained.push((child.id(), child.kind_id()));
1343        }
1344        assert_eq!(
1345            iter.len(),
1346            0,
1347            "{what}() was not drained to zero len at kind {kind}"
1348        );
1349        drained
1350    }
1351
1352    /// Ancestor ids yielded by `ancestors`, nearest first.
1353    fn ancestor_ids(ancestors: Ancestors<'_, '_>, node: &Node<'_>) -> Vec<usize> {
1354        ancestors.iter(node).map(|(a, _)| a.id()).collect()
1355    }
1356
1357    /// A known chain must answer every ancestor question exactly as
1358    /// climbing with `Node::parent` does — that equivalence is the whole
1359    /// premise of #1084, and it is what lets the predicates keep their
1360    /// original logic while dropping the `O(depth)` lookup.
1361    ///
1362    /// Checked node-by-node over one fixture per grammar family that
1363    /// actually consults an ancestor: C-family (`is_else_if` via the
1364    /// parent clause, `loc`'s declaration gate), JVM-family
1365    /// (`is_else_if` via the preceding `else` token), Python (the
1366    /// grandparent shape), and Elixir (`quote` templates).
1367    #[test]
1368    fn a_known_chain_answers_exactly_what_climbing_answers() {
1369        /// `must_nest` names kinds that have to appear *inside another
1370        /// node of the same kind* in the fixture. `visited > 20` alone
1371        /// does not keep a fixture honest: a grammar bump that flattened
1372        /// the nesting a row was added for would leave a large,
1373        /// clean-parsing tree that no longer exercises the shape, and
1374        /// the parity assertions would keep passing over it.
1375        fn assert_parity<L: LanguageInfo>(label: &str, code: &[u8], must_nest: &[&str]) {
1376            let mut nested_seen = vec![false; must_nest.len()];
1377            let visited = for_each_node_with_chain::<L>(code, |node, chain| {
1378                for (slot, kind) in nested_seen.iter_mut().zip(must_nest) {
1379                    *slot |= node.kind() == *kind
1380                        && chain.iter().any(|ancestor| ancestor.kind() == *kind);
1381                }
1382                let known = Ancestors::known(chain);
1383                let climbing = Ancestors::unknown();
1384                assert_eq!(
1385                    known.parent(node).map(|p| p.id()),
1386                    climbing.parent(node).map(|p| p.id()),
1387                    "{label}: parent of {} disagrees",
1388                    node.kind()
1389                );
1390                assert_eq!(
1391                    known.previous_sibling(node).map(|p| p.id()),
1392                    climbing.previous_sibling(node).map(|p| p.id()),
1393                    "{label}: previous sibling of {} disagrees",
1394                    node.kind()
1395                );
1396                assert_eq!(
1397                    ancestor_ids(known, node),
1398                    ancestor_ids(climbing, node),
1399                    "{label}: ancestor chain of {} disagrees",
1400                    node.kind()
1401                );
1402                // Each ancestor is handed *its* own chain, so a
1403                // predicate applied one level up stays as cheap and as
1404                // correct as one applied to the node itself.
1405                for (ancestor, above) in known.iter(node) {
1406                    assert_eq!(
1407                        above.parent(&ancestor).map(|p| p.id()),
1408                        ancestor.parent().map(|p| p.id()),
1409                        "{label}: sub-chain handed to {} is not its own",
1410                        ancestor.kind()
1411                    );
1412                }
1413            });
1414            assert!(visited > 20, "{label}: fixture is too small to prove much");
1415            for (found, kind) in nested_seen.iter().zip(must_nest) {
1416                assert!(
1417                    found,
1418                    "{label}: no `{kind}` sits inside another `{kind}`, so the \
1419                     fixture no longer exercises the nesting it was added for"
1420                );
1421            }
1422        }
1423
1424        assert_parity::<crate::langs::CCode>(
1425            "c",
1426            b"int main() { if (a) { int x; } else if (b) { for (int i = 0; i < 2; i++) x; } }",
1427            &[],
1428        );
1429        assert_parity::<crate::langs::JavaCode>(
1430            "java",
1431            b"class A { void m() { if (a) {} else if (b) {} else {} for (int i = 0; i < 2; i++) {} } }",
1432            &[],
1433        );
1434        assert_parity::<crate::langs::PythonCode>(
1435            "python",
1436            b"def f(a, b):\n    if a:\n        pass\n    else:\n        if b:\n            pass\n    return a and b or a\n",
1437            &[],
1438        );
1439        assert_parity::<crate::langs::ElixirCode>(
1440            "elixir",
1441            b"defmodule M do\n  def g do\n    :ok\n  end\n  quote do\n    def f do\n      :ok\n    end\n  end\nend\n",
1442            &["call"],
1443        );
1444
1445        // The shapes #1062 added as consumers, which the four fixtures
1446        // above do not contain: a function nested inside a function
1447        // (every language's `increment_function_depth` arm walks the
1448        // chain looking for one) and the two default-arm checks that
1449        // now read `Ancestors::parent` — Kotlin's `else ->` inside a
1450        // `when` and Ruby's `else` inside a `case`. Parity over the
1451        // machinery is not parity over the shape a caller asks about.
1452        assert_parity::<crate::langs::RustCode>(
1453            "rust",
1454            b"fn f(a: bool) { if a { } else if a { } fn g(b: bool) { if b { } } }\n",
1455            &["function_item"],
1456        );
1457        assert_parity::<crate::langs::KotlinCode>(
1458            "kotlin",
1459            b"fun f(x: Int) {\n    when (x) {\n        1 -> {}\n        else -> {}\n    }\n    fun g() {\n        if (x > 0) {}\n    }\n}\n",
1460            &["function_declaration"],
1461        );
1462        assert_parity::<crate::langs::RubyCode>(
1463            "ruby",
1464            b"def f(x)\n  case x\n  when 1 then 1\n  else 2\n  end\n  def g\n    if x\n    end\n  end\nend\n",
1465            &["method"],
1466        );
1467
1468        // The shape #1088 added as a consumer: the JS-family
1469        // `Checker::is_func` / `is_closure` walk upward from an
1470        // `arrow_function` / `function_expression` looking for the
1471        // binding that names it, and end on `Ancestors::previous_sibling`
1472        // through `has_sibling`. None of the fixtures above contains
1473        // either node.
1474        assert_parity::<crate::langs::JavascriptCode>(
1475            "javascript",
1476            b"const f = a => { a => { g(() => 1); }; };\nconst o = { m: function () { return 1; } };\n",
1477            &["arrow_function"],
1478        );
1479    }
1480
1481    /// The traversals #1112 moved onto [`Node::children_with`] must scan
1482    /// a whole tree on one cursor, not one per node.
1483    ///
1484    /// Nothing in the output says so: `children_with` yields exactly
1485    /// what `children` yields, so every metric, marker, and pre-order
1486    /// assertion in the suite holds just as well with a fresh
1487    /// `TreeCursor` built and freed per visited node. The counter is the
1488    /// only observable, which is why reverting one of these loops has to
1489    /// be a test failure rather than a silent allocation per node.
1490    ///
1491    /// Seeding a real scan first is what makes it falsifiable: compared
1492    /// against zero these assertions would also pass with `record()`
1493    /// never wired up at all.
1494    #[cfg(all(feature = "c", feature = "mozjs", feature = "python", feature = "rust"))]
1495    #[test]
1496    fn the_converted_traversals_scan_a_tree_on_one_cursor() {
1497        use crate::traits::ParserTrait;
1498
1499        let seed_tree = Tree::new::<crate::langs::CCode>(b"int main() { int a; }");
1500        let _ = seed_tree.get_root().children().count();
1501        assert!(
1502            child_scan_cursors::observed() > 0,
1503            "the seed scan must be counted"
1504        );
1505
1506        // `preorder` over a tree far larger than any per-call constant,
1507        // so "one per node" and "one per walk" cannot be confused.
1508        let tree = Tree::new::<MozjsCode>(
1509            b"const o = { m: (a) => a + 1, n: function () { return [1, 2, 3]; } };\nfoo(o);\n",
1510        );
1511        let before = child_scan_cursors::observed();
1512        let visited = tree.get_root().preorder().count();
1513        assert!(visited > 40, "fixture is too small to prove much");
1514        assert_eq!(
1515            child_scan_cursors::observed(),
1516            before,
1517            "preorder built a cursor per node; it holds one for the walk (#1112)"
1518        );
1519
1520        // The Python instance-attribute scan walks every method body of
1521        // a class. Before #1112 it was 92 % of the metric walk's child
1522        // scans on the Python corpus slice — one per node under the
1523        // class. It is not the only scan a `metrics()` call makes, so
1524        // the bound is a fraction of the node count rather than zero.
1525        // Measured on this fixture: 18 scans over 81 nodes with the
1526        // cursor hoisted, 91 without, so the bound separates the two
1527        // with room on both sides.
1528        let source = "class C:\n    def a(self):\n        self.x = 1\n        self.y = [1, 2]\n\
1529                      \n    def b(self):\n        self.z, self.w = 1, 2\n        \
1530                      if self.x:\n            self.v = self.y\n";
1531        let ast = crate::test_support::parse_named(crate::LANG::Python, "c.py", source);
1532        let nodes = ast.root_node().preorder().count();
1533        let before = child_scan_cursors::observed();
1534        ast.metrics(crate::MetricsOptions::default())
1535            .expect("the walk must yield a top-level space");
1536        let scans = child_scan_cursors::observed() - before;
1537        assert!(nodes > 60, "fixture is too small to prove much");
1538        assert!(
1539            scans < nodes / 2,
1540            "the Python metric walk built {scans} cursors over {nodes} nodes; the \
1541             instance-attribute scan is meant to hold one for the subtree (#1112)"
1542        );
1543
1544        // The suppression scan is a full-tree DFS of its own: 0 scans
1545        // over this fixture's 29 nodes with the cursor hoisted, 29
1546        // without.
1547        let parser = crate::langs::RustParser::new(
1548            b"// bca: suppress(cognitive)\nfn f() { if a { g(1, 2); } }\n".to_vec(),
1549            std::path::Path::new("lib.rs"),
1550            None,
1551        );
1552        let nodes = parser.root().preorder().count();
1553        let before = child_scan_cursors::observed();
1554        let markers = crate::suppression::suppression_markers(&parser);
1555        let scans = child_scan_cursors::observed() - before;
1556        assert_eq!(markers.len(), 1, "fixture carries one marker");
1557        assert!(nodes > 20, "fixture is too small to prove much");
1558        assert!(
1559            scans < nodes / 2,
1560            "the suppression scan built {scans} cursors over {nodes} nodes (#1112)"
1561        );
1562
1563        // The `Search` walk, `act_on_node`. The counter records in
1564        // `children()`, the allocating form, so a walk that hoists its
1565        // cursor records nothing at all and a per-node one records once
1566        // per interior node. Asserting the exact zero is what tells a
1567        // hoisted cursor from a per-node one; a bound like `< nodes / 2`
1568        // would hold for either on a small fixture.
1569        let tree = Tree::new::<MozjsCode>(
1570            b"function f(a) { return { g: (b) => b + 1, h: [1, 2, 3] }; }\nf(2);\n",
1571        );
1572        let root = tree.get_root();
1573        let nodes = root.preorder().count();
1574        assert!(nodes > 30, "fixture is too small to prove much");
1575
1576        let before = child_scan_cursors::observed();
1577        let mut seen = 0_usize;
1578        root.act_on_node(&mut |_, _| seen += 1);
1579        let scans = child_scan_cursors::observed() - before;
1580        assert_eq!(seen, nodes, "act_on_node must visit every node");
1581        assert_eq!(
1582            scans, 0,
1583            "act_on_node built {scans} cursors over {nodes} nodes; it holds one \
1584             for the walk (#1112)"
1585        );
1586    }
1587
1588    /// [`Node::parent_grandparent_match`] must answer `false` when
1589    /// either link is missing, rather than degrading to a
1590    /// single-predicate check.
1591    ///
1592    /// Its doc states that invariant and Python's `Cyclomatic` `else`
1593    /// arm depends on it, but nothing exercised either absent-link
1594    /// return: every call in the suite runs on a node that has both a
1595    /// parent and a grandparent. Both predicates answer `true` here, so
1596    /// a `false` result can only come from the missing link — an
1597    /// implementation that skipped the second `climb.next()` and
1598    /// returned `parent_pred`'s answer would pass every other test and
1599    /// fail this one.
1600    ///
1601    /// Checked through both `Ancestors` constructors: the chain and the
1602    /// climb reach the end by different code paths (`split_last` on an
1603    /// empty slice, versus `Node::parent` returning `None`).
1604    #[test]
1605    fn parent_grandparent_match_is_false_when_either_link_is_absent() {
1606        let tree = Tree::new::<crate::langs::CCode>(b"int main() { int a; }");
1607        let root = tree.get_root();
1608        let child = root.children().next().expect("the file has an item");
1609        let grandchild = child
1610            .children()
1611            .next()
1612            .expect("the function definition has children");
1613        let yes: fn(&Node) -> bool = |_| true;
1614
1615        // No parent at all: the root, reached either way.
1616        assert!(!root.parent_grandparent_match(Ancestors::unknown(), yes, yes));
1617        assert!(!root.parent_grandparent_match(Ancestors::known(&[]), yes, yes));
1618
1619        // A parent but no grandparent: a direct child of the root.
1620        assert!(!child.parent_grandparent_match(Ancestors::unknown(), yes, yes));
1621        assert!(!child.parent_grandparent_match(Ancestors::known(&[root]), yes, yes));
1622
1623        // Both links present, so the same predicates now answer `true`.
1624        // Without this the assertions above would also hold for a
1625        // function that always returned `false`.
1626        assert!(grandchild.parent_grandparent_match(Ancestors::unknown(), yes, yes));
1627        assert!(grandchild.parent_grandparent_match(Ancestors::known(&[root, child]), yes, yes));
1628    }
1629
1630    /// [`Node::children_with`] must yield exactly what
1631    /// [`Node::children`] yields — same nodes, same order, same
1632    /// `ExactSizeIterator` length at every step — for every node of a
1633    /// real tree.
1634    ///
1635    /// Checked against the raw `child(i)` walk rather than against
1636    /// `children()`: the two iterators share [`ChildScan`], so a
1637    /// comparison between them would pass just as happily if the shared
1638    /// step were wrong. It also covers the reuse itself — one cursor
1639    /// drives every node's scan here, so a `reset` that failed to rewind
1640    /// would show as the second node inheriting the first's position.
1641    #[test]
1642    fn children_with_yields_exactly_what_children_does() {
1643        let code = b"const o = { m: (a) => a + 1, n: function () {} }; foo(); ;";
1644        let tree = Tree::new::<MozjsCode>(code);
1645        let root = tree.get_root();
1646
1647        let mut cursor = root.cursor();
1648        let mut leaves = 0;
1649        let mut widest = 0;
1650        for node in root.preorder() {
1651            // Ground truth is the raw `child(i)` walk, not `children()`.
1652            // The two iterators share `ChildScan`, so checking one
1653            // against the other would pass just as happily if the shared
1654            // step were wrong.
1655            let raw = node.as_tree_sitter();
1656            let expected: Vec<_> = (0..raw.child_count() as u32)
1657                .filter_map(|i| raw.child(i))
1658                .map(|c| (c.id(), c.kind_id()))
1659                .collect();
1660
1661            let actual = drain_checking_exact_size(
1662                node.children_with(&mut cursor),
1663                expected.len(),
1664                "children_with",
1665                node.kind(),
1666            );
1667            assert_eq!(
1668                actual,
1669                expected,
1670                "children_with diverged from the child(i) walk at kind {}",
1671                node.kind()
1672            );
1673
1674            leaves += usize::from(expected.is_empty());
1675            widest = widest.max(expected.len());
1676        }
1677        // Both ends of the arity range, else the comparison could hold
1678        // over nothing but one-child wrappers.
1679        assert!(leaves > 0, "fixture must contain childless nodes");
1680        assert!(widest > 2, "fixture must contain a multi-child node");
1681    }
1682
1683    /// The `O(1)` guard [`Ancestors::checked`] keeps on by default must
1684    /// accept every chain a walker really builds — over several grammar
1685    /// families, not just the one fixture a failure would surface in.
1686    ///
1687    /// The assertion that an equal-span pair was seen is what makes the
1688    /// containment non-strict on purpose rather than by luck: a
1689    /// single-child wrapper (`expression_statement` over its expression,
1690    /// say) spans exactly what its child spans, so tightening either
1691    /// bound to `<` would reject a correct chain on most real input.
1692    #[test]
1693    fn checked_accepts_the_chains_the_walkers_build() {
1694        let mut equal_span_pairs = 0;
1695        let mut check = |node: &Node<'_>, chain: &[Node<'_>]| {
1696            let _ = Ancestors::checked(chain, node);
1697            if let Some(parent) = chain.last()
1698                && parent.start_byte() == node.start_byte()
1699                && parent.end_byte() == node.end_byte()
1700            {
1701                equal_span_pairs += 1;
1702            }
1703        };
1704        let visited = for_each_node_with_chain::<crate::langs::CCode>(
1705            b"int main() { if (a) { int x; } else { f(a, b); } }",
1706            &mut check,
1707        ) + for_each_node_with_chain::<crate::langs::JavascriptCode>(
1708            b"const o = { m: (a) => a + 1 };\nfoo.bar();\n",
1709            &mut check,
1710        ) + for_each_node_with_chain::<crate::langs::PythonCode>(
1711            b"def f(a):\n    if a:\n        return [x for x in a]\n",
1712            &mut check,
1713        );
1714
1715        assert!(visited > 60, "fixtures are too small to prove much");
1716        assert!(
1717            equal_span_pairs > 0,
1718            "no parent spans exactly what its child does, so this fixture set \
1719             cannot tell non-strict containment from strict"
1720        );
1721    }
1722
1723    /// A `push` moved ahead of the per-node computes leaves the node
1724    /// itself as `chain.last()`. Spans alone cannot see that — a node
1725    /// contains itself — so the identity half of the guard is what
1726    /// catches it.
1727    ///
1728    /// Debug-gated because `debug_assert!` compiles out under
1729    /// `--release`, where `checked` degrades to `known` by design.
1730    #[test]
1731    #[cfg(debug_assertions)]
1732    #[should_panic(expected = "ancestor chain desynchronised")]
1733    fn checked_rejects_a_chain_ending_in_the_node_itself() {
1734        let tree = Tree::new::<crate::langs::CCode>(b"int main() { int a; }");
1735        let body = tree
1736            .get_root()
1737            .preorder()
1738            .find(|n| n.kind() == "compound_statement")
1739            .expect("fixture has a function body");
1740        let _ = Ancestors::checked(std::slice::from_ref(&body), &body);
1741    }
1742
1743    /// A dropped `truncate` leaves the previous subtree's path in place,
1744    /// so the next node up gets a `chain.last()` from a sibling subtree —
1745    /// disjoint from it in bytes. That is the containment half.
1746    #[test]
1747    #[cfg(debug_assertions)]
1748    #[should_panic(expected = "ancestor chain desynchronised")]
1749    fn checked_rejects_a_chain_from_a_disjoint_subtree() {
1750        let tree = Tree::new::<crate::langs::CCode>(b"int main() { int a; int b; }");
1751        let body = tree
1752            .get_root()
1753            .preorder()
1754            .find(|n| n.kind() == "compound_statement")
1755            .expect("fixture has a function body");
1756        let declarations: Vec<Node<'_>> = body
1757            .children()
1758            .filter(|n| n.kind() == "declaration")
1759            .collect();
1760        assert_eq!(declarations.len(), 2, "fixture has two declarations");
1761        // `int a;` neither contains nor equals `int b;`.
1762        let _ = Ancestors::checked(&declarations[..1], &declarations[1]);
1763    }
1764
1765    /// [`Node::has_sibling`] must answer the same whether its parent
1766    /// comes off a known chain or from `Node::parent`.
1767    ///
1768    /// The parent lookup is the only thing #1088 changed here, and it is
1769    /// the half a caller cannot see: `check_if_arrow_func!` folds the
1770    /// answer into a disjunction, so a wrong parent would silently
1771    /// reclassify an arrow function rather than fail. Checked for every
1772    /// node against every kind the fixture contains, plus one that never
1773    /// occurs so the absent-sibling answer is covered too.
1774    #[test]
1775    fn has_sibling_agrees_between_known_and_climbing() {
1776        // Object-literal methods and an arrow bound to a property are
1777        // the shapes whose `PropertyIdentifier` sibling the JS closure
1778        // check asks about.
1779        let code = b"const o = { m: (a) => a + 1, n: function () {} };\nconst p = a => a;\n";
1780        let mut kinds = std::collections::BTreeSet::new();
1781        for_each_node_with_chain::<crate::langs::JavascriptCode>(code, |node, _| {
1782            kinds.insert(node.kind_id());
1783        });
1784        // An id no node in the fixture carries, so the `false` answer is
1785        // exercised as well as the `true` one.
1786        let absent = u16::MAX;
1787        let mut agreed_true = 0;
1788        let visited =
1789            for_each_node_with_chain::<crate::langs::JavascriptCode>(code, |node, chain| {
1790                for &id in kinds.iter().chain(std::iter::once(&absent)) {
1791                    let known = node.has_sibling(Ancestors::known(chain), id);
1792                    let climbing = node.has_sibling(Ancestors::unknown(), id);
1793                    assert_eq!(
1794                        known,
1795                        climbing,
1796                        "has_sibling({id}) on {} disagrees between chain and climb",
1797                        node.kind()
1798                    );
1799                    agreed_true += usize::from(known);
1800                }
1801            });
1802        assert!(visited > 20, "fixture is too small to prove much");
1803        assert!(
1804            agreed_true > 0,
1805            "every answer was `false`, so the sibling scan never ran to a match"
1806        );
1807    }
1808
1809    /// [`Search::act_on_node`] must hand each node its true ancestry
1810    /// even when the walk starts below the tree root.
1811    ///
1812    /// The seed is what decides this. [`Ancestors`] reads an empty chain
1813    /// as "this node is the root", so seeding empty — which is correct
1814    /// for the one caller that exists today, `bca function`, whose walk
1815    /// starts at the root — would report no parent for the subtree root
1816    /// and shift every answer beneath it. For the JS getters that means
1817    /// losing the `variable_declarator` a `function_expression` takes
1818    /// its name from, so the space would silently be named
1819    /// `<anonymous>`.
1820    ///
1821    /// No caller passes a subtree yet, so nothing else would catch this;
1822    /// the fixture below is the guard, and it fails against an empty
1823    /// seed both here and through `Ancestors::checked`'s debug
1824    /// assertion.
1825    #[test]
1826    fn act_on_node_hands_a_subtree_its_real_ancestry() {
1827        let code = b"var outer = function () { return 1; };\n";
1828        let tree = Tree::new::<MozjsCode>(code);
1829        let root = tree.get_root();
1830        let subtree = root
1831            .preorder()
1832            .find(|n| n.kind() == "variable_declarator")
1833            .expect("fixture has a variable_declarator");
1834        assert!(
1835            subtree.parent().is_some(),
1836            "the walk must start below the root, else the seed is vacuous"
1837        );
1838
1839        let mut visited = 0;
1840        subtree.act_on_node(&mut |node, ancestors| {
1841            assert_eq!(
1842                ancestors.parent(node).map(|p| p.id()),
1843                node.parent().map(|p| p.id()),
1844                "parent of {} disagrees with the tree",
1845                node.kind()
1846            );
1847            visited += 1;
1848        });
1849        assert!(visited > 3, "subtree is too small to prove much");
1850    }
1851
1852    /// `previous_sibling` must not answer "no previous sibling" when the
1853    /// chain it was handed belongs to a different node.
1854    ///
1855    /// The known path finds the answer by scanning the chain's last
1856    /// entry for `node`; a miss means the caller paired the two wrongly.
1857    /// Reporting `None` there would be a wrong answer dressed as a
1858    /// legitimate one, so the fallback re-asks the tree.
1859    #[test]
1860    fn previous_sibling_falls_back_on_a_chain_that_is_not_this_nodes() {
1861        let code = b"int main() { int a; int b; }";
1862        let tree = Tree::new::<crate::langs::CCode>(code);
1863        let root = tree.get_root();
1864        let body = root
1865            .preorder()
1866            .find(|n| n.kind() == "compound_statement")
1867            .expect("fixture has a function body");
1868        let declarations: Vec<Node<'_>> = body
1869            .children()
1870            .filter(|n| n.kind() == "declaration")
1871            .collect();
1872        assert_eq!(declarations.len(), 2, "fixture has two declarations");
1873
1874        let second = declarations[1];
1875        let expected = second
1876            .previous_sibling()
1877            .map(|p| p.id())
1878            .expect("the second declaration has a previous sibling");
1879        // A chain ending in the *root* does not describe `second`, whose
1880        // parent is the function body.
1881        let foreign = [root];
1882        assert_eq!(
1883            Ancestors::known(&foreign)
1884                .previous_sibling(&second)
1885                .map(|p| p.id()),
1886            Some(expected),
1887            "a mismatched chain must fall back, not report `None`"
1888        );
1889        assert!(
1890            Ancestors::known(&[]).previous_sibling(&second).is_none(),
1891            "an empty chain means `second` is the root, which has no siblings"
1892        );
1893    }
1894
1895    /// `count_specific_ancestors` must return the same count whichever
1896    /// way it reaches the ancestors. Uses `loc`'s real C predicate pair
1897    /// (`while`/`for`/`if` header, stopping at the enclosing block), so
1898    /// the fixture exercises both the counted case (the `for`-header
1899    /// declaration) and the stopped case (the block-scoped ones).
1900    #[test]
1901    fn count_specific_ancestors_agrees_between_known_and_climbing() {
1902        let code =
1903            b"int main() { int a; if (x) { int b; } for (int i = 0; i < 2; i++) { int c; } }";
1904        let mut counted = 0;
1905        let mut nonzero = 0;
1906        let visited = for_each_node_with_chain::<crate::langs::CCode>(code, |node, chain| {
1907            if node.kind() != "declaration" {
1908                return;
1909            }
1910            let check: fn(&Node) -> bool = |n| {
1911                matches!(
1912                    n.kind(),
1913                    "while_statement" | "for_statement" | "if_statement"
1914                )
1915            };
1916            let stop: fn(&Node) -> bool = |n| n.kind() == "compound_statement";
1917            let known = node.count_specific_ancestors::<crate::langs::CCode>(
1918                Ancestors::known(chain),
1919                check,
1920                stop,
1921            );
1922            let climbing = node.count_specific_ancestors::<crate::langs::CCode>(
1923                Ancestors::unknown(),
1924                check,
1925                stop,
1926            );
1927            assert_eq!(
1928                known,
1929                climbing,
1930                "declaration at row {}: known chain counted {known}, climbing counted {climbing}",
1931                node.start_row()
1932            );
1933            counted += 1;
1934            nonzero += usize::from(known > 0);
1935        });
1936        assert!(visited > 20);
1937        assert_eq!(counted, 4, "fixture must hold four declarations");
1938        assert_eq!(
1939            nonzero, 1,
1940            "only the `for`-header declaration sits under a header with no block between"
1941        );
1942    }
1943}