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