Skip to main content

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}