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