thread_ast_engine/tree_sitter/traversal.rs
1// SPDX-FileCopyrightText: 2022 Herrington Darkholme <2883231+HerringtonDarkholme@users.noreply.github.com>
2// SPDX-FileCopyrightText: 2025 Knitli Inc. <knitli@knit.li>
3// SPDX-FileContributor: Adam Poulemanos <adam@knit.li>
4//
5// SPDX-License-Identifier: AGPL-3.0-or-later AND MIT
6
7//! # AST Tree Traversal Algorithms
8//!
9//! Efficient tree traversal implementations for navigating and processing AST nodes.
10//! Provides multiple traversal strategies optimized for different use cases, with
11//! built-in support for pattern matching and reentrancy control.
12//!
13//! ## Traversal Algorithms
14//!
15//! - **Pre-order**: Visit parent before children (useful for top-down processing)
16//! - **Post-order**: Visit children before parent (useful for bottom-up processing)
17//! - **Level-order**: Visit nodes level by level (breadth-first search)
18//!
19//! ## Key Features
20//!
21//! ### Pattern-Based Traversal
22//!
23//! Combine traversal with pattern matching to find specific nodes efficiently:
24//!
25//! ```rust,no_run
26//! # use thread_ast_engine::tree_sitter::traversal::Visitor;
27//! # use thread_ast_engine::Language;
28//! # use thread_ast_engine::tree_sitter::LanguageExt;
29//! # struct Tsx;
30//! # impl thread_ast_engine::Language for Tsx {
31//! # fn kind_to_id(&self, _: &str) -> u16 { 0 }
32//! # fn field_to_id(&self, _: &str) -> Option<u16> { None }
33//! # fn build_pattern(&self, _: &thread_ast_engine::PatternBuilder) -> Result<thread_ast_engine::Pattern, thread_ast_engine::PatternError> { todo!() }
34//! # }
35//! # impl LanguageExt for Tsx {
36//! # fn get_ts_language(&self) -> thread_ast_engine::tree_sitter::TSLanguage { todo!() }
37//! # }
38//! let ast = Tsx.ast_grep("function foo() { foo(); }");
39//! let root = ast.root();
40//!
41//! // Find all function calls
42//! for call in Visitor::new("$FUNC()").visit(root) {
43//! println!("Found call: {}", call.get_node().text());
44//! }
45//! ```
46//!
47//! ### Reentrancy Control
48//!
49//! Control whether nested matches should be reported. For example, when finding
50//! function calls in `foo(bar(baz()))`, you can choose to find:
51//! - Only outer calls: `foo(...)`
52//! - Only inner calls: `bar(...)`, `baz()`
53//! - All calls: `foo(...)`, `bar(...)`, `baz()`
54//!
55//! ```rust,no_run
56//! # use thread_ast_engine::tree_sitter::traversal::Visitor;
57//! # use thread_ast_engine::Language;
58//! # use thread_ast_engine::tree_sitter::LanguageExt;
59//! # struct Tsx;
60//! # impl thread_ast_engine::Language for Tsx {
61//! # fn kind_to_id(&self, _: &str) -> u16 { 0 }
62//! # fn field_to_id(&self, _: &str) -> Option<u16> { None }
63//! # fn build_pattern(&self, _: &thread_ast_engine::PatternBuilder) -> Result<thread_ast_engine::Pattern, thread_ast_engine::PatternError> { todo!() }
64//! # }
65//! # impl LanguageExt for Tsx {
66//! # fn get_ts_language(&self) -> thread_ast_engine::tree_sitter::TSLanguage { todo!() }
67//! # }
68//! let ast = Tsx.ast_grep("foo(bar())");
69//! let root = ast.root();
70//!
71//! // Non-reentrant: only finds outer matches
72//! let outer_only: Vec<_> = Visitor::new("$FUNC($$$)")
73//! .reentrant(false)
74//! .visit(root)
75//! .collect();
76//!
77//! // Reentrant: finds all matches including nested ones
78//! let all_matches: Vec<_> = Visitor::new("$FUNC($$$)")
79//! .reentrant(true)
80//! .visit(root)
81//! .collect();
82//! ```
83//!
84//! ## Performance
85//!
86//! - **Pre/Post-order**: Use tree-sitter's cursor API with O(1) memory overhead
87//! - **Level-order**: Uses a queue with O(width) memory, use sparingly for large trees
88//! - **Stack-safe**: All traversals avoid recursion to prevent stack overflow
89//!
90//! Prefer traversal over manual recursion for performance and safety.
91
92use super::StrDoc;
93use crate::tree_sitter::LanguageExt;
94use crate::{Doc, Matcher, Node, Root};
95#[cfg(feature = "matching")]
96use crate::{MatcherExt, NodeMatch};
97
98use tree_sitter as ts;
99
100use std::collections::VecDeque;
101use std::marker::PhantomData;
102
103/// Configurable tree visitor that combines traversal algorithms with pattern matching.
104///
105/// `Visitor` allows you to traverse an AST while filtering nodes based on patterns.
106/// It supports different traversal algorithms and provides fine-grained control
107/// over matching behavior through reentrancy and node type filtering.
108///
109/// # Type Parameters
110///
111/// - `M: Matcher` - The pattern matcher to filter nodes
112/// - `A` - The traversal algorithm (defaults to [`PreOrder`])
113///
114/// # Example
115///
116/// ```rust,no_run
117/// # use thread_ast_engine::tree_sitter::traversal::Visitor;
118/// # use thread_ast_engine::Language;
119/// # use thread_ast_engine::tree_sitter::LanguageExt;
120/// # struct Tsx;
121/// # impl thread_ast_engine::Language for Tsx {
122/// # fn kind_to_id(&self, _: &str) -> u16 { 0 }
123/// # fn field_to_id(&self, _: &str) -> Option<u16> { None }
124/// # fn build_pattern(&self, _: &thread_ast_engine::PatternBuilder) -> Result<thread_ast_engine::Pattern, thread_ast_engine::PatternError> { todo!() }
125/// # }
126/// # impl LanguageExt for Tsx {
127/// # fn get_ts_language(&self) -> thread_ast_engine::tree_sitter::TSLanguage { todo!() }
128/// # }
129/// let ast = Tsx.ast_grep("let x = foo(); let y = bar();");
130/// let root = ast.root();
131///
132/// // Find all identifiers, visiting only named nodes
133/// let identifiers: Vec<_> = Visitor::new("$ID")
134/// .named_only(true)
135/// .reentrant(false)
136/// .visit(root)
137/// .collect();
138/// ```
139pub struct Visitor<M, A = PreOrder> {
140 /// Whether nested matches should be reported (true) or skipped (false)
141 reentrant: bool,
142 /// Whether to visit only named AST nodes, skipping anonymous syntax tokens
143 named_only: bool,
144 /// Pattern matcher used to filter nodes during traversal
145 matcher: M,
146 /// Traversal algorithm type marker (pre-order, post-order, level-order)
147 algorithm: PhantomData<A>,
148}
149
150impl<M> Visitor<M> {
151 pub const fn new(matcher: M) -> Self {
152 Self {
153 reentrant: true,
154 named_only: false,
155 matcher,
156 algorithm: PhantomData,
157 }
158 }
159}
160
161impl<M, A> Visitor<M, A> {
162 pub fn algorithm<Algo>(self) -> Visitor<M, Algo> {
163 Visitor {
164 reentrant: self.reentrant,
165 named_only: self.named_only,
166 matcher: self.matcher,
167 algorithm: PhantomData,
168 }
169 }
170
171 #[must_use]
172 pub fn reentrant(self, reentrant: bool) -> Self {
173 Self { reentrant, ..self }
174 }
175
176 #[must_use]
177 pub fn named_only(self, named_only: bool) -> Self {
178 Self { named_only, ..self }
179 }
180}
181
182impl<M, A> Visitor<M, A>
183where
184 A: Algorithm,
185{
186 pub fn visit<L: LanguageExt>(
187 self,
188 node: Node<'_, StrDoc<L>>,
189 ) -> Visit<'_, StrDoc<L>, A::Traversal<'_, L>, M>
190 where
191 M: Matcher,
192 {
193 let traversal = A::traverse(node);
194 Visit {
195 reentrant: self.reentrant,
196 named: self.named_only,
197 matcher: self.matcher,
198 traversal,
199 lang: PhantomData,
200 }
201 }
202}
203
204#[cfg_attr(not(feature = "matching"), allow(dead_code))]
205pub struct Visit<'t, D, T, M> {
206 reentrant: bool,
207 named: bool,
208 matcher: M,
209 traversal: T,
210 lang: PhantomData<&'t D>,
211}
212impl<'t, D, T, M> Visit<'t, D, T, M>
213where
214 D: Doc + 't,
215 T: Traversal<'t, D>,
216 M: Matcher,
217{
218 #[cfg_attr(feature = "matching", inline)]
219 #[cfg_attr(not(feature = "matching"), allow(dead_code))]
220 fn mark_match(&mut self, depth: Option<usize>) {
221 if !self.reentrant {
222 self.traversal.calibrate_for_match(depth);
223 }
224 }
225}
226
227#[cfg(feature = "matching")]
228impl<'t, D, T, M> Iterator for Visit<'t, D, T, M>
229where
230 D: Doc + 't,
231 T: Traversal<'t, D>,
232 M: Matcher + MatcherExt,
233{
234 type Item = NodeMatch<'t, D>;
235 fn next(&mut self) -> Option<Self::Item> {
236 loop {
237 let match_depth = self.traversal.get_current_depth();
238 let node = self.traversal.next()?;
239 let pass_named = !self.named || node.is_named();
240 if let Some(node_match) = pass_named.then(|| self.matcher.match_node(node)).flatten() {
241 self.mark_match(Some(match_depth));
242 return Some(node_match);
243 }
244 self.mark_match(None);
245 }
246 }
247}
248
249/// Trait for tree traversal algorithms.
250///
251/// Defines how to create traversal iterators for different algorithms
252/// (pre-order, post-order, level-order). Each algorithm has its own
253/// traversal implementation with specific visiting order and performance
254/// characteristics.
255pub trait Algorithm {
256 /// The specific traversal iterator type for this algorithm
257 type Traversal<'t, L: LanguageExt>: Traversal<'t, StrDoc<L>>;
258
259 /// Create a traversal iterator starting from the given node
260 fn traverse<L: LanguageExt>(node: Node<'_, StrDoc<L>>) -> Self::Traversal<'_, L>;
261}
262
263/// Pre-order traversal algorithm.
264///
265/// Visits parent nodes before their children. Useful for top-down processing
266/// where you need to process a node before its descendants.
267///
268/// **Visit order**: Root → Left subtree → Right subtree
269pub struct PreOrder;
270
271impl Algorithm for PreOrder {
272 type Traversal<'t, L: LanguageExt> = Pre<'t, L>;
273 fn traverse<L: LanguageExt>(node: Node<'_, StrDoc<L>>) -> Self::Traversal<'_, L> {
274 Pre::new(&node)
275 }
276}
277
278/// Post-order traversal algorithm.
279///
280/// Visits children before their parent. Useful for bottom-up processing
281/// where you need to process all descendants before the node itself.
282///
283/// **Visit order**: Left subtree → Right subtree → Root
284pub struct PostOrder;
285
286impl Algorithm for PostOrder {
287 type Traversal<'t, L: LanguageExt> = Post<'t, L>;
288 fn traverse<L: LanguageExt>(node: Node<'_, StrDoc<L>>) -> Self::Traversal<'_, L> {
289 Post::new(&node)
290 }
291}
292
293/// Core trait for tree traversal implementations.
294///
295/// Provides the interface for iterating over AST nodes using different algorithms.
296/// Supports both reentrant and non-reentrant traversal through calibration methods.
297///
298/// # Reentrancy Control
299///
300/// The traversal can be configured to skip nested matches when a pattern is found.
301/// The `calibrate_for_match` method is called to adjust the cursor position when
302/// matches are found, allowing non-reentrant behavior.
303///
304/// # Implementation Notes
305///
306/// - The `next` method handles normal iteration over all nodes
307/// - Depth tracking enables proper calibration for non-reentrant traversal
308/// - Root node is at depth 0, children are at depth 1, etc.
309pub trait Traversal<'t, D: Doc + 't>: Iterator<Item = Node<'t, D>> {
310 /// Adjust cursor position to implement non-reentrant matching behavior.
311 ///
312 /// Called by [`Visit`] to skip overlapping matches when reentrancy is disabled.
313 /// If a node matches a pattern, this method can skip its descendants to avoid
314 /// finding nested matches.
315 ///
316 /// # Parameters
317 ///
318 /// - `depth` - Depth of the matched node if a match was found, `None` otherwise
319 fn calibrate_for_match(&mut self, depth: Option<usize>);
320
321 /// Get the current depth in the tree traversal.
322 ///
323 /// Depth increases by 1 when moving from parent to child nodes.
324 /// The root node has depth 0.
325 ///
326 /// # Returns
327 ///
328 /// Current traversal depth (0 = root, 1 = root's children, etc.)
329 fn get_current_depth(&self) -> usize;
330}
331
332/// Represents a pre-order traversal
333pub struct TsPre<'tree> {
334 cursor: ts::TreeCursor<'tree>,
335 // record the starting node, if we return back to starting point
336 // we should terminate the dfs.
337 start_id: Option<usize>,
338 current_depth: usize,
339}
340
341impl<'tree> TsPre<'tree> {
342 #[must_use]
343 pub fn new(node: &ts::Node<'tree>) -> Self {
344 Self {
345 cursor: node.walk(),
346 start_id: Some(node.id()),
347 current_depth: 0,
348 }
349 }
350 fn step_down(&mut self) -> bool {
351 if self.cursor.goto_first_child() {
352 self.current_depth += 1;
353 true
354 } else {
355 false
356 }
357 }
358
359 // retrace back to ancestors and find next node to explore
360 fn trace_up(&mut self, start: usize) {
361 let cursor = &mut self.cursor;
362 while cursor.node().id() != start {
363 // try visit sibling nodes
364 if cursor.goto_next_sibling() {
365 return;
366 }
367 self.current_depth -= 1;
368 // go back to parent node
369 if !cursor.goto_parent() {
370 // it should never fail here. However, tree-sitter has bad parsing bugs
371 // stop to avoid panic. https://github.com/ast-grep/ast-grep/issues/713
372 break;
373 }
374 }
375 // terminate traversal here
376 self.start_id = None;
377 }
378}
379
380/// Amortized time complexity is O(NlgN), depending on branching factor.
381impl<'tree> Iterator for TsPre<'tree> {
382 type Item = ts::Node<'tree>;
383 // 1. Yield the node itself
384 // 2. Try visit the child node until no child available
385 // 3. Try visit next sibling after going back to parent
386 // 4. Repeat step 3 until returning to the starting node
387 fn next(&mut self) -> Option<Self::Item> {
388 // start_id will always be Some until the dfs terminates
389 let start = self.start_id?;
390 let cursor = &mut self.cursor;
391 let inner = cursor.node(); // get current node
392 let ret = Some(inner);
393 // try going to children first
394 if self.step_down() {
395 return ret;
396 }
397 // if no child available, go to ancestor nodes
398 // until we get to the starting point
399 self.trace_up(start);
400 ret
401 }
402}
403
404pub struct Pre<'tree, L: LanguageExt> {
405 root: &'tree Root<StrDoc<L>>,
406 inner: TsPre<'tree>,
407}
408impl<'tree, L: LanguageExt> Iterator for Pre<'tree, L> {
409 type Item = Node<'tree, StrDoc<L>>;
410 fn next(&mut self) -> Option<Self::Item> {
411 let inner = self.inner.next()?;
412 Some(self.root.adopt(inner))
413 }
414}
415
416impl<'t, L: LanguageExt> Pre<'t, L> {
417 #[must_use]
418 pub fn new(node: &Node<'t, StrDoc<L>>) -> Self {
419 let inner = TsPre::new(&node.inner);
420 Self {
421 root: node.root,
422 inner,
423 }
424 }
425}
426
427impl<'t, L: LanguageExt> Traversal<'t, StrDoc<L>> for Pre<'t, L> {
428 fn calibrate_for_match(&mut self, depth: Option<usize>) {
429 // not entering the node, ignore
430 let Some(depth) = depth else {
431 return;
432 };
433 // if already entering sibling or traced up, ignore
434 if self.inner.current_depth <= depth {
435 return;
436 }
437 debug_assert!(self.inner.current_depth > depth);
438 if let Some(start) = self.inner.start_id {
439 // revert the step down
440 self.inner.cursor.goto_parent();
441 self.inner.trace_up(start);
442 }
443 }
444
445 #[inline]
446 fn get_current_depth(&self) -> usize {
447 self.inner.current_depth
448 }
449}
450
451/// Represents a post-order traversal
452pub struct Post<'tree, L: LanguageExt> {
453 cursor: ts::TreeCursor<'tree>,
454 root: &'tree Root<StrDoc<L>>,
455 start_id: Option<usize>,
456 current_depth: usize,
457 match_depth: usize,
458}
459
460/// Amortized time complexity is O(NlgN), depending on branching factor.
461impl<'tree, L: LanguageExt> Post<'tree, L> {
462 #[must_use]
463 pub fn new(node: &Node<'tree, StrDoc<L>>) -> Self {
464 let mut ret = Self {
465 cursor: node.inner.walk(),
466 root: node.root,
467 start_id: Some(node.inner.id()),
468 current_depth: 0,
469 match_depth: 0,
470 };
471 ret.trace_down();
472 ret
473 }
474 fn trace_down(&mut self) {
475 while self.cursor.goto_first_child() {
476 self.current_depth += 1;
477 }
478 }
479 fn step_up(&mut self) {
480 self.current_depth -= 1;
481 self.cursor.goto_parent();
482 }
483}
484
485/// Amortized time complexity is O(NlgN), depending on branching factor.
486impl<'tree, L: LanguageExt> Iterator for Post<'tree, L> {
487 type Item = Node<'tree, StrDoc<L>>;
488 fn next(&mut self) -> Option<Self::Item> {
489 // start_id will always be Some until the dfs terminates
490 let start = self.start_id?;
491 let cursor = &mut self.cursor;
492 let node = self.root.adopt(cursor.node());
493 // return to start
494 if node.inner.id() == start {
495 self.start_id = None;
496 } else if cursor.goto_next_sibling() {
497 // try visit sibling
498 self.trace_down();
499 } else {
500 self.step_up();
501 }
502 Some(node)
503 }
504}
505
506impl<'t, L: LanguageExt> Traversal<'t, StrDoc<L>> for Post<'t, L> {
507 fn calibrate_for_match(&mut self, depth: Option<usize>) {
508 if let Some(depth) = depth {
509 // Later matches' depth should always be greater than former matches.
510 // because we bump match_depth in `step_up` during traversal.
511 debug_assert!(depth >= self.match_depth);
512 self.match_depth = depth;
513 return;
514 }
515 // found new nodes to explore in trace_down, skip calibration.
516 if self.current_depth >= self.match_depth {
517 return;
518 }
519 let Some(start) = self.start_id else {
520 return;
521 };
522 while self.cursor.node().id() != start {
523 self.match_depth = self.current_depth;
524 if self.cursor.goto_next_sibling() {
525 // try visit sibling
526 self.trace_down();
527 return;
528 }
529 self.step_up();
530 }
531 // terminate because all ancestors are skipped
532 self.start_id = None;
533 }
534
535 #[inline]
536 fn get_current_depth(&self) -> usize {
537 self.current_depth
538 }
539}
540
541/// Represents a level-order traversal.
542///
543/// It is implemented with [`VecDeque`] since quadratic backtracking is too time consuming.
544/// Though level-order is not used as frequently as other DFS traversals,
545/// traversing a big AST with level-order should be done with caution since it might increase the memory usage.
546pub struct Level<'tree, L: LanguageExt> {
547 deque: VecDeque<ts::Node<'tree>>,
548 cursor: ts::TreeCursor<'tree>,
549 root: &'tree Root<StrDoc<L>>,
550}
551
552impl<'tree, L: LanguageExt> Level<'tree, L> {
553 #[must_use]
554 pub fn new(node: &Node<'tree, StrDoc<L>>) -> Self {
555 let mut deque = VecDeque::new();
556 deque.push_back(node.inner);
557 let cursor = node.inner.walk();
558 Self {
559 deque,
560 cursor,
561 root: node.root,
562 }
563 }
564}
565
566/// Time complexity is O(N). Space complexity is O(N)
567impl<'tree, L: LanguageExt> Iterator for Level<'tree, L> {
568 type Item = Node<'tree, StrDoc<L>>;
569 fn next(&mut self) -> Option<Self::Item> {
570 let inner = self.deque.pop_front()?;
571 let children = inner.children(&mut self.cursor);
572 self.deque.extend(children);
573 Some(self.root.adopt(inner))
574 }
575}
576
577#[cfg(test)]
578mod test {
579 use super::*;
580 use crate::language::Tsx;
581 use std::ops::Range;
582
583 // recursive pre order as baseline
584 fn pre_order(node: &Node<StrDoc<Tsx>>) -> Vec<Range<usize>> {
585 let mut ret = vec![node.range()];
586 ret.extend(node.children().flat_map(|n| pre_order(&n)));
587 ret
588 }
589
590 // recursion baseline
591 fn post_order(node: &Node<StrDoc<Tsx>>) -> Vec<Range<usize>> {
592 let mut ret: Vec<_> = node.children().flat_map(|n| post_order(&n)).collect();
593 ret.push(node.range());
594 ret
595 }
596
597 fn pre_order_equivalent(source: &str) {
598 let grep = Tsx.ast_grep(source);
599 let node = grep.root();
600 let iterative: Vec<_> = Pre::new(&node).map(|n| n.range()).collect();
601 let recursive = pre_order(&node);
602 assert_eq!(iterative, recursive);
603 }
604
605 fn post_order_equivalent(source: &str) {
606 let grep = Tsx.ast_grep(source);
607 let node = grep.root();
608 let iterative: Vec<_> = Post::new(&node).map(|n| n.range()).collect();
609 let recursive = post_order(&node);
610 assert_eq!(iterative, recursive);
611 }
612
613 const CASES: &[&str] = &[
614 "console.log('hello world')",
615 "let a = (a, b, c)",
616 "function test() { let a = 1; let b = 2; a === b}",
617 "[[[[[[]]]]], 1 , 2 ,3]",
618 "class A { test() { class B {} } }",
619 ];
620
621 #[test]
622 fn tes_pre_order() {
623 for case in CASES {
624 pre_order_equivalent(case);
625 }
626 }
627
628 #[test]
629 fn test_post_order() {
630 for case in CASES {
631 post_order_equivalent(case);
632 }
633 }
634
635 #[test]
636 fn test_different_order() {
637 for case in CASES {
638 let grep = Tsx.ast_grep(case);
639 let node = grep.root();
640 let pre: Vec<_> = Pre::new(&node).map(|n| n.range()).collect();
641 let post: Vec<_> = Post::new(&node).map(|n| n.range()).collect();
642 let level: Vec<_> = Level::new(&node).map(|n| n.range()).collect();
643 assert_ne!(pre, post);
644 assert_ne!(pre, level);
645 assert_ne!(post, level);
646 }
647 }
648
649 #[test]
650 fn test_fused_traversal() {
651 for case in CASES {
652 let grep = Tsx.ast_grep(case);
653 let node = grep.root();
654 let mut pre = Pre::new(&node);
655 let mut post = Post::new(&node);
656 while pre.next().is_some() {}
657 while post.next().is_some() {}
658 assert!(pre.next().is_none());
659 assert!(pre.next().is_none());
660 assert!(post.next().is_none());
661 assert!(post.next().is_none());
662 }
663 }
664
665 #[test]
666 fn test_non_root_traverse() {
667 let grep = Tsx.ast_grep("let a = 123; let b = 123;");
668 let node = grep.root();
669 let pre: Vec<_> = Pre::new(&node).map(|n| n.range()).collect();
670 let post: Vec<_> = Post::new(&node).map(|n| n.range()).collect();
671 let node2 = node.child(0).unwrap();
672 let pre2: Vec<_> = Pre::new(&node2).map(|n| n.range()).collect();
673 let post2: Vec<_> = Post::new(&node2).map(|n| n.range()).collect();
674 // traversal should stop at node
675 assert_ne!(pre, pre2);
676 assert_ne!(post, post2);
677 // child traversal should be a part of parent traversal
678 assert!(pre[1..].starts_with(&pre2));
679 assert!(post.starts_with(&post2));
680 }
681
682 fn pre_order_with_matcher(node: &Node<StrDoc<Tsx>>, matcher: &str) -> Vec<Range<usize>> {
683 if node.matches(matcher) {
684 vec![node.range()]
685 } else {
686 node.children()
687 .flat_map(|n| pre_order_with_matcher(&n, matcher))
688 .collect()
689 }
690 }
691
692 fn post_order_with_matcher(node: &Node<StrDoc<Tsx>>, matcher: &str) -> Vec<Range<usize>> {
693 let mut ret: Vec<_> = node
694 .children()
695 .flat_map(|n| post_order_with_matcher(&n, matcher))
696 .collect();
697 if ret.is_empty() && node.matches(matcher) {
698 ret.push(node.range());
699 }
700 ret
701 }
702
703 const MATCHER_CASES: &[&str] = &[
704 "Some(123)",
705 "Some(1, 2, Some(2))",
706 "NoMatch",
707 "NoMatch(Some(123))",
708 "Some(1, Some(2), Some(3))",
709 "Some(1, Some(2), Some(Some(3)))",
710 ];
711
712 #[test]
713 fn test_pre_order_visitor() {
714 let matcher = "Some($$$)";
715 for case in MATCHER_CASES {
716 let grep = Tsx.ast_grep(case);
717 let node = grep.root();
718 let recur = pre_order_with_matcher(&node, matcher);
719 let visit: Vec<_> = Visitor::new(matcher)
720 .reentrant(false)
721 .visit(node)
722 .map(|n| n.range())
723 .collect();
724 assert_eq!(recur, visit);
725 }
726 }
727 #[test]
728 fn test_post_order_visitor() {
729 let matcher = "Some($$$)";
730 for case in MATCHER_CASES {
731 let grep = Tsx.ast_grep(case);
732 let node = grep.root();
733 let recur = post_order_with_matcher(&node, matcher);
734 let visit: Vec<_> = Visitor::new(matcher)
735 .algorithm::<PostOrder>()
736 .reentrant(false)
737 .visit(node)
738 .map(|n| n.range())
739 .collect();
740 assert_eq!(recur, visit);
741 }
742 }
743
744 // match a leaf node will trace_up the cursor
745 #[test]
746 fn test_traversal_leaf() {
747 let matcher = "true";
748 let case = "((((true))));true";
749 let grep = Tsx.ast_grep(case);
750 let recur = pre_order_with_matcher(&grep.root(), matcher);
751 let visit: Vec<_> = Visitor::new(matcher)
752 .reentrant(false)
753 .visit(grep.root())
754 .map(|n| n.range())
755 .collect();
756 assert_eq!(recur, visit);
757 let recur = post_order_with_matcher(&grep.root(), matcher);
758 let visit: Vec<_> = Visitor::new(matcher)
759 .algorithm::<PostOrder>()
760 .reentrant(false)
761 .visit(grep.root())
762 .map(|n| n.range())
763 .collect();
764 assert_eq!(recur, visit);
765 }
766}