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