Skip to main content

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