1use std::collections::hash_map::DefaultHasher;
2use std::hash::{Hash, Hasher};
3
4use cha_core::{ClassInfo, FunctionInfo, ImportInfo, SourceFile, SourceModel};
5use tree_sitter::{Node, Parser};
6
7use crate::LanguageParser;
8
9pub struct PythonParser;
10
11impl LanguageParser for PythonParser {
12 fn language_name(&self) -> &str {
13 "python"
14 }
15
16 fn parse(&self, file: &SourceFile) -> Option<SourceModel> {
17 let mut parser = Parser::new();
18 parser
19 .set_language(&tree_sitter_python::LANGUAGE.into())
20 .ok()?;
21 let tree = parser.parse(&file.content, None)?;
22 let root = tree.root_node();
23 let src = file.content.as_bytes();
24
25 let mut functions = Vec::new();
26 let mut classes = Vec::new();
27 let mut imports = Vec::new();
28 let mut type_aliases = Vec::new();
29
30 let imports_map = crate::python_imports::build(root, src);
31 collect_top_level(
32 root,
33 src,
34 &imports_map,
35 &mut functions,
36 &mut classes,
37 &mut imports,
38 &mut type_aliases,
39 );
40
41 Some(SourceModel {
42 language: "python".into(),
43 total_lines: file.line_count(),
44 functions,
45 classes,
46 imports,
47 comments: collect_comments(root, src),
48 type_aliases,
49 })
50 }
51}
52
53fn push_definition(
54 node: Node,
55 src: &[u8],
56 imports_map: &crate::type_ref::ImportsMap,
57 functions: &mut Vec<FunctionInfo>,
58 classes: &mut Vec<ClassInfo>,
59) {
60 match node.kind() {
61 "function_definition" => {
62 if let Some(f) = extract_function(node, src, imports_map) {
63 functions.push(f);
64 }
65 }
66 "class_definition" => {
67 if let Some(c) = extract_class(node, src, imports_map, functions) {
68 classes.push(c);
69 }
70 }
71 _ => {}
72 }
73}
74
75fn collect_top_level(
76 node: Node,
77 src: &[u8],
78 imports_map: &crate::type_ref::ImportsMap,
79 functions: &mut Vec<FunctionInfo>,
80 classes: &mut Vec<ClassInfo>,
81 imports: &mut Vec<ImportInfo>,
82 type_aliases: &mut Vec<(String, String)>,
83) {
84 let mut cursor = node.walk();
85 for child in node.children(&mut cursor) {
86 match child.kind() {
87 "function_definition" | "class_definition" => {
88 push_definition(child, src, imports_map, functions, classes);
89 }
90 "import_statement" => collect_import(child, src, imports),
91 "import_from_statement" => collect_import_from(child, src, imports),
92 "type_alias_statement" => collect_type_alias_statement(child, src, type_aliases),
93 "expression_statement" => collect_typed_alias_assignment(child, src, type_aliases),
94 "decorated_definition" => {
95 let mut inner = child.walk();
96 for c in child.children(&mut inner) {
97 push_definition(c, src, imports_map, functions, classes);
98 }
99 }
100 _ => {}
101 }
102 }
103}
104
105fn collect_type_alias_statement(node: Node, src: &[u8], out: &mut Vec<(String, String)>) {
106 if let Some(pair) = crate::type_aliases::python_statement(node, src) {
107 out.push(pair);
108 }
109}
110
111fn collect_typed_alias_assignment(node: Node, src: &[u8], out: &mut Vec<(String, String)>) {
112 if let Some(pair) = crate::type_aliases::python_assignment(node, src) {
113 out.push(pair);
114 }
115}
116
117fn extract_function(
118 node: Node,
119 src: &[u8],
120 imports_map: &crate::type_ref::ImportsMap,
121) -> Option<FunctionInfo> {
122 let name_node = node.child_by_field_name("name")?;
123 let name = node_text(name_node, src).to_string();
124 let name_col = name_node.start_position().column;
125 let name_end_col = name_node.end_position().column;
126 let start_line = node.start_position().row + 1;
127 let end_line = node.end_position().row + 1;
128 let body = node.child_by_field_name("body");
129 let params = node.child_by_field_name("parameters");
130 let (param_count, param_types, param_names) = params
131 .map(|p| extract_params(p, src, imports_map))
132 .unwrap_or((0, vec![], vec![]));
133
134 Some(FunctionInfo {
135 name,
136 start_line,
137 end_line,
138 name_col,
139 name_end_col,
140 line_count: end_line - start_line + 1,
141 complexity: count_complexity(node),
142 body_hash: body.map(hash_ast_structure),
143 is_exported: true,
144 parameter_count: param_count,
145 parameter_types: param_types,
146 parameter_names: param_names,
147 chain_depth: body.map(max_chain_depth).unwrap_or(0),
148 switch_arms: body.map(count_match_arms).unwrap_or(0),
149 switch_arm_values: body
150 .map(|b| collect_py_arm_values(b, src))
151 .unwrap_or_default(),
152 external_refs: body
153 .map(|b| collect_external_refs(b, src))
154 .unwrap_or_default(),
155 is_delegating: body.map(|b| check_delegating(b, src)).unwrap_or(false),
156 comment_lines: count_comment_lines(node, src),
157 referenced_fields: body.map(|b| collect_self_refs(b, src)).unwrap_or_default(),
158 null_check_fields: body
159 .map(|b| collect_none_checks(b, src))
160 .unwrap_or_default(),
161 switch_dispatch_target: body.and_then(|b| extract_match_target_py(b, src)),
162 optional_param_count: params.map(count_optional).unwrap_or(0),
163 called_functions: body.map(|b| collect_calls_py(b, src)).unwrap_or_default(),
164 cognitive_complexity: body.map(cognitive_complexity_py).unwrap_or(0),
165 return_type: node
166 .child_by_field_name("return_type")
167 .map(|rt| crate::type_ref::resolve(node_text(rt, src), imports_map)),
168 })
169}
170
171fn find_method_def(child: Node) -> Option<Node> {
172 if child.kind() == "function_definition" {
173 return Some(child);
174 }
175 if child.kind() == "decorated_definition" {
176 let mut inner = child.walk();
177 return child
178 .children(&mut inner)
179 .find(|c| c.kind() == "function_definition");
180 }
181 None
182}
183
184fn extract_parent_name(node: Node, src: &[u8]) -> Option<String> {
185 node.child_by_field_name("superclasses").and_then(|sc| {
186 let mut c = sc.walk();
187 sc.children(&mut c)
188 .find(|n| n.kind() != "(" && n.kind() != ")" && n.kind() != ",")
189 .map(|n| node_text(n, src).to_string())
190 })
191}
192
193fn has_listener_name(name: &str) -> bool {
194 name.contains("listener")
195 || name.contains("handler")
196 || name.contains("callback")
197 || name.contains("observer")
198}
199
200fn process_method(
201 func_node: Node,
202 f: &mut FunctionInfo,
203 src: &[u8],
204 field_names: &mut Vec<String>,
205) -> (bool, bool, bool, usize) {
206 let method_name = &f.name;
207 let mut has_behavior = false;
208 let mut is_override = false;
209 let mut is_notify = false;
210 if method_name == "__init__" {
211 collect_init_fields(func_node, src, field_names);
212 } else {
213 has_behavior = true;
214 }
215 let sc = func_node
216 .child_by_field_name("body")
217 .map(|b| count_self_calls(b, src))
218 .unwrap_or(0);
219 if method_name.starts_with("__") && method_name.ends_with("__") && method_name != "__init__" {
220 is_override = true;
221 }
222 if method_name.contains("notify") || method_name.contains("emit") {
223 is_notify = true;
224 }
225 f.is_exported = !method_name.starts_with('_');
226 (has_behavior, is_override, is_notify, sc)
227}
228
229struct ClassScan {
230 methods: Vec<FunctionInfo>,
231 field_names: Vec<String>,
232 delegating_count: usize,
233 has_behavior: bool,
234 override_count: usize,
235 self_call_count: usize,
236 has_notify_method: bool,
237}
238
239fn scan_class_methods(
240 body: Node,
241 src: &[u8],
242 imports_map: &crate::type_ref::ImportsMap,
243) -> ClassScan {
244 let mut s = ClassScan {
245 methods: Vec::new(),
246 field_names: Vec::new(),
247 delegating_count: 0,
248 has_behavior: false,
249 override_count: 0,
250 self_call_count: 0,
251 has_notify_method: false,
252 };
253 let mut cursor = body.walk();
254 for child in body.children(&mut cursor) {
255 let Some(func_node) = find_method_def(child) else {
256 continue;
257 };
258 let Some(mut f) = extract_function(func_node, src, imports_map) else {
259 continue;
260 };
261 if f.is_delegating {
262 s.delegating_count += 1;
263 }
264 let (behav, over, notify, sc) = process_method(func_node, &mut f, src, &mut s.field_names);
265 s.has_behavior |= behav;
266 if over {
267 s.override_count += 1;
268 }
269 if notify {
270 s.has_notify_method = true;
271 }
272 s.self_call_count += sc;
273 s.methods.push(f);
274 }
275 s
276}
277
278fn extract_class(
279 node: Node,
280 src: &[u8],
281 imports_map: &crate::type_ref::ImportsMap,
282 top_functions: &mut Vec<FunctionInfo>,
283) -> Option<ClassInfo> {
284 let name_node = node.child_by_field_name("name")?;
285 let name = node_text(name_node, src).to_string();
286 let name_col = name_node.start_position().column;
287 let name_end_col = name_node.end_position().column;
288 let start_line = node.start_position().row + 1;
289 let end_line = node.end_position().row + 1;
290 let body = node.child_by_field_name("body")?;
291 let s = scan_class_methods(body, src, imports_map);
292 let method_count = s.methods.len();
293 top_functions.extend(s.methods);
294
295 Some(ClassInfo {
296 name,
297 start_line,
298 end_line,
299 name_col,
300 name_end_col,
301 line_count: end_line - start_line + 1,
302 method_count,
303 is_exported: true,
304 delegating_method_count: s.delegating_count,
305 field_count: s.field_names.len(),
306 has_listener_field: s.field_names.iter().any(|n| has_listener_name(n)),
307 field_names: s.field_names,
308 field_types: Vec::new(),
309 has_behavior: s.has_behavior,
310 is_interface: has_only_pass_or_ellipsis(body, src),
311 parent_name: extract_parent_name(node, src),
312 override_count: s.override_count,
313 self_call_count: s.self_call_count,
314 has_notify_method: s.has_notify_method,
315 })
316}
317
318fn collect_import(node: Node, src: &[u8], imports: &mut Vec<ImportInfo>) {
321 let line = node.start_position().row + 1;
322 let col = node.start_position().column;
323 let mut cursor = node.walk();
324 for child in node.children(&mut cursor) {
325 if child.kind() == "dotted_name" || child.kind() == "aliased_import" {
326 let text = node_text(child, src);
327 imports.push(ImportInfo {
328 source: text.to_string(),
329 line,
330 col,
331 ..Default::default()
332 });
333 }
334 }
335}
336
337fn collect_import_from(node: Node, src: &[u8], imports: &mut Vec<ImportInfo>) {
338 let line = node.start_position().row + 1;
339 let col = node.start_position().column;
340 let module = node
341 .child_by_field_name("module_name")
342 .map(|n| node_text(n, src).to_string())
343 .unwrap_or_default();
344 let mut cursor = node.walk();
345 let mut has_names = false;
346 for child in node.children(&mut cursor) {
347 if child.kind() == "dotted_name" || child.kind() == "aliased_import" {
348 let n = node_text(child, src).to_string();
349 if n != module {
350 imports.push(ImportInfo {
351 source: format!("{module}.{n}"),
352 line,
353 col,
354 ..Default::default()
355 });
356 has_names = true;
357 }
358 }
359 }
360 if !has_names {
361 imports.push(ImportInfo {
362 source: module,
363 line,
364 col,
365 ..Default::default()
366 });
367 }
368}
369
370fn node_text<'a>(node: Node, src: &'a [u8]) -> &'a str {
373 node.utf8_text(src).unwrap_or("")
374}
375
376fn count_complexity(node: Node) -> usize {
377 let mut complexity = 1usize;
378 let mut cursor = node.walk();
379 visit_all(node, &mut cursor, &mut |n| {
380 match n.kind() {
381 "if_statement"
382 | "elif_clause"
383 | "for_statement"
384 | "while_statement"
385 | "except_clause"
386 | "with_statement"
387 | "assert_statement"
388 | "conditional_expression"
389 | "boolean_operator"
390 | "list_comprehension"
391 | "set_comprehension"
392 | "dictionary_comprehension"
393 | "generator_expression" => {
394 complexity += 1;
395 }
396 "match_statement" => {} "case_clause" => {
398 complexity += 1;
399 }
400 _ => {}
401 }
402 });
403 complexity
404}
405
406fn hash_ast_structure(node: Node) -> u64 {
407 let mut hasher = DefaultHasher::new();
408 hash_node(node, &mut hasher);
409 hasher.finish()
410}
411
412fn hash_node(node: Node, hasher: &mut DefaultHasher) {
413 node.kind().hash(hasher);
414 let mut cursor = node.walk();
415 for child in node.children(&mut cursor) {
416 hash_node(child, hasher);
417 }
418}
419
420fn max_chain_depth(node: Node) -> usize {
421 let mut max = 0usize;
422 let mut cursor = node.walk();
423 visit_all(node, &mut cursor, &mut |n| {
424 if n.kind() == "attribute" {
425 let depth = chain_len(n);
426 if depth > max {
427 max = depth;
428 }
429 }
430 });
431 max
432}
433
434fn chain_len(node: Node) -> usize {
435 let mut depth = 0usize;
436 let mut current = node;
437 while current.kind() == "attribute" || current.kind() == "call" {
438 if current.kind() == "attribute" {
439 depth += 1;
440 }
441 if let Some(obj) = current.child(0) {
442 current = obj;
443 } else {
444 break;
445 }
446 }
447 depth
448}
449
450fn collect_py_arm_values(body: Node, src: &[u8]) -> Vec<cha_core::ArmValue> {
451 let mut out = Vec::new();
452 crate::switch_arms::walk_arms(body, src, &mut out, &|n| n.kind() == "case_clause");
453 out
454}
455
456fn count_match_arms(node: Node) -> usize {
457 let mut count = 0usize;
458 let mut cursor = node.walk();
459 visit_all(node, &mut cursor, &mut |n| {
460 if n.kind() == "case_clause" {
461 count += 1;
462 }
463 });
464 count
465}
466
467fn collect_external_refs(node: Node, src: &[u8]) -> Vec<String> {
468 let mut refs = Vec::new();
469 let mut cursor = node.walk();
470 visit_all(node, &mut cursor, &mut |n| {
471 if n.kind() != "attribute" {
472 return;
473 }
474 let Some(obj) = n.child(0) else { return };
475 let text = node_text(obj, src);
476 if text != "self"
477 && !text.is_empty()
478 && text.starts_with(|c: char| c.is_lowercase())
479 && !refs.contains(&text.to_string())
480 {
481 refs.push(text.to_string());
482 }
483 });
484 refs
485}
486
487fn unwrap_single_call(body: Node) -> Option<Node> {
488 let mut c = body.walk();
489 let stmts: Vec<Node> = body
490 .children(&mut c)
491 .filter(|n| !n.is_extra() && n.kind() != "pass_statement" && n.kind() != "comment")
492 .collect();
493 if stmts.len() != 1 {
494 return None;
495 }
496 let stmt = stmts[0];
497 match stmt.kind() {
498 "return_statement" => stmt.child(1).filter(|v| v.kind() == "call"),
499 "expression_statement" => stmt.child(0).filter(|v| v.kind() == "call"),
500 _ => None,
501 }
502}
503
504fn check_delegating(body: Node, src: &[u8]) -> bool {
505 let Some(func) = unwrap_single_call(body).and_then(|c| c.child(0)) else {
506 return false;
507 };
508 let text = node_text(func, src);
509 text.contains('.') && !text.starts_with("self.")
510}
511
512fn count_comment_lines(node: Node, src: &[u8]) -> usize {
513 let mut count = 0usize;
514 let mut cursor = node.walk();
515 visit_all(node, &mut cursor, &mut |n| {
516 if n.kind() == "comment" {
517 count += 1;
518 } else if n.kind() == "string" || n.kind() == "expression_statement" {
519 let text = node_text(n, src);
521 if text.starts_with("\"\"\"") || text.starts_with("'''") {
522 count += text.lines().count();
523 }
524 }
525 });
526 count
527}
528
529fn collect_self_refs(body: Node, src: &[u8]) -> Vec<String> {
530 let mut refs = Vec::new();
531 let mut cursor = body.walk();
532 visit_all(body, &mut cursor, &mut |n| {
533 if n.kind() != "attribute" {
534 return;
535 }
536 let is_self = n.child(0).is_some_and(|o| node_text(o, src) == "self");
537 if !is_self {
538 return;
539 }
540 if let Some(attr) = n.child_by_field_name("attribute") {
541 let name = node_text(attr, src).to_string();
542 if !refs.contains(&name) {
543 refs.push(name);
544 }
545 }
546 });
547 refs
548}
549
550fn collect_none_checks(body: Node, src: &[u8]) -> Vec<String> {
551 let mut fields = Vec::new();
552 let mut cursor = body.walk();
553 visit_all(body, &mut cursor, &mut |n| {
554 if n.kind() != "comparison_operator" {
555 return;
556 }
557 let text = node_text(n, src);
558 if !text.contains("is None") && !text.contains("is not None") && !text.contains("== None") {
559 return;
560 }
561 if let Some(left) = n.child(0) {
562 let name = node_text(left, src).to_string();
563 if !fields.contains(&name) {
564 fields.push(name);
565 }
566 }
567 });
568 fields
569}
570
571fn is_self_or_cls(name: &str) -> bool {
572 name == "self" || name == "cls"
573}
574
575fn param_name_and_type(child: Node, src: &[u8]) -> Option<(String, String)> {
576 match child.kind() {
577 "identifier" => {
578 let name = node_text(child, src);
579 (!is_self_or_cls(name)).then(|| (name.to_string(), "Any".to_string()))
580 }
581 "typed_parameter" | "default_parameter" | "typed_default_parameter" => {
582 let name = child
583 .child_by_field_name("name")
584 .or_else(|| child.child(0))
585 .map(|n| node_text(n, src))
586 .unwrap_or("");
587 if is_self_or_cls(name) {
588 return None;
589 }
590 let ty = child
591 .child_by_field_name("type")
592 .map(|n| node_text(n, src).to_string())
593 .unwrap_or_else(|| "Any".to_string());
594 Some((name.to_string(), ty))
595 }
596 "list_splat_pattern" | "dictionary_splat_pattern" => {
597 Some(("*".to_string(), "Any".to_string()))
598 }
599 _ => None,
600 }
601}
602
603fn extract_params(
604 params_node: Node,
605 src: &[u8],
606 imports_map: &crate::type_ref::ImportsMap,
607) -> (usize, Vec<cha_core::TypeRef>, Vec<String>) {
608 let mut count = 0usize;
609 let mut types = Vec::new();
610 let mut names = Vec::new();
611 let mut cursor = params_node.walk();
612 for child in params_node.children(&mut cursor) {
613 if let Some((name, ty)) = param_name_and_type(child, src) {
614 count += 1;
615 types.push(crate::type_ref::resolve(ty, imports_map));
616 names.push(name.to_string());
617 }
618 }
619 (count, types, names)
620}
621
622fn count_optional(params_node: Node) -> usize {
623 let mut count = 0usize;
624 let mut cursor = params_node.walk();
625 for child in params_node.children(&mut cursor) {
626 if child.kind() == "default_parameter" || child.kind() == "typed_default_parameter" {
627 count += 1;
628 }
629 }
630 count
631}
632
633fn collect_init_fields(func_node: Node, src: &[u8], fields: &mut Vec<String>) {
634 let Some(body) = func_node.child_by_field_name("body") else {
635 return;
636 };
637 let mut cursor = body.walk();
638 visit_all(body, &mut cursor, &mut |n| {
639 if n.kind() != "assignment" {
640 return;
641 }
642 let Some(left) = n.child_by_field_name("left") else {
643 return;
644 };
645 if left.kind() != "attribute" {
646 return;
647 }
648 let is_self = left.child(0).is_some_and(|o| node_text(o, src) == "self");
649 if !is_self {
650 return;
651 }
652 if let Some(attr) = left.child_by_field_name("attribute") {
653 let name = node_text(attr, src).to_string();
654 if !fields.contains(&name) {
655 fields.push(name);
656 }
657 }
658 });
659}
660
661fn count_self_calls(body: Node, src: &[u8]) -> usize {
662 let mut count = 0;
663 let mut cursor = body.walk();
664 visit_all(body, &mut cursor, &mut |n| {
665 if n.kind() != "call" {
666 return;
667 }
668 let is_self_call = n
669 .child(0)
670 .filter(|f| f.kind() == "attribute")
671 .and_then(|f| f.child(0))
672 .is_some_and(|obj| node_text(obj, src) == "self");
673 if is_self_call {
674 count += 1;
675 }
676 });
677 count
678}
679
680fn is_stub_body(node: Node, src: &[u8]) -> bool {
681 node.child_by_field_name("body")
682 .is_none_or(|b| has_only_pass_or_ellipsis(b, src))
683}
684
685fn has_only_pass_or_ellipsis(body: Node, src: &[u8]) -> bool {
686 let mut cursor = body.walk();
687 for child in body.children(&mut cursor) {
688 let ok = match child.kind() {
689 "pass_statement" | "ellipsis" | "comment" => true,
690 "expression_statement" => child.child(0).is_none_or(|expr| {
691 let text = node_text(expr, src);
692 text == "..." || text.starts_with("\"\"\"") || text.starts_with("'''")
693 }),
694 "function_definition" => is_stub_body(child, src),
695 "decorated_definition" => {
696 let mut inner = child.walk();
697 child
698 .children(&mut inner)
699 .filter(|c| c.kind() == "function_definition")
700 .all(|c| is_stub_body(c, src))
701 }
702 _ => false,
703 };
704 if !ok {
705 return false;
706 }
707 }
708 true
709}
710
711fn cognitive_complexity_py(node: tree_sitter::Node) -> usize {
712 let mut score = 0;
713 cc_walk_py(node, 0, &mut score);
714 score
715}
716
717fn cc_walk_py(node: tree_sitter::Node, nesting: usize, score: &mut usize) {
718 match node.kind() {
719 "if_statement" => {
720 *score += 1 + nesting;
721 cc_children_py(node, nesting + 1, score);
722 return;
723 }
724 "for_statement" | "while_statement" => {
725 *score += 1 + nesting;
726 cc_children_py(node, nesting + 1, score);
727 return;
728 }
729 "match_statement" => {
730 *score += 1 + nesting;
731 cc_children_py(node, nesting + 1, score);
732 return;
733 }
734 "elif_clause" | "else_clause" => {
735 *score += 1;
736 }
737 "boolean_operator" => {
738 *score += 1;
739 }
740 "except_clause" => {
741 *score += 1 + nesting;
742 cc_children_py(node, nesting + 1, score);
743 return;
744 }
745 "lambda" => {
746 cc_children_py(node, nesting + 1, score);
747 return;
748 }
749 _ => {}
750 }
751 cc_children_py(node, nesting, score);
752}
753
754fn cc_children_py(node: tree_sitter::Node, nesting: usize, score: &mut usize) {
755 let mut cursor = node.walk();
756 for child in node.children(&mut cursor) {
757 cc_walk_py(child, nesting, score);
758 }
759}
760
761fn extract_match_target_py(body: tree_sitter::Node, src: &[u8]) -> Option<String> {
762 let mut target = None;
763 let mut cursor = body.walk();
764 visit_all(body, &mut cursor, &mut |n| {
765 if n.kind() == "match_statement"
766 && target.is_none()
767 && let Some(subj) = n.child_by_field_name("subject")
768 {
769 target = Some(node_text(subj, src).to_string());
770 }
771 });
772 target
773}
774
775fn collect_calls_py(body: tree_sitter::Node, src: &[u8]) -> Vec<String> {
776 let mut calls = Vec::new();
777 let mut cursor = body.walk();
778 visit_all(body, &mut cursor, &mut |n| {
779 if n.kind() == "call"
780 && let Some(func) = n.child(0)
781 {
782 let name = node_text(func, src).to_string();
783 if !calls.contains(&name) {
784 calls.push(name);
785 }
786 }
787 });
788 calls
789}
790
791fn collect_comments(root: Node, src: &[u8]) -> Vec<cha_core::CommentInfo> {
792 let mut comments = Vec::new();
793 let mut cursor = root.walk();
794 visit_all(root, &mut cursor, &mut |n| {
795 if n.kind().contains("comment") {
796 comments.push(cha_core::CommentInfo {
797 text: node_text(n, src).to_string(),
798 line: n.start_position().row + 1,
799 });
800 }
801 });
802 comments
803}
804
805fn visit_all<F: FnMut(Node)>(node: Node, cursor: &mut tree_sitter::TreeCursor, f: &mut F) {
806 f(node);
807 if cursor.goto_first_child() {
808 loop {
809 let child_node = cursor.node();
810 let mut child_cursor = child_node.walk();
811 visit_all(child_node, &mut child_cursor, f);
812 if !cursor.goto_next_sibling() {
813 break;
814 }
815 }
816 cursor.goto_parent();
817 }
818}