1use tree_sitter::Node;
16
17pub const MAX_TREE_DEPTH: usize = 512;
22
23#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
25pub struct WalkStats {
26 pub visited: usize,
28 pub skipped_subtrees: usize,
30}
31
32impl WalkStats {
33 pub fn truncated(&self) -> bool {
36 self.skipped_subtrees > 0
37 }
38}
39
40pub fn walk<'t, F>(root: Node<'t>, visit: &mut F) -> WalkStats
49where
50 F: FnMut(Node<'t>),
51{
52 let mut stats = WalkStats::default();
53 let mut cursor = root.walk();
54 let mut depth = 0usize;
55
56 loop {
57 visit(cursor.node());
58 stats.visited += 1;
59
60 if depth < MAX_TREE_DEPTH {
61 if cursor.goto_first_child() {
62 depth += 1;
63 continue;
64 }
65 } else if cursor.node().child_count() > 0 {
66 stats.skipped_subtrees += 1;
67 }
68
69 loop {
72 if depth == 0 {
73 return stats;
74 }
75 if cursor.goto_next_sibling() {
76 break;
77 }
78 cursor.goto_parent();
79 depth -= 1;
80 }
81 }
82}
83
84pub fn node_text<'a>(node: Node<'_>, source: &'a [u8]) -> Option<&'a str> {
86 node.utf8_text(source).ok()
87}
88
89pub fn field_text<'a>(node: Node<'_>, field: &str, source: &'a [u8]) -> Option<&'a str> {
91 node_text(node.child_by_field_name(field)?, source)
92}
93
94pub fn start_line(node: Node<'_>) -> u32 {
96 node.start_position().row as u32 + 1
97}
98
99pub fn end_line(node: Node<'_>) -> u32 {
101 node.end_position().row as u32 + 1
102}
103
104pub fn first_line_of(node: Node<'_>, source: &[u8]) -> Option<String> {
109 let text = node_text(node, source)?;
110 Some(text.lines().next().unwrap_or("").trim_end().to_string())
111}
112
113pub fn has_parse_error(root: Node<'_>) -> bool {
119 root.has_error()
122}
123
124pub fn first_error_line(root: Node<'_>) -> Option<u32> {
127 if !root.has_error() {
128 return None;
129 }
130 let mut line = None;
131 walk(root, &mut |node| {
132 if line.is_none() && (node.is_error() || node.is_missing()) {
133 line = Some(start_line(node));
134 }
135 });
136 line
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 fn parse_rust(source: &str) -> tree_sitter::Tree {
144 let mut parser = tree_sitter::Parser::new();
145 parser
146 .set_language(&tree_sitter_rust::language())
147 .expect("rust grammar loads");
148 parser.parse(source, None).expect("parser returns a tree")
149 }
150
151 #[test]
152 fn walk_visits_in_source_order() {
153 let tree = parse_rust("struct A; struct B; struct C;");
154 let src = "struct A; struct B; struct C;".as_bytes();
155 let mut names = Vec::new();
156 walk(tree.root_node(), &mut |node| {
157 if node.kind() == "type_identifier" {
158 if let Some(t) = node_text(node, src) {
159 names.push(t.to_string());
160 }
161 }
162 });
163 assert_eq!(names, vec!["A", "B", "C"], "sibling order must be preserved");
164 }
165
166 #[test]
167 fn walk_covers_the_whole_subtree() {
168 let tree = parse_rust("fn f() { let x = Foo { a: 1 }; }");
169 let mut count = 0usize;
170 let stats = walk(tree.root_node(), &mut |_| count += 1);
171 assert_eq!(stats.visited, count);
172 assert!(count > 10, "a non-trivial function has many nodes");
173 assert!(!stats.truncated());
174 }
175
176 #[test]
177 fn walk_bounds_pathological_nesting_instead_of_overflowing() {
178 let depth = MAX_TREE_DEPTH * 4;
181 let source = format!("fn f() {{ let x = {}1{}; }}", "(".repeat(depth), ")".repeat(depth));
182 let tree = parse_rust(&source);
183 let stats = walk(tree.root_node(), &mut |_| {});
184 assert!(
185 stats.truncated(),
186 "input nests deeper than the limit, so truncation must be reported"
187 );
188 assert!(stats.visited >= MAX_TREE_DEPTH);
189 }
190
191 #[test]
192 fn walk_stays_inside_the_requested_subtree() {
193 let source = "struct A; struct B;";
194 let tree = parse_rust(source);
195 let first = tree.root_node().child(0).expect("first item exists");
196
197 let mut seen = Vec::new();
198 walk(first, &mut |node| {
199 if node.kind() == "type_identifier" {
200 if let Some(t) = node_text(node, source.as_bytes()) {
201 seen.push(t.to_string());
202 }
203 }
204 });
205 assert_eq!(seen, vec!["A"], "walking one item must not reach its sibling");
206 }
207
208 #[test]
209 fn parse_errors_are_detected_and_located() {
210 let tree = parse_rust("fn broken( {");
211 assert!(has_parse_error(tree.root_node()));
212 assert!(first_error_line(tree.root_node()).is_some());
213
214 let clean = parse_rust("fn ok() {}");
215 assert!(!has_parse_error(clean.root_node()));
216 assert_eq!(first_error_line(clean.root_node()), None);
217 }
218}