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