Skip to main content

depyler_analyzer/
lib.rs

1pub mod complexity;
2pub mod metrics;
3pub mod type_flow;
4
5// Re-export complexity functions for easier use
6pub use complexity::{
7    calculate_cognitive, calculate_cyclomatic, calculate_max_nesting, count_statements,
8};
9
10use anyhow::Result;
11use depyler_core::hir::{HirFunction, HirModule};
12use serde::{Deserialize, Serialize};
13
14#[cfg(test)]
15use depyler_annotations::TranspilationAnnotations;
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct AnalysisResult {
19    pub module_metrics: ModuleMetrics,
20    pub function_metrics: Vec<FunctionMetrics>,
21    pub type_coverage: TypeCoverage,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct ModuleMetrics {
26    pub total_functions: usize,
27    pub total_lines: usize,
28    pub avg_cyclomatic_complexity: f64,
29    pub max_cyclomatic_complexity: u32,
30    pub avg_cognitive_complexity: f64,
31    pub max_cognitive_complexity: u32,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct FunctionMetrics {
36    pub name: String,
37    pub cyclomatic_complexity: u32,
38    pub cognitive_complexity: u32,
39    pub lines_of_code: usize,
40    pub parameters: usize,
41    pub max_nesting_depth: usize,
42    pub has_type_annotations: bool,
43    pub return_type_annotated: bool,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct TypeCoverage {
48    pub total_parameters: usize,
49    pub annotated_parameters: usize,
50    pub total_functions: usize,
51    pub functions_with_return_type: usize,
52    pub coverage_percentage: f64,
53}
54
55pub struct Analyzer {
56    #[allow(dead_code)]
57    enable_type_inference: bool,
58}
59
60impl Analyzer {
61    pub fn new() -> Self {
62        Self {
63            enable_type_inference: true,
64        }
65    }
66
67    pub fn analyze(&self, module: &HirModule) -> Result<AnalysisResult> {
68        let function_metrics: Vec<FunctionMetrics> = module
69            .functions
70            .iter()
71            .map(|f| self.analyze_function(f))
72            .collect::<Result<Vec<_>>>()?;
73
74        let module_metrics = self.calculate_module_metrics(&function_metrics);
75        let type_coverage = self.calculate_type_coverage(module);
76
77        Ok(AnalysisResult {
78            module_metrics,
79            function_metrics,
80            type_coverage,
81        })
82    }
83
84    fn analyze_function(&self, func: &HirFunction) -> Result<FunctionMetrics> {
85        let cyclomatic = complexity::calculate_cyclomatic(&func.body);
86        let cognitive = complexity::calculate_cognitive(&func.body);
87        let max_nesting = complexity::calculate_max_nesting(&func.body);
88        let loc = complexity::count_statements(&func.body);
89
90        let has_type_annotations = func
91            .params
92            .iter()
93            .all(|param| !matches!(param.ty, depyler_core::hir::Type::Unknown));
94        let return_type_annotated = !matches!(func.ret_type, depyler_core::hir::Type::Unknown);
95
96        Ok(FunctionMetrics {
97            name: func.name.clone(),
98            cyclomatic_complexity: cyclomatic,
99            cognitive_complexity: cognitive,
100            lines_of_code: loc,
101            parameters: func.params.len(),
102            max_nesting_depth: max_nesting,
103            has_type_annotations,
104            return_type_annotated,
105        })
106    }
107
108    fn calculate_module_metrics(&self, functions: &[FunctionMetrics]) -> ModuleMetrics {
109        let total_functions = functions.len();
110        let total_lines: usize = functions.iter().map(|f| f.lines_of_code).sum();
111
112        let avg_cyclomatic = if total_functions > 0 {
113            functions
114                .iter()
115                .map(|f| f.cyclomatic_complexity as f64)
116                .sum::<f64>()
117                / total_functions as f64
118        } else {
119            0.0
120        };
121
122        let max_cyclomatic = functions
123            .iter()
124            .map(|f| f.cyclomatic_complexity)
125            .max()
126            .unwrap_or(0);
127
128        let avg_cognitive = if total_functions > 0 {
129            functions
130                .iter()
131                .map(|f| f.cognitive_complexity as f64)
132                .sum::<f64>()
133                / total_functions as f64
134        } else {
135            0.0
136        };
137
138        let max_cognitive = functions
139            .iter()
140            .map(|f| f.cognitive_complexity)
141            .max()
142            .unwrap_or(0);
143
144        ModuleMetrics {
145            total_functions,
146            total_lines,
147            avg_cyclomatic_complexity: avg_cyclomatic,
148            max_cyclomatic_complexity: max_cyclomatic,
149            avg_cognitive_complexity: avg_cognitive,
150            max_cognitive_complexity: max_cognitive,
151        }
152    }
153
154    fn calculate_type_coverage(&self, module: &HirModule) -> TypeCoverage {
155        let mut total_parameters = 0;
156        let mut annotated_parameters = 0;
157        let mut functions_with_return_type = 0;
158
159        for func in &module.functions {
160            total_parameters += func.params.len();
161            annotated_parameters += func
162                .params
163                .iter()
164                .filter(|param| !matches!(param.ty, depyler_core::hir::Type::Unknown))
165                .count();
166
167            if !matches!(func.ret_type, depyler_core::hir::Type::Unknown) {
168                functions_with_return_type += 1;
169            }
170        }
171
172        let total_annotations = annotated_parameters + functions_with_return_type;
173        let total_possible = total_parameters + module.functions.len();
174        let coverage_percentage = if total_possible > 0 {
175            (total_annotations as f64 / total_possible as f64) * 100.0
176        } else {
177            100.0
178        };
179
180        TypeCoverage {
181            total_parameters,
182            annotated_parameters,
183            total_functions: module.functions.len(),
184            functions_with_return_type,
185            coverage_percentage,
186        }
187    }
188}
189
190impl Default for Analyzer {
191    fn default() -> Self {
192        Self::new()
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199    use depyler_core::hir::*;
200
201    fn create_test_function() -> HirFunction {
202        use smallvec::smallvec;
203        HirFunction {
204            name: "test_func".to_string(),
205            params: smallvec![
206                HirParam {
207                    name: Symbol::from("x"),
208                    ty: Type::Int,
209                    default: None,
210                    is_vararg: false,
211                },
212                HirParam {
213                    name: Symbol::from("y"),
214                    ty: Type::String,
215                    default: None,
216                    is_vararg: false,
217                }
218            ],
219            ret_type: Type::Int,
220            body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(42))))],
221            properties: FunctionProperties::default(),
222            annotations: TranspilationAnnotations::default(),
223            docstring: None,
224        }
225    }
226
227    #[test]
228    fn test_analyzer_creation() {
229        let analyzer = Analyzer::new();
230        assert!(analyzer.enable_type_inference);
231
232        let default_analyzer = Analyzer::default();
233        assert!(default_analyzer.enable_type_inference);
234    }
235
236    #[test]
237    fn test_analyze_empty_module() {
238        let analyzer = Analyzer::new();
239        let module = HirModule {
240            functions: vec![],
241            imports: vec![],
242            type_aliases: vec![],
243            protocols: vec![],
244            classes: vec![],
245            constants: vec![],
246            top_level_stmts: vec![],
247        };
248
249        let result = analyzer.analyze(&module).unwrap();
250        assert_eq!(result.module_metrics.total_functions, 0);
251        assert_eq!(result.function_metrics.len(), 0);
252        assert_eq!(result.type_coverage.total_functions, 0);
253        assert_eq!(result.type_coverage.coverage_percentage, 100.0);
254    }
255
256    #[test]
257    fn test_analyze_single_function() {
258        let analyzer = Analyzer::new();
259        let func = create_test_function();
260        let module = HirModule {
261            functions: vec![func],
262            imports: vec![],
263            type_aliases: vec![],
264            protocols: vec![],
265            classes: vec![],
266            constants: vec![],
267            top_level_stmts: vec![],
268        };
269
270        let result = analyzer.analyze(&module).unwrap();
271        assert_eq!(result.module_metrics.total_functions, 1);
272        assert_eq!(result.function_metrics.len(), 1);
273
274        let func_metrics = &result.function_metrics[0];
275        assert_eq!(func_metrics.name, "test_func");
276        assert_eq!(func_metrics.parameters, 2);
277        assert!(func_metrics.has_type_annotations);
278        assert!(func_metrics.return_type_annotated);
279    }
280
281    #[test]
282    fn test_type_coverage_calculation() {
283        let analyzer = Analyzer::new();
284        use smallvec::smallvec;
285        let func_with_types = HirFunction {
286            name: "typed_func".to_string(),
287            params: smallvec![HirParam {
288                name: Symbol::from("x"),
289                ty: Type::Int,
290                default: None,
291                is_vararg: false,
292            }],
293            ret_type: Type::String,
294            body: vec![],
295            properties: FunctionProperties::default(),
296            annotations: TranspilationAnnotations::default(),
297            docstring: None,
298        };
299
300        let func_without_types = HirFunction {
301            name: "untyped_func".to_string(),
302            params: smallvec![HirParam {
303                name: Symbol::from("y"),
304                ty: Type::Unknown,
305                default: None,
306                is_vararg: false,
307            }],
308            ret_type: Type::Unknown,
309            body: vec![],
310            properties: FunctionProperties::default(),
311            annotations: TranspilationAnnotations::default(),
312            docstring: None,
313        };
314
315        let module = HirModule {
316            functions: vec![func_with_types, func_without_types],
317            imports: vec![],
318            type_aliases: vec![],
319            protocols: vec![],
320            classes: vec![],
321            constants: vec![],
322            top_level_stmts: vec![],
323        };
324
325        let coverage = analyzer.calculate_type_coverage(&module);
326        assert_eq!(coverage.total_parameters, 2);
327        assert_eq!(coverage.annotated_parameters, 1);
328        assert_eq!(coverage.total_functions, 2);
329        assert_eq!(coverage.functions_with_return_type, 1);
330        assert_eq!(coverage.coverage_percentage, 50.0); // 2 annotations out of 4 possible
331    }
332
333    // ========================================================================
334    // Additional coverage tests
335    // ========================================================================
336
337    #[test]
338    fn test_analyze_function_with_unknown_types() {
339        use smallvec::smallvec;
340        let analyzer = Analyzer::new();
341        let func = HirFunction {
342            name: "untyped".to_string(),
343            params: smallvec![HirParam {
344                name: Symbol::from("a"),
345                ty: Type::Unknown,
346                default: None,
347                is_vararg: false,
348            }],
349            ret_type: Type::Unknown,
350            body: vec![HirStmt::Return(None)],
351            properties: FunctionProperties::default(),
352            annotations: TranspilationAnnotations::default(),
353            docstring: None,
354        };
355        let module = HirModule {
356            functions: vec![func],
357            imports: vec![],
358            type_aliases: vec![],
359            protocols: vec![],
360            classes: vec![],
361            constants: vec![],
362            top_level_stmts: vec![],
363        };
364        let result = analyzer.analyze(&module).unwrap();
365        let fm = &result.function_metrics[0];
366        assert!(!fm.has_type_annotations);
367        assert!(!fm.return_type_annotated);
368    }
369
370    #[test]
371    fn test_analyze_multiple_functions() {
372        use smallvec::smallvec;
373        let analyzer = Analyzer::new();
374        let func1 = create_test_function();
375        let func2 = HirFunction {
376            name: "func2".to_string(),
377            params: smallvec![],
378            ret_type: Type::Bool,
379            body: vec![
380                HirStmt::If {
381                    condition: HirExpr::Literal(Literal::Bool(true)),
382                    then_body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Bool(
383                        true,
384                    ))))],
385                    else_body: Some(vec![HirStmt::Return(Some(HirExpr::Literal(
386                        Literal::Bool(false),
387                    )))]),
388                },
389            ],
390            properties: FunctionProperties::default(),
391            annotations: TranspilationAnnotations::default(),
392            docstring: None,
393        };
394        let module = HirModule {
395            functions: vec![func1, func2],
396            imports: vec![],
397            type_aliases: vec![],
398            protocols: vec![],
399            classes: vec![],
400            constants: vec![],
401            top_level_stmts: vec![],
402        };
403        let result = analyzer.analyze(&module).unwrap();
404        assert_eq!(result.module_metrics.total_functions, 2);
405        assert!(result.module_metrics.max_cyclomatic_complexity >= 2);
406    }
407
408    #[test]
409    fn test_analysis_result_serialization() {
410        let result = AnalysisResult {
411            module_metrics: ModuleMetrics {
412                total_functions: 1,
413                total_lines: 10,
414                avg_cyclomatic_complexity: 2.0,
415                max_cyclomatic_complexity: 2,
416                avg_cognitive_complexity: 1.0,
417                max_cognitive_complexity: 1,
418            },
419            function_metrics: vec![FunctionMetrics {
420                name: "test".to_string(),
421                cyclomatic_complexity: 2,
422                cognitive_complexity: 1,
423                lines_of_code: 10,
424                parameters: 1,
425                max_nesting_depth: 1,
426                has_type_annotations: true,
427                return_type_annotated: true,
428            }],
429            type_coverage: TypeCoverage {
430                total_parameters: 1,
431                annotated_parameters: 1,
432                total_functions: 1,
433                functions_with_return_type: 1,
434                coverage_percentage: 100.0,
435            },
436        };
437        let json = serde_json::to_string(&result).unwrap();
438        assert!(json.contains("test"));
439        let deserialized: AnalysisResult = serde_json::from_str(&json).unwrap();
440        assert_eq!(deserialized.module_metrics.total_functions, 1);
441    }
442
443    #[test]
444    fn test_module_metrics_debug_clone() {
445        let metrics = ModuleMetrics {
446            total_functions: 5,
447            total_lines: 50,
448            avg_cyclomatic_complexity: 3.0,
449            max_cyclomatic_complexity: 8,
450            avg_cognitive_complexity: 2.5,
451            max_cognitive_complexity: 6,
452        };
453        let debug = format!("{:?}", metrics);
454        assert!(debug.contains("ModuleMetrics"));
455        let cloned = metrics.clone();
456        assert_eq!(cloned.total_functions, 5);
457    }
458
459    #[test]
460    fn test_function_metrics_debug_clone() {
461        let fm = FunctionMetrics {
462            name: "my_func".to_string(),
463            cyclomatic_complexity: 3,
464            cognitive_complexity: 2,
465            lines_of_code: 15,
466            parameters: 2,
467            max_nesting_depth: 3,
468            has_type_annotations: false,
469            return_type_annotated: true,
470        };
471        let debug = format!("{:?}", fm);
472        assert!(debug.contains("my_func"));
473        let cloned = fm.clone();
474        assert_eq!(cloned.name, "my_func");
475    }
476
477    #[test]
478    fn test_type_coverage_debug_clone() {
479        let tc = TypeCoverage {
480            total_parameters: 10,
481            annotated_parameters: 7,
482            total_functions: 5,
483            functions_with_return_type: 3,
484            coverage_percentage: 66.67,
485        };
486        let debug = format!("{:?}", tc);
487        assert!(debug.contains("TypeCoverage"));
488        let cloned = tc.clone();
489        assert_eq!(cloned.total_parameters, 10);
490    }
491
492    #[test]
493    fn test_module_metrics_empty_functions() {
494        let analyzer = Analyzer::new();
495        let empty: Vec<FunctionMetrics> = vec![];
496        let metrics = analyzer.calculate_module_metrics(&empty);
497        assert_eq!(metrics.total_functions, 0);
498        assert_eq!(metrics.total_lines, 0);
499        assert_eq!(metrics.avg_cyclomatic_complexity, 0.0);
500        assert_eq!(metrics.max_cyclomatic_complexity, 0);
501        assert_eq!(metrics.avg_cognitive_complexity, 0.0);
502        assert_eq!(metrics.max_cognitive_complexity, 0);
503    }
504
505    #[test]
506    fn test_type_coverage_all_annotated() {
507        use smallvec::smallvec;
508        let analyzer = Analyzer::new();
509        let func = HirFunction {
510            name: "all_typed".to_string(),
511            params: smallvec![
512                HirParam {
513                    name: Symbol::from("a"),
514                    ty: Type::Int,
515                    default: None,
516                    is_vararg: false,
517                },
518                HirParam {
519                    name: Symbol::from("b"),
520                    ty: Type::String,
521                    default: None,
522                    is_vararg: false,
523                },
524            ],
525            ret_type: Type::Bool,
526            body: vec![],
527            properties: FunctionProperties::default(),
528            annotations: TranspilationAnnotations::default(),
529            docstring: None,
530        };
531        let module = HirModule {
532            functions: vec![func],
533            imports: vec![],
534            type_aliases: vec![],
535            protocols: vec![],
536            classes: vec![],
537            constants: vec![],
538            top_level_stmts: vec![],
539        };
540        let coverage = analyzer.calculate_type_coverage(&module);
541        assert_eq!(coverage.coverage_percentage, 100.0);
542    }
543
544    // ========================================================================
545    // S9B7: Additional coverage tests for analyzer edge cases
546    // ========================================================================
547
548    #[test]
549    fn test_s9b7_analyze_function_with_nested_if() {
550        use smallvec::smallvec;
551        let analyzer = Analyzer::new();
552        let func = HirFunction {
553            name: "nested".to_string(),
554            params: smallvec![],
555            ret_type: Type::Int,
556            body: vec![HirStmt::If {
557                condition: HirExpr::Literal(Literal::Bool(true)),
558                then_body: vec![HirStmt::If {
559                    condition: HirExpr::Literal(Literal::Bool(false)),
560                    then_body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(1))))],
561                    else_body: None,
562                }],
563                else_body: Some(vec![HirStmt::Return(Some(HirExpr::Literal(
564                    Literal::Int(2),
565                )))]),
566            }],
567            properties: FunctionProperties::default(),
568            annotations: TranspilationAnnotations::default(),
569            docstring: None,
570        };
571        let module = HirModule {
572            functions: vec![func],
573            imports: vec![],
574            type_aliases: vec![],
575            protocols: vec![],
576            classes: vec![],
577            constants: vec![],
578            top_level_stmts: vec![],
579        };
580        let result = analyzer.analyze(&module).unwrap();
581        let fm = &result.function_metrics[0];
582        assert!(fm.cyclomatic_complexity >= 3);
583        assert!(fm.max_nesting_depth >= 2);
584    }
585
586    #[test]
587    fn test_s9b7_type_coverage_no_functions() {
588        let analyzer = Analyzer::new();
589        let module = HirModule {
590            functions: vec![],
591            imports: vec![],
592            type_aliases: vec![],
593            protocols: vec![],
594            classes: vec![],
595            constants: vec![],
596            top_level_stmts: vec![],
597        };
598        let coverage = analyzer.calculate_type_coverage(&module);
599        assert_eq!(coverage.total_parameters, 0);
600        assert_eq!(coverage.annotated_parameters, 0);
601        assert_eq!(coverage.total_functions, 0);
602        assert_eq!(coverage.functions_with_return_type, 0);
603        assert_eq!(coverage.coverage_percentage, 100.0);
604    }
605
606    #[test]
607    fn test_s9b7_analyze_function_empty_body() {
608        use smallvec::smallvec;
609        let analyzer = Analyzer::new();
610        let func = HirFunction {
611            name: "empty_body".to_string(),
612            params: smallvec![],
613            ret_type: Type::None,
614            body: vec![],
615            properties: FunctionProperties::default(),
616            annotations: TranspilationAnnotations::default(),
617            docstring: None,
618        };
619        let result = analyzer.analyze_function(&func).unwrap();
620        assert_eq!(result.name, "empty_body");
621        assert_eq!(result.lines_of_code, 0);
622        assert_eq!(result.parameters, 0);
623        assert_eq!(result.max_nesting_depth, 0);
624        assert!(result.has_type_annotations);
625        assert!(result.return_type_annotated);
626    }
627
628    #[test]
629    fn test_s9b7_type_coverage_mixed_annotations() {
630        use smallvec::smallvec;
631        let analyzer = Analyzer::new();
632        let func = HirFunction {
633            name: "mixed".to_string(),
634            params: smallvec![
635                HirParam {
636                    name: Symbol::from("a"),
637                    ty: Type::Int,
638                    default: None,
639                    is_vararg: false,
640                },
641                HirParam {
642                    name: Symbol::from("b"),
643                    ty: Type::Unknown,
644                    default: None,
645                    is_vararg: false,
646                },
647                HirParam {
648                    name: Symbol::from("c"),
649                    ty: Type::Float,
650                    default: None,
651                    is_vararg: false,
652                },
653            ],
654            ret_type: Type::Unknown,
655            body: vec![],
656            properties: FunctionProperties::default(),
657            annotations: TranspilationAnnotations::default(),
658            docstring: None,
659        };
660        let module = HirModule {
661            functions: vec![func],
662            imports: vec![],
663            type_aliases: vec![],
664            protocols: vec![],
665            classes: vec![],
666            constants: vec![],
667            top_level_stmts: vec![],
668        };
669        let coverage = analyzer.calculate_type_coverage(&module);
670        assert_eq!(coverage.total_parameters, 3);
671        assert_eq!(coverage.annotated_parameters, 2);
672        assert_eq!(coverage.functions_with_return_type, 0);
673        // 2 annotated params + 0 return types out of 3 params + 1 function = 2/4 = 50%
674        assert!((coverage.coverage_percentage - 50.0).abs() < 0.01);
675    }
676
677    #[test]
678    fn test_s9b7_module_metrics_single_function() {
679        let analyzer = Analyzer::new();
680        let metrics = vec![FunctionMetrics {
681            name: "solo".to_string(),
682            cyclomatic_complexity: 5,
683            cognitive_complexity: 3,
684            lines_of_code: 10,
685            parameters: 2,
686            max_nesting_depth: 1,
687            has_type_annotations: true,
688            return_type_annotated: true,
689        }];
690        let mm = analyzer.calculate_module_metrics(&metrics);
691        assert_eq!(mm.total_functions, 1);
692        assert_eq!(mm.total_lines, 10);
693        assert_eq!(mm.avg_cyclomatic_complexity, 5.0);
694        assert_eq!(mm.max_cyclomatic_complexity, 5);
695        assert_eq!(mm.avg_cognitive_complexity, 3.0);
696        assert_eq!(mm.max_cognitive_complexity, 3);
697    }
698
699    #[test]
700    fn test_s9b7_analysis_result_debug_clone() {
701        let result = AnalysisResult {
702            module_metrics: ModuleMetrics {
703                total_functions: 2,
704                total_lines: 20,
705                avg_cyclomatic_complexity: 3.0,
706                max_cyclomatic_complexity: 4,
707                avg_cognitive_complexity: 2.0,
708                max_cognitive_complexity: 3,
709            },
710            function_metrics: vec![],
711            type_coverage: TypeCoverage {
712                total_parameters: 0,
713                annotated_parameters: 0,
714                total_functions: 0,
715                functions_with_return_type: 0,
716                coverage_percentage: 100.0,
717            },
718        };
719        let debug = format!("{:?}", result);
720        assert!(debug.contains("AnalysisResult"));
721        let cloned = result.clone();
722        assert_eq!(cloned.module_metrics.total_functions, 2);
723    }
724
725    #[test]
726    fn test_s9b7_analyze_function_partial_type_annotations() {
727        use smallvec::smallvec;
728        let analyzer = Analyzer::new();
729        let func = HirFunction {
730            name: "partial".to_string(),
731            params: smallvec![
732                HirParam {
733                    name: Symbol::from("x"),
734                    ty: Type::Int,
735                    default: None,
736                    is_vararg: false,
737                },
738                HirParam {
739                    name: Symbol::from("y"),
740                    ty: Type::Unknown,
741                    default: None,
742                    is_vararg: false,
743                },
744            ],
745            ret_type: Type::String,
746            body: vec![],
747            properties: FunctionProperties::default(),
748            annotations: TranspilationAnnotations::default(),
749            docstring: None,
750        };
751        let result = analyzer.analyze_function(&func).unwrap();
752        // Not all params have type annotations
753        assert!(!result.has_type_annotations);
754        assert!(result.return_type_annotated);
755    }
756
757    #[test]
758    fn test_module_metrics_calculation() {
759        let analyzer = Analyzer::new();
760        let metrics = vec![
761            FunctionMetrics {
762                name: "func1".to_string(),
763                cyclomatic_complexity: 2,
764                cognitive_complexity: 3,
765                lines_of_code: 5,
766                parameters: 1,
767                max_nesting_depth: 1,
768                has_type_annotations: true,
769                return_type_annotated: true,
770            },
771            FunctionMetrics {
772                name: "func2".to_string(),
773                cyclomatic_complexity: 4,
774                cognitive_complexity: 6,
775                lines_of_code: 10,
776                parameters: 2,
777                max_nesting_depth: 2,
778                has_type_annotations: false,
779                return_type_annotated: false,
780            },
781        ];
782
783        let module_metrics = analyzer.calculate_module_metrics(&metrics);
784        assert_eq!(module_metrics.total_functions, 2);
785        assert_eq!(module_metrics.total_lines, 15);
786        assert_eq!(module_metrics.avg_cyclomatic_complexity, 3.0);
787        assert_eq!(module_metrics.max_cyclomatic_complexity, 4);
788        assert_eq!(module_metrics.avg_cognitive_complexity, 4.5);
789        assert_eq!(module_metrics.max_cognitive_complexity, 6);
790    }
791}