codegraph_c/
visitor.rs

1//! AST visitor for extracting C entities
2//!
3//! This visitor traverses the tree-sitter AST and extracts:
4//! - Functions (with complexity metrics)
5//! - Structs, unions, enums
6//! - Include directives
7//! - Function calls (for call graph building)
8
9use codegraph_parser_api::{
10    ClassEntity, ComplexityBuilder, ComplexityMetrics, Field, FunctionEntity, ImportRelation,
11    Parameter, ParserConfig,
12};
13use tree_sitter::Node;
14
15/// Represents a function call found in the source
16#[derive(Debug, Clone)]
17pub struct FunctionCall {
18    /// Name of the called function
19    pub callee: String,
20    /// Line number where the call occurs
21    pub line: usize,
22    /// Name of the calling function (if inside a function)
23    pub caller: Option<String>,
24}
25
26pub struct CVisitor<'a> {
27    pub source: &'a [u8],
28    #[allow(dead_code)]
29    pub config: ParserConfig,
30    pub functions: Vec<FunctionEntity>,
31    pub structs: Vec<ClassEntity>,
32    pub imports: Vec<ImportRelation>,
33    /// Function calls extracted from the source
34    pub calls: Vec<FunctionCall>,
35    /// Whether to extract function calls
36    extract_calls: bool,
37    /// Current function being visited (for tracking caller)
38    current_function: Option<String>,
39}
40
41impl<'a> CVisitor<'a> {
42    pub fn new(source: &'a [u8], config: ParserConfig) -> Self {
43        Self {
44            source,
45            config,
46            functions: Vec::new(),
47            structs: Vec::new(),
48            imports: Vec::new(),
49            calls: Vec::new(),
50            extract_calls: false,
51            current_function: None,
52        }
53    }
54
55    /// Enable or disable call extraction
56    pub fn set_extract_calls(&mut self, extract: bool) {
57        self.extract_calls = extract;
58    }
59
60    fn node_text(&self, node: Node) -> String {
61        node.utf8_text(self.source).unwrap_or("").to_string()
62    }
63
64    pub fn visit_node(&mut self, node: Node) {
65        // Skip ERROR nodes - tree-sitter marks unparseable sections as ERROR
66        // We continue visiting children to extract what we can
67        if node.is_error() {
68            // Still visit children of ERROR nodes to extract valid nested content
69            let mut cursor = node.walk();
70            for child in node.children(&mut cursor) {
71                self.visit_node(child);
72            }
73            return;
74        }
75
76        match node.kind() {
77            "function_definition" => self.visit_function(node),
78            "struct_specifier" => self.visit_struct(node),
79            "union_specifier" => self.visit_union(node),
80            "enum_specifier" => self.visit_enum(node),
81            "preproc_include" => self.visit_include(node),
82            "call_expression" if self.extract_calls => self.visit_call(node),
83            _ => {}
84        }
85
86        // Don't recurse into function bodies for top-level visits
87        // (we handle them specially in visit_function)
88        if node.kind() != "function_definition" {
89            let mut cursor = node.walk();
90            for child in node.children(&mut cursor) {
91                self.visit_node(child);
92            }
93        } else {
94            // For function definitions, only recurse into the body for calls
95            // but not for other top-level entities
96            let mut cursor = node.walk();
97            for child in node.children(&mut cursor) {
98                if child.kind() != "compound_statement" {
99                    self.visit_node(child);
100                } else if self.extract_calls {
101                    // Visit body for call extraction
102                    self.visit_node_for_calls(child);
103                }
104            }
105        }
106    }
107
108    /// Visit nodes specifically for call extraction (doesn't extract entities)
109    fn visit_node_for_calls(&mut self, node: Node) {
110        if node.is_error() {
111            return;
112        }
113
114        if node.kind() == "call_expression" {
115            self.visit_call(node);
116        }
117
118        let mut cursor = node.walk();
119        for child in node.children(&mut cursor) {
120            self.visit_node_for_calls(child);
121        }
122    }
123
124    /// Extract a function call
125    fn visit_call(&mut self, node: Node) {
126        // call_expression has a "function" field that contains the callee
127        if let Some(function_node) = node.child_by_field_name("function") {
128            let callee = match function_node.kind() {
129                "identifier" => self.node_text(function_node),
130                "field_expression" => {
131                    // For method calls like obj->method or obj.method
132                    if let Some(field) = function_node.child_by_field_name("field") {
133                        self.node_text(field)
134                    } else {
135                        self.node_text(function_node)
136                    }
137                }
138                "parenthesized_expression" => {
139                    // Function pointer call: (*func_ptr)(args)
140                    "(*indirect)".to_string()
141                }
142                _ => self.node_text(function_node),
143            };
144
145            if !callee.is_empty() {
146                self.calls.push(FunctionCall {
147                    callee,
148                    line: node.start_position().row + 1,
149                    caller: self.current_function.clone(),
150                });
151            }
152        }
153    }
154
155    fn visit_function(&mut self, node: Node) {
156        let mut name = String::new();
157        let mut return_type = String::new();
158        let mut parameters = Vec::new();
159        let mut is_static = false;
160
161        // Check for storage class specifier (static)
162        let mut cursor = node.walk();
163        for child in node.children(&mut cursor) {
164            if child.kind() == "storage_class_specifier" {
165                let text = self.node_text(child);
166                if text == "static" {
167                    is_static = true;
168                }
169            }
170        }
171
172        // Extract return type
173        if let Some(type_node) = node.child_by_field_name("type") {
174            return_type = self.extract_type_string(type_node);
175        }
176
177        // Extract function name and parameters from declarator
178        if let Some(declarator) = node.child_by_field_name("declarator") {
179            self.extract_function_declarator(declarator, &mut name, &mut parameters);
180        }
181
182        // Set current function for call tracking
183        let prev_function = self.current_function.take();
184        if !name.is_empty() {
185            self.current_function = Some(name.clone());
186        }
187
188        // Calculate complexity from function body
189        let complexity = node
190            .child_by_field_name("body")
191            .map(|body| self.calculate_complexity(body));
192
193        let visibility = if is_static { "private" } else { "public" };
194
195        let signature = self
196            .node_text(node)
197            .lines()
198            .next()
199            .unwrap_or("")
200            .to_string();
201
202        let func = FunctionEntity {
203            name,
204            signature,
205            visibility: visibility.to_string(),
206            line_start: node.start_position().row + 1,
207            line_end: node.end_position().row + 1,
208            is_async: false,
209            is_test: false,
210            is_static,
211            is_abstract: false,
212            parameters,
213            return_type: if return_type.is_empty() {
214                None
215            } else {
216                Some(return_type)
217            },
218            doc_comment: None,
219            attributes: Vec::new(),
220            parent_class: None,
221            complexity,
222        };
223
224        self.functions.push(func);
225
226        // Restore previous function context
227        self.current_function = prev_function;
228    }
229
230    fn extract_function_declarator(
231        &self,
232        node: Node,
233        name: &mut String,
234        parameters: &mut Vec<Parameter>,
235    ) {
236        match node.kind() {
237            "function_declarator" => {
238                // Get function name from nested declarator
239                if let Some(decl) = node.child_by_field_name("declarator") {
240                    *name = self.extract_identifier(decl);
241                }
242                // Get parameters
243                if let Some(params) = node.child_by_field_name("parameters") {
244                    self.extract_parameters(params, parameters);
245                }
246            }
247            "pointer_declarator" => {
248                // Handle pointer return type: int *func()
249                let mut cursor = node.walk();
250                for child in node.children(&mut cursor) {
251                    if child.kind() == "function_declarator" {
252                        self.extract_function_declarator(child, name, parameters);
253                        return;
254                    }
255                }
256            }
257            "identifier" => {
258                *name = self.node_text(node);
259            }
260            _ => {}
261        }
262    }
263
264    fn extract_identifier(&self, node: Node) -> String {
265        match node.kind() {
266            "identifier" => self.node_text(node),
267            "pointer_declarator" => {
268                let mut cursor = node.walk();
269                for child in node.children(&mut cursor) {
270                    let id = self.extract_identifier(child);
271                    if !id.is_empty() {
272                        return id;
273                    }
274                }
275                String::new()
276            }
277            _ => {
278                let mut cursor = node.walk();
279                for child in node.children(&mut cursor) {
280                    let id = self.extract_identifier(child);
281                    if !id.is_empty() {
282                        return id;
283                    }
284                }
285                String::new()
286            }
287        }
288    }
289
290    fn extract_parameters(&self, node: Node, parameters: &mut Vec<Parameter>) {
291        let mut cursor = node.walk();
292        for child in node.children(&mut cursor) {
293            if child.kind() == "parameter_declaration" {
294                if let Some(param) = self.extract_parameter(child) {
295                    parameters.push(param);
296                }
297            } else if child.kind() == "variadic_parameter" {
298                parameters.push(Parameter {
299                    name: "...".to_string(),
300                    type_annotation: Some("...".to_string()),
301                    default_value: None,
302                    is_variadic: true,
303                });
304            }
305        }
306    }
307
308    fn extract_parameter(&self, node: Node) -> Option<Parameter> {
309        let mut type_str = String::new();
310        let mut name = String::new();
311
312        // Extract type
313        if let Some(type_node) = node.child_by_field_name("type") {
314            type_str = self.extract_type_string(type_node);
315        }
316
317        // Extract name from declarator
318        if let Some(declarator) = node.child_by_field_name("declarator") {
319            let (decl_name, pointer_prefix) = self.extract_declarator_info(declarator);
320            name = decl_name;
321            if !pointer_prefix.is_empty() {
322                type_str = format!("{type_str}{pointer_prefix}");
323            }
324        }
325
326        // Handle case where there's no declarator (just type)
327        if name.is_empty() {
328            name = "param".to_string();
329        }
330
331        Some(Parameter {
332            name,
333            type_annotation: if type_str.is_empty() {
334                None
335            } else {
336                Some(type_str)
337            },
338            default_value: None,
339            is_variadic: false,
340        })
341    }
342
343    fn extract_declarator_info(&self, node: Node) -> (String, String) {
344        match node.kind() {
345            "identifier" | "field_identifier" => (self.node_text(node), String::new()),
346            "pointer_declarator" => {
347                let mut pointer_count = 0;
348                let mut cursor = node.walk();
349                for child in node.children(&mut cursor) {
350                    if child.kind() == "*" {
351                        pointer_count += 1;
352                    } else {
353                        let (name, extra_ptrs) = self.extract_declarator_info(child);
354                        if !name.is_empty() {
355                            return (name, "*".repeat(pointer_count) + &extra_ptrs);
356                        }
357                    }
358                }
359                (String::new(), "*".repeat(pointer_count))
360            }
361            "array_declarator" => {
362                if let Some(decl) = node.child_by_field_name("declarator") {
363                    let (name, _) = self.extract_declarator_info(decl);
364                    return (name, "[]".to_string());
365                }
366                (String::new(), "[]".to_string())
367            }
368            _ => {
369                let mut cursor = node.walk();
370                for child in node.children(&mut cursor) {
371                    let (name, ptrs) = self.extract_declarator_info(child);
372                    if !name.is_empty() {
373                        return (name, ptrs);
374                    }
375                }
376                (String::new(), String::new())
377            }
378        }
379    }
380
381    fn extract_type_string(&self, node: Node) -> String {
382        let text = self.node_text(node);
383        text.trim().to_string()
384    }
385
386    fn visit_struct(&mut self, node: Node) {
387        // Only extract structs that have a body (not forward declarations)
388        let has_body = node.child_by_field_name("body").is_some();
389        if !has_body {
390            return;
391        }
392
393        let name = node
394            .child_by_field_name("name")
395            .map(|n| self.node_text(n))
396            .unwrap_or_else(|| format!("__anon_struct_{}", node.start_position().row + 1));
397
398        let fields = self.extract_struct_fields(node);
399
400        let struct_entity = ClassEntity {
401            name,
402            visibility: "public".to_string(),
403            line_start: node.start_position().row + 1,
404            line_end: node.end_position().row + 1,
405            is_abstract: false,
406            is_interface: false,
407            base_classes: Vec::new(),
408            implemented_traits: Vec::new(),
409            methods: Vec::new(),
410            fields,
411            doc_comment: None,
412            attributes: vec!["struct".to_string()],
413            type_parameters: Vec::new(),
414        };
415
416        self.structs.push(struct_entity);
417    }
418
419    fn visit_union(&mut self, node: Node) {
420        // Only extract unions that have a body
421        let has_body = node.child_by_field_name("body").is_some();
422        if !has_body {
423            return;
424        }
425
426        let name = node
427            .child_by_field_name("name")
428            .map(|n| self.node_text(n))
429            .unwrap_or_else(|| format!("__anon_union_{}", node.start_position().row + 1));
430
431        let fields = self.extract_struct_fields(node);
432
433        let union_entity = ClassEntity {
434            name,
435            visibility: "public".to_string(),
436            line_start: node.start_position().row + 1,
437            line_end: node.end_position().row + 1,
438            is_abstract: false,
439            is_interface: false,
440            base_classes: Vec::new(),
441            implemented_traits: Vec::new(),
442            methods: Vec::new(),
443            fields,
444            doc_comment: None,
445            attributes: vec!["union".to_string()],
446            type_parameters: Vec::new(),
447        };
448
449        self.structs.push(union_entity);
450    }
451
452    fn visit_enum(&mut self, node: Node) {
453        // Only extract enums that have a body
454        let has_body = node.child_by_field_name("body").is_some();
455        if !has_body {
456            return;
457        }
458
459        let name = node
460            .child_by_field_name("name")
461            .map(|n| self.node_text(n))
462            .unwrap_or_else(|| format!("__anon_enum_{}", node.start_position().row + 1));
463
464        // Extract enum constants as fields
465        let mut fields = Vec::new();
466        if let Some(body) = node.child_by_field_name("body") {
467            let mut cursor = body.walk();
468            for child in body.children(&mut cursor) {
469                if child.kind() == "enumerator" {
470                    let enumerator_name = child
471                        .child_by_field_name("name")
472                        .map(|n| self.node_text(n))
473                        .unwrap_or_default();
474
475                    let default_value = child
476                        .child_by_field_name("value")
477                        .map(|n| self.node_text(n));
478
479                    if !enumerator_name.is_empty() {
480                        fields.push(Field {
481                            name: enumerator_name,
482                            type_annotation: Some("int".to_string()),
483                            visibility: "public".to_string(),
484                            is_static: true,
485                            is_constant: true,
486                            default_value,
487                        });
488                    }
489                }
490            }
491        }
492
493        let enum_entity = ClassEntity {
494            name,
495            visibility: "public".to_string(),
496            line_start: node.start_position().row + 1,
497            line_end: node.end_position().row + 1,
498            is_abstract: false,
499            is_interface: false,
500            base_classes: Vec::new(),
501            implemented_traits: Vec::new(),
502            methods: Vec::new(),
503            fields,
504            doc_comment: None,
505            attributes: vec!["enum".to_string()],
506            type_parameters: Vec::new(),
507        };
508
509        self.structs.push(enum_entity);
510    }
511
512    fn extract_struct_fields(&self, node: Node) -> Vec<Field> {
513        let mut fields = Vec::new();
514
515        if let Some(body) = node.child_by_field_name("body") {
516            let mut cursor = body.walk();
517            for child in body.children(&mut cursor) {
518                if child.kind() == "field_declaration" {
519                    let field_type = child
520                        .child_by_field_name("type")
521                        .map(|n| self.extract_type_string(n))
522                        .unwrap_or_default();
523
524                    // Try to get declarator - field can have multiple declarators
525                    // e.g., "int x, y;" has two declarators
526                    let mut field_cursor = child.walk();
527                    for field_child in child.children(&mut field_cursor) {
528                        // Handle different declarator types
529                        match field_child.kind() {
530                            "field_identifier" => {
531                                // Simple field: int x;
532                                let field_name = self.node_text(field_child);
533                                if !field_name.is_empty() {
534                                    fields.push(Field {
535                                        name: field_name,
536                                        type_annotation: Some(field_type.clone()),
537                                        visibility: "public".to_string(),
538                                        is_static: false,
539                                        is_constant: false,
540                                        default_value: None,
541                                    });
542                                }
543                            }
544                            "pointer_declarator" | "array_declarator" => {
545                                // Pointer/array field: char *name; int arr[10];
546                                let (field_name, pointer_suffix) =
547                                    self.extract_declarator_info(field_child);
548                                let full_type = if pointer_suffix.is_empty() {
549                                    field_type.clone()
550                                } else {
551                                    format!("{field_type}{pointer_suffix}")
552                                };
553
554                                if !field_name.is_empty() {
555                                    fields.push(Field {
556                                        name: field_name,
557                                        type_annotation: Some(full_type),
558                                        visibility: "public".to_string(),
559                                        is_static: false,
560                                        is_constant: false,
561                                        default_value: None,
562                                    });
563                                }
564                            }
565                            _ => {}
566                        }
567                    }
568                }
569            }
570        }
571
572        fields
573    }
574
575    fn visit_include(&mut self, node: Node) {
576        let mut path = String::new();
577        let mut is_system = false;
578
579        let mut cursor = node.walk();
580        for child in node.children(&mut cursor) {
581            match child.kind() {
582                "system_lib_string" => {
583                    // Remove < and >
584                    path = self.node_text(child);
585                    path = path
586                        .trim_start_matches('<')
587                        .trim_end_matches('>')
588                        .to_string();
589                    is_system = true;
590                }
591                "string_literal" => {
592                    // Remove quotes
593                    path = self.node_text(child);
594                    path = path.trim_matches('"').to_string();
595                    is_system = false;
596                }
597                _ => {}
598            }
599        }
600
601        if !path.is_empty() {
602            let import = ImportRelation {
603                importer: "current_file".to_string(),
604                imported: path,
605                symbols: Vec::new(),
606                is_wildcard: true, // C includes always import everything
607                alias: if is_system {
608                    Some("system".to_string())
609                } else {
610                    None
611                },
612            };
613            self.imports.push(import);
614        }
615    }
616
617    fn calculate_complexity(&self, body: Node) -> ComplexityMetrics {
618        let mut builder = ComplexityBuilder::new();
619        self.visit_for_complexity(body, &mut builder);
620        builder.build()
621    }
622
623    fn visit_for_complexity(&self, node: Node, builder: &mut ComplexityBuilder) {
624        match node.kind() {
625            "if_statement" => {
626                builder.add_branch();
627                builder.enter_scope();
628            }
629            "else_clause" => {
630                builder.add_branch();
631            }
632            "for_statement" => {
633                builder.add_loop();
634                builder.enter_scope();
635            }
636            "while_statement" => {
637                builder.add_loop();
638                builder.enter_scope();
639            }
640            "do_statement" => {
641                builder.add_loop();
642                builder.enter_scope();
643            }
644            "switch_statement" => {
645                builder.enter_scope();
646            }
647            "case_statement" => {
648                builder.add_branch();
649            }
650            "default_statement" => {
651                builder.add_branch();
652            }
653            "conditional_expression" => {
654                // Ternary operator ?:
655                builder.add_branch();
656            }
657            "goto_statement" => {
658                // C-specific: goto adds complexity
659                builder.add_branch();
660            }
661            "binary_expression" => {
662                // Check for && and ||
663                if let Some(op) = node.child_by_field_name("operator") {
664                    let op_text = self.node_text(op);
665                    if op_text == "&&" || op_text == "||" {
666                        builder.add_logical_operator();
667                    }
668                }
669            }
670            "return_statement" => {
671                // Early return detection (not at the end of function)
672                // For now, count all returns except the last one would require more context
673                // Simplified: just count as potential early return
674            }
675            _ => {}
676        }
677
678        let mut cursor = node.walk();
679        for child in node.children(&mut cursor) {
680            self.visit_for_complexity(child, builder);
681        }
682
683        // Exit scope for control structures
684        match node.kind() {
685            "if_statement" | "for_statement" | "while_statement" | "do_statement"
686            | "switch_statement" => {
687                builder.exit_scope();
688            }
689            _ => {}
690        }
691    }
692}
693
694#[cfg(test)]
695mod tests {
696    use super::*;
697    use tree_sitter::Parser;
698
699    fn parse_and_visit(source: &[u8]) -> CVisitor {
700        let mut parser = Parser::new();
701        parser.set_language(tree_sitter_c::language()).unwrap();
702        let tree = parser.parse(source, None).unwrap();
703
704        let mut visitor = CVisitor::new(source, ParserConfig::default());
705        visitor.visit_node(tree.root_node());
706        visitor
707    }
708
709    #[test]
710    fn test_visitor_basics() {
711        let visitor = CVisitor::new(b"int main() {}", ParserConfig::default());
712        assert_eq!(visitor.functions.len(), 0);
713        assert_eq!(visitor.structs.len(), 0);
714        assert_eq!(visitor.imports.len(), 0);
715    }
716
717    #[test]
718    fn test_visitor_function_extraction() {
719        let source = b"int greet(char *name) { return 0; }";
720        let visitor = parse_and_visit(source);
721
722        assert_eq!(visitor.functions.len(), 1);
723        assert_eq!(visitor.functions[0].name, "greet");
724        assert_eq!(visitor.functions[0].return_type, Some("int".to_string()));
725    }
726
727    #[test]
728    fn test_visitor_static_function() {
729        let source = b"static void helper() {}";
730        let visitor = parse_and_visit(source);
731
732        assert_eq!(visitor.functions.len(), 1);
733        assert_eq!(visitor.functions[0].visibility, "private");
734        assert!(visitor.functions[0].is_static);
735    }
736
737    #[test]
738    fn test_visitor_struct_extraction() {
739        let source = b"struct Person { char *name; int age; };";
740        let visitor = parse_and_visit(source);
741
742        assert_eq!(visitor.structs.len(), 1);
743        assert_eq!(visitor.structs[0].name, "Person");
744        assert_eq!(visitor.structs[0].fields.len(), 2);
745        assert!(visitor.structs[0]
746            .attributes
747            .contains(&"struct".to_string()));
748    }
749
750    #[test]
751    fn test_visitor_union_extraction() {
752        let source = b"union Data { int i; float f; };";
753        let visitor = parse_and_visit(source);
754
755        assert_eq!(visitor.structs.len(), 1);
756        assert_eq!(visitor.structs[0].name, "Data");
757        assert!(visitor.structs[0].attributes.contains(&"union".to_string()));
758    }
759
760    #[test]
761    fn test_visitor_enum_extraction() {
762        let source = b"enum Color { RED, GREEN, BLUE };";
763        let visitor = parse_and_visit(source);
764
765        assert_eq!(visitor.structs.len(), 1);
766        assert_eq!(visitor.structs[0].name, "Color");
767        assert!(visitor.structs[0].attributes.contains(&"enum".to_string()));
768        assert_eq!(visitor.structs[0].fields.len(), 3);
769    }
770
771    #[test]
772    fn test_visitor_system_include() {
773        let source = b"#include <stdio.h>";
774        let visitor = parse_and_visit(source);
775
776        assert_eq!(visitor.imports.len(), 1);
777        assert_eq!(visitor.imports[0].imported, "stdio.h");
778        assert_eq!(visitor.imports[0].alias, Some("system".to_string()));
779    }
780
781    #[test]
782    fn test_visitor_local_include() {
783        let source = b"#include \"myheader.h\"";
784        let visitor = parse_and_visit(source);
785
786        assert_eq!(visitor.imports.len(), 1);
787        assert_eq!(visitor.imports[0].imported, "myheader.h");
788        assert_eq!(visitor.imports[0].alias, None);
789    }
790
791    #[test]
792    fn test_visitor_multiple_includes() {
793        let source = b"#include <stdio.h>\n#include <stdlib.h>\n#include \"myheader.h\"";
794        let visitor = parse_and_visit(source);
795
796        assert_eq!(visitor.imports.len(), 3);
797    }
798
799    #[test]
800    fn test_visitor_function_with_params() {
801        let source = b"int add(int a, int b) { return a + b; }";
802        let visitor = parse_and_visit(source);
803
804        assert_eq!(visitor.functions.len(), 1);
805        assert_eq!(visitor.functions[0].parameters.len(), 2);
806        assert_eq!(visitor.functions[0].parameters[0].name, "a");
807        assert_eq!(visitor.functions[0].parameters[1].name, "b");
808    }
809
810    #[test]
811    fn test_visitor_pointer_params() {
812        let source = b"void process(int *arr, char **argv) {}";
813        let visitor = parse_and_visit(source);
814
815        assert_eq!(visitor.functions.len(), 1);
816        assert_eq!(visitor.functions[0].parameters.len(), 2);
817        // Pointer types should be captured
818        let param1 = &visitor.functions[0].parameters[0];
819        assert!(param1
820            .type_annotation
821            .as_ref()
822            .map(|t| t.contains("*"))
823            .unwrap_or(false));
824    }
825
826    #[test]
827    fn test_visitor_variadic_function() {
828        let source = b"int printf(const char *fmt, ...) { return 0; }";
829        let visitor = parse_and_visit(source);
830
831        assert_eq!(visitor.functions.len(), 1);
832        let params = &visitor.functions[0].parameters;
833        assert!(params.iter().any(|p| p.is_variadic));
834    }
835
836    #[test]
837    fn test_visitor_complexity_if() {
838        let source = b"void test() { if (1) {} }";
839        let visitor = parse_and_visit(source);
840
841        assert_eq!(visitor.functions.len(), 1);
842        let complexity = visitor.functions[0].complexity.as_ref().unwrap();
843        assert!(complexity.branches >= 1);
844    }
845
846    #[test]
847    fn test_visitor_complexity_loop() {
848        let source = b"void test() { for (int i = 0; i < 10; i++) {} while(1) {} }";
849        let visitor = parse_and_visit(source);
850
851        assert_eq!(visitor.functions.len(), 1);
852        let complexity = visitor.functions[0].complexity.as_ref().unwrap();
853        assert!(complexity.loops >= 2);
854    }
855
856    #[test]
857    fn test_visitor_complexity_switch() {
858        let source =
859            b"void test(int x) { switch(x) { case 1: break; case 2: break; default: break; } }";
860        let visitor = parse_and_visit(source);
861
862        assert_eq!(visitor.functions.len(), 1);
863        let complexity = visitor.functions[0].complexity.as_ref().unwrap();
864        assert!(complexity.branches >= 3);
865    }
866
867    #[test]
868    fn test_visitor_complexity_logical_operators() {
869        let source = b"void test(int a, int b) { if (a && b || a) {} }";
870        let visitor = parse_and_visit(source);
871
872        assert_eq!(visitor.functions.len(), 1);
873        let complexity = visitor.functions[0].complexity.as_ref().unwrap();
874        assert!(complexity.logical_operators >= 2);
875    }
876
877    #[test]
878    fn test_visitor_complexity_goto() {
879        let source = b"void test() { label: goto label; }";
880        let visitor = parse_and_visit(source);
881
882        assert_eq!(visitor.functions.len(), 1);
883        let complexity = visitor.functions[0].complexity.as_ref().unwrap();
884        // goto should add to branches
885        assert!(complexity.branches >= 1);
886    }
887
888    #[test]
889    fn test_visitor_forward_declaration_ignored() {
890        let source = b"struct Forward;";
891        let visitor = parse_and_visit(source);
892
893        // Forward declarations should not be extracted
894        assert_eq!(visitor.structs.len(), 0);
895    }
896
897    #[test]
898    fn test_visitor_anonymous_struct() {
899        let source = b"struct { int x; int y; } point;";
900        let visitor = parse_and_visit(source);
901
902        assert_eq!(visitor.structs.len(), 1);
903        // Anonymous struct should get a generated name
904        assert!(visitor.structs[0].name.starts_with("__anon_struct_"));
905    }
906
907    #[test]
908    fn test_visitor_enum_with_values() {
909        let source = b"enum Size { SMALL = 1, MEDIUM = 5, LARGE = 10 };";
910        let visitor = parse_and_visit(source);
911
912        assert_eq!(visitor.structs.len(), 1);
913        let enum_entity = &visitor.structs[0];
914        assert_eq!(enum_entity.fields.len(), 3);
915
916        // Check that values are captured
917        let small = enum_entity.fields.iter().find(|f| f.name == "SMALL");
918        assert!(small.is_some());
919        assert_eq!(small.unwrap().default_value, Some("1".to_string()));
920    }
921}