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