From 07a3eac6ba4c5bfb0097caf225701b342d85c53c Mon Sep 17 00:00:00 2001 From: Taishi Naka Date: Thu, 23 Oct 2025 20:11:57 +0900 Subject: [PATCH 1/4] feat: add new api (Node::children and Range::extended_by) --- .../postgresql-cst-parser/src/tree_sitter.rs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/crates/postgresql-cst-parser/src/tree_sitter.rs b/crates/postgresql-cst-parser/src/tree_sitter.rs index 85adab5..acf5570 100644 --- a/crates/postgresql-cst-parser/src/tree_sitter.rs +++ b/crates/postgresql-cst-parser/src/tree_sitter.rs @@ -98,6 +98,24 @@ impl std::fmt::Display for Range { } } +impl Range { + pub fn extended_by(&self, other: &Self) -> Self { + Range { + start_byte: self.start_byte.min(other.start_byte), + end_byte: self.end_byte.max(other.end_byte), + + start_position: Point { + row: self.start_position.row.min(other.start_position.row), + column: self.start_position.column.min(other.start_position.column), + }, + end_position: Point { + row: self.end_position.row.max(other.end_position.row), + column: self.end_position.column.max(other.end_position.column), + }, + } + } +} + impl<'a> Node<'a> { pub fn walk(&self) -> TreeCursor<'a> { TreeCursor { @@ -144,6 +162,20 @@ impl<'a> Node<'a> { } } + pub fn children(&self) -> Vec> { + if let Some(node) = self.node_or_token.as_node() { + node.children_with_tokens() + .map(|node| Node { + input: self.input, + range_map: Rc::clone(&self.range_map), + node_or_token: node, + }) + .collect() + } else { + vec![] + } + } + pub fn next_sibling(&self) -> Option> { self.node_or_token .next_sibling_or_token() From 47aa0d7121be2d194a0860dcf9f9d0c7f392151a Mon Sep 17 00:00:00 2001 From: Taishi Naka Date: Fri, 31 Oct 2025 17:37:37 +0900 Subject: [PATCH 2/4] feat(tree-sitter module): add previous sibling apis --- .../postgresql-cst-parser/src/tree_sitter.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/crates/postgresql-cst-parser/src/tree_sitter.rs b/crates/postgresql-cst-parser/src/tree_sitter.rs index acf5570..62b2af4 100644 --- a/crates/postgresql-cst-parser/src/tree_sitter.rs +++ b/crates/postgresql-cst-parser/src/tree_sitter.rs @@ -186,6 +186,16 @@ impl<'a> Node<'a> { }) } + pub fn prev_sibling(&self) -> Option> { + self.node_or_token + .prev_sibling_or_token() + .map(|sibling| Node { + input: self.input, + range_map: Rc::clone(&self.range_map), + node_or_token: sibling, + }) + } + pub fn parent(&self) -> Option> { self.node_or_token.parent().map(|parent| Node { input: self.input, @@ -246,6 +256,15 @@ impl<'a> TreeCursor<'a> { } } + pub fn goto_prev_sibling(&mut self) -> bool { + if let Some(sibling) = self.node_or_token.prev_sibling_or_token() { + self.node_or_token = sibling; + true + } else { + false + } + } + pub fn is_comment(&self) -> bool { matches!( self.node_or_token.kind(), From 7a436e80ac598f3f538742da50d09d7a5589e82e Mon Sep 17 00:00:00 2001 From: Taishi Naka Date: Tue, 4 Nov 2025 18:38:55 +0900 Subject: [PATCH 3/4] feat: add Node::last_token, Node::descendants --- .../postgresql-cst-parser/src/tree_sitter.rs | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/crates/postgresql-cst-parser/src/tree_sitter.rs b/crates/postgresql-cst-parser/src/tree_sitter.rs index 62b2af4..1b87bf6 100644 --- a/crates/postgresql-cst-parser/src/tree_sitter.rs +++ b/crates/postgresql-cst-parser/src/tree_sitter.rs @@ -207,6 +207,59 @@ impl<'a> Node<'a> { pub fn is_comment(&self) -> bool { matches!(self.kind(), SyntaxKind::C_COMMENT | SyntaxKind::SQL_COMMENT) } + + /// Return the rightmost token in the subtree of this node + /// this is not tree-sitter's API + pub fn last_token(&self) -> Option> { + match &self.node_or_token { + NodeOrToken::Node(node) => node.last_token().map(|token| Node { + input: self.input, + range_map: Rc::clone(&self.range_map), + node_or_token: NodeOrToken::Token(token), + }), + NodeOrToken::Token(token) => Some(Node { + input: self.input, + range_map: Rc::clone(&self.range_map), + node_or_token: NodeOrToken::Token(token), + }), + } + } + + /// Returns an iterator over all descendant nodes (not including tokens) + /// this is not tree-sitter's API + pub fn descendants(&self) -> impl Iterator> { + struct Descendants<'a> { + input: &'a str, + range_map: Rc>, + iter: Box + 'a>, + } + + impl<'a> Iterator for Descendants<'a> { + type Item = Node<'a>; + + fn next(&mut self) -> Option { + self.iter.next().map(|node| Node { + input: self.input, + range_map: Rc::clone(&self.range_map), + node_or_token: NodeOrToken::Node(node), + }) + } + } + + if let Some(node) = self.node_or_token.as_node() { + Descendants { + input: self.input, + range_map: Rc::clone(&self.range_map), + iter: Box::new(node.descendants()), + } + } else { + Descendants { + input: self.input, + range_map: Rc::clone(&self.range_map), + iter: Box::new(std::iter::empty()), + } + } + } } impl<'a> From> for TreeCursor<'a> { @@ -513,4 +566,36 @@ from assert_eq!(stmt_count, 2); } + + #[test] + fn test_last_token_returns_rightmost_token() { + let src = "SELECT u.*, (v).id, name;"; + let tree = parse(src).unwrap(); + let root = tree.root_node(); + + let target_list = root + .descendants() + .find(|node| node.kind() == SyntaxKind::target_list) + .expect("should find target_list"); + + // last token of the target_list is returned + let last_token = target_list.last_token().expect("should have last token"); + assert_eq!(last_token.text(), "name"); + + let target_els = target_list + .children() + .into_iter() + .filter(|node| node.kind() == SyntaxKind::target_el) + .collect::>(); + + let mut last_tokens = target_els + .iter() + .map(|node| node.last_token().expect("should have last token")); + + // last token of each target_el is returned + assert_eq!(last_tokens.next().unwrap().text(), "*"); + assert_eq!(last_tokens.next().unwrap().text(), "id"); + assert_eq!(last_tokens.next().unwrap().text(), "name"); + assert!(last_tokens.next().is_none()); + } } From 4b36aef5c17b4ac1930a07fb05630642831f79be Mon Sep 17 00:00:00 2001 From: Taishi Naka Date: Tue, 4 Nov 2025 19:02:11 +0900 Subject: [PATCH 4/4] change: rename last_token to last_node --- .../postgresql-cst-parser/src/tree_sitter.rs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/postgresql-cst-parser/src/tree_sitter.rs b/crates/postgresql-cst-parser/src/tree_sitter.rs index 1b87bf6..94af1a3 100644 --- a/crates/postgresql-cst-parser/src/tree_sitter.rs +++ b/crates/postgresql-cst-parser/src/tree_sitter.rs @@ -210,7 +210,7 @@ impl<'a> Node<'a> { /// Return the rightmost token in the subtree of this node /// this is not tree-sitter's API - pub fn last_token(&self) -> Option> { + pub fn last_node(&self) -> Option> { match &self.node_or_token { NodeOrToken::Node(node) => node.last_token().map(|token| Node { input: self.input, @@ -568,7 +568,7 @@ from } #[test] - fn test_last_token_returns_rightmost_token() { + fn test_last_node_returns_rightmost_node() { let src = "SELECT u.*, (v).id, name;"; let tree = parse(src).unwrap(); let root = tree.root_node(); @@ -578,9 +578,9 @@ from .find(|node| node.kind() == SyntaxKind::target_list) .expect("should find target_list"); - // last token of the target_list is returned - let last_token = target_list.last_token().expect("should have last token"); - assert_eq!(last_token.text(), "name"); + // last node of the target_list is returned + let last_node = target_list.last_node().expect("should have last node"); + assert_eq!(last_node.text(), "name"); let target_els = target_list .children() @@ -588,14 +588,14 @@ from .filter(|node| node.kind() == SyntaxKind::target_el) .collect::>(); - let mut last_tokens = target_els + let mut last_nodes = target_els .iter() - .map(|node| node.last_token().expect("should have last token")); + .map(|node| node.last_node().expect("should have last node")); - // last token of each target_el is returned - assert_eq!(last_tokens.next().unwrap().text(), "*"); - assert_eq!(last_tokens.next().unwrap().text(), "id"); - assert_eq!(last_tokens.next().unwrap().text(), "name"); - assert!(last_tokens.next().is_none()); + // last node of each target_el is returned + assert_eq!(last_nodes.next().unwrap().text(), "*"); + assert_eq!(last_nodes.next().unwrap().text(), "id"); + assert_eq!(last_nodes.next().unwrap().text(), "name"); + assert!(last_nodes.next().is_none()); } }