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 /// The children after `child`, nearest first: what
510 /// `children().skip_while(|c| c.id() != child.id()).skip(1)` yields,
511 /// and nothing when `child` is not one of this node's children.
512 ///
513 /// That scan costs `child`'s index, so a walk that asks it of every
514 /// child of a wide list is quadratic in the list (#1545). This finds
515 /// `child` in `O(log n)` instead; see [`Node::children_before`].
516 pub fn children_after(&self, child: &Node<'a>) -> impl Iterator<Item = Node<'a>> + use<'a> {
517 let mut cursor = self.cursor();
518 let mut more =
519 self.seat_on_child(child, &mut cursor).is_some() && cursor.goto_next_sibling();
520 std::iter::from_fn(move || {
521 let next = more.then(|| cursor.node())?;
522 more = cursor.goto_next_sibling();
523 Some(next)
524 })
525 }
526
527 /// The children before `child`, nearest first, and nothing when
528 /// `child` is not one of this node's children.
529 ///
530 /// `child`'s index comes from [`Cursor::goto_first_child_for_byte`]
531 /// and each sibling from [`Node::child`], both `O(log n)` in the
532 /// child count, for the reason the first gives: a `repeat()` list is
533 /// stored as a balanced tree of hidden nodes.
534 pub fn children_before(&self, child: &Node<'a>) -> impl Iterator<Item = Node<'a>> + use<'a> {
535 let node = *self;
536 let index = self.seat_on_child(child, &mut self.cursor()).unwrap_or(0);
537 (0..index).rev().filter_map(move |i| node.child(i))
538 }
539
540 // Seats `cursor`, which sits on this node, on `child` and returns its
541 // index, or `None` when `child` is not one of this node's children.
542 fn seat_on_child(&self, child: &Node<'a>, cursor: &mut Cursor<'a>) -> Option<usize> {
543 if let Some(index) = cursor.goto_first_child_for_byte(child.start_byte())
544 && cursor.node().id() == child.id()
545 {
546 return Some(index);
547 }
548 // The lookup passes over a zero-width child, which ends at the
549 // byte it starts at; only error recovery inserts one.
550 cursor.reset(self);
551 let mut index = 0;
552 let mut found = cursor.goto_first_child();
553 while found && cursor.node().id() != child.id() {
554 found = cursor.goto_next_sibling();
555 index += 1;
556 }
557 found.then_some(index)
558 }
559
560 /// A fresh cursor positioned on this node, for the traversals that
561 /// reuse one cursor across many child scans
562 /// ([`children_with`](Self::children_with)).
563 #[inline]
564 #[must_use]
565 pub fn cursor(&self) -> Cursor<'a> {
566 Cursor(self.0.walk())
567 }
568
569 /// Counts this node's ancestors satisfying `check`, walking upward
570 /// from the parent and stopping at (and excluding) the first
571 /// ancestor satisfying `stop`. An ancestor that is the `if` of an
572 /// `else if` chain never counts — it is a continuation of the
573 /// branch above it, not a new enclosing one.
574 ///
575 /// `ancestors` is the chain the caller descended through. Passing
576 /// [`Ancestors::unknown`] is always correct and answers identically;
577 /// it just pays `O(depth)` per step instead of `O(1)`.
578 pub fn count_specific_ancestors<C: Checker>(
579 &self,
580 ancestors: Ancestors<'a, '_>,
581 check: fn(&Node) -> bool,
582 stop: fn(&Node) -> bool,
583 ) -> usize {
584 let mut count = 0;
585 for (parent, above_parent) in ancestors.iter(self) {
586 if stop(&parent) {
587 break;
588 }
589 if check(&parent) && !C::is_else_if(&parent, above_parent) {
590 count += 1;
591 }
592 }
593 count
594 }
595
596 /// Returns `true` iff this node's parent satisfies `parent_pred`
597 /// AND that parent's own parent (this node's grandparent)
598 /// satisfies `grand_pred`. Returns `false` as soon as either link
599 /// is absent or its predicate fails, so a misordered predicate
600 /// cannot silently degrade to a single-predicate check.
601 ///
602 /// `ancestors` is the chain the caller descended through. Passing
603 /// [`Ancestors::unknown`] is always correct and answers
604 /// identically; it just pays [`Node::parent`]'s `O(depth)` for each
605 /// of the two links, which on a per-node metric arm is quadratic in
606 /// nesting depth (#1096).
607 pub fn parent_grandparent_match(
608 &self,
609 ancestors: Ancestors<'a, '_>,
610 parent_pred: fn(&Node) -> bool,
611 grand_pred: fn(&Node) -> bool,
612 ) -> bool {
613 let mut climb = ancestors.iter(self);
614 let Some((parent, _)) = climb.next() else {
615 return false;
616 };
617 if !parent_pred(&parent) {
618 return false;
619 }
620 let Some((grand, _)) = climb.next() else {
621 return false;
622 };
623 grand_pred(&grand)
624 }
625
626 /// Returns a pre-order iterator over this node and all of its
627 /// descendants (this node first, then each child subtree left to
628 /// right).
629 ///
630 /// The traversal is allocation-light: it reuses one work stack *and
631 /// one cursor*, and visits each node exactly once, so a full walk is
632 /// O(n) in the subtree size and allocates only the stack's growth.
633 /// Building a fresh cursor per visited node instead would cost a
634 /// `malloc`/`free` pair per node (#1112). Every yielded [`Node`]
635 /// carries the underlying tree lifetime `'a`, so callers may collect
636 /// or retain the handles.
637 ///
638 /// This is the Rust counterpart of the Python `Node.walk()` binding
639 /// (issue #728): the binding wraps each yielded node, so Rust and
640 /// Python share one traversal order.
641 #[must_use]
642 pub fn preorder(&self) -> Preorder<'a> {
643 Preorder {
644 stack: vec![*self],
645 cursor: self.cursor(),
646 }
647 }
648
649 /// Collects every node in this subtree (this node included) whose
650 /// [`kind`](tree_sitter::Node::kind) is listed in `kinds`, in
651 /// pre-order.
652 ///
653 /// Membership is an exact match against the raw grammar kind — the
654 /// same unaltered vocabulary `big_code_analysis::Ast::root_node` exposes, not
655 /// the `Alterator`-curated kinds `big_code_analysis::Ast::dump` emits. This is
656 /// the Rust counterpart of the Python `Node.descendants_by_kind()`
657 /// binding (issue #728).
658 #[must_use]
659 pub fn descendants_by_kind(&self, kinds: &[&str]) -> Vec<Node<'a>> {
660 self.preorder()
661 .filter(|node| kinds.contains(&node.kind()))
662 .collect()
663 }
664}
665
666/// The chain of a node's ancestors, root first, as recorded by a walker
667/// that descended to that node.
668///
669/// A predicate that asks a node for its parent pays [`Node::parent`]'s
670/// `O(depth)` once per node, so `O(depth²)` over a deeply nested file,
671/// however few parent steps it takes (#1084). A walker that visits
672/// parents before children already holds the chain, and handing it down
673/// turns each step into a slice index — the upward counterpart of the
674/// downward flag propagation #1052 and #1062 used.
675///
676/// Callers that reached a node some other way pass
677/// [`Ancestors::unknown`], which climbs with [`Node::parent`]: the same
678/// answers at the original cost.
679#[derive(Clone, Copy, Debug)]
680pub struct Ancestors<'tree, 'chain>(Option<&'chain [Node<'tree>]>);
681
682impl<'tree, 'chain> Ancestors<'tree, 'chain> {
683 /// No chain is available; every query climbs with [`Node::parent`].
684 #[inline]
685 #[must_use]
686 pub const fn unknown() -> Self {
687 Self(None)
688 }
689
690 /// `chain` lists every ancestor of the node about to be queried,
691 /// root first — so `chain.last()` is its parent and an empty chain
692 /// means the node is the root.
693 #[inline]
694 #[must_use]
695 pub const fn known(chain: &'chain [Node<'tree>]) -> Self {
696 Self(Some(chain))
697 }
698
699 /// [`Ancestors::known`], but first checks that `chain` really is
700 /// `node`'s ancestry.
701 ///
702 /// The parity test below proves the walker's truncate/push rule on a
703 /// *replica* walker, so it cannot see `metrics_inner` itself
704 /// desynchronising — a `chain.push` moved ahead of the per-node
705 /// computes, say, or a `continue` inserted above the truncate.
706 /// [`Ancestors::parent`] trusts `chain.last()` unvalidated, so such a
707 /// drift would feed every predicate a wrong ancestor silently rather
708 /// than fail. Walkers that maintain a chain should construct through
709 /// here; [`Ancestors::known`] stays unchecked for the callers that
710 /// deliberately pair a chain with a foreign node.
711 ///
712 /// # Two checks, because the exact one is not affordable by default
713 ///
714 /// The invariant is "`chain.last()` **is** `node.parent()`", and
715 /// asking that outright costs [`Node::parent`]'s `O(depth)` — the
716 /// very lookup #1084 exists to remove. Per node, on every walk that
717 /// constructs a checked chain, it made every debug-build walk
718 /// `O(nodes × depth)` while the shipped walk is `O(nodes)`: a tax on
719 /// every `cargo test`, worst on the deep-nesting regression tests
720 /// that exist to pin the shipped walk's linearity (#1122). It now
721 /// runs only under `--cfg chain_audit`, which `make chain-audit` and
722 /// the CI lane of the same name set — and there as a plain
723 /// `assert_eq!`, so the audit has teeth in a release profile too.
724 ///
725 /// What stays on in every debug build is an `O(1)` *consequence* of
726 /// that invariant: a parent's byte span contains its child's, and no
727 /// node is its own parent. Strictly weaker — a grandparent contains
728 /// the node as well — but it is shaped to the two ways a walker
729 /// desynchronises. A `push` moved ahead of the per-node computes
730 /// leaves `chain.last() == node`; a dropped `truncate` leaves the
731 /// previous subtree's path, whose last entry is disjoint from the
732 /// node that follows it. Both trip here, on the first node that
733 /// shows them, at four integer comparisons.
734 #[must_use]
735 pub fn checked(chain: &'chain [Node<'tree>], node: &Node<'tree>) -> Self {
736 // Opt-in only: `Node::parent` restarts at the root, so this is
737 // the `O(nodes × depth)` walk described above.
738 #[cfg(chain_audit)]
739 assert_eq!(
740 chain.last().map(Node::id),
741 node.parent().map(|parent| parent.id()),
742 "ancestor chain desynchronised on a {} node",
743 node.kind()
744 );
745 debug_assert!(
746 chain.last().is_none_or(|parent| {
747 parent.id() != node.id()
748 && parent.start_byte() <= node.start_byte()
749 && node.end_byte() <= parent.end_byte()
750 }),
751 "ancestor chain desynchronised on a {} node: chain.last() neither \
752 contains it nor differs from it",
753 node.kind()
754 );
755 Self::known(chain)
756 }
757
758 /// How far the node this chain describes sits from the root, or
759 /// `None` when no chain is known — deriving it then would cost the
760 /// [`Node::parent`] climb the chain exists to remove.
761 #[inline]
762 pub fn depth(self) -> Option<usize> {
763 self.0.map(<[Node<'tree>]>::len)
764 }
765
766 /// `node`'s parent.
767 #[inline]
768 #[must_use]
769 pub fn parent(self, node: &Node<'tree>) -> Option<Node<'tree>> {
770 match self.0 {
771 Some(chain) => chain.last().copied(),
772 None => node.parent(),
773 }
774 }
775
776 /// Whether `node`'s parent has kind id `kind`.
777 ///
778 /// The compound-leaf guard of `.claude/rules/grammar-dispatch.md`
779 /// section 5 asks this and nothing else: a delimiter or keyword
780 /// token is suppressed *only* directly under the construct that
781 /// owns it, never under an arbitrary ancestor. Eleven dispatch
782 /// arms across `getter`, `checker` and the metric walkers spelled
783 /// it out as `.parent(node).is_some_and(|p| p.kind_id() == X as
784 /// u16)`, which wraps onto four rustfmt lines inside a match guard
785 /// and buries the question under the plumbing; #1314 added six more
786 /// and folded all seventeen onto this. Three sites spelling their
787 /// binding `|parent|` were left alone and would each fit:
788 /// `impl_is_else_if_parent_clause!` (`src/checker.rs`),
789 /// `is_stabby_lambda_body` (`src/checker/ruby.rs`) and
790 /// `is_useful_comment` (`src/checker/rust.rs`, a let-chain).
791 ///
792 /// A `false` when `node` has no parent is the answer every one of
793 /// those call sites wants: a root node's token is not inside the
794 /// construct, so it is not suppressed.
795 #[inline]
796 #[must_use]
797 pub fn parent_has_kind(self, node: &Node<'tree>, kind: u16) -> bool {
798 self.parent(node).is_some_and(|p| p.kind_id() == kind)
799 }
800
801 /// The sibling immediately before `node`, or `None` when `node` is
802 /// its parent's first child or has no parent.
803 ///
804 /// `tree_sitter`'s own `prev_sibling` resolves the parent first
805 /// (`ts_node__prev_sibling` opens with `ts_node_parent`), so it
806 /// carries the same `O(depth)` cost [`Node::parent`] does. With a
807 /// known chain the parent is free and what remains is a cursor walk
808 /// over the siblings.
809 #[inline]
810 #[must_use]
811 pub fn previous_sibling(self, node: &Node<'tree>) -> Option<Node<'tree>> {
812 let Some(chain) = self.0 else {
813 return node.previous_sibling();
814 };
815 let parent = chain.last()?;
816 let mut previous = None;
817 for child in parent.children() {
818 if child.id() == node.id() {
819 return previous;
820 }
821 previous = Some(child);
822 }
823 // `node` is not among `parent.children()`, so this chain does
824 // not describe `node`. Answering `None` would claim "no
825 // previous sibling", which is a different (and wrong) answer;
826 // fall back to the authoritative lookup instead.
827 node.previous_sibling()
828 }
829
830 /// `node`'s ancestors, nearest first, each paired with *its* own
831 /// ancestry so a predicate applied to an ancestor stays as cheap as
832 /// one applied to `node`.
833 #[inline]
834 #[must_use]
835 pub fn iter(self, node: &Node<'tree>) -> AncestorIter<'tree, 'chain> {
836 match self.0 {
837 Some(chain) => AncestorIter::Chain(chain),
838 None => AncestorIter::Climb(node.parent()),
839 }
840 }
841}
842
843/// Ancestor iterator returned by [`Ancestors::iter`], nearest first.
844pub enum AncestorIter<'tree, 'chain> {
845 /// The not-yet-yielded prefix of a known chain. Its last element is
846 /// the next ancestor, and the prefix before it is that ancestor's
847 /// own chain — so splitting from the back hands out both at once.
848 Chain(&'chain [Node<'tree>]),
849 /// The next ancestor to yield, reached by climbing.
850 Climb(Option<Node<'tree>>),
851}
852
853impl<'tree, 'chain> Iterator for AncestorIter<'tree, 'chain> {
854 type Item = (Node<'tree>, Ancestors<'tree, 'chain>);
855
856 fn next(&mut self) -> Option<Self::Item> {
857 match self {
858 Self::Chain(remaining) => {
859 let (&nearest, above) = remaining.split_last()?;
860 *remaining = above;
861 Some((nearest, Ancestors::known(above)))
862 }
863 Self::Climb(next) => {
864 let nearest = (*next)?;
865 *next = nearest.parent();
866 Some((nearest, Ancestors::unknown()))
867 }
868 }
869 }
870}
871
872/// Pre-order iterator over a node and its descendants, returned by
873/// [`Node::preorder`].
874///
875/// Holds a single work stack of not-yet-visited nodes, and a single
876/// cursor to enumerate each node's children with. Each step pops the
877/// next node, pushes its children so the leftmost is visited first, and
878/// yields the popped node — so the sequence is the node, then each child
879/// subtree in order. Both are reused across steps (children are pushed
880/// then the freshly-pushed slice is reversed in place), so the walk
881/// allocates only the stack's growth: no fresh buffer per node, and no
882/// fresh `TreeCursor` either (#1112).
883pub struct Preorder<'a> {
884 stack: Vec<Node<'a>>,
885 cursor: Cursor<'a>,
886}
887
888impl<'a> Iterator for Preorder<'a> {
889 type Item = Node<'a>;
890
891 fn next(&mut self) -> Option<Self::Item> {
892 let node = self.stack.pop()?;
893 // Push children in document order, then reverse just the slice we
894 // appended so the leftmost child ends up on top of the stack and
895 // is visited next — pre-order without a per-node temporary.
896 let first_child = self.stack.len();
897 // Destructured so the stack and the cursor are borrowed as the
898 // disjoint fields they are.
899 let Self { stack, cursor } = self;
900 stack.extend(node.children_with(cursor));
901 stack[first_child..].reverse();
902 Some(node)
903 }
904}
905
906/// An `AST` cursor.
907#[derive(Clone)]
908pub struct Cursor<'a>(TreeCursor<'a>);
909
910impl<'a> Cursor<'a> {
911 /// Re-seats the cursor on `node`.
912 #[inline]
913 pub fn reset(&mut self, node: &Node<'a>) {
914 self.0.reset(node.0);
915 }
916
917 /// Moves to the next sibling; `false` when there is none.
918 #[inline]
919 pub fn goto_next_sibling(&mut self) -> bool {
920 self.0.goto_next_sibling()
921 }
922
923 /// Moves to the first child; `false` when there is none.
924 #[inline]
925 pub fn goto_first_child(&mut self) -> bool {
926 self.0.goto_first_child()
927 }
928
929 /// Moves to the first child that ends after `byte` and returns its
930 /// index in [`Node::children`] order, or `None` when no child does.
931 ///
932 /// "Ends after" is strict, so a **zero-width** child at `byte` — a
933 /// MISSING token the parser inserted during error recovery — is
934 /// skipped and the cursor lands on its successor. That is the one way
935 /// this differs from `children().position(..)`, which finds such a
936 /// node; a caller resolving a node's own index must therefore check
937 /// [`Cursor::node`] against it, as both call sites here do.
938 ///
939 /// The index is what makes this worth having over a sibling scan:
940 /// tree-sitter stores a `repeat()` child list under balanced hidden
941 /// nodes and skips each by its cached visible-child count, so this
942 /// is `O(log n)` in the sibling count where `children().position(..)`
943 /// is `O(n)` — the difference between a linear and a quadratic walk
944 /// when every child of a wide list asks for its own index.
945 #[inline]
946 pub fn goto_first_child_for_byte(&mut self, byte: usize) -> Option<usize> {
947 self.0.goto_first_child_for_byte(byte)
948 }
949
950 /// The node the cursor currently sits on.
951 #[inline]
952 #[must_use]
953 pub fn node(&self) -> Node<'a> {
954 Node(self.0.node())
955 }
956}
957
958/// Position of a child scan, independent of who owns the cursor driving
959/// it.
960///
961/// [`Children`] and [`ChildrenWith`] differ only in that — one owns its
962/// cursor, the other borrows the caller's — and `children_with` exists
963/// to save an allocation, not to answer differently. Keeping the
964/// termination rule here rather than in each iterator is what stops the
965/// two from drifting.
966struct ChildScan {
967 done: bool,
968 remaining: usize,
969}
970
971impl ChildScan {
972 /// Seats `cursor` on `node`'s first child.
973 ///
974 /// `goto_first_child` returns false when the node has no children,
975 /// in which case the scan is exhausted from the outset. Termination
976 /// is then driven entirely by the cursor (see [`ChildScan::step`]),
977 /// so the iterator stops exactly when the tree reports no further
978 /// siblings — it can never pad the sequence with duplicate nodes if
979 /// `child_count` and the cursor walk ever disagree.
980 ///
981 /// `child_count` is the authoritative length for the
982 /// `ExactSizeIterator` contract; for well-formed trees it equals the
983 /// cursor sibling walk, so the reported length and the emitted data
984 /// agree. A childless node reports `0` so the empty iterator's
985 /// length matches its (lack of) data.
986 fn seed<'a>(node: &Node<'a>, cursor: &mut Cursor<'a>) -> Self {
987 cursor.reset(node);
988 Self::descend(node, cursor)
989 }
990
991 /// [`ChildScan::seed`], for a cursor already seated on `node` —
992 /// which is what [`Node::cursor`] hands back, and `ts_node_walk` and
993 /// `ts_tree_cursor_reset` run the same `ts_tree_cursor_init`. Only
994 /// [`Node::children`] may skip the reset; every other caller reuses
995 /// a cursor left wherever the previous scan ended.
996 fn descend<'a>(node: &Node<'a>, cursor: &mut Cursor<'a>) -> Self {
997 let done = !cursor.goto_first_child();
998 Self {
999 done,
1000 remaining: if done { 0 } else { node.child_count() },
1001 }
1002 }
1003
1004 /// Yields the cursor's current child and advances past it.
1005 fn step<'a>(&mut self, cursor: &mut Cursor<'a>) -> Option<Node<'a>> {
1006 if self.done {
1007 return None;
1008 }
1009 let result = cursor.node();
1010 // The cursor is the single source of truth for termination:
1011 // once there is no next sibling this yield is the last one.
1012 self.done = !cursor.goto_next_sibling();
1013 // Keep the advertised length consistent with termination: when
1014 // the cursor stops, nothing remains. For well-formed trees this
1015 // equals `child_count - emitted`; if the cursor walk and
1016 // `child_count` ever disagree, this still honors the
1017 // `ExactSizeIterator` contract (`len() == 0` exactly at
1018 // exhaustion) rather than reporting a phantom remainder.
1019 self.remaining = if self.done {
1020 0
1021 } else {
1022 self.remaining.saturating_sub(1)
1023 };
1024 Some(result)
1025 }
1026
1027 fn size_hint(&self) -> (usize, Option<usize>) {
1028 (self.remaining, Some(self.remaining))
1029 }
1030}
1031
1032/// Iterator over a node's direct children, returned by
1033/// [`Node::children`]. Owns the cursor it walks with.
1034///
1035/// Termination is driven by the cursor alone: each step yields the
1036/// cursor's current node, then advances with `goto_next_sibling`,
1037/// stopping the moment that returns false. This makes the cursor the
1038/// single source of truth for both the emitted data and when to stop, so
1039/// the sequence can never be padded with duplicates if `child_count` and
1040/// the actual sibling walk disagree.
1041///
1042/// The `ExactSizeIterator` length is reported from `child_count` (tracked
1043/// in `ChildScan`). For well-formed trees the cursor walk and
1044/// `child_count` agree, so the advertised length matches the data.
1045pub struct Children<'a> {
1046 cursor: Cursor<'a>,
1047 scan: ChildScan,
1048}
1049
1050impl<'a> Iterator for Children<'a> {
1051 type Item = Node<'a>;
1052
1053 fn next(&mut self) -> Option<Self::Item> {
1054 self.scan.step(&mut self.cursor)
1055 }
1056
1057 fn size_hint(&self) -> (usize, Option<usize>) {
1058 self.scan.size_hint()
1059 }
1060}
1061
1062impl ExactSizeIterator for Children<'_> {}
1063
1064/// Iterator over a node's direct children, returned by
1065/// [`Node::children_with`]. Borrows the caller's cursor rather than
1066/// building one, which is the whole of the difference: it yields exactly
1067/// what [`Children`] yields, through the same `ChildScan`.
1068pub struct ChildrenWith<'c, 'a> {
1069 cursor: &'c mut Cursor<'a>,
1070 scan: ChildScan,
1071}
1072
1073impl<'a> Iterator for ChildrenWith<'_, 'a> {
1074 type Item = Node<'a>;
1075
1076 fn next(&mut self) -> Option<Self::Item> {
1077 self.scan.step(self.cursor)
1078 }
1079
1080 fn size_hint(&self) -> (usize, Option<usize>) {
1081 self.scan.size_hint()
1082 }
1083}
1084
1085impl ExactSizeIterator for ChildrenWith<'_, '_> {}
1086
1087impl<'a> Search<'a> for Node<'a> {
1088 fn act_on_node(&self, action: &mut dyn FnMut(&Node<'a>, Ancestors<'a, '_>)) {
1089 let mut cursor = self.cursor();
1090 let mut stack = Vec::new();
1091 // Ancestor chain of the node being visited, root first. Kept by
1092 // the same truncate/push rule as the metric walk, so a predicate
1093 // the action applies can read an ancestor as a slice index
1094 // rather than through the `O(depth)` `Node::parent` (#1088).
1095 //
1096 // Seeded with this subtree root's own ancestry rather than left
1097 // empty: `Ancestors` reads an empty chain as "this node is the
1098 // tree root", so on a subtree an empty seed would report no
1099 // parent for `*self` — silently costing e.g. the JS getters the
1100 // binding a `function_expression` takes its name from. One
1101 // climb, and none at all for the tree root this is called on
1102 // today.
1103 let mut chain: Vec<Node<'a>> = std::iter::successors(self.parent(), Node::parent).collect();
1104 chain.reverse();
1105 let depth = chain.len();
1106
1107 stack.push((*self, depth));
1108
1109 while let Some((node, depth)) = stack.pop() {
1110 chain.truncate(depth);
1111 action(&node, Ancestors::checked(&chain, &node));
1112 chain.push(node);
1113 // Source order in, tail reversed in place, so the LIFO
1114 // `stack` yields the leftmost child first — pre-order with
1115 // no staging buffer.
1116 let first_child = stack.len();
1117 stack.extend(
1118 node.children_with(&mut cursor)
1119 .map(|child| (child, depth + 1)),
1120 );
1121 stack[first_child..].reverse();
1122 }
1123 }
1124
1125 fn first_child(&self, pred: fn(u16) -> bool) -> Option<Node<'a>> {
1126 self.children().find(|&child| pred(child.kind_id()))
1127 }
1128
1129 fn act_on_child(&self, action: &mut dyn FnMut(&Node<'a>)) {
1130 for child in self.children() {
1131 action(&child);
1132 }
1133 }
1134}
1135
1136#[cfg(test)]
1137mod tests {
1138 use super::*;
1139 use crate::langs::MozjsCode;
1140 use crate::test_support::for_each_node_with_chain;
1141
1142 /// The `child(0)` + `next_sibling()` chain [`Node::wraps_any`] used
1143 /// between #217 and #1088, kept here as the reference the cursor
1144 /// walk that replaced it is checked against.
1145 ///
1146 /// The swap was made for cost, not for behaviour: a sibling step
1147 /// resolves its parent, and `tree_sitter` resolves a parent by
1148 /// descending from the root, so the chain was `O(children × depth)`
1149 /// where the cursor is `O(children)`. Nothing about the *set* of
1150 /// children was supposed to change, and this is what says so —
1151 /// node-by-node over a real tree, same order and same short-circuit,
1152 /// without hardcoding grammar `kind_id`s.
1153 #[cfg(feature = "mozjs")]
1154 fn sibling_chain_has_sibling(node: OtherNode, id: u16) -> bool {
1155 node.parent().is_some_and(|parent| {
1156 let mut cur = parent.child(0);
1157 while let Some(c) = cur {
1158 if c.kind_id() == id {
1159 return true;
1160 }
1161 cur = c.next_sibling();
1162 }
1163 false
1164 })
1165 }
1166
1167 /// `utf8_text` answers `None` for a span outside the buffer instead
1168 /// of aborting the process.
1169 ///
1170 /// The only way to reach that branch is to read against a buffer
1171 /// other than the one the tree was parsed from, which no caller does
1172 /// today — so this is the whole coverage the guard can have, and it
1173 /// is a real one: against the delegating implementation
1174 /// (`self.0.utf8_text(data).ok()`) this test panics at
1175 /// `str::from_utf8(&source[start..end])` rather than failing, which
1176 /// is precisely the behaviour the guard removes. Verified by revert.
1177 #[test]
1178 #[cfg(feature = "rust")]
1179 fn utf8_text_is_none_for_a_span_past_the_buffer() {
1180 let code = b"fn f() {}\n";
1181 let tree = Tree::new::<crate::langs::RustCode>(code);
1182 let root = tree.get_root();
1183
1184 // Sanity: the span is in range against its own buffer, so a
1185 // `None` below means the bound fired and not that the node is
1186 // empty or the text is not UTF-8.
1187 assert_eq!(root.utf8_text(code), Some("fn f() {}\n"));
1188
1189 // The same node, read against a buffer that ends mid-span.
1190 assert_eq!(root.utf8_text(&code[..4]), None);
1191 }
1192
1193 #[cfg(feature = "mozjs")]
1194 #[test]
1195 fn has_sibling_matches_the_retired_sibling_chain() {
1196 // Arrow functions exercise the `check_if_arrow_func!` call site
1197 // that motivated #521 (PropertyIdentifier siblings on the JS/TS
1198 // closure-classification hot path).
1199 let code = b"const o = { m: (a) => a + 1, n: function () {} }; foo.bar();";
1200 let tree = Tree::new::<MozjsCode>(code);
1201 let ts_tree = tree.as_ts_tree();
1202
1203 // Collect the grammar kinds that actually occur, so the
1204 // equivalence check covers present-sibling (true) cases.
1205 let mut kinds = std::collections::BTreeSet::new();
1206 let mut stack = vec![ts_tree.root_node()];
1207 while let Some(n) = stack.pop() {
1208 kinds.insert(n.kind_id());
1209 let mut child = n.child(0);
1210 while let Some(c) = child {
1211 stack.push(c);
1212 child = c.next_sibling();
1213 }
1214 }
1215 // Include an id that does not occur anywhere for absent-sibling
1216 // (false) coverage.
1217 let absent_id = u16::MAX;
1218
1219 let mut stack = vec![ts_tree.root_node()];
1220 let mut matched = 0;
1221 while let Some(n) = stack.pop() {
1222 let wrapped = Node(n);
1223 for &id in kinds.iter().chain(std::iter::once(&absent_id)) {
1224 let found = wrapped.has_sibling(Ancestors::unknown(), id);
1225 assert_eq!(
1226 found,
1227 sibling_chain_has_sibling(n, id),
1228 "has_sibling diverged from the retired sibling chain at node kind {} for id {id}",
1229 n.kind(),
1230 );
1231 matched += usize::from(found);
1232 }
1233 let mut child = n.child(0);
1234 while let Some(c) = child {
1235 stack.push(c);
1236 child = c.next_sibling();
1237 }
1238 }
1239 // The comment above claims the collected kinds cover the
1240 // present-sibling case; this enforces it. Both sides answering
1241 // `false` everywhere would agree without either scan ever
1242 // running to a match.
1243 assert!(
1244 matched > 0,
1245 "every answer was `false`, so the sibling scan was never exercised"
1246 );
1247
1248 // No-parent node (root) always reports no sibling.
1249 let root = Node(ts_tree.root_node());
1250 assert!(!root.has_sibling(Ancestors::unknown(), absent_id));
1251 for &id in &kinds {
1252 assert!(
1253 !root.has_sibling(Ancestors::unknown(), id),
1254 "root node has no parent → no sibling"
1255 );
1256 }
1257 }
1258
1259 /// `children()` must yield exactly the node's direct children, in
1260 /// order, for every node in a real tree — including the empty
1261 /// (leaf) and single-child cases. Termination is cursor-driven, so
1262 /// the emitted set is compared node-by-node against the raw
1263 /// tree-sitter `child(i)` walk (the ground truth for both order and
1264 /// count). This pins the no-duplicate-padding property: a desync
1265 /// between `child_count` and the cursor walk would surface here as
1266 /// extra trailing duplicates or a length mismatch.
1267 #[cfg(feature = "mozjs")]
1268 #[test]
1269 fn children_matches_tree_sitter_child_walk() {
1270 // Mix of leaf nodes (no children), single-child wrappers, and
1271 // multi-child constructs to cover all arities.
1272 let code = b"const o = { m: (a) => a + 1 }; foo(); ;";
1273 let tree = Tree::new::<MozjsCode>(code);
1274 let ts_tree = tree.as_ts_tree();
1275
1276 let mut stack = vec![ts_tree.root_node()];
1277 while let Some(n) = stack.pop() {
1278 let wrapped = Node(n);
1279
1280 // Ground truth: walk children by index off the raw node.
1281 let expected: Vec<_> = (0..n.child_count() as u32)
1282 .filter_map(|i| n.child(i))
1283 .map(|c| (c.id(), c.kind_id()))
1284 .collect();
1285
1286 let actual =
1287 drain_checking_exact_size(wrapped.children(), expected.len(), "children", n.kind());
1288 assert_eq!(
1289 actual,
1290 expected,
1291 "children() diverged from child(i) walk at kind {}",
1292 n.kind(),
1293 );
1294
1295 for i in 0..n.child_count() as u32 {
1296 if let Some(c) = n.child(i) {
1297 stack.push(c);
1298 }
1299 }
1300 }
1301 }
1302
1303 /// `child_by_field_name` (issue #786) must return the child at the
1304 /// underlying tree lifetime `'a`, not the method-call borrow of
1305 /// `&self`. The proof is a helper whose return type *requires* the
1306 /// child to outlive an intermediate `&Node` borrow: under the old
1307 /// `Option<Node<'_>>` signature the returned node would be tied to
1308 /// `parent`'s borrow and this would fail to compile. Binding the
1309 /// child to a variable that outlives the `&parent` reborrow inside
1310 /// the helper exercises the widened lifetime.
1311 #[cfg(feature = "cpp")]
1312 #[test]
1313 fn child_by_field_name_outlives_self_borrow() {
1314 // `find_named_child` takes the parent by value, reborrows it
1315 // through a `&` reference to call `child_by_field_name`, and
1316 // returns the child. The returned `Node<'a>` must survive past
1317 // that inner `&parent` borrow — only possible because the child
1318 // carries the tree lifetime, not the borrow of `&parent`.
1319 fn find_named_child<'a>(parent: Node<'a>) -> Option<Node<'a>> {
1320 let borrowed: &Node<'a> = &parent;
1321 borrowed.child_by_field_name("declarator")
1322 }
1323
1324 let code = b"int answer = 42;";
1325 let tree = Tree::new::<crate::langs::CppCode>(code);
1326 let root = tree.get_root();
1327
1328 // Walk to the `declaration` node, then pull its `declarator`
1329 // child out and hold it after the producing borrow has ended.
1330 let mut held: Option<Node> = None;
1331 let mut stack = vec![root];
1332 while let Some(n) = stack.pop() {
1333 if n.kind() == "declaration" {
1334 // `find_named_child` consumes a copy of `n`; the result
1335 // must remain valid here, well past the inner borrow.
1336 held = find_named_child(n);
1337 break;
1338 }
1339 for child in n.children() {
1340 stack.push(child);
1341 }
1342 }
1343
1344 let declarator = held.expect("C declaration has a `declarator` field");
1345 // The held node is still usable: it kept its tree linkage rather
1346 // than dangling at the end of the producing borrow.
1347 assert_eq!(declarator.kind(), "init_declarator");
1348 }
1349
1350 /// `Node::as_tree_sitter` (issue #556) must hand back the *same*
1351 /// underlying `tree_sitter::Node` the wrapper holds: identical
1352 /// `kind()` / `kind_id()` and a usable tree-sitter API. Obtaining
1353 /// the wrapper through the public `CppParser` + `ParserTrait::root`
1354 /// path (rather than the in-module `Tree::new`) proves the accessor
1355 /// is the public seam that replaced the former `pub` `.0` field.
1356 #[cfg(feature = "cpp")]
1357 #[test]
1358 fn as_tree_sitter_round_trips_wrapper_kind() {
1359 use crate::{CppParser, ParserTrait};
1360 use std::path::Path;
1361
1362 let source = b"int main() { return 0; }";
1363 let parser = CppParser::new(source.to_vec(), Path::new("example.cpp"), None);
1364 let root = parser.root();
1365
1366 let ts_root = root.as_tree_sitter();
1367
1368 // A well-formed C++ translation unit roots at `translation_unit`.
1369 assert_eq!(ts_root.kind(), "translation_unit");
1370 // The accessor must agree with the wrapper's own kind views.
1371 assert_eq!(ts_root.kind(), root.kind());
1372 assert_eq!(ts_root.kind_id(), root.kind_id());
1373 // The returned node is usable as a tree-sitter node, not a copy
1374 // that has lost its tree linkage: the parse is error-free and
1375 // the root has children.
1376 assert!(!ts_root.has_error());
1377 assert!(ts_root.child_count() > 0);
1378 }
1379
1380 /// Ground-truth pre-order walk over the raw tree-sitter node, by
1381 /// document order (`child(0..child_count)`). [`Node::preorder`] must
1382 /// emit exactly this sequence of node ids — node first, then each
1383 /// child subtree left to right.
1384 #[cfg(feature = "cpp")]
1385 fn ground_truth_preorder(node: OtherNode) -> Vec<usize> {
1386 let mut out = vec![node.id()];
1387 for i in 0..node.child_count() as u32 {
1388 if let Some(child) = node.child(i) {
1389 out.extend(ground_truth_preorder(child));
1390 }
1391 }
1392 out
1393 }
1394
1395 #[cfg(feature = "cpp")]
1396 #[test]
1397 fn preorder_matches_recursive_document_order() {
1398 // A nested construct (function holding a declaration and a call)
1399 // gives the walk real depth and sibling fan-out to order.
1400 let code = b"int main() { int x = 1; foo(x); return 0; }";
1401 let tree = Tree::new::<crate::langs::CppCode>(code);
1402 let root = tree.get_root();
1403
1404 let actual: Vec<usize> = root.preorder().map(|n| n.id()).collect();
1405 let expected = ground_truth_preorder(root.as_tree_sitter());
1406
1407 assert_eq!(
1408 actual, expected,
1409 "preorder diverged from recursive child(0..n) document order"
1410 );
1411 // Sanity: a non-trivial tree, and the root is visited first.
1412 assert!(actual.len() > 5, "expected a multi-node tree");
1413 assert_eq!(actual[0], root.id(), "root must be yielded first");
1414 }
1415
1416 #[cfg(feature = "cpp")]
1417 #[test]
1418 fn descendants_by_kind_collects_matching_subtree_nodes() {
1419 // `x` is declared once and used twice, so three `identifier`
1420 // nodes exist under the function; `main` is an identifier too.
1421 let code = b"int main() { int x = 1; return x + x; }";
1422 let tree = Tree::new::<crate::langs::CppCode>(code);
1423 let root = tree.get_root();
1424
1425 let found = root.descendants_by_kind(&["identifier"]);
1426 // Cross-check against an independent pre-order count so the helper
1427 // cannot pass by matching everything or nothing.
1428 let expected: Vec<usize> = root
1429 .preorder()
1430 .filter(|n| n.kind() == "identifier")
1431 .map(|n| n.id())
1432 .collect();
1433 let actual: Vec<usize> = found.iter().map(Node::id).collect();
1434 assert_eq!(actual, expected);
1435 assert!(
1436 found.len() >= 3,
1437 "expected at least the `main`, `x` decl, and `x` uses"
1438 );
1439 assert!(
1440 found.iter().all(|n| n.kind() == "identifier"),
1441 "every collected node must match the requested kind"
1442 );
1443
1444 // An absent kind yields nothing; a multi-kind filter unions.
1445 assert!(root.descendants_by_kind(&["no_such_kind"]).is_empty());
1446 assert!(
1447 root.descendants_by_kind(&["identifier", "number_literal"])
1448 .len()
1449 > found.len(),
1450 "adding `number_literal` must widen the match set"
1451 );
1452 }
1453
1454 /// Drains `iter`, holding it to the `ExactSizeIterator` contract at
1455 /// every step, and returns the `(id, kind_id)` of each child yielded.
1456 ///
1457 /// `len()` must equal the node's `child_count` before the first step,
1458 /// fall by exactly one per yield, and be zero at exhaustion. Both
1459 /// child iterators are checked against it, so the contract is stated
1460 /// once — `children_with` exists to save an allocation, and a
1461 /// separate copy of this is how the two would come to disagree.
1462 #[cfg(feature = "mozjs")]
1463 fn drain_checking_exact_size<'a>(
1464 mut iter: impl ExactSizeIterator<Item = Node<'a>>,
1465 child_count: usize,
1466 what: &str,
1467 kind: &str,
1468 ) -> Vec<(usize, u16)> {
1469 assert_eq!(
1470 iter.len(),
1471 child_count,
1472 "{what}().len() disagreed with child_count at kind {kind}"
1473 );
1474 let mut remaining = child_count;
1475 let mut drained = Vec::with_capacity(remaining);
1476 while let Some(child) = iter.next() {
1477 remaining -= 1;
1478 assert_eq!(
1479 iter.len(),
1480 remaining,
1481 "{what}() size_hint drifted mid-iteration at kind {kind}"
1482 );
1483 drained.push((child.id(), child.kind_id()));
1484 }
1485 assert_eq!(
1486 iter.len(),
1487 0,
1488 "{what}() was not drained to zero len at kind {kind}"
1489 );
1490 drained
1491 }
1492
1493 /// Ancestor ids yielded by `ancestors`, nearest first.
1494 #[cfg(any(
1495 feature = "c",
1496 feature = "elixir",
1497 feature = "java",
1498 feature = "javascript",
1499 feature = "kotlin",
1500 feature = "python",
1501 feature = "ruby",
1502 feature = "rust",
1503 ))]
1504 fn ancestor_ids(ancestors: Ancestors<'_, '_>, node: &Node<'_>) -> Vec<usize> {
1505 ancestors.iter(node).map(|(a, _)| a.id()).collect()
1506 }
1507
1508 /// A known chain must answer every ancestor question exactly as
1509 /// climbing with `Node::parent` does — that equivalence is the whole
1510 /// premise of #1084, and it is what lets the predicates keep their
1511 /// original logic while dropping the `O(depth)` lookup.
1512 ///
1513 /// Checked node-by-node over one fixture per grammar family that
1514 /// actually consults an ancestor: C-family (`is_else_if` via the
1515 /// parent clause, `loc`'s declaration gate), JVM-family
1516 /// (`is_else_if` via the preceding `else` token), Python (the
1517 /// grandparent shape), and Elixir (`quote` templates).
1518 #[cfg(all(
1519 feature = "c",
1520 feature = "elixir",
1521 feature = "java",
1522 feature = "javascript",
1523 feature = "kotlin",
1524 feature = "python",
1525 feature = "ruby",
1526 feature = "rust",
1527 ))]
1528 #[test]
1529 fn a_known_chain_answers_exactly_what_climbing_answers() {
1530 /// `must_nest` names kinds that have to appear *inside another
1531 /// node of the same kind* in the fixture. `visited > 20` alone
1532 /// does not keep a fixture honest: a grammar bump that flattened
1533 /// the nesting a row was added for would leave a large,
1534 /// clean-parsing tree that no longer exercises the shape, and
1535 /// the parity assertions would keep passing over it.
1536 #[cfg(any(
1537 feature = "c",
1538 feature = "elixir",
1539 feature = "java",
1540 feature = "javascript",
1541 feature = "kotlin",
1542 feature = "python",
1543 feature = "ruby",
1544 feature = "rust",
1545 ))]
1546 fn assert_parity<L: LanguageInfo>(label: &str, code: &[u8], must_nest: &[&str]) {
1547 let mut nested_seen = vec![false; must_nest.len()];
1548 let visited = for_each_node_with_chain::<L>(code, |node, chain| {
1549 for (slot, kind) in nested_seen.iter_mut().zip(must_nest) {
1550 *slot |= node.kind() == *kind
1551 && chain.iter().any(|ancestor| ancestor.kind() == *kind);
1552 }
1553 let known = Ancestors::known(chain);
1554 let climbing = Ancestors::unknown();
1555 assert_eq!(
1556 known.parent(node).map(|p| p.id()),
1557 climbing.parent(node).map(|p| p.id()),
1558 "{label}: parent of {} disagrees",
1559 node.kind()
1560 );
1561 assert_eq!(
1562 known.previous_sibling(node).map(|p| p.id()),
1563 climbing.previous_sibling(node).map(|p| p.id()),
1564 "{label}: previous sibling of {} disagrees",
1565 node.kind()
1566 );
1567 assert_eq!(
1568 ancestor_ids(known, node),
1569 ancestor_ids(climbing, node),
1570 "{label}: ancestor chain of {} disagrees",
1571 node.kind()
1572 );
1573 // Each ancestor is handed *its* own chain, so a
1574 // predicate applied one level up stays as cheap and as
1575 // correct as one applied to the node itself.
1576 for (ancestor, above) in known.iter(node) {
1577 assert_eq!(
1578 above.parent(&ancestor).map(|p| p.id()),
1579 ancestor.parent().map(|p| p.id()),
1580 "{label}: sub-chain handed to {} is not its own",
1581 ancestor.kind()
1582 );
1583 }
1584 });
1585 assert!(visited > 20, "{label}: fixture is too small to prove much");
1586 for (found, kind) in nested_seen.iter().zip(must_nest) {
1587 assert!(
1588 found,
1589 "{label}: no `{kind}` sits inside another `{kind}`, so the \
1590 fixture no longer exercises the nesting it was added for"
1591 );
1592 }
1593 }
1594
1595 assert_parity::<crate::langs::CCode>(
1596 "c",
1597 b"int main() { if (a) { int x; } else if (b) { for (int i = 0; i < 2; i++) x; } }",
1598 &[],
1599 );
1600 assert_parity::<crate::langs::JavaCode>(
1601 "java",
1602 b"class A { void m() { if (a) {} else if (b) {} else {} for (int i = 0; i < 2; i++) {} } }",
1603 &[],
1604 );
1605 assert_parity::<crate::langs::PythonCode>(
1606 "python",
1607 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",
1608 &[],
1609 );
1610 assert_parity::<crate::langs::ElixirCode>(
1611 "elixir",
1612 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",
1613 &["call"],
1614 );
1615
1616 // The shapes #1062 added as consumers, which the four fixtures
1617 // above do not contain: a function nested inside a function
1618 // (every language's `increment_function_depth` arm walks the
1619 // chain looking for one) and the two default-arm checks that
1620 // now read `Ancestors::parent` — Kotlin's `else ->` inside a
1621 // `when` and Ruby's `else` inside a `case`. Parity over the
1622 // machinery is not parity over the shape a caller asks about.
1623 assert_parity::<crate::langs::RustCode>(
1624 "rust",
1625 b"fn f(a: bool) { if a { } else if a { } fn g(b: bool) { if b { } } }\n",
1626 &["function_item"],
1627 );
1628 assert_parity::<crate::langs::KotlinCode>(
1629 "kotlin",
1630 b"fun f(x: Int) {\n when (x) {\n 1 -> {}\n else -> {}\n }\n fun g() {\n if (x > 0) {}\n }\n}\n",
1631 &["function_declaration"],
1632 );
1633 assert_parity::<crate::langs::RubyCode>(
1634 "ruby",
1635 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",
1636 &["method"],
1637 );
1638
1639 // The shape #1088 added as a consumer: the JS-family
1640 // `Checker::is_func` / `is_closure` walk upward from an
1641 // `arrow_function` / `function_expression` looking for the
1642 // binding that names it, and end on `Ancestors::previous_sibling`
1643 // through `has_sibling`. None of the fixtures above contains
1644 // either node.
1645 assert_parity::<crate::langs::JavascriptCode>(
1646 "javascript",
1647 b"const f = a => { a => { g(() => 1); }; };\nconst o = { m: function () { return 1; } };\n",
1648 &["arrow_function"],
1649 );
1650 }
1651
1652 /// The traversals #1112 moved onto [`Node::children_with`] must scan
1653 /// a whole tree on one cursor, not one per node.
1654 ///
1655 /// Nothing in the output says so: `children_with` yields exactly
1656 /// what `children` yields, so every metric, marker, and pre-order
1657 /// assertion in the suite holds just as well with a fresh
1658 /// `TreeCursor` built and freed per visited node. The counter is the
1659 /// only observable, which is why reverting one of these loops has to
1660 /// be a test failure rather than a silent allocation per node. The
1661 /// metric and suppression walks are guarded the same way from
1662 /// `big-code-analysis`, where they live.
1663 ///
1664 /// Seeding a real scan first is what makes it falsifiable: compared
1665 /// against zero these assertions would also pass with `record()`
1666 /// never wired up at all.
1667 // `python` and `rust` were stale: the body builds only `CCode` and
1668 // `MozjsCode`, so naming them kept the test out of builds that could
1669 // run it.
1670 #[cfg(all(feature = "c", feature = "mozjs"))]
1671 #[test]
1672 fn the_converted_traversals_scan_a_tree_on_one_cursor() {
1673 let seed_tree = Tree::new::<crate::langs::CCode>(b"int main() { int a; }");
1674 let _ = seed_tree.get_root().children().count();
1675 assert!(
1676 child_scan_cursors::observed() > 0,
1677 "the seed scan must be counted"
1678 );
1679
1680 // `preorder` over a tree far larger than any per-call constant,
1681 // so "one per node" and "one per walk" cannot be confused.
1682 let tree = Tree::new::<MozjsCode>(
1683 b"const o = { m: (a) => a + 1, n: function () { return [1, 2, 3]; } };\nfoo(o);\n",
1684 );
1685 let before = child_scan_cursors::observed();
1686 let visited = tree.get_root().preorder().count();
1687 assert!(visited > 40, "fixture is too small to prove much");
1688 assert_eq!(
1689 child_scan_cursors::observed(),
1690 before,
1691 "preorder built a cursor per node; it holds one for the walk (#1112)"
1692 );
1693
1694 // The `Search` walk, `act_on_node`. The counter records in
1695 // `children()`, the allocating form, so a walk that hoists its
1696 // cursor records nothing at all and a per-node one records once
1697 // per interior node. Asserting the exact zero is what tells a
1698 // hoisted cursor from a per-node one; a bound like `< nodes / 2`
1699 // would hold for either on a small fixture.
1700 let tree = Tree::new::<MozjsCode>(
1701 b"function f(a) { return { g: (b) => b + 1, h: [1, 2, 3] }; }\nf(2);\n",
1702 );
1703 let root = tree.get_root();
1704 let nodes = root.preorder().count();
1705 assert!(nodes > 30, "fixture is too small to prove much");
1706
1707 let before = child_scan_cursors::observed();
1708 let mut seen = 0_usize;
1709 root.act_on_node(&mut |_, _| seen += 1);
1710 let scans = child_scan_cursors::observed() - before;
1711 assert_eq!(seen, nodes, "act_on_node must visit every node");
1712 assert_eq!(
1713 scans, 0,
1714 "act_on_node built {scans} cursors over {nodes} nodes; it holds one \
1715 for the walk (#1112)"
1716 );
1717 }
1718
1719 /// [`Node::parent_grandparent_match`] must answer `false` when
1720 /// either link is missing, rather than degrading to a
1721 /// single-predicate check.
1722 ///
1723 /// Its doc states that invariant and Python's `Cyclomatic` `else`
1724 /// arm depends on it, but nothing exercised either absent-link
1725 /// return: every call in the suite runs on a node that has both a
1726 /// parent and a grandparent. Both predicates answer `true` here, so
1727 /// a `false` result can only come from the missing link — an
1728 /// implementation that skipped the second `climb.next()` and
1729 /// returned `parent_pred`'s answer would pass every other test and
1730 /// fail this one.
1731 ///
1732 /// Checked through both `Ancestors` constructors: the chain and the
1733 /// climb reach the end by different code paths (`split_last` on an
1734 /// empty slice, versus `Node::parent` returning `None`).
1735 #[cfg(feature = "c")]
1736 #[test]
1737 fn parent_grandparent_match_is_false_when_either_link_is_absent() {
1738 let tree = Tree::new::<crate::langs::CCode>(b"int main() { int a; }");
1739 let root = tree.get_root();
1740 let child = root.children().next().expect("the file has an item");
1741 let grandchild = child
1742 .children()
1743 .next()
1744 .expect("the function definition has children");
1745 let yes: fn(&Node) -> bool = |_| true;
1746
1747 // No parent at all: the root, reached either way.
1748 assert!(!root.parent_grandparent_match(Ancestors::unknown(), yes, yes));
1749 assert!(!root.parent_grandparent_match(Ancestors::known(&[]), yes, yes));
1750
1751 // A parent but no grandparent: a direct child of the root.
1752 assert!(!child.parent_grandparent_match(Ancestors::unknown(), yes, yes));
1753 assert!(!child.parent_grandparent_match(Ancestors::known(&[root]), yes, yes));
1754
1755 // Both links present, so the same predicates now answer `true`.
1756 // Without this the assertions above would also hold for a
1757 // function that always returned `false`.
1758 assert!(grandchild.parent_grandparent_match(Ancestors::unknown(), yes, yes));
1759 assert!(grandchild.parent_grandparent_match(Ancestors::known(&[root, child]), yes, yes));
1760 }
1761
1762 /// [`Node::children_with`] must yield exactly what
1763 /// [`Node::children`] yields — same nodes, same order, same
1764 /// `ExactSizeIterator` length at every step — for every node of a
1765 /// real tree.
1766 ///
1767 /// Checked against the raw `child(i)` walk rather than against
1768 /// `children()`: the two iterators share `ChildScan`, so a
1769 /// comparison between them would pass just as happily if the shared
1770 /// step were wrong. It also covers the reuse itself — one cursor
1771 /// drives every node's scan here, so a `reset` that failed to rewind
1772 /// would show as the second node inheriting the first's position.
1773 #[cfg(feature = "mozjs")]
1774 #[test]
1775 fn children_with_yields_exactly_what_children_does() {
1776 let code = b"const o = { m: (a) => a + 1, n: function () {} }; foo(); ;";
1777 let tree = Tree::new::<MozjsCode>(code);
1778 let root = tree.get_root();
1779
1780 let mut cursor = root.cursor();
1781 let mut leaves = 0;
1782 let mut widest = 0;
1783 for node in root.preorder() {
1784 // Ground truth is the raw `child(i)` walk, not `children()`.
1785 // The two iterators share `ChildScan`, so checking one
1786 // against the other would pass just as happily if the shared
1787 // step were wrong.
1788 let raw = node.as_tree_sitter();
1789 let expected: Vec<_> = (0..raw.child_count() as u32)
1790 .filter_map(|i| raw.child(i))
1791 .map(|c| (c.id(), c.kind_id()))
1792 .collect();
1793
1794 let actual = drain_checking_exact_size(
1795 node.children_with(&mut cursor),
1796 expected.len(),
1797 "children_with",
1798 node.kind(),
1799 );
1800 assert_eq!(
1801 actual,
1802 expected,
1803 "children_with diverged from the child(i) walk at kind {}",
1804 node.kind()
1805 );
1806
1807 leaves += usize::from(expected.is_empty());
1808 widest = widest.max(expected.len());
1809 }
1810 // Both ends of the arity range, else the comparison could hold
1811 // over nothing but one-child wrappers.
1812 assert!(leaves > 0, "fixture must contain childless nodes");
1813 assert!(widest > 2, "fixture must contain a multi-child node");
1814 }
1815
1816 /// `children_after` and `children_before` must yield, for every child
1817 /// of every node, the raw `child(i)` walk on either side of it — and
1818 /// nothing for a node that is not a child.
1819 ///
1820 /// The Kotlin fixture's recovery inserts a zero-width parameter name
1821 /// (`vararg: Int`), which `goto_first_child_for_byte` passes over, so
1822 /// it is the only input that reaches the scan fallback in
1823 /// `seat_on_child`; the count of such children pins that it does.
1824 #[cfg(feature = "kotlin")]
1825 #[test]
1826 fn children_after_and_before_match_the_child_walk() {
1827 let code = b"fun f(vararg: Int, /* c */ b: Int) { g(1, 2, 3) }";
1828 let tree = Tree::new::<crate::langs::KotlinCode>(code);
1829 let root = tree.get_root();
1830
1831 // Each row is (parent kind, child index, after ids, before ids), and
1832 // a non-child row is (kind, after count, before count) against the
1833 // root, so a failing diff names the node without a message argument.
1834 let mut zero_width = 0;
1835 let (mut observed, mut expected) = (Vec::new(), Vec::new());
1836 let (mut stray_observed, mut stray_expected) = (Vec::new(), Vec::new());
1837 for node in root.preorder() {
1838 let raw = node.as_tree_sitter();
1839 let ids: Vec<_> = (0..raw.child_count() as u32)
1840 .filter_map(|i| raw.child(i))
1841 .map(|c| c.id())
1842 .collect();
1843 for (index, child) in node.children().enumerate() {
1844 zero_width += usize::from(child.start_byte() == child.end_byte());
1845 observed.push((
1846 node.kind(),
1847 index,
1848 node.children_after(&child)
1849 .map(|c| c.id())
1850 .collect::<Vec<_>>(),
1851 node.children_before(&child)
1852 .map(|c| c.id())
1853 .collect::<Vec<_>>(),
1854 ));
1855 expected.push((
1856 node.kind(),
1857 index,
1858 ids[index + 1..].to_vec(),
1859 ids[..index].iter().rev().copied().collect(),
1860 ));
1861 }
1862 if node.id() != root.id() {
1863 stray_observed.push((
1864 node.kind(),
1865 root.children_after(&node).count(),
1866 root.children_before(&node).count(),
1867 ));
1868 stray_expected.push((node.kind(), 0, 0));
1869 }
1870 }
1871 assert_eq!(observed, expected);
1872 assert_eq!(stray_observed, stray_expected);
1873 assert!(
1874 zero_width > 0,
1875 "fixture must hold an inserted zero-width child"
1876 );
1877 }
1878
1879 /// The `O(1)` guard [`Ancestors::checked`] keeps on by default must
1880 /// accept every chain a walker really builds — over several grammar
1881 /// families, not just the one fixture a failure would surface in.
1882 ///
1883 /// The assertion that an equal-span pair was seen is what makes the
1884 /// containment non-strict on purpose rather than by luck: a
1885 /// single-child wrapper (`expression_statement` over its expression,
1886 /// say) spans exactly what its child spans, so tightening either
1887 /// bound to `<` would reject a correct chain on most real input.
1888 #[cfg(all(feature = "c", feature = "javascript", feature = "python"))]
1889 #[test]
1890 fn checked_accepts_the_chains_the_walkers_build() {
1891 let mut equal_span_pairs = 0;
1892 let mut check = |node: &Node<'_>, chain: &[Node<'_>]| {
1893 let _ = Ancestors::checked(chain, node);
1894 if let Some(parent) = chain.last()
1895 && parent.start_byte() == node.start_byte()
1896 && parent.end_byte() == node.end_byte()
1897 {
1898 equal_span_pairs += 1;
1899 }
1900 };
1901 let visited = for_each_node_with_chain::<crate::langs::CCode>(
1902 b"int main() { if (a) { int x; } else { f(a, b); } }",
1903 &mut check,
1904 ) + for_each_node_with_chain::<crate::langs::JavascriptCode>(
1905 b"const o = { m: (a) => a + 1 };\nfoo.bar();\n",
1906 &mut check,
1907 ) + for_each_node_with_chain::<crate::langs::PythonCode>(
1908 b"def f(a):\n if a:\n return [x for x in a]\n",
1909 &mut check,
1910 );
1911
1912 assert!(visited > 60, "fixtures are too small to prove much");
1913 assert!(
1914 equal_span_pairs > 0,
1915 "no parent spans exactly what its child does, so this fixture set \
1916 cannot tell non-strict containment from strict"
1917 );
1918 }
1919
1920 /// A `push` moved ahead of the per-node computes leaves the node
1921 /// itself as `chain.last()`. Spans alone cannot see that — a node
1922 /// contains itself — so the identity half of the guard is what
1923 /// catches it.
1924 ///
1925 /// Debug-gated because `debug_assert!` compiles out under
1926 /// `--release`, where `checked` degrades to `known` by design.
1927 #[cfg(feature = "c")]
1928 #[test]
1929 #[cfg(debug_assertions)]
1930 #[should_panic(expected = "ancestor chain desynchronised")]
1931 fn checked_rejects_a_chain_ending_in_the_node_itself() {
1932 let tree = Tree::new::<crate::langs::CCode>(b"int main() { int a; }");
1933 let body = tree
1934 .get_root()
1935 .preorder()
1936 .find(|n| n.kind() == "compound_statement")
1937 .expect("fixture has a function body");
1938 let _ = Ancestors::checked(std::slice::from_ref(&body), &body);
1939 }
1940
1941 /// A dropped `truncate` leaves the previous subtree's path in place,
1942 /// so the next node up gets a `chain.last()` from a sibling subtree —
1943 /// disjoint from it in bytes. That is the containment half.
1944 #[cfg(feature = "c")]
1945 #[test]
1946 #[cfg(debug_assertions)]
1947 #[should_panic(expected = "ancestor chain desynchronised")]
1948 fn checked_rejects_a_chain_from_a_disjoint_subtree() {
1949 let tree = Tree::new::<crate::langs::CCode>(b"int main() { int a; int b; }");
1950 let body = tree
1951 .get_root()
1952 .preorder()
1953 .find(|n| n.kind() == "compound_statement")
1954 .expect("fixture has a function body");
1955 let declarations: Vec<Node<'_>> = body
1956 .children()
1957 .filter(|n| n.kind() == "declaration")
1958 .collect();
1959 assert_eq!(declarations.len(), 2, "fixture has two declarations");
1960 // `int a;` neither contains nor equals `int b;`.
1961 let _ = Ancestors::checked(&declarations[..1], &declarations[1]);
1962 }
1963
1964 /// [`Node::has_sibling`] must answer the same whether its parent
1965 /// comes off a known chain or from `Node::parent`.
1966 ///
1967 /// The parent lookup is the only thing #1088 changed here, and it is
1968 /// the half a caller cannot see: `check_if_arrow_func!` folds the
1969 /// answer into a disjunction, so a wrong parent would silently
1970 /// reclassify an arrow function rather than fail. Checked for every
1971 /// node against every kind the fixture contains, plus one that never
1972 /// occurs so the absent-sibling answer is covered too.
1973 #[cfg(feature = "javascript")]
1974 #[test]
1975 fn has_sibling_agrees_between_known_and_climbing() {
1976 // Object-literal methods and an arrow bound to a property are
1977 // the shapes whose `PropertyIdentifier` sibling the JS closure
1978 // check asks about.
1979 let code = b"const o = { m: (a) => a + 1, n: function () {} };\nconst p = a => a;\n";
1980 let mut kinds = std::collections::BTreeSet::new();
1981 for_each_node_with_chain::<crate::langs::JavascriptCode>(code, |node, _| {
1982 kinds.insert(node.kind_id());
1983 });
1984 // An id no node in the fixture carries, so the `false` answer is
1985 // exercised as well as the `true` one.
1986 let absent = u16::MAX;
1987 let mut agreed_true = 0;
1988 let visited =
1989 for_each_node_with_chain::<crate::langs::JavascriptCode>(code, |node, chain| {
1990 for &id in kinds.iter().chain(std::iter::once(&absent)) {
1991 let known = node.has_sibling(Ancestors::known(chain), id);
1992 let climbing = node.has_sibling(Ancestors::unknown(), id);
1993 assert_eq!(
1994 known,
1995 climbing,
1996 "has_sibling({id}) on {} disagrees between chain and climb",
1997 node.kind()
1998 );
1999 agreed_true += usize::from(known);
2000 }
2001 });
2002 assert!(visited > 20, "fixture is too small to prove much");
2003 assert!(
2004 agreed_true > 0,
2005 "every answer was `false`, so the sibling scan never ran to a match"
2006 );
2007 }
2008
2009 /// [`Search::act_on_node`] must hand each node its true ancestry
2010 /// even when the walk starts below the tree root.
2011 ///
2012 /// The seed is what decides this. [`Ancestors`] reads an empty chain
2013 /// as "this node is the root", so seeding empty — which is correct
2014 /// for every caller today (`bca function`, `find` and `count`, whose
2015 /// walks all start at the root) — would report no parent for the
2016 /// subtree root and shift every answer beneath it. For the JS getters
2017 /// that means
2018 /// losing the `variable_declarator` a `function_expression` takes
2019 /// its name from, so the space would silently be named
2020 /// `<anonymous>`.
2021 ///
2022 /// No caller passes a subtree yet, so nothing else would catch this;
2023 /// the fixture below is the guard, and it fails against an empty
2024 /// seed both here and through `Ancestors::checked`'s debug
2025 /// assertion.
2026 #[cfg(feature = "mozjs")]
2027 #[test]
2028 fn act_on_node_hands_a_subtree_its_real_ancestry() {
2029 let code = b"var outer = function () { return 1; };\n";
2030 let tree = Tree::new::<MozjsCode>(code);
2031 let root = tree.get_root();
2032 let subtree = root
2033 .preorder()
2034 .find(|n| n.kind() == "variable_declarator")
2035 .expect("fixture has a variable_declarator");
2036 assert!(
2037 subtree.parent().is_some(),
2038 "the walk must start below the root, else the seed is vacuous"
2039 );
2040
2041 let mut visited = 0;
2042 subtree.act_on_node(&mut |node, ancestors| {
2043 assert_eq!(
2044 ancestors.parent(node).map(|p| p.id()),
2045 node.parent().map(|p| p.id()),
2046 "parent of {} disagrees with the tree",
2047 node.kind()
2048 );
2049 visited += 1;
2050 });
2051 assert!(visited > 3, "subtree is too small to prove much");
2052 }
2053
2054 /// `previous_sibling` must not answer "no previous sibling" when the
2055 /// chain it was handed belongs to a different node.
2056 ///
2057 /// The known path finds the answer by scanning the chain's last
2058 /// entry for `node`; a miss means the caller paired the two wrongly.
2059 /// Reporting `None` there would be a wrong answer dressed as a
2060 /// legitimate one, so the fallback re-asks the tree.
2061 #[cfg(feature = "c")]
2062 #[test]
2063 fn previous_sibling_falls_back_on_a_chain_that_is_not_this_nodes() {
2064 let code = b"int main() { int a; int b; }";
2065 let tree = Tree::new::<crate::langs::CCode>(code);
2066 let root = tree.get_root();
2067 let body = root
2068 .preorder()
2069 .find(|n| n.kind() == "compound_statement")
2070 .expect("fixture has a function body");
2071 let declarations: Vec<Node<'_>> = body
2072 .children()
2073 .filter(|n| n.kind() == "declaration")
2074 .collect();
2075 assert_eq!(declarations.len(), 2, "fixture has two declarations");
2076
2077 let second = declarations[1];
2078 let expected = second
2079 .previous_sibling()
2080 .map(|p| p.id())
2081 .expect("the second declaration has a previous sibling");
2082 // A chain ending in the *root* does not describe `second`, whose
2083 // parent is the function body.
2084 let foreign = [root];
2085 assert_eq!(
2086 Ancestors::known(&foreign)
2087 .previous_sibling(&second)
2088 .map(|p| p.id()),
2089 Some(expected),
2090 "a mismatched chain must fall back, not report `None`"
2091 );
2092 assert!(
2093 Ancestors::known(&[]).previous_sibling(&second).is_none(),
2094 "an empty chain means `second` is the root, which has no siblings"
2095 );
2096 }
2097
2098 /// `count_specific_ancestors` must return the same count whichever
2099 /// way it reaches the ancestors. Uses `loc`'s real C predicate pair
2100 /// (`while`/`for`/`if` header, stopping at the enclosing block), so
2101 /// the fixture exercises both the counted case (the `for`-header
2102 /// declaration) and the stopped case (the block-scoped ones).
2103 #[cfg(feature = "c")]
2104 #[test]
2105 fn count_specific_ancestors_agrees_between_known_and_climbing() {
2106 let code =
2107 b"int main() { int a; if (x) { int b; } for (int i = 0; i < 2; i++) { int c; } }";
2108 let mut counted = 0;
2109 let mut nonzero = 0;
2110 let visited = for_each_node_with_chain::<crate::langs::CCode>(code, |node, chain| {
2111 if node.kind() != "declaration" {
2112 return;
2113 }
2114 let check: fn(&Node) -> bool = |n| {
2115 matches!(
2116 n.kind(),
2117 "while_statement" | "for_statement" | "if_statement"
2118 )
2119 };
2120 let stop: fn(&Node) -> bool = |n| n.kind() == "compound_statement";
2121 let known = node.count_specific_ancestors::<crate::langs::CCode>(
2122 Ancestors::known(chain),
2123 check,
2124 stop,
2125 );
2126 let climbing = node.count_specific_ancestors::<crate::langs::CCode>(
2127 Ancestors::unknown(),
2128 check,
2129 stop,
2130 );
2131 assert_eq!(
2132 known,
2133 climbing,
2134 "declaration at row {}: known chain counted {known}, climbing counted {climbing}",
2135 node.start_row()
2136 );
2137 counted += 1;
2138 nonzero += usize::from(known > 0);
2139 });
2140 assert!(visited > 20);
2141 assert_eq!(counted, 4, "fixture must hold four declarations");
2142 assert_eq!(
2143 nonzero, 1,
2144 "only the `for`-header declaration sits under a header with no block between"
2145 );
2146 }
2147}