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) = 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 Some(FunctionInfo {
280 name,
281 start_line,
282 end_line,
283 name_col,
284 name_end_col,
285 line_count: end_line - start_line + 1,
286 complexity: count_complexity(node),
287 body_hash,
288 is_exported: false,
289 parameter_count,
290 parameter_types,
291 chain_depth,
292 switch_arms,
293 external_refs,
294 is_delegating,
295 comment_lines: count_comment_lines(node, src),
296 referenced_fields: collect_field_refs(body, src),
297 null_check_fields: collect_null_checks(body, src),
298 switch_dispatch_target: extract_switch_target(body, src),
299 optional_param_count: count_optional_params(node, src),
300 called_functions: collect_calls_rs(body, src),
301 cognitive_complexity: body.map(cognitive_complexity_rs).unwrap_or(0),
302 })
303}
304
305fn extract_struct(node: Node, src: &[u8]) -> Option<(ClassInfo, Vec<String>)> {
306 let name_node = node.child_by_field_name("name")?;
307 let name = node_text(name_node, src).to_string();
308 let name_col = name_node.start_position().column;
309 let name_end_col = name_node.end_position().column;
310 let start_line = node.start_position().row + 1;
311 let end_line = node.end_position().row + 1;
312 let (field_count, field_names, callback_fields) = extract_fields(node, src);
313 let is_interface = node.kind() == "trait_item";
314 let has_listener_field = !callback_fields.is_empty();
315 Some((
316 ClassInfo {
317 name,
318 start_line,
319 end_line,
320 name_col,
321 name_end_col,
322 method_count: 0,
323 line_count: end_line - start_line + 1,
324 is_exported: false,
325 delegating_method_count: 0,
326 field_count,
327 field_names,
328 field_types: Vec::new(),
329 has_behavior: false,
330 is_interface,
331 parent_name: None,
332 override_count: 0,
333 self_call_count: 0,
334 has_listener_field,
335 has_notify_method: false,
336 },
337 callback_fields,
338 ))
339}
340
341fn count_parameters(node: Node) -> usize {
342 let params = match node.child_by_field_name("parameters") {
343 Some(p) => p,
344 None => return 0,
345 };
346 let mut cursor = params.walk();
347 params
348 .children(&mut cursor)
349 .filter(|c| c.kind() == "parameter" || c.kind() == "self_parameter")
350 .count()
351}
352
353fn extract_param_types(
354 node: Node,
355 src: &[u8],
356 imports_map: &crate::type_ref::ImportsMap,
357) -> Vec<cha_core::TypeRef> {
358 let params = match node.child_by_field_name("parameters") {
359 Some(p) => p,
360 None => return vec![],
361 };
362 let mut types = Vec::new();
363 let mut cursor = params.walk();
364 for child in params.children(&mut cursor) {
365 if child.kind() == "parameter"
366 && let Some(ty) = child.child_by_field_name("type")
367 {
368 types.push(crate::type_ref::resolve(node_text(ty, src), imports_map));
369 }
370 }
371 types
372}
373
374fn max_chain_depth(node: Node) -> usize {
375 let mut max = 0;
376 walk_chain_depth(node, &mut max);
377 max
378}
379
380fn walk_chain_depth(node: Node, max: &mut usize) {
381 if node.kind() == "field_expression" {
382 let depth = measure_chain(node);
383 if depth > *max {
384 *max = depth;
385 }
386 }
387 let mut cursor = node.walk();
388 for child in node.children(&mut cursor) {
389 walk_chain_depth(child, max);
390 }
391}
392
393fn measure_chain(node: Node) -> usize {
395 let mut depth = 0;
396 let mut current = node;
397 while current.kind() == "field_expression" {
398 depth += 1;
399 if let Some(obj) = current.child_by_field_name("value") {
400 current = obj;
401 } else {
402 break;
403 }
404 }
405 depth
406}
407
408fn count_switch_arms(node: Node) -> usize {
409 let mut count = 0;
410 walk_switch_arms(node, &mut count);
411 count
412}
413
414fn walk_switch_arms(node: Node, count: &mut usize) {
415 if node.kind() == "match_arm" {
416 *count += 1;
417 }
418 let mut cursor = node.walk();
419 for child in node.children(&mut cursor) {
420 walk_switch_arms(child, count);
421 }
422}
423
424fn collect_external_refs(node: Node, src: &[u8]) -> Vec<String> {
425 let mut refs = Vec::new();
426 walk_external_refs(node, src, &mut refs);
427 refs.sort();
428 refs.dedup();
429 refs
430}
431
432fn field_chain_root(node: Node) -> Node {
434 let mut current = node;
435 while current.kind() == "field_expression" {
436 match current.child_by_field_name("value") {
437 Some(child) => current = child,
438 None => break,
439 }
440 }
441 current
442}
443
444fn walk_external_refs(node: Node, src: &[u8], refs: &mut Vec<String>) {
445 if node.kind() == "field_expression" {
446 let root = field_chain_root(node);
448 let text = node_text(root, src);
449 if text != "self" && !text.is_empty() {
450 refs.push(text.to_string());
451 }
452 }
453 let mut cursor = node.walk();
454 for child in node.children(&mut cursor) {
455 walk_external_refs(child, src, refs);
456 }
457}
458
459fn single_stmt(body: Node) -> Option<Node> {
461 let mut cursor = body.walk();
462 let stmts: Vec<_> = body
463 .children(&mut cursor)
464 .filter(|c| c.kind() != "{" && c.kind() != "}")
465 .collect();
466 (stmts.len() == 1).then(|| stmts[0])
467}
468
469fn is_external_call(node: Node, src: &[u8]) -> bool {
471 node.kind() == "call_expression"
472 && node.child_by_field_name("function").is_some_and(|func| {
473 func.kind() == "field_expression"
474 && func
475 .child_by_field_name("value")
476 .is_some_and(|obj| node_text(obj, src) != "self")
477 })
478}
479
480fn check_delegating(body: Node, src: &[u8]) -> bool {
481 let Some(stmt) = single_stmt(body) else {
482 return false;
483 };
484 let expr = match stmt.kind() {
485 "expression_statement" => stmt.child(0).unwrap_or(stmt),
486 "return_expression" => stmt.child(1).unwrap_or(stmt),
487 _ => stmt,
488 };
489 is_external_call(expr, src)
490}
491
492fn extract_use(node: Node, src: &[u8]) -> Option<ImportInfo> {
493 let source = node_text(node, src)
495 .strip_prefix("use ")?
496 .trim_end_matches(';')
497 .trim()
498 .to_string();
499 Some(ImportInfo {
500 source,
501 line: node.start_position().row + 1,
502 col: node.start_position().column,
503 ..Default::default()
504 })
505}
506
507fn count_comment_lines(node: Node, src: &[u8]) -> usize {
509 let mut count = 0;
510 let mut cursor = node.walk();
511 for child in node.children(&mut cursor) {
512 if child.kind() == "line_comment" || child.kind() == "block_comment" {
513 count += child.end_position().row - child.start_position().row + 1;
514 }
515 }
516 if let Some(body) = node.child_by_field_name("body") {
518 count += count_comment_lines_recursive(body, src);
519 }
520 count
521}
522
523fn count_comment_lines_recursive(node: Node, _src: &[u8]) -> usize {
524 let mut count = 0;
525 let mut cursor = node.walk();
526 for child in node.children(&mut cursor) {
527 if child.kind() == "line_comment" || child.kind() == "block_comment" {
528 count += child.end_position().row - child.start_position().row + 1;
529 } else if child.child_count() > 0 {
530 count += count_comment_lines_recursive(child, _src);
531 }
532 }
533 count
534}
535
536fn collect_field_refs(body: Option<Node>, src: &[u8]) -> Vec<String> {
539 let Some(body) = body else { return vec![] };
540 let mut refs = Vec::new();
541 collect_self_fields(body, src, &mut refs);
542 refs.sort();
543 refs.dedup();
544 refs
545}
546
547fn collect_self_fields(node: Node, src: &[u8], refs: &mut Vec<String>) {
548 if node.kind() == "field_expression"
549 && let Some(obj) = node.child_by_field_name("value")
550 && node_text(obj, src) == "self"
551 && let Some(field) = node.child_by_field_name("field")
552 {
553 refs.push(node_text(field, src).to_string());
554 }
555 let mut cursor = node.walk();
556 for child in node.children(&mut cursor) {
557 collect_self_fields(child, src, refs);
558 }
559}
560
561fn extract_fields(node: Node, src: &[u8]) -> (usize, Vec<String>, Vec<String>) {
564 let mut names = Vec::new();
565 let mut callback_fields = Vec::new();
566 if let Some(body) = node.child_by_field_name("body") {
567 let mut cursor = body.walk();
568 for child in body.children(&mut cursor) {
569 if child.kind() == "field_declaration"
570 && let Some(name_node) = child.child_by_field_name("name")
571 {
572 let name = node_text(name_node, src).to_string();
573 if let Some(ty) = child.child_by_field_name("type")
574 && is_callback_collection_type_rs(node_text(ty, src))
575 {
576 callback_fields.push(name.clone());
577 }
578 names.push(name);
579 }
580 }
581 }
582 (names.len(), names, callback_fields)
583}
584
585fn is_callback_collection_type_rs(ty: &str) -> bool {
587 if !ty.contains("Vec<") {
588 return false;
589 }
590 ty.contains("Fn(") || ty.contains("FnMut(") || ty.contains("FnOnce(") || ty.contains("fn(")
591}
592
593fn collect_null_checks(body: Option<Node>, src: &[u8]) -> Vec<String> {
595 let Some(body) = body else { return vec![] };
596 let mut fields = Vec::new();
597 walk_null_checks_rs(body, src, &mut fields);
598 fields.sort();
599 fields.dedup();
600 fields
601}
602
603fn walk_null_checks_rs(node: Node, src: &[u8], fields: &mut Vec<String>) {
604 if node.kind() == "if_let_expression" {
605 if let Some(pattern) = node.child_by_field_name("pattern")
607 && node_text(pattern, src).contains("Some")
608 && let Some(value) = node.child_by_field_name("value")
609 {
610 let vtext = node_text(value, src);
611 if let Some(f) = vtext.strip_prefix("self.") {
612 fields.push(f.to_string());
613 }
614 }
615 } else if node.kind() == "if_expression"
616 && let Some(cond) = node.child_by_field_name("condition")
617 {
618 let text = node_text(cond, src);
619 if text.contains("is_some") || text.contains("is_none") {
620 extract_null_checked_fields(text, fields);
621 }
622 }
623 let mut cursor = node.walk();
624 for child in node.children(&mut cursor) {
625 walk_null_checks_rs(child, src, fields);
626 }
627}
628
629fn extract_null_checked_fields(text: &str, fields: &mut Vec<String>) {
631 if !(text.contains("is_some") || text.contains("is_none") || text.contains("Some")) {
632 return;
633 }
634 for part in text.split("self.") {
635 if let Some(field) = part
636 .split(|c: char| !c.is_alphanumeric() && c != '_')
637 .next()
638 && !field.is_empty()
639 && field != "is_some"
640 && field != "is_none"
641 {
642 fields.push(field.to_string());
643 }
644 }
645}
646
647fn extract_switch_target(body: Option<Node>, src: &[u8]) -> Option<String> {
649 let body = body?;
650 find_match_target(body, src)
651}
652
653fn find_match_target(node: Node, src: &[u8]) -> Option<String> {
654 if node.kind() == "match_expression"
655 && let Some(value) = node.child_by_field_name("value")
656 {
657 return Some(node_text(value, src).to_string());
658 }
659 let mut cursor = node.walk();
660 for child in node.children(&mut cursor) {
661 if let Some(t) = find_match_target(child, src) {
662 return Some(t);
663 }
664 }
665 None
666}
667
668fn count_optional_params(node: Node, src: &[u8]) -> usize {
670 let Some(params) = node.child_by_field_name("parameters") else {
671 return 0;
672 };
673 let mut count = 0;
674 let mut cursor = params.walk();
675 for child in params.children(&mut cursor) {
676 if child.kind() == "parameter" {
677 let text = node_text(child, src);
678 if text.contains("Option<") {
679 count += 1;
680 }
681 }
682 }
683 count
684}
685
686fn count_self_method_calls(body: Option<Node>, src: &[u8]) -> usize {
688 let Some(body) = body else { return 0 };
689 let mut count = 0;
690 walk_self_calls(body, src, &mut count);
691 count
692}
693
694fn walk_self_calls(node: Node, src: &[u8], count: &mut usize) {
695 if node.kind() == "call_expression"
696 && let Some(func) = node.child_by_field_name("function")
697 && node_text(func, src).starts_with("self.")
698 {
699 *count += 1;
700 }
701 let mut cursor = node.walk();
702 for child in node.children(&mut cursor) {
703 walk_self_calls(child, src, count);
704 }
705}
706
707fn has_iterate_and_call(body: Option<Node>, src: &[u8], cb_fields: &[String]) -> bool {
710 let Some(body) = body else { return false };
711 for field in cb_fields {
712 let self_field = format!("self.{field}");
713 if walk_for_iterate_call(body, src, &self_field) {
714 return true;
715 }
716 }
717 false
718}
719
720fn walk_for_iterate_call(node: Node, src: &[u8], self_field: &str) -> bool {
721 if node.kind() == "for_expression"
723 && let Some(value) = node.child_by_field_name("value")
724 && node_text(value, src).contains(self_field)
725 && let Some(loop_body) = node.child_by_field_name("body")
726 && has_call_expression(loop_body)
727 {
728 return true;
729 }
730 if node.kind() == "call_expression" {
732 let text = node_text(node, src);
733 if text.contains(self_field) && text.contains("for_each") {
734 return true;
735 }
736 }
737 let mut cursor = node.walk();
738 for child in node.children(&mut cursor) {
739 if walk_for_iterate_call(child, src, self_field) {
740 return true;
741 }
742 }
743 false
744}
745
746fn cognitive_complexity_rs(node: Node) -> usize {
747 let mut score = 0;
748 cc_walk_rs(node, 0, &mut score);
749 score
750}
751
752fn cc_walk_rs(node: Node, nesting: usize, score: &mut usize) {
753 match node.kind() {
754 "if_expression" => {
755 *score += 1 + nesting;
756 cc_children_rs(node, nesting + 1, score);
757 return;
758 }
759 "for_expression" | "while_expression" | "loop_expression" => {
760 *score += 1 + nesting;
761 cc_children_rs(node, nesting + 1, score);
762 return;
763 }
764 "match_expression" => {
765 *score += 1 + nesting;
766 cc_children_rs(node, nesting + 1, score);
767 return;
768 }
769 "else_clause" => {
770 *score += 1;
771 }
772 "binary_expression" => {
773 if let Some(op) = node.child_by_field_name("operator")
774 && (op.kind() == "&&" || op.kind() == "||")
775 {
776 *score += 1;
777 }
778 }
779 "closure_expression" => {
780 cc_children_rs(node, nesting + 1, score);
781 return;
782 }
783 _ => {}
784 }
785 cc_children_rs(node, nesting, score);
786}
787
788fn cc_children_rs(node: Node, nesting: usize, score: &mut usize) {
789 let mut cursor = node.walk();
790 for child in node.children(&mut cursor) {
791 cc_walk_rs(child, nesting, score);
792 }
793}
794
795fn collect_calls_rs(body: Option<tree_sitter::Node>, src: &[u8]) -> Vec<String> {
796 let Some(body) = body else { return Vec::new() };
797 let mut calls = Vec::new();
798 let mut cursor = body.walk();
799 visit_all(body, &mut cursor, &mut |n| {
800 if n.kind() == "call_expression"
801 && let Some(func) = n.child(0)
802 {
803 let name = node_text(func, src).to_string();
804 if !calls.contains(&name) {
805 calls.push(name);
806 }
807 }
808 });
809 calls
810}
811
812fn visit_all<F: FnMut(Node)>(node: Node, cursor: &mut tree_sitter::TreeCursor, f: &mut F) {
813 f(node);
814 if cursor.goto_first_child() {
815 loop {
816 visit_all(cursor.node(), cursor, f);
817 if !cursor.goto_next_sibling() {
818 break;
819 }
820 }
821 cursor.goto_parent();
822 }
823}
824
825fn collect_comments(root: Node, src: &[u8]) -> Vec<cha_core::CommentInfo> {
826 let mut comments = Vec::new();
827 let mut cursor = root.walk();
828 visit_all(root, &mut cursor, &mut |n| {
829 if n.kind().contains("comment") {
830 comments.push(cha_core::CommentInfo {
831 text: node_text(n, src).to_string(),
832 line: n.start_position().row + 1,
833 });
834 }
835 });
836 comments
837}
838
839fn has_call_expression(node: Node) -> bool {
840 if node.kind() == "call_expression" {
841 return true;
842 }
843 let mut cursor = node.walk();
844 for child in node.children(&mut cursor) {
845 if has_call_expression(child) {
846 return true;
847 }
848 }
849 false
850}