From 1c6bc31d45bba2455cd6b802c0492d6f22b669bf Mon Sep 17 00:00:00 2001 From: Marlon Costa Date: Tue, 20 Jan 2026 13:31:21 -0300 Subject: [PATCH 1/3] feat(node): Add utility methods for AST traversal - Add has_ancestor() for checking if any ancestor matches a predicate - Add all_occurrences() to Search trait for finding all matching nodes --- src/node.rs | 44 ++++++++++++++++++++++++++++++++++++++++++++ src/traits.rs | 1 + 2 files changed, 45 insertions(+) diff --git a/src/node.rs b/src/node.rs index 49b8df104..914eadc50 100644 --- a/src/node.rs +++ b/src/node.rs @@ -183,6 +183,21 @@ impl<'a> Node<'a> { } res } + + /// Checks if this node has any ancestor that meets the given predicate. + /// + /// Traverses up the tree from this node's parent to the root, + /// returning true if any ancestor satisfies the predicate. + pub fn has_ancestor bool>(&self, pred: F) -> bool { + let mut node = *self; + while let Some(parent) = node.parent() { + if pred(&parent) { + return true; + } + node = parent; + } + false + } } /// An `AST` cursor. @@ -236,6 +251,35 @@ impl<'a> Search<'a> for Node<'a> { None } + fn all_occurrences(&self, pred: fn(u16) -> bool) -> Vec> { + let mut cursor = self.cursor(); + let mut stack = Vec::new(); + let mut children = Vec::new(); + let mut results = Vec::new(); + + stack.push(*self); + + while let Some(node) = stack.pop() { + if pred(node.kind_id()) { + results.push(node); + } + cursor.reset(&node); + if cursor.goto_first_child() { + loop { + children.push(cursor.node()); + if !cursor.goto_next_sibling() { + break; + } + } + for child in children.drain(..).rev() { + stack.push(child); + } + } + } + + results + } + fn act_on_node(&self, action: &mut dyn FnMut(&Node<'a>)) { let mut cursor = self.cursor(); let mut stack = Vec::new(); diff --git a/src/traits.rs b/src/traits.rs index 16d4ed9cb..d04692263 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -68,6 +68,7 @@ pub trait ParserTrait { pub(crate) trait Search<'a> { fn first_occurrence(&self, pred: fn(u16) -> bool) -> Option>; + fn all_occurrences(&self, pred: fn(u16) -> bool) -> Vec>; fn act_on_node(&self, pred: &mut dyn FnMut(&Node<'a>)); fn first_child(&self, pred: fn(u16) -> bool) -> Option>; fn act_on_child(&self, action: &mut dyn FnMut(&Node<'a>)); From 0b54dc57f273a77c04f113875760b59f66363e54 Mon Sep 17 00:00:00 2001 From: marlon-costa-dc <128386606+marlon-costa-dc@users.noreply.github.com> Date: Tue, 20 Jan 2026 14:23:49 -0300 Subject: [PATCH 2/3] Update src/node.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/node.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/node.rs b/src/node.rs index 914eadc50..7030ed80c 100644 --- a/src/node.rs +++ b/src/node.rs @@ -251,6 +251,8 @@ impl<'a> Search<'a> for Node<'a> { None } + /// Performs a depth-first search starting from this node (including `self`) + /// and returns all descendant nodes whose `kind_id` satisfies the given predicate. fn all_occurrences(&self, pred: fn(u16) -> bool) -> Vec> { let mut cursor = self.cursor(); let mut stack = Vec::new(); From e4c1f4444ffa2733de7367fbde7e43b3836c7927 Mon Sep 17 00:00:00 2001 From: marlon-costa-dc <128386606+marlon-costa-dc@users.noreply.github.com> Date: Tue, 20 Jan 2026 14:23:57 -0300 Subject: [PATCH 3/3] Update src/node.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/node.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/node.rs b/src/node.rs index 7030ed80c..f6a014706 100644 --- a/src/node.rs +++ b/src/node.rs @@ -186,8 +186,14 @@ impl<'a> Node<'a> { /// Checks if this node has any ancestor that meets the given predicate. /// - /// Traverses up the tree from this node's parent to the root, - /// returning true if any ancestor satisfies the predicate. + /// This method walks up the tree from this node's parent to the root, + /// returning `true` as soon as it finds any ancestor for which `pred` + /// returns `true`. + /// + /// Note: This differs from [`Node::has_ancestors`], which checks for a + /// specific pattern of ancestors (immediate parent and grandparent + /// satisfying two separate predicates). `has_ancestor` instead uses a + /// single predicate and considers all ancestors in the hierarchy. pub fn has_ancestor bool>(&self, pred: F) -> bool { let mut node = *self; while let Some(parent) = node.parent() {