Skip to main content

cha_parser/
rust_lang.rs

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        // Pre-scan every `use` declaration so types in function signatures
26        // can be resolved to project-local / external / primitive / unknown.
27        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/// Accumulator for collected AST items.
44#[derive(Default)]
45struct Collector {
46    functions: Vec<FunctionInfo>,
47    classes: Vec<ClassInfo>,
48    imports: Vec<ImportInfo>,
49    type_aliases: Vec<(String, String)>,
50}
51
52/// Bundles source bytes and collector to eliminate repeated parameter passing.
53struct ParseContext<'a> {
54    src: &'a [u8],
55    col: Collector,
56    last_self_call_count: usize,
57    last_has_notify: bool,
58    /// Callback collection field names per class name.
59    callback_fields: std::collections::HashMap<String, Vec<String>>,
60    /// Short type name -> resolved origin (built in a pre-scan pass).
61    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    /// Treat `mod foo;` as an import of `foo.rs` (file-level dependency).
125    fn push_mod_as_import(&mut self, node: Node) {
126        // Skip inline mod blocks: `mod foo { ... }` has a body
127        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                // Structural Observer: method iterates a callback field and calls elements
197                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        // Store extra signals in the class via the caller
204        self.last_self_call_count = max_self_calls;
205        self.last_has_notify = has_notify;
206        (methods, delegating, has_behavior)
207    }
208}
209
210// Extract and push a function item node.
211fn 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 switch_arm_values = body
281        .map(|b| crate::rust_imports::rust_collect_arm_values(b, src))
282        .unwrap_or_default();
283    let external_refs = body
284        .map(|b| collect_external_refs(b, src))
285        .unwrap_or_default();
286    let is_delegating = body.map(|b| check_delegating(b, src)).unwrap_or(false);
287    let return_type = crate::rust_imports::rust_return_type(node, src, imports_map);
288    Some(FunctionInfo {
289        name,
290        start_line,
291        end_line,
292        name_col,
293        name_end_col,
294        line_count: end_line - start_line + 1,
295        complexity: count_complexity(node),
296        body_hash,
297        is_exported: false,
298        parameter_count,
299        parameter_types,
300        parameter_names: crate::rust_imports::rust_param_names(node, src),
301        chain_depth,
302        switch_arms,
303        switch_arm_values,
304        external_refs,
305        is_delegating,
306        comment_lines: count_comment_lines(node, src),
307        referenced_fields: collect_field_refs(body, src),
308        null_check_fields: collect_null_checks(body, src),
309        switch_dispatch_target: extract_switch_target(body, src),
310        optional_param_count: count_optional_params(node, src),
311        called_functions: collect_calls_rs(body, src),
312        cognitive_complexity: body.map(cognitive_complexity_rs).unwrap_or(0),
313        return_type,
314    })
315}
316
317fn extract_struct(node: Node, src: &[u8]) -> Option<(ClassInfo, Vec<String>)> {
318    let name_node = node.child_by_field_name("name")?;
319    let name = node_text(name_node, src).to_string();
320    let name_col = name_node.start_position().column;
321    let name_end_col = name_node.end_position().column;
322    let start_line = node.start_position().row + 1;
323    let end_line = node.end_position().row + 1;
324    let (field_count, field_names, callback_fields) = extract_fields(node, src);
325    let is_interface = node.kind() == "trait_item";
326    let has_listener_field = !callback_fields.is_empty();
327    Some((
328        ClassInfo {
329            name,
330            start_line,
331            end_line,
332            name_col,
333            name_end_col,
334            method_count: 0,
335            line_count: end_line - start_line + 1,
336            is_exported: false,
337            delegating_method_count: 0,
338            field_count,
339            field_names,
340            field_types: Vec::new(),
341            has_behavior: false,
342            is_interface,
343            parent_name: None,
344            override_count: 0,
345            self_call_count: 0,
346            has_listener_field,
347            has_notify_method: false,
348        },
349        callback_fields,
350    ))
351}
352
353fn count_parameters(node: Node) -> usize {
354    let params = match node.child_by_field_name("parameters") {
355        Some(p) => p,
356        None => return 0,
357    };
358    let mut cursor = params.walk();
359    params
360        .children(&mut cursor)
361        .filter(|c| c.kind() == "parameter" || c.kind() == "self_parameter")
362        .count()
363}
364
365fn extract_param_types(
366    node: Node,
367    src: &[u8],
368    imports_map: &crate::type_ref::ImportsMap,
369) -> Vec<cha_core::TypeRef> {
370    let params = match node.child_by_field_name("parameters") {
371        Some(p) => p,
372        None => return vec![],
373    };
374    let mut types = Vec::new();
375    let mut cursor = params.walk();
376    for child in params.children(&mut cursor) {
377        if child.kind() == "parameter"
378            && let Some(ty) = child.child_by_field_name("type")
379        {
380            types.push(crate::type_ref::resolve(node_text(ty, src), imports_map));
381        }
382    }
383    types
384}
385
386fn max_chain_depth(node: Node) -> usize {
387    let mut max = 0;
388    walk_chain_depth(node, &mut max);
389    max
390}
391
392fn walk_chain_depth(node: Node, max: &mut usize) {
393    if node.kind() == "field_expression" {
394        let depth = measure_chain(node);
395        if depth > *max {
396            *max = depth;
397        }
398    }
399    let mut cursor = node.walk();
400    for child in node.children(&mut cursor) {
401        walk_chain_depth(child, max);
402    }
403}
404
405/// Count consecutive field accesses (a.b.c.d), skipping method calls.
406fn measure_chain(node: Node) -> usize {
407    let mut depth = 0;
408    let mut current = node;
409    while current.kind() == "field_expression" {
410        depth += 1;
411        if let Some(obj) = current.child_by_field_name("value") {
412            current = obj;
413        } else {
414            break;
415        }
416    }
417    depth
418}
419
420fn count_switch_arms(node: Node) -> usize {
421    let mut count = 0;
422    walk_switch_arms(node, &mut count);
423    count
424}
425
426fn walk_switch_arms(node: Node, count: &mut usize) {
427    if node.kind() == "match_arm" {
428        *count += 1;
429    }
430    let mut cursor = node.walk();
431    for child in node.children(&mut cursor) {
432        walk_switch_arms(child, count);
433    }
434}
435
436fn collect_external_refs(node: Node, src: &[u8]) -> Vec<String> {
437    let mut refs = Vec::new();
438    walk_external_refs(node, src, &mut refs);
439    refs.sort();
440    refs.dedup();
441    refs
442}
443
444/// Walk a field_expression chain to its root identifier.
445fn field_chain_root(node: Node) -> Node {
446    let mut current = node;
447    while current.kind() == "field_expression" {
448        match current.child_by_field_name("value") {
449            Some(child) => current = child,
450            None => break,
451        }
452    }
453    current
454}
455
456fn walk_external_refs(node: Node, src: &[u8], refs: &mut Vec<String>) {
457    if node.kind() == "field_expression" {
458        // Walk to the root object of the chain (a.b.c → a)
459        let root = field_chain_root(node);
460        let text = node_text(root, src);
461        if text != "self" && !text.is_empty() {
462            refs.push(text.to_string());
463        }
464    }
465    let mut cursor = node.walk();
466    for child in node.children(&mut cursor) {
467        walk_external_refs(child, src, refs);
468    }
469}
470
471/// Extract the single statement from a block body, if exactly one exists.
472fn single_stmt(body: Node) -> Option<Node> {
473    let mut cursor = body.walk();
474    let stmts: Vec<_> = body
475        .children(&mut cursor)
476        .filter(|c| c.kind() != "{" && c.kind() != "}")
477        .collect();
478    (stmts.len() == 1).then(|| stmts[0])
479}
480
481/// Check if a node is a method call on an external object (not self).
482fn is_external_call(node: Node, src: &[u8]) -> bool {
483    node.kind() == "call_expression"
484        && node.child_by_field_name("function").is_some_and(|func| {
485            func.kind() == "field_expression"
486                && func
487                    .child_by_field_name("value")
488                    .is_some_and(|obj| node_text(obj, src) != "self")
489        })
490}
491
492fn check_delegating(body: Node, src: &[u8]) -> bool {
493    let Some(stmt) = single_stmt(body) else {
494        return false;
495    };
496    let expr = match stmt.kind() {
497        "expression_statement" => stmt.child(0).unwrap_or(stmt),
498        "return_expression" => stmt.child(1).unwrap_or(stmt),
499        _ => stmt,
500    };
501    is_external_call(expr, src)
502}
503
504/// Count comment lines inside a function node.
505fn count_comment_lines(node: Node, src: &[u8]) -> usize {
506    let mut count = 0;
507    let mut cursor = node.walk();
508    for child in node.children(&mut cursor) {
509        if child.kind() == "line_comment" || child.kind() == "block_comment" {
510            count += child.end_position().row - child.start_position().row + 1;
511        }
512    }
513    // Also recurse into body
514    if let Some(body) = node.child_by_field_name("body") {
515        count += count_comment_lines_recursive(body, src);
516    }
517    count
518}
519
520fn count_comment_lines_recursive(node: Node, _src: &[u8]) -> usize {
521    let mut count = 0;
522    let mut cursor = node.walk();
523    for child in node.children(&mut cursor) {
524        if child.kind() == "line_comment" || child.kind() == "block_comment" {
525            count += child.end_position().row - child.start_position().row + 1;
526        } else if child.child_count() > 0 {
527            count += count_comment_lines_recursive(child, _src);
528        }
529    }
530    count
531}
532
533// cha:ignore todo_comment
534/// Collect field references (self.xxx) from a function body.
535fn collect_field_refs(body: Option<Node>, src: &[u8]) -> Vec<String> {
536    let Some(body) = body else { return vec![] };
537    let mut refs = Vec::new();
538    collect_self_fields(body, src, &mut refs);
539    refs.sort();
540    refs.dedup();
541    refs
542}
543
544fn collect_self_fields(node: Node, src: &[u8], refs: &mut Vec<String>) {
545    if node.kind() == "field_expression"
546        && let Some(obj) = node.child_by_field_name("value")
547        && node_text(obj, src) == "self"
548        && let Some(field) = node.child_by_field_name("field")
549    {
550        refs.push(node_text(field, src).to_string());
551    }
552    let mut cursor = node.walk();
553    for child in node.children(&mut cursor) {
554        collect_self_fields(child, src, refs);
555    }
556}
557
558/// Extract field names from a struct body.
559/// Returns (field_count, field_names, callback_collection_field_names).
560fn extract_fields(node: Node, src: &[u8]) -> (usize, Vec<String>, Vec<String>) {
561    let mut names = Vec::new();
562    let mut callback_fields = Vec::new();
563    if let Some(body) = node.child_by_field_name("body") {
564        let mut cursor = body.walk();
565        for child in body.children(&mut cursor) {
566            if child.kind() == "field_declaration"
567                && let Some(name_node) = child.child_by_field_name("name")
568            {
569                let name = node_text(name_node, src).to_string();
570                if let Some(ty) = child.child_by_field_name("type")
571                    && is_callback_collection_type_rs(node_text(ty, src))
572                {
573                    callback_fields.push(name.clone());
574                }
575                names.push(name);
576            }
577        }
578    }
579    (names.len(), names, callback_fields)
580}
581
582/// Check if a type looks like a collection of callbacks: Vec<Box<dyn Fn*>>, Vec<fn(...)>.
583fn is_callback_collection_type_rs(ty: &str) -> bool {
584    if !ty.contains("Vec<") {
585        return false;
586    }
587    ty.contains("Fn(") || ty.contains("FnMut(") || ty.contains("FnOnce(") || ty.contains("fn(")
588}
589
590/// Collect field names checked for None/null in match/if-let patterns.
591fn collect_null_checks(body: Option<Node>, src: &[u8]) -> Vec<String> {
592    let Some(body) = body else { return vec![] };
593    let mut fields = Vec::new();
594    walk_null_checks_rs(body, src, &mut fields);
595    fields.sort();
596    fields.dedup();
597    fields
598}
599
600fn walk_null_checks_rs(node: Node, src: &[u8], fields: &mut Vec<String>) {
601    if node.kind() == "if_let_expression" {
602        // if let Some(x) = self.field { ... }
603        if let Some(pattern) = node.child_by_field_name("pattern")
604            && node_text(pattern, src).contains("Some")
605            && let Some(value) = node.child_by_field_name("value")
606        {
607            let vtext = node_text(value, src);
608            if let Some(f) = vtext.strip_prefix("self.") {
609                fields.push(f.to_string());
610            }
611        }
612    } else if node.kind() == "if_expression"
613        && let Some(cond) = node.child_by_field_name("condition")
614    {
615        let text = node_text(cond, src);
616        if text.contains("is_some") || text.contains("is_none") {
617            extract_null_checked_fields(text, fields);
618        }
619    }
620    let mut cursor = node.walk();
621    for child in node.children(&mut cursor) {
622        walk_null_checks_rs(child, src, fields);
623    }
624}
625
626/// Extract self.field names from a condition text containing null checks.
627fn extract_null_checked_fields(text: &str, fields: &mut Vec<String>) {
628    if !(text.contains("is_some") || text.contains("is_none") || text.contains("Some")) {
629        return;
630    }
631    for part in text.split("self.") {
632        if let Some(field) = part
633            .split(|c: char| !c.is_alphanumeric() && c != '_')
634            .next()
635            && !field.is_empty()
636            && field != "is_some"
637            && field != "is_none"
638        {
639            fields.push(field.to_string());
640        }
641    }
642}
643
644/// Extract the variable/field being dispatched on in a match expression.
645fn extract_switch_target(body: Option<Node>, src: &[u8]) -> Option<String> {
646    let body = body?;
647    find_match_target(body, src)
648}
649
650fn find_match_target(node: Node, src: &[u8]) -> Option<String> {
651    if node.kind() == "match_expression"
652        && let Some(value) = node.child_by_field_name("value")
653    {
654        return Some(node_text(value, src).to_string());
655    }
656    let mut cursor = node.walk();
657    for child in node.children(&mut cursor) {
658        if let Some(t) = find_match_target(child, src) {
659            return Some(t);
660        }
661    }
662    None
663}
664
665/// Count optional parameters (those with default values or Option<T> type).
666fn count_optional_params(node: Node, src: &[u8]) -> usize {
667    let Some(params) = node.child_by_field_name("parameters") else {
668        return 0;
669    };
670    let mut count = 0;
671    let mut cursor = params.walk();
672    for child in params.children(&mut cursor) {
673        if child.kind() == "parameter" {
674            let text = node_text(child, src);
675            if text.contains("Option<") {
676                count += 1;
677            }
678        }
679    }
680    count
681}
682
683/// Count self.method() calls in a function body (for Template Method detection).
684fn count_self_method_calls(body: Option<Node>, src: &[u8]) -> usize {
685    let Some(body) = body else { return 0 };
686    let mut count = 0;
687    walk_self_calls(body, src, &mut count);
688    count
689}
690
691fn walk_self_calls(node: Node, src: &[u8], count: &mut usize) {
692    if node.kind() == "call_expression"
693        && let Some(func) = node.child_by_field_name("function")
694        && node_text(func, src).starts_with("self.")
695    {
696        *count += 1;
697    }
698    let mut cursor = node.walk();
699    for child in node.children(&mut cursor) {
700        walk_self_calls(child, src, count);
701    }
702}
703
704/// Structural Observer detection: method body iterates a callback collection field and calls elements.
705/// Pattern: `for cb in &self.field { cb(...) }` or `self.field.iter().for_each(|cb| cb(...))`
706fn has_iterate_and_call(body: Option<Node>, src: &[u8], cb_fields: &[String]) -> bool {
707    let Some(body) = body else { return false };
708    for field in cb_fields {
709        let self_field = format!("self.{field}");
710        if walk_for_iterate_call(body, src, &self_field) {
711            return true;
712        }
713    }
714    false
715}
716
717fn walk_for_iterate_call(node: Node, src: &[u8], self_field: &str) -> bool {
718    // for x in &self.field { x(...) }
719    if node.kind() == "for_expression"
720        && let Some(value) = node.child_by_field_name("value")
721        && node_text(value, src).contains(self_field)
722        && let Some(loop_body) = node.child_by_field_name("body")
723        && has_call_expression(loop_body)
724    {
725        return true;
726    }
727    // self.field.iter().for_each(|cb| cb(...))
728    if node.kind() == "call_expression" {
729        let text = node_text(node, src);
730        if text.contains(self_field) && text.contains("for_each") {
731            return true;
732        }
733    }
734    let mut cursor = node.walk();
735    for child in node.children(&mut cursor) {
736        if walk_for_iterate_call(child, src, self_field) {
737            return true;
738        }
739    }
740    false
741}
742
743fn cognitive_complexity_rs(node: Node) -> usize {
744    let mut score = 0;
745    cc_walk_rs(node, 0, &mut score);
746    score
747}
748
749fn cc_walk_rs(node: Node, nesting: usize, score: &mut usize) {
750    match node.kind() {
751        "if_expression" => {
752            *score += 1 + nesting;
753            cc_children_rs(node, nesting + 1, score);
754            return;
755        }
756        "for_expression" | "while_expression" | "loop_expression" => {
757            *score += 1 + nesting;
758            cc_children_rs(node, nesting + 1, score);
759            return;
760        }
761        "match_expression" => {
762            *score += 1 + nesting;
763            cc_children_rs(node, nesting + 1, score);
764            return;
765        }
766        "else_clause" => {
767            *score += 1;
768        }
769        "binary_expression" => {
770            if let Some(op) = node.child_by_field_name("operator")
771                && (op.kind() == "&&" || op.kind() == "||")
772            {
773                *score += 1;
774            }
775        }
776        "closure_expression" => {
777            cc_children_rs(node, nesting + 1, score);
778            return;
779        }
780        _ => {}
781    }
782    cc_children_rs(node, nesting, score);
783}
784
785fn cc_children_rs(node: Node, nesting: usize, score: &mut usize) {
786    let mut cursor = node.walk();
787    for child in node.children(&mut cursor) {
788        cc_walk_rs(child, nesting, score);
789    }
790}
791
792fn collect_calls_rs(body: Option<tree_sitter::Node>, src: &[u8]) -> Vec<String> {
793    let Some(body) = body else { return Vec::new() };
794    let mut calls = Vec::new();
795    let mut cursor = body.walk();
796    visit_all(body, &mut cursor, &mut |n| {
797        if n.kind() == "call_expression"
798            && let Some(func) = n.child(0)
799        {
800            let name = node_text(func, src).to_string();
801            if !calls.contains(&name) {
802                calls.push(name);
803            }
804        }
805    });
806    calls
807}
808
809fn visit_all<F: FnMut(Node)>(node: Node, cursor: &mut tree_sitter::TreeCursor, f: &mut F) {
810    f(node);
811    if cursor.goto_first_child() {
812        loop {
813            visit_all(cursor.node(), cursor, f);
814            if !cursor.goto_next_sibling() {
815                break;
816            }
817        }
818        cursor.goto_parent();
819    }
820}
821
822fn collect_comments(root: Node, src: &[u8]) -> Vec<cha_core::CommentInfo> {
823    let mut comments = Vec::new();
824    let mut cursor = root.walk();
825    visit_all(root, &mut cursor, &mut |n| {
826        if n.kind().contains("comment") {
827            comments.push(cha_core::CommentInfo {
828                text: node_text(n, src).to_string(),
829                line: n.start_position().row + 1,
830            });
831        }
832    });
833    comments
834}
835
836fn has_call_expression(node: Node) -> bool {
837    if node.kind() == "call_expression" {
838        return true;
839    }
840    let mut cursor = node.walk();
841    for child in node.children(&mut cursor) {
842        if has_call_expression(child) {
843            return true;
844        }
845    }
846    false
847}