Skip to main content

cha_parser/
rust_lang.rs

1// cha:ignore large_file
2use 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        // Pre-scan every `use` declaration so types in function signatures
31        // can be resolved to project-local / external / primitive / unknown.
32        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/// Accumulator for collected AST items.
49#[derive(Default)]
50struct Collector {
51    functions: Vec<FunctionInfo>,
52    classes: Vec<ClassInfo>,
53    imports: Vec<ImportInfo>,
54    type_aliases: Vec<(String, String)>,
55}
56
57/// Bundles source bytes and collector to eliminate repeated parameter passing.
58struct ParseContext<'a> {
59    src: &'a [u8],
60    col: Collector,
61    last_self_call_count: usize,
62    last_has_notify: bool,
63    /// Callback collection field names per class name.
64    callback_fields: std::collections::HashMap<String, Vec<String>>,
65    /// Short type name -> resolved origin (built in a pre-scan pass).
66    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    /// Treat `mod foo;` as an import of `foo.rs` (file-level dependency).
130    fn push_mod_as_import(&mut self, node: Node) {
131        // Skip inline mod blocks: `mod foo { ... }` has a body
132        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                // Structural Observer: method iterates a callback field and calls elements
202                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        // Store extra signals in the class via the caller
209        self.last_self_call_count = max_self_calls;
210        self.last_has_notify = has_notify;
211        (methods, delegating, has_behavior)
212    }
213}
214
215// Extract and push a function item node.
216fn 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
410/// Count consecutive field accesses (a.b.c.d), skipping method calls.
411fn 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
449/// Walk a field_expression chain to its root identifier.
450fn 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        // Walk to the root object of the chain (a.b.c → a)
464        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
476/// Extract the single statement from a block body, if exactly one exists.
477fn 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
486/// Check if a node is a method call on an external object (not self).
487fn 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
509/// Count comment lines inside a function node.
510fn 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    // Also recurse into body
519    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
538// cha:ignore todo_comment
539/// Collect field references (self.xxx) from a function body.
540fn 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
563/// Extract field names from a struct body.
564/// Returns (field_count, field_names, callback_collection_field_names).
565fn 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
587/// Check if a type looks like a collection of callbacks: Vec<Box<dyn Fn*>>, Vec<fn(...)>.
588fn 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
595/// Collect field names checked for None/null in match/if-let patterns.
596fn 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(x) = self.field { ... }
608        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
631/// Extract self.field names from a condition text containing null checks.
632fn 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
649/// Extract the variable/field being dispatched on in a match expression.
650fn 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
670/// Count optional parameters (those with default values or Option<T> type).
671fn 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
688/// Count self.method() calls in a function body (for Template Method detection).
689fn 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
709/// Structural Observer detection: method body iterates a callback collection field and calls elements.
710/// Pattern: `for cb in &self.field { cb(...) }` or `self.field.iter().for_each(|cb| cb(...))`
711fn 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    // for x in &self.field { x(...) }
724    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    // self.field.iter().for_each(|cb| cb(...))
733    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}