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 RustParser;
10
11impl LanguageParser for RustParser {
12 fn language_name(&self) -> &str {
13 "rust"
14 }
15
16 fn parse(&self, file: &SourceFile) -> Option<SourceModel> {
17 let mut parser = Parser::new();
18 parser
19 .set_language(&tree_sitter_rust::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 ctx = ParseContext::new(src);
26 ctx.collect_nodes(root, false);
27
28 Some(SourceModel {
29 language: "rust".into(),
30 total_lines: file.line_count(),
31 functions: ctx.col.functions,
32 classes: ctx.col.classes,
33 imports: ctx.col.imports,
34 comments: collect_comments(root, src),
35 type_aliases: vec![], })
37 }
38}
39
40struct Collector {
42 functions: Vec<FunctionInfo>,
43 classes: Vec<ClassInfo>,
44 imports: Vec<ImportInfo>,
45}
46
47struct ParseContext<'a> {
49 src: &'a [u8],
50 col: Collector,
51 last_self_call_count: usize,
52 last_has_notify: bool,
53 callback_fields: std::collections::HashMap<String, Vec<String>>,
55}
56
57impl<'a> ParseContext<'a> {
58 fn new(src: &'a [u8]) -> Self {
59 Self {
60 src,
61 last_self_call_count: 0,
62 last_has_notify: false,
63 callback_fields: std::collections::HashMap::new(),
64 col: Collector {
65 functions: Vec::new(),
66 classes: Vec::new(),
67 imports: Vec::new(),
68 },
69 }
70 }
71
72 fn collect_nodes(&mut self, node: Node, exported: bool) {
73 let mut cursor = node.walk();
74 for child in node.children(&mut cursor) {
75 self.collect_single_node(child, exported);
76 }
77 }
78
79 fn collect_single_node(&mut self, child: Node, exported: bool) {
80 match child.kind() {
81 "function_item" => self.push_function(child, exported),
82 "impl_item" => self.extract_impl_methods(child),
83 "struct_item" | "enum_item" | "trait_item" => self.push_struct(child),
84 "use_declaration" => self.push_import(child),
85 "mod_item" => self.push_mod_as_import(child),
86 _ => self.collect_nodes(child, false),
87 }
88 }
89
90 fn push_function(&mut self, node: Node, exported: bool) {
91 if let Some(mut f) = extract_function(node, self.src) {
92 f.is_exported = exported || has_pub(node);
93 self.col.functions.push(f);
94 }
95 }
96
97 fn push_struct(&mut self, node: Node) {
98 if let Some((mut c, cb_fields)) = extract_struct(node, self.src) {
99 c.is_exported = has_pub(node);
100 if !cb_fields.is_empty() {
101 self.callback_fields.insert(c.name.clone(), cb_fields);
102 }
103 self.col.classes.push(c);
104 }
105 }
106
107 fn push_import(&mut self, node: Node) {
108 if let Some(i) = extract_use(node, self.src) {
109 self.col.imports.push(i);
110 }
111 }
112
113 fn push_mod_as_import(&mut self, node: Node) {
115 if node.child_by_field_name("body").is_some() {
117 return;
118 }
119 if let Some(name_node) = node.child_by_field_name("name") {
120 let name = node_text(name_node, self.src);
121 self.col.imports.push(crate::ImportInfo {
122 source: format!("{name}.rs"),
123 line: node.start_position().row + 1,
124 col: node.start_position().column,
125 is_module_decl: true,
126 });
127 }
128 }
129
130 fn extract_impl_methods(&mut self, node: Node) {
131 let Some(body) = node.child_by_field_name("body") else {
132 return;
133 };
134 let impl_name = node
135 .child_by_field_name("type")
136 .map(|t| node_text(t, self.src).to_string());
137 let trait_name = node
138 .child_by_field_name("trait")
139 .map(|t| node_text(t, self.src).to_string());
140
141 let cb_fields = impl_name
142 .as_ref()
143 .and_then(|n| self.callback_fields.get(n))
144 .cloned()
145 .unwrap_or_default();
146
147 let (methods, delegating, has_behavior) = self.scan_impl_body(body, &cb_fields);
148
149 if let Some(name) = &impl_name
150 && let Some(class) = self.col.classes.iter_mut().find(|c| &c.name == name)
151 {
152 class.method_count += methods;
153 class.delegating_method_count += delegating;
154 class.has_behavior |= has_behavior;
155 class.self_call_count = class.self_call_count.max(self.last_self_call_count);
156 class.has_notify_method |= self.last_has_notify;
157 if let Some(t) = &trait_name {
158 class.parent_name = Some(t.clone());
159 }
160 }
161 }
162
163 fn scan_impl_body(&mut self, body: Node, cb_fields: &[String]) -> (usize, usize, bool) {
164 let mut methods = 0;
165 let mut delegating = 0;
166 let mut has_behavior = false;
167 let mut max_self_calls = 0;
168 let mut has_notify = false;
169 let mut cursor = body.walk();
170 for child in body.children(&mut cursor) {
171 if child.kind() == "function_item"
172 && let Some(mut f) = extract_function(child, self.src)
173 {
174 f.is_exported = has_pub(child);
175 methods += 1;
176 if f.is_delegating {
177 delegating += 1;
178 }
179 if f.line_count > 3 {
180 has_behavior = true;
181 }
182 let fn_body = child.child_by_field_name("body");
183 let self_calls = count_self_method_calls(fn_body, self.src);
184 max_self_calls = max_self_calls.max(self_calls);
185 if !has_notify && has_iterate_and_call(fn_body, self.src, cb_fields) {
187 has_notify = true;
188 }
189 self.col.functions.push(f);
190 }
191 }
192 self.last_self_call_count = max_self_calls;
194 self.last_has_notify = has_notify;
195 (methods, delegating, has_behavior)
196 }
197}
198
199fn node_text<'a>(node: Node, src: &'a [u8]) -> &'a str {
201 node.utf8_text(src).unwrap_or("")
202}
203
204fn has_pub(node: Node) -> bool {
205 let mut cursor = node.walk();
206 node.children(&mut cursor)
207 .any(|c| c.kind() == "visibility_modifier")
208}
209
210fn hash_ast_structure(node: Node) -> u64 {
211 let mut hasher = DefaultHasher::new();
212 walk_hash(node, &mut hasher);
213 hasher.finish()
214}
215
216fn walk_hash(node: Node, hasher: &mut DefaultHasher) {
217 node.kind().hash(hasher);
218 let mut cursor = node.walk();
219 for child in node.children(&mut cursor) {
220 walk_hash(child, hasher);
221 }
222}
223
224fn count_complexity(node: Node) -> usize {
225 let mut complexity = 1;
226 walk_complexity(node, &mut complexity);
227 complexity
228}
229
230fn walk_complexity(node: Node, count: &mut usize) {
231 match node.kind() {
232 "if_expression" | "else_clause" | "for_expression" | "while_expression"
233 | "loop_expression" | "match_arm" | "closure_expression" => {
234 *count += 1;
235 }
236 "binary_expression" => {
237 let mut cursor = node.walk();
238 for child in node.children(&mut cursor) {
239 if child.kind() == "&&" || child.kind() == "||" {
240 *count += 1;
241 }
242 }
243 }
244 _ => {}
245 }
246 let mut cursor = node.walk();
247 for child in node.children(&mut cursor) {
248 walk_complexity(child, count);
249 }
250}
251
252fn extract_function(node: Node, src: &[u8]) -> Option<FunctionInfo> {
253 let name_node = node.child_by_field_name("name")?;
254 let name = node_text(name_node, src).to_string();
255 let name_col = name_node.start_position().column;
256 let name_end_col = name_node.end_position().column;
257 let start_line = node.start_position().row + 1;
258 let end_line = node.end_position().row + 1;
259 let body = node.child_by_field_name("body");
260 let body_hash = body.map(hash_ast_structure);
261 let parameter_count = count_parameters(node);
262 let parameter_types = extract_param_types(node, src);
263 let chain_depth = body.map(max_chain_depth).unwrap_or(0);
264 let switch_arms = body.map(count_switch_arms).unwrap_or(0);
265 let external_refs = body
266 .map(|b| collect_external_refs(b, src))
267 .unwrap_or_default();
268 let is_delegating = body.map(|b| check_delegating(b, src)).unwrap_or(false);
269 Some(FunctionInfo {
270 name,
271 start_line,
272 end_line,
273 name_col,
274 name_end_col,
275 line_count: end_line - start_line + 1,
276 complexity: count_complexity(node),
277 body_hash,
278 is_exported: false,
279 parameter_count,
280 parameter_types,
281 chain_depth,
282 switch_arms,
283 external_refs,
284 is_delegating,
285 comment_lines: count_comment_lines(node, src),
286 referenced_fields: collect_field_refs(body, src),
287 null_check_fields: collect_null_checks(body, src),
288 switch_dispatch_target: extract_switch_target(body, src),
289 optional_param_count: count_optional_params(node, src),
290 called_functions: collect_calls_rs(body, src),
291 cognitive_complexity: body.map(cognitive_complexity_rs).unwrap_or(0),
292 })
293}
294
295fn extract_struct(node: Node, src: &[u8]) -> Option<(ClassInfo, Vec<String>)> {
296 let name_node = node.child_by_field_name("name")?;
297 let name = node_text(name_node, src).to_string();
298 let name_col = name_node.start_position().column;
299 let name_end_col = name_node.end_position().column;
300 let start_line = node.start_position().row + 1;
301 let end_line = node.end_position().row + 1;
302 let (field_count, field_names, callback_fields) = extract_fields(node, src);
303 let is_interface = node.kind() == "trait_item";
304 let has_listener_field = !callback_fields.is_empty();
305 Some((
306 ClassInfo {
307 name,
308 start_line,
309 end_line,
310 name_col,
311 name_end_col,
312 method_count: 0,
313 line_count: end_line - start_line + 1,
314 is_exported: false,
315 delegating_method_count: 0,
316 field_count,
317 field_names,
318 field_types: Vec::new(),
319 has_behavior: false,
320 is_interface,
321 parent_name: None,
322 override_count: 0,
323 self_call_count: 0,
324 has_listener_field,
325 has_notify_method: false,
326 },
327 callback_fields,
328 ))
329}
330
331fn count_parameters(node: Node) -> usize {
332 let params = match node.child_by_field_name("parameters") {
333 Some(p) => p,
334 None => return 0,
335 };
336 let mut cursor = params.walk();
337 params
338 .children(&mut cursor)
339 .filter(|c| c.kind() == "parameter" || c.kind() == "self_parameter")
340 .count()
341}
342
343fn extract_param_types(node: Node, src: &[u8]) -> Vec<String> {
344 let params = match node.child_by_field_name("parameters") {
345 Some(p) => p,
346 None => return vec![],
347 };
348 let mut types = Vec::new();
349 let mut cursor = params.walk();
350 for child in params.children(&mut cursor) {
351 if child.kind() == "parameter"
352 && let Some(ty) = child.child_by_field_name("type")
353 {
354 types.push(normalize_type(node_text(ty, src)));
355 }
356 }
357 types.sort();
358 types
359}
360
361fn normalize_type(raw: &str) -> String {
363 raw.trim_start_matches('&')
364 .trim_start_matches("mut ")
365 .trim()
366 .to_string()
367}
368
369fn max_chain_depth(node: Node) -> usize {
370 let mut max = 0;
371 walk_chain_depth(node, &mut max);
372 max
373}
374
375fn walk_chain_depth(node: Node, max: &mut usize) {
376 if node.kind() == "field_expression" {
377 let depth = measure_chain(node);
378 if depth > *max {
379 *max = depth;
380 }
381 }
382 let mut cursor = node.walk();
383 for child in node.children(&mut cursor) {
384 walk_chain_depth(child, max);
385 }
386}
387
388fn measure_chain(node: Node) -> usize {
390 let mut depth = 0;
391 let mut current = node;
392 while current.kind() == "field_expression" {
393 depth += 1;
394 if let Some(obj) = current.child_by_field_name("value") {
395 current = obj;
396 } else {
397 break;
398 }
399 }
400 depth
401}
402
403fn count_switch_arms(node: Node) -> usize {
404 let mut count = 0;
405 walk_switch_arms(node, &mut count);
406 count
407}
408
409fn walk_switch_arms(node: Node, count: &mut usize) {
410 if node.kind() == "match_arm" {
411 *count += 1;
412 }
413 let mut cursor = node.walk();
414 for child in node.children(&mut cursor) {
415 walk_switch_arms(child, count);
416 }
417}
418
419fn collect_external_refs(node: Node, src: &[u8]) -> Vec<String> {
420 let mut refs = Vec::new();
421 walk_external_refs(node, src, &mut refs);
422 refs.sort();
423 refs.dedup();
424 refs
425}
426
427fn field_chain_root(node: Node) -> Node {
429 let mut current = node;
430 while current.kind() == "field_expression" {
431 match current.child_by_field_name("value") {
432 Some(child) => current = child,
433 None => break,
434 }
435 }
436 current
437}
438
439fn walk_external_refs(node: Node, src: &[u8], refs: &mut Vec<String>) {
440 if node.kind() == "field_expression" {
441 let root = field_chain_root(node);
443 let text = node_text(root, src);
444 if text != "self" && !text.is_empty() {
445 refs.push(text.to_string());
446 }
447 }
448 let mut cursor = node.walk();
449 for child in node.children(&mut cursor) {
450 walk_external_refs(child, src, refs);
451 }
452}
453
454fn single_stmt(body: Node) -> Option<Node> {
456 let mut cursor = body.walk();
457 let stmts: Vec<_> = body
458 .children(&mut cursor)
459 .filter(|c| c.kind() != "{" && c.kind() != "}")
460 .collect();
461 (stmts.len() == 1).then(|| stmts[0])
462}
463
464fn is_external_call(node: Node, src: &[u8]) -> bool {
466 node.kind() == "call_expression"
467 && node.child_by_field_name("function").is_some_and(|func| {
468 func.kind() == "field_expression"
469 && func
470 .child_by_field_name("value")
471 .is_some_and(|obj| node_text(obj, src) != "self")
472 })
473}
474
475fn check_delegating(body: Node, src: &[u8]) -> bool {
476 let Some(stmt) = single_stmt(body) else {
477 return false;
478 };
479 let expr = match stmt.kind() {
480 "expression_statement" => stmt.child(0).unwrap_or(stmt),
481 "return_expression" => stmt.child(1).unwrap_or(stmt),
482 _ => stmt,
483 };
484 is_external_call(expr, src)
485}
486
487fn extract_use(node: Node, src: &[u8]) -> Option<ImportInfo> {
488 let text = node_text(node, src);
489 let source = text
491 .strip_prefix("use ")?
492 .trim_end_matches(';')
493 .trim()
494 .to_string();
495 Some(ImportInfo {
496 source,
497 line: node.start_position().row + 1,
498 col: node.start_position().column,
499 ..Default::default()
500 })
501}
502
503fn count_comment_lines(node: Node, src: &[u8]) -> usize {
505 let mut count = 0;
506 let mut cursor = node.walk();
507 for child in node.children(&mut cursor) {
508 if child.kind() == "line_comment" || child.kind() == "block_comment" {
509 count += child.end_position().row - child.start_position().row + 1;
510 }
511 }
512 if let Some(body) = node.child_by_field_name("body") {
514 count += count_comment_lines_recursive(body, src);
515 }
516 count
517}
518
519fn count_comment_lines_recursive(node: Node, _src: &[u8]) -> usize {
520 let mut count = 0;
521 let mut cursor = node.walk();
522 for child in node.children(&mut cursor) {
523 if child.kind() == "line_comment" || child.kind() == "block_comment" {
524 count += child.end_position().row - child.start_position().row + 1;
525 } else if child.child_count() > 0 {
526 count += count_comment_lines_recursive(child, _src);
527 }
528 }
529 count
530}
531
532fn collect_field_refs(body: Option<Node>, src: &[u8]) -> Vec<String> {
535 let Some(body) = body else { return vec![] };
536 let mut refs = Vec::new();
537 collect_self_fields(body, src, &mut refs);
538 refs.sort();
539 refs.dedup();
540 refs
541}
542
543fn collect_self_fields(node: Node, src: &[u8], refs: &mut Vec<String>) {
544 if node.kind() == "field_expression"
545 && let Some(obj) = node.child_by_field_name("value")
546 && node_text(obj, src) == "self"
547 && let Some(field) = node.child_by_field_name("field")
548 {
549 refs.push(node_text(field, src).to_string());
550 }
551 let mut cursor = node.walk();
552 for child in node.children(&mut cursor) {
553 collect_self_fields(child, src, refs);
554 }
555}
556
557fn extract_fields(node: Node, src: &[u8]) -> (usize, Vec<String>, Vec<String>) {
560 let mut names = Vec::new();
561 let mut callback_fields = Vec::new();
562 if let Some(body) = node.child_by_field_name("body") {
563 let mut cursor = body.walk();
564 for child in body.children(&mut cursor) {
565 if child.kind() == "field_declaration"
566 && let Some(name_node) = child.child_by_field_name("name")
567 {
568 let name = node_text(name_node, src).to_string();
569 if let Some(ty) = child.child_by_field_name("type")
570 && is_callback_collection_type_rs(node_text(ty, src))
571 {
572 callback_fields.push(name.clone());
573 }
574 names.push(name);
575 }
576 }
577 }
578 (names.len(), names, callback_fields)
579}
580
581fn is_callback_collection_type_rs(ty: &str) -> bool {
583 if !ty.contains("Vec<") {
584 return false;
585 }
586 ty.contains("Fn(") || ty.contains("FnMut(") || ty.contains("FnOnce(") || ty.contains("fn(")
587}
588
589fn collect_null_checks(body: Option<Node>, src: &[u8]) -> Vec<String> {
591 let Some(body) = body else { return vec![] };
592 let mut fields = Vec::new();
593 walk_null_checks_rs(body, src, &mut fields);
594 fields.sort();
595 fields.dedup();
596 fields
597}
598
599fn walk_null_checks_rs(node: Node, src: &[u8], fields: &mut Vec<String>) {
600 if node.kind() == "if_let_expression" {
601 if let Some(pattern) = node.child_by_field_name("pattern")
603 && node_text(pattern, src).contains("Some")
604 && let Some(value) = node.child_by_field_name("value")
605 {
606 let vtext = node_text(value, src);
607 if let Some(f) = vtext.strip_prefix("self.") {
608 fields.push(f.to_string());
609 }
610 }
611 } else if node.kind() == "if_expression"
612 && let Some(cond) = node.child_by_field_name("condition")
613 {
614 let text = node_text(cond, src);
615 if text.contains("is_some") || text.contains("is_none") {
616 extract_null_checked_fields(text, fields);
617 }
618 }
619 let mut cursor = node.walk();
620 for child in node.children(&mut cursor) {
621 walk_null_checks_rs(child, src, fields);
622 }
623}
624
625fn extract_null_checked_fields(text: &str, fields: &mut Vec<String>) {
627 if !(text.contains("is_some") || text.contains("is_none") || text.contains("Some")) {
628 return;
629 }
630 for part in text.split("self.") {
631 if let Some(field) = part
632 .split(|c: char| !c.is_alphanumeric() && c != '_')
633 .next()
634 && !field.is_empty()
635 && field != "is_some"
636 && field != "is_none"
637 {
638 fields.push(field.to_string());
639 }
640 }
641}
642
643fn extract_switch_target(body: Option<Node>, src: &[u8]) -> Option<String> {
645 let body = body?;
646 find_match_target(body, src)
647}
648
649fn find_match_target(node: Node, src: &[u8]) -> Option<String> {
650 if node.kind() == "match_expression"
651 && let Some(value) = node.child_by_field_name("value")
652 {
653 return Some(node_text(value, src).to_string());
654 }
655 let mut cursor = node.walk();
656 for child in node.children(&mut cursor) {
657 if let Some(t) = find_match_target(child, src) {
658 return Some(t);
659 }
660 }
661 None
662}
663
664fn count_optional_params(node: Node, src: &[u8]) -> usize {
666 let Some(params) = node.child_by_field_name("parameters") else {
667 return 0;
668 };
669 let mut count = 0;
670 let mut cursor = params.walk();
671 for child in params.children(&mut cursor) {
672 if child.kind() == "parameter" {
673 let text = node_text(child, src);
674 if text.contains("Option<") {
675 count += 1;
676 }
677 }
678 }
679 count
680}
681
682fn count_self_method_calls(body: Option<Node>, src: &[u8]) -> usize {
684 let Some(body) = body else { return 0 };
685 let mut count = 0;
686 walk_self_calls(body, src, &mut count);
687 count
688}
689
690fn walk_self_calls(node: Node, src: &[u8], count: &mut usize) {
691 if node.kind() == "call_expression"
692 && let Some(func) = node.child_by_field_name("function")
693 && node_text(func, src).starts_with("self.")
694 {
695 *count += 1;
696 }
697 let mut cursor = node.walk();
698 for child in node.children(&mut cursor) {
699 walk_self_calls(child, src, count);
700 }
701}
702
703fn has_iterate_and_call(body: Option<Node>, src: &[u8], cb_fields: &[String]) -> bool {
706 let Some(body) = body else { return false };
707 for field in cb_fields {
708 let self_field = format!("self.{field}");
709 if walk_for_iterate_call(body, src, &self_field) {
710 return true;
711 }
712 }
713 false
714}
715
716fn walk_for_iterate_call(node: Node, src: &[u8], self_field: &str) -> bool {
717 if node.kind() == "for_expression"
719 && let Some(value) = node.child_by_field_name("value")
720 && node_text(value, src).contains(self_field)
721 && let Some(loop_body) = node.child_by_field_name("body")
722 && has_call_expression(loop_body)
723 {
724 return true;
725 }
726 if node.kind() == "call_expression" {
728 let text = node_text(node, src);
729 if text.contains(self_field) && text.contains("for_each") {
730 return true;
731 }
732 }
733 let mut cursor = node.walk();
734 for child in node.children(&mut cursor) {
735 if walk_for_iterate_call(child, src, self_field) {
736 return true;
737 }
738 }
739 false
740}
741
742fn cognitive_complexity_rs(node: Node) -> usize {
743 let mut score = 0;
744 cc_walk_rs(node, 0, &mut score);
745 score
746}
747
748fn cc_walk_rs(node: Node, nesting: usize, score: &mut usize) {
749 match node.kind() {
750 "if_expression" => {
751 *score += 1 + nesting;
752 cc_children_rs(node, nesting + 1, score);
753 return;
754 }
755 "for_expression" | "while_expression" | "loop_expression" => {
756 *score += 1 + nesting;
757 cc_children_rs(node, nesting + 1, score);
758 return;
759 }
760 "match_expression" => {
761 *score += 1 + nesting;
762 cc_children_rs(node, nesting + 1, score);
763 return;
764 }
765 "else_clause" => {
766 *score += 1;
767 }
768 "binary_expression" => {
769 if let Some(op) = node.child_by_field_name("operator")
770 && (op.kind() == "&&" || op.kind() == "||")
771 {
772 *score += 1;
773 }
774 }
775 "closure_expression" => {
776 cc_children_rs(node, nesting + 1, score);
777 return;
778 }
779 _ => {}
780 }
781 cc_children_rs(node, nesting, score);
782}
783
784fn cc_children_rs(node: Node, nesting: usize, score: &mut usize) {
785 let mut cursor = node.walk();
786 for child in node.children(&mut cursor) {
787 cc_walk_rs(child, nesting, score);
788 }
789}
790
791fn collect_calls_rs(body: Option<tree_sitter::Node>, src: &[u8]) -> Vec<String> {
792 let Some(body) = body else { return Vec::new() };
793 let mut calls = Vec::new();
794 let mut cursor = body.walk();
795 visit_all(body, &mut cursor, &mut |n| {
796 if n.kind() == "call_expression"
797 && let Some(func) = n.child(0)
798 {
799 let name = node_text(func, src).to_string();
800 if !calls.contains(&name) {
801 calls.push(name);
802 }
803 }
804 });
805 calls
806}
807
808fn visit_all<F: FnMut(Node)>(node: Node, cursor: &mut tree_sitter::TreeCursor, f: &mut F) {
809 f(node);
810 if cursor.goto_first_child() {
811 loop {
812 visit_all(cursor.node(), cursor, f);
813 if !cursor.goto_next_sibling() {
814 break;
815 }
816 }
817 cursor.goto_parent();
818 }
819}
820
821fn collect_comments(root: Node, src: &[u8]) -> Vec<cha_core::CommentInfo> {
822 let mut comments = Vec::new();
823 let mut cursor = root.walk();
824 visit_all(root, &mut cursor, &mut |n| {
825 if n.kind().contains("comment") {
826 comments.push(cha_core::CommentInfo {
827 text: node_text(n, src).to_string(),
828 line: n.start_position().row + 1,
829 });
830 }
831 });
832 comments
833}
834
835fn has_call_expression(node: Node) -> bool {
836 if node.kind() == "call_expression" {
837 return true;
838 }
839 let mut cursor = node.walk();
840 for child in node.children(&mut cursor) {
841 if has_call_expression(child) {
842 return true;
843 }
844 }
845 false
846}