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