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