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
13use tree_sitter::Node as OtherNode;
14use tree_sitter::Tree as OtherTree;
15use tree_sitter::{Parser, TreeCursor};
16
17use crate::checker::Checker;
18use crate::traits::{LanguageInfo, Search};
19
20/// A parsed source tree wrapping a [`tree_sitter::Tree`].
21///
22/// The "open parse seam" (see issue #251) is reached by external
23/// callers through [`crate::Ast::from_tree_sitter`], which accepts a
24/// caller-built `tree_sitter::Tree` directly; this wrapper stays
25/// internal so the metric walker is the only thing that observes it.
26#[derive(Clone, Debug)]
27pub(crate) struct Tree(OtherTree);
28
29impl Tree {
30 pub(crate) fn new<T: LanguageInfo>(code: &[u8]) -> Self {
31 let mut parser = Parser::new();
32 // `Tree::new::<T>` is only reachable from the `mk_action!`
33 // dispatchers, which themselves cfg-gate each `LANG::*` arm
34 // behind the matching per-language feature (see #252). When
35 // the feature is off the dispatcher returns
36 // `Err(LanguageDisabled)` before we get here, so
37 // `get_ts_language` is provably `Ok` at this call site.
38 let language = T::lang().get_ts_language().expect(
39 "invariant: dispatcher cfg-gates this call behind the per-language Cargo feature",
40 );
41 parser
42 .set_language(&language)
43 .expect("invariant: grammar version is pinned and compatible with bundled tree-sitter");
44
45 Self(
46 parser
47 .parse(code, None)
48 .expect("invariant: parser has a language set and no cancellation flag"),
49 )
50 }
51
52 pub(crate) fn from_ts_tree(tree: OtherTree) -> Self {
53 Self(tree)
54 }
55
56 pub(crate) fn get_root(&self) -> Node<'_> {
57 Node(self.0.root_node())
58 }
59
60 pub(crate) fn as_ts_tree(&self) -> &OtherTree {
61 &self.0
62 }
63}
64
65/// An `AST` node.
66///
67/// The inner [`tree_sitter::Node`] is reached through
68/// [`Node::as_tree_sitter`] for advanced use cases that need direct
69/// access to the underlying tree-sitter API; the field itself is
70/// private so a `tree-sitter` version bump cannot silently reshape
71/// this struct's public layout.
72#[derive(Clone, Copy, Debug)]
73pub struct Node<'a>(OtherNode<'a>);
74
75impl<'a> Node<'a> {
76 /// Returns the underlying [`tree_sitter::Node`] for callers that
77 /// want to drive their own traversal alongside the metric walker.
78 ///
79 /// `tree_sitter::Node` is [`Copy`], so the node is returned by
80 /// value. The returned node borrows the same source-tree lifetime
81 /// as `self`.
82 ///
83 /// The `tree-sitter` re-export this exposes is *value-not-stable*:
84 /// the underlying pin may bump in any minor release, so node shape
85 /// and node-kind ids are not part of this crate's stability
86 /// contract (see the [`tree_sitter`](crate::tree_sitter) re-export
87 /// note in the crate root).
88 #[must_use]
89 #[inline]
90 pub fn as_tree_sitter(&self) -> OtherNode<'a> {
91 self.0
92 }
93
94 /// Checks if a node represents a syntax error or contains any syntax errors
95 /// anywhere within it.
96 #[must_use]
97 pub fn has_error(&self) -> bool {
98 self.0.has_error()
99 }
100
101 pub(crate) fn id(&self) -> usize {
102 self.0.id()
103 }
104
105 pub(crate) fn kind(&self) -> &'static str {
106 self.0.kind()
107 }
108
109 pub(crate) fn kind_id(&self) -> u16 {
110 self.0.kind_id()
111 }
112
113 pub(crate) fn utf8_text(&self, data: &'a [u8]) -> Option<&'a str> {
114 self.0.utf8_text(data).ok()
115 }
116
117 pub(crate) fn start_byte(&self) -> usize {
118 self.0.start_byte()
119 }
120
121 pub(crate) fn end_byte(&self) -> usize {
122 self.0.end_byte()
123 }
124
125 pub(crate) fn start_position(&self) -> (usize, usize) {
126 let temp = self.0.start_position();
127 (temp.row, temp.column)
128 }
129
130 pub(crate) fn end_position(&self) -> (usize, usize) {
131 let temp = self.0.end_position();
132 (temp.row, temp.column)
133 }
134
135 pub(crate) fn start_row(&self) -> usize {
136 self.0.start_position().row
137 }
138
139 pub(crate) fn end_row(&self) -> usize {
140 self.0.end_position().row
141 }
142
143 pub(crate) fn parent(&self) -> Option<Node<'a>> {
144 self.0.parent().map(Node)
145 }
146
147 /// Returns `true` if this node's parent has any direct child with
148 /// the given grammar `kind_id` (the parent's children include this
149 /// node itself, so a self-match counts). Delegates to [`wraps_any`]
150 /// on the parent so the scan reuses the allocation-free `child(0)` +
151 /// `next_sibling()` walk rather than `children(&mut parent.walk())`,
152 /// which heap-allocates a `TreeCursor` per call. This sits on the
153 /// JS/TS arrow-function closure-classification hot path
154 /// (`check_if_arrow_func!`), the same path #217 optimized for
155 /// `wraps_any` / `is_child` but missed here. See #521.
156 ///
157 /// [`wraps_any`]: Self::wraps_any
158 #[inline]
159 pub(crate) fn has_sibling(&self, id: u16) -> bool {
160 self.0
161 .parent()
162 .is_some_and(|parent| Node(parent).is_child(id))
163 }
164
165 pub(crate) fn previous_sibling(&self) -> Option<Node<'a>> {
166 self.0.prev_sibling().map(Node)
167 }
168
169 /// Returns `true` if any direct child has the given grammar
170 /// `kind_id`. Walks via `child(0)` + `next_sibling()` instead of
171 /// `children(&mut self.0.walk())` so the implementation avoids
172 /// the per-call `TreeCursor` heap allocation that the iterator
173 /// form requires. Each `next_sibling()` is O(1) (tree-sitter
174 /// stores siblings as a linked list), so total cost is O(n)
175 /// without cursor overhead. See #217 for the motivating perf
176 /// finding from the JS/TS template-literal hot path.
177 #[inline]
178 pub(crate) fn is_child(&self, id: u16) -> bool {
179 self.wraps_any(&[id])
180 }
181
182 /// Returns `true` if any direct child matches one of the given
183 /// grammar `kind_id`s. The single-id [`is_child`] delegates here, so
184 /// both share one allocation-free sibling walk (the `#[inline]` makes
185 /// the single-element `contains` collapse to an equality check — the
186 /// #217 hot-path optimization is preserved). Generalizing the check to
187 /// a set lets the shared string-interpolation operand skip declare its
188 /// rule once (issue #420).
189 ///
190 /// [`is_child`]: Self::is_child
191 #[inline]
192 pub(crate) fn wraps_any(&self, ids: &[u16]) -> bool {
193 let mut cur = self.0.child(0);
194 while let Some(c) = cur {
195 if ids.contains(&c.kind_id()) {
196 return true;
197 }
198 cur = c.next_sibling();
199 }
200 false
201 }
202
203 pub(crate) fn child_count(&self) -> usize {
204 self.0.child_count()
205 }
206
207 // Returns `true` if this node is a named grammar production
208 // (as opposed to an anonymous token such as a punctuation or
209 // keyword literal). Used to skip anonymous tokens like the
210 // leading `|` in an or-pattern.
211 pub(crate) fn is_named(&self) -> bool {
212 self.0.is_named()
213 }
214
215 /// Returns the direct child reached through the grammar `field_name`,
216 /// if any. The child carries the underlying tree lifetime `'a` (the
217 /// `tree_sitter::Node` it wraps is [`Copy`] and valid for the whole
218 /// tree), so callers may hold it past the borrow of `&self` — matching
219 /// the sibling accessors ([`child`], [`parent`], [`children`], …) rather
220 /// than over-narrowing to the method-call borrow (see issue #786).
221 ///
222 /// [`child`]: Self::child
223 /// [`parent`]: Self::parent
224 /// [`children`]: Self::children
225 pub(crate) fn child_by_field_name(&self, name: &str) -> Option<Node<'a>> {
226 self.0.child_by_field_name(name).map(Node)
227 }
228
229 pub(crate) fn child(&self, pos: usize) -> Option<Node<'a>> {
230 self.0.child(pos as u32).map(Node)
231 }
232
233 /// Returns the tree-sitter grammar field name through which this
234 /// node reaches the child at `child_index`, if any. Used by the
235 /// AST builder to thread the parent's `field_name` into each child
236 /// without a parallel cursor walk.
237 pub(crate) fn field_name_for_child(&self, child_index: u32) -> Option<&'static str> {
238 self.0.field_name_for_child(child_index)
239 }
240
241 pub(crate) fn children(&self) -> Children<'a> {
242 let mut cursor = self.cursor();
243 // `goto_first_child` returns false when the node has no
244 // children, in which case the iterator is empty from the
245 // outset. Termination is then driven entirely by the cursor
246 // (see `Children::next`), so the iterator stops exactly when
247 // the tree reports no further siblings — it can never pad the
248 // sequence with duplicate nodes if `child_count` and the
249 // cursor walk ever disagree.
250 let done = !cursor.goto_first_child();
251 Children {
252 cursor,
253 done,
254 // `child_count` is the authoritative length for the
255 // `ExactSizeIterator` contract; for well-formed trees it
256 // equals the cursor sibling walk, so the reported length
257 // and the emitted data agree. A childless node (`done`
258 // already set) reports `0` so the empty iterator's length
259 // matches its (lack of) data.
260 remaining: if done { 0 } else { self.child_count() },
261 }
262 }
263
264 pub(crate) fn cursor(&self) -> Cursor<'a> {
265 Cursor(self.0.walk())
266 }
267
268 #[allow(dead_code)]
269 pub(crate) fn get_parent(&self, level: usize) -> Option<Node<'a>> {
270 let mut level = level;
271 let mut node = *self;
272 while level != 0 {
273 if let Some(parent) = node.parent() {
274 node = parent;
275 } else {
276 return None;
277 }
278 level -= 1;
279 }
280
281 Some(node)
282 }
283
284 pub(crate) fn count_specific_ancestors<C: Checker>(
285 &self,
286 check: fn(&Node) -> bool,
287 stop: fn(&Node) -> bool,
288 ) -> usize {
289 let mut count = 0;
290 let mut node = *self;
291 while let Some(parent) = node.parent() {
292 if stop(&parent) {
293 break;
294 }
295 if check(&parent) && !C::is_else_if(&parent) {
296 count += 1;
297 }
298 node = parent;
299 }
300 count
301 }
302
303 /// Returns `true` iff this node's parent satisfies `parent_pred`
304 /// AND that parent's own parent (this node's grandparent)
305 /// satisfies `grand_pred`. Returns `false` as soon as either link
306 /// is absent or its predicate fails, so a misordered predicate
307 /// cannot silently degrade to a single-predicate check.
308 pub(crate) fn parent_grandparent_match(
309 &self,
310 parent_pred: fn(&Node) -> bool,
311 grand_pred: fn(&Node) -> bool,
312 ) -> bool {
313 let Some(parent) = self.parent() else {
314 return false;
315 };
316 if !parent_pred(&parent) {
317 return false;
318 }
319 let Some(grand) = parent.parent() else {
320 return false;
321 };
322 grand_pred(&grand)
323 }
324
325 /// Returns a pre-order iterator over this node and all of its
326 /// descendants (this node first, then each child subtree left to
327 /// right).
328 ///
329 /// The traversal is allocation-light: it reuses one work stack and
330 /// visits each node exactly once, so a full walk is O(n) in the
331 /// subtree size. Every yielded [`Node`] carries the underlying tree
332 /// lifetime `'a`, so callers may collect or retain the handles.
333 ///
334 /// This is the Rust counterpart of the Python `Node.walk()` binding
335 /// (issue #728): the binding wraps each yielded node, so Rust and
336 /// Python share one traversal order.
337 #[must_use]
338 pub fn preorder(&self) -> Preorder<'a> {
339 Preorder { stack: vec![*self] }
340 }
341
342 /// Collects every node in this subtree (this node included) whose
343 /// [`kind`](tree_sitter::Node::kind) is listed in `kinds`, in
344 /// pre-order.
345 ///
346 /// Membership is an exact match against the raw grammar kind — the
347 /// same unaltered vocabulary [`crate::Ast::root_node`] exposes, not
348 /// the `Alterator`-curated kinds [`crate::Ast::dump`] emits. This is
349 /// the Rust counterpart of the Python `Node.descendants_by_kind()`
350 /// binding (issue #728).
351 #[must_use]
352 pub fn descendants_by_kind(&self, kinds: &[&str]) -> Vec<Node<'a>> {
353 self.preorder()
354 .filter(|node| kinds.contains(&node.kind()))
355 .collect()
356 }
357}
358
359/// Pre-order iterator over a node and its descendants, returned by
360/// [`Node::preorder`].
361///
362/// Holds a single work stack of not-yet-visited nodes. Each step pops the
363/// next node, pushes its children so the leftmost is visited first, and
364/// yields the popped node — so the sequence is the node, then each child
365/// subtree in order. The stack is reused across steps (children are pushed
366/// then the freshly-pushed slice is reversed in place), so the walk
367/// allocates only the stack's growth, not a fresh buffer per node.
368pub struct Preorder<'a> {
369 stack: Vec<Node<'a>>,
370}
371
372impl<'a> Iterator for Preorder<'a> {
373 type Item = Node<'a>;
374
375 fn next(&mut self) -> Option<Self::Item> {
376 let node = self.stack.pop()?;
377 // Push children in document order, then reverse just the slice we
378 // appended so the leftmost child ends up on top of the stack and
379 // is visited next — pre-order without a per-node temporary.
380 let first_child = self.stack.len();
381 self.stack.extend(node.children());
382 self.stack[first_child..].reverse();
383 Some(node)
384 }
385}
386
387/// An `AST` cursor.
388#[derive(Clone)]
389pub(crate) struct Cursor<'a>(TreeCursor<'a>);
390
391impl<'a> Cursor<'a> {
392 pub(crate) fn reset(&mut self, node: &Node<'a>) {
393 self.0.reset(node.0);
394 }
395
396 pub(crate) fn goto_next_sibling(&mut self) -> bool {
397 self.0.goto_next_sibling()
398 }
399
400 pub(crate) fn goto_first_child(&mut self) -> bool {
401 self.0.goto_first_child()
402 }
403
404 pub(crate) fn node(&self) -> Node<'a> {
405 Node(self.0.node())
406 }
407}
408
409/// Iterator over a node's direct children, returned by
410/// [`Node::children`].
411///
412/// Termination is driven by the cursor alone: each step yields the
413/// cursor's current node, then advances with `goto_next_sibling`,
414/// stopping the moment that returns false. This makes the cursor the
415/// single source of truth for both the emitted data and when to stop,
416/// so the sequence can never be padded with duplicates if
417/// `child_count` and the actual sibling walk disagree.
418///
419/// The `ExactSizeIterator` length is reported from `child_count`
420/// (tracked in `remaining`). For well-formed trees the cursor walk and
421/// `child_count` agree, so the advertised length matches the data
422/// exactly.
423pub(crate) struct Children<'a> {
424 cursor: Cursor<'a>,
425 done: bool,
426 remaining: usize,
427}
428
429impl<'a> Iterator for Children<'a> {
430 type Item = Node<'a>;
431
432 fn next(&mut self) -> Option<Self::Item> {
433 if self.done {
434 return None;
435 }
436 let result = self.cursor.node();
437 // The cursor is the single source of truth for termination:
438 // once there is no next sibling this yield is the last one.
439 self.done = !self.cursor.goto_next_sibling();
440 // Keep the advertised length consistent with termination: when
441 // the cursor stops, nothing remains. For well-formed trees this
442 // equals `child_count - emitted`; if the cursor walk and
443 // `child_count` ever disagree, this still honors the
444 // `ExactSizeIterator` contract (`len() == 0` exactly at
445 // exhaustion) rather than reporting a phantom remainder.
446 self.remaining = if self.done {
447 0
448 } else {
449 self.remaining.saturating_sub(1)
450 };
451 Some(result)
452 }
453
454 fn size_hint(&self) -> (usize, Option<usize>) {
455 (self.remaining, Some(self.remaining))
456 }
457}
458
459impl ExactSizeIterator for Children<'_> {}
460
461impl<'a> Search<'a> for Node<'a> {
462 fn first_occurrence(&self, pred: fn(u16) -> bool) -> Option<Node<'a>> {
463 let mut cursor = self.cursor();
464 let mut stack = Vec::new();
465 let mut children = Vec::new();
466
467 stack.push(*self);
468
469 while let Some(node) = stack.pop() {
470 if pred(node.kind_id()) {
471 return Some(node);
472 }
473 cursor.reset(&node);
474 if cursor.goto_first_child() {
475 loop {
476 children.push(cursor.node());
477 if !cursor.goto_next_sibling() {
478 break;
479 }
480 }
481 for child in children.drain(..).rev() {
482 stack.push(child);
483 }
484 }
485 }
486
487 None
488 }
489
490 fn act_on_node(&self, action: &mut dyn FnMut(&Node<'a>)) {
491 let mut cursor = self.cursor();
492 let mut stack = Vec::new();
493 let mut children = Vec::new();
494
495 stack.push(*self);
496
497 while let Some(node) = stack.pop() {
498 action(&node);
499 cursor.reset(&node);
500 if cursor.goto_first_child() {
501 loop {
502 children.push(cursor.node());
503 if !cursor.goto_next_sibling() {
504 break;
505 }
506 }
507 for child in children.drain(..).rev() {
508 stack.push(child);
509 }
510 }
511 }
512 }
513
514 fn first_child(&self, pred: fn(u16) -> bool) -> Option<Node<'a>> {
515 self.children().find(|&child| pred(child.kind_id()))
516 }
517
518 fn act_on_child(&self, action: &mut dyn FnMut(&Node<'a>)) {
519 for child in self.children() {
520 action(&child);
521 }
522 }
523}
524
525#[cfg(test)]
526mod tests {
527 use super::*;
528 use crate::langs::MozjsCode;
529
530 /// The cursor-free [`Node::has_sibling`] (issue #521) must yield the
531 /// exact same result as the original `parent.children(&mut
532 /// parent.walk()).any(...)` form for every node and every kind in a
533 /// real tree: same child set (named + anonymous), same order, same
534 /// short-circuit. Comparing against the literal old logic node-by-node
535 /// proves equivalence without hardcoding grammar `kind_id`s.
536 fn old_has_sibling(node: OtherNode, id: u16) -> bool {
537 node.parent().is_some_and(|parent| {
538 parent
539 .children(&mut parent.walk())
540 .any(|child| child.kind_id() == id)
541 })
542 }
543
544 #[test]
545 fn has_sibling_matches_cursor_iterator_form() {
546 // Arrow functions exercise the `check_if_arrow_func!` call site
547 // that motivated #521 (PropertyIdentifier siblings on the JS/TS
548 // closure-classification hot path).
549 let code = b"const o = { m: (a) => a + 1, n: function () {} }; foo.bar();";
550 let tree = Tree::new::<MozjsCode>(code);
551 let ts_tree = tree.as_ts_tree();
552
553 // Collect the grammar kinds that actually occur, so the
554 // equivalence check covers present-sibling (true) cases.
555 let mut kinds = std::collections::BTreeSet::new();
556 let mut stack = vec![ts_tree.root_node()];
557 while let Some(n) = stack.pop() {
558 kinds.insert(n.kind_id());
559 let mut child = n.child(0);
560 while let Some(c) = child {
561 stack.push(c);
562 child = c.next_sibling();
563 }
564 }
565 // Include an id that does not occur anywhere for absent-sibling
566 // (false) coverage.
567 let absent_id = u16::MAX;
568
569 let mut stack = vec![ts_tree.root_node()];
570 while let Some(n) = stack.pop() {
571 let wrapped = Node(n);
572 for &id in kinds.iter().chain(std::iter::once(&absent_id)) {
573 assert_eq!(
574 wrapped.has_sibling(id),
575 old_has_sibling(n, id),
576 "has_sibling diverged from cursor-iterator form at node kind {} for id {id}",
577 n.kind(),
578 );
579 }
580 let mut child = n.child(0);
581 while let Some(c) = child {
582 stack.push(c);
583 child = c.next_sibling();
584 }
585 }
586
587 // No-parent node (root) always reports no sibling.
588 let root = Node(ts_tree.root_node());
589 assert!(!root.has_sibling(absent_id));
590 for &id in &kinds {
591 assert!(
592 !root.has_sibling(id),
593 "root node has no parent → no sibling"
594 );
595 }
596 }
597
598 /// `children()` must yield exactly the node's direct children, in
599 /// order, for every node in a real tree — including the empty
600 /// (leaf) and single-child cases. Termination is cursor-driven, so
601 /// the emitted set is compared node-by-node against the raw
602 /// tree-sitter `child(i)` walk (the ground truth for both order and
603 /// count). This pins the no-duplicate-padding property: a desync
604 /// between `child_count` and the cursor walk would surface here as
605 /// extra trailing duplicates or a length mismatch.
606 #[test]
607 fn children_matches_tree_sitter_child_walk() {
608 // Mix of leaf nodes (no children), single-child wrappers, and
609 // multi-child constructs to cover all arities.
610 let code = b"const o = { m: (a) => a + 1 }; foo(); ;";
611 let tree = Tree::new::<MozjsCode>(code);
612 let ts_tree = tree.as_ts_tree();
613
614 let mut stack = vec![ts_tree.root_node()];
615 while let Some(n) = stack.pop() {
616 let wrapped = Node(n);
617
618 // Ground truth: walk children by index off the raw node.
619 let expected: Vec<_> = (0..n.child_count() as u32)
620 .filter_map(|i| n.child(i))
621 .map(|c| (c.id(), c.kind_id()))
622 .collect();
623
624 let mut iter = wrapped.children();
625 // ExactSizeIterator length must equal the child count up
626 // front and stay exact as the iterator is consumed.
627 assert_eq!(
628 iter.len(),
629 expected.len(),
630 "children().len() disagreed with child_count at kind {}",
631 n.kind(),
632 );
633
634 let mut actual = Vec::new();
635 let mut remaining = expected.len();
636 while let Some(child) = iter.next() {
637 remaining -= 1;
638 assert_eq!(
639 iter.len(),
640 remaining,
641 "size_hint drifted mid-iteration at kind {}",
642 n.kind(),
643 );
644 actual.push((child.id(), child.kind_id()));
645 }
646 assert_eq!(iter.len(), 0, "iterator not drained to zero len");
647 assert_eq!(
648 actual,
649 expected,
650 "children() diverged from child(i) walk at kind {}",
651 n.kind(),
652 );
653
654 for i in 0..n.child_count() as u32 {
655 if let Some(c) = n.child(i) {
656 stack.push(c);
657 }
658 }
659 }
660 }
661
662 /// `child_by_field_name` (issue #786) must return the child at the
663 /// underlying tree lifetime `'a`, not the method-call borrow of
664 /// `&self`. The proof is a helper whose return type *requires* the
665 /// child to outlive an intermediate `&Node` borrow: under the old
666 /// `Option<Node<'_>>` signature the returned node would be tied to
667 /// `parent`'s borrow and this would fail to compile. Binding the
668 /// child to a variable that outlives the `&parent` reborrow inside
669 /// the helper exercises the widened lifetime.
670 #[test]
671 fn child_by_field_name_outlives_self_borrow() {
672 // `find_named_child` takes the parent by value, reborrows it
673 // through a `&` reference to call `child_by_field_name`, and
674 // returns the child. The returned `Node<'a>` must survive past
675 // that inner `&parent` borrow — only possible because the child
676 // carries the tree lifetime, not the borrow of `&parent`.
677 fn find_named_child<'a>(parent: Node<'a>) -> Option<Node<'a>> {
678 let borrowed: &Node<'a> = &parent;
679 borrowed.child_by_field_name("declarator")
680 }
681
682 let code = b"int answer = 42;";
683 let tree = Tree::new::<crate::langs::CppCode>(code);
684 let root = tree.get_root();
685
686 // Walk to the `declaration` node, then pull its `declarator`
687 // child out and hold it after the producing borrow has ended.
688 let mut held: Option<Node> = None;
689 let mut stack = vec![root];
690 while let Some(n) = stack.pop() {
691 if n.kind() == "declaration" {
692 // `find_named_child` consumes a copy of `n`; the result
693 // must remain valid here, well past the inner borrow.
694 held = find_named_child(n);
695 break;
696 }
697 for child in n.children() {
698 stack.push(child);
699 }
700 }
701
702 let declarator = held.expect("C declaration has a `declarator` field");
703 // The held node is still usable: it kept its tree linkage rather
704 // than dangling at the end of the producing borrow.
705 assert_eq!(declarator.kind(), "init_declarator");
706 }
707
708 /// `Node::as_tree_sitter` (issue #556) must hand back the *same*
709 /// underlying `tree_sitter::Node` the wrapper holds: identical
710 /// `kind()` / `kind_id()` and a usable tree-sitter API. Obtaining
711 /// the wrapper through the public `CppParser` + `ParserTrait::root`
712 /// path (rather than the in-module `Tree::new`) proves the accessor
713 /// is the public seam that replaced the former `pub` `.0` field.
714 #[test]
715 fn as_tree_sitter_round_trips_wrapper_kind() {
716 use crate::{CppParser, ParserTrait};
717 use std::path::Path;
718
719 let source = b"int main() { return 0; }";
720 let parser = CppParser::new(source.to_vec(), Path::new("example.cpp"), None);
721 let root = parser.root();
722
723 let ts_root = root.as_tree_sitter();
724
725 // A well-formed C++ translation unit roots at `translation_unit`.
726 assert_eq!(ts_root.kind(), "translation_unit");
727 // The accessor must agree with the wrapper's own kind views.
728 assert_eq!(ts_root.kind(), root.kind());
729 assert_eq!(ts_root.kind_id(), root.kind_id());
730 // The returned node is usable as a tree-sitter node, not a copy
731 // that has lost its tree linkage: the parse is error-free and
732 // the root has children.
733 assert!(!ts_root.has_error());
734 assert!(ts_root.child_count() > 0);
735 }
736
737 /// Ground-truth pre-order walk over the raw tree-sitter node, by
738 /// document order (`child(0..child_count)`). [`Node::preorder`] must
739 /// emit exactly this sequence of node ids — node first, then each
740 /// child subtree left to right.
741 fn ground_truth_preorder(node: OtherNode) -> Vec<usize> {
742 let mut out = vec![node.id()];
743 for i in 0..node.child_count() as u32 {
744 if let Some(child) = node.child(i) {
745 out.extend(ground_truth_preorder(child));
746 }
747 }
748 out
749 }
750
751 #[test]
752 fn preorder_matches_recursive_document_order() {
753 // A nested construct (function holding a declaration and a call)
754 // gives the walk real depth and sibling fan-out to order.
755 let code = b"int main() { int x = 1; foo(x); return 0; }";
756 let tree = Tree::new::<crate::langs::CppCode>(code);
757 let root = tree.get_root();
758
759 let actual: Vec<usize> = root.preorder().map(|n| n.id()).collect();
760 let expected = ground_truth_preorder(root.as_tree_sitter());
761
762 assert_eq!(
763 actual, expected,
764 "preorder diverged from recursive child(0..n) document order"
765 );
766 // Sanity: a non-trivial tree, and the root is visited first.
767 assert!(actual.len() > 5, "expected a multi-node tree");
768 assert_eq!(actual[0], root.id(), "root must be yielded first");
769 }
770
771 #[test]
772 fn descendants_by_kind_collects_matching_subtree_nodes() {
773 // `x` is declared once and used twice, so three `identifier`
774 // nodes exist under the function; `main` is an identifier too.
775 let code = b"int main() { int x = 1; return x + x; }";
776 let tree = Tree::new::<crate::langs::CppCode>(code);
777 let root = tree.get_root();
778
779 let found = root.descendants_by_kind(&["identifier"]);
780 // Cross-check against an independent pre-order count so the helper
781 // cannot pass by matching everything or nothing.
782 let expected: Vec<usize> = root
783 .preorder()
784 .filter(|n| n.kind() == "identifier")
785 .map(|n| n.id())
786 .collect();
787 let actual: Vec<usize> = found.iter().map(Node::id).collect();
788 assert_eq!(actual, expected);
789 assert!(
790 found.len() >= 3,
791 "expected at least the `main`, `x` decl, and `x` uses"
792 );
793 assert!(
794 found.iter().all(|n| n.kind() == "identifier"),
795 "every collected node must match the requested kind"
796 );
797
798 // An absent kind yields nothing; a multi-kind filter unions.
799 assert!(root.descendants_by_kind(&["no_such_kind"]).is_empty());
800 assert!(
801 root.descendants_by_kind(&["identifier", "number_literal"])
802 .len()
803 > found.len(),
804 "adding `number_literal` must widen the match set"
805 );
806 }
807}