scribe_analysis/
complexity.rs

1//! # Code Complexity Analysis Module
2//!
3//! This module provides comprehensive code complexity metrics to enhance the heuristic
4//! scoring system with deeper analysis of code structure and maintainability.
5//!
6//! ## Complexity Metrics
7//!
8//! - **Cyclomatic Complexity**: Measures the number of linearly independent paths through code
9//! - **Nesting Depth**: Maximum depth of nested control structures
10//! - **Function Count**: Number of functions/methods in a file
11//! - **Line Complexity**: Analysis of code lines vs comment lines vs blank lines
12//! - **Cognitive Complexity**: Human-focused complexity measure
13//! - **Maintainability Index**: Composite metric for code maintainability
14//!
15//! ## Usage
16//!
17//! ```rust
18//! use scribe_analysis::complexity::{ComplexityAnalyzer, ComplexityMetrics};
19//!
20//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
21//! let analyzer = ComplexityAnalyzer::new();
22//! let content = "fn main() { if x > 0 { println!(\"positive\"); } }";
23//! let metrics = analyzer.analyze_content(content, "rust")?;
24//!
25//! println!("Cyclomatic Complexity: {}", metrics.cyclomatic_complexity);
26//! println!("Maintainability Index: {:.2}", metrics.maintainability_index);
27//! # Ok(())
28//! # }
29//! ```
30
31use scribe_core::Result;
32use serde::{Deserialize, Serialize};
33use std::collections::HashMap;
34
35/// Comprehensive complexity metrics for a code file
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct ComplexityMetrics {
38    /// Cyclomatic complexity (McCabe complexity)
39    pub cyclomatic_complexity: usize,
40
41    /// Maximum nesting depth
42    pub max_nesting_depth: usize,
43
44    /// Total number of functions/methods
45    pub function_count: usize,
46
47    /// Number of logical lines of code (excluding comments and blanks)
48    pub logical_lines: usize,
49
50    /// Number of comment lines
51    pub comment_lines: usize,
52
53    /// Number of blank lines
54    pub blank_lines: usize,
55
56    /// Total physical lines
57    pub total_lines: usize,
58
59    /// Cognitive complexity (easier to understand than cyclomatic)
60    pub cognitive_complexity: usize,
61
62    /// Maintainability index (0-100, higher is better)
63    pub maintainability_index: f64,
64
65    /// Average function length
66    pub average_function_length: f64,
67
68    /// Code density (logical lines / total lines)
69    pub code_density: f64,
70
71    /// Comment ratio (comment lines / logical lines)
72    pub comment_ratio: f64,
73
74    /// Language-specific metrics
75    pub language_metrics: LanguageSpecificMetrics,
76}
77
78/// Language-specific complexity metrics
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct LanguageSpecificMetrics {
81    /// Language identifier
82    pub language: String,
83
84    /// Language-specific complexity factors
85    pub complexity_factors: HashMap<String, f64>,
86
87    /// Number of imports/includes
88    pub import_count: usize,
89
90    /// Number of exported symbols
91    pub export_count: usize,
92
93    /// Estimated API surface area
94    pub api_surface_area: usize,
95}
96
97/// Core complexity analyzer
98#[derive(Debug)]
99pub struct ComplexityAnalyzer {
100    /// Configuration for complexity analysis
101    config: ComplexityConfig,
102}
103
104/// Configuration for complexity analysis
105#[derive(Debug, Clone)]
106pub struct ComplexityConfig {
107    /// Enable cognitive complexity calculation
108    pub enable_cognitive_complexity: bool,
109
110    /// Enable maintainability index calculation
111    pub enable_maintainability_index: bool,
112
113    /// Language-specific analysis
114    pub enable_language_specific: bool,
115
116    /// Thresholds for complexity warnings
117    pub thresholds: ComplexityThresholds,
118}
119
120/// Thresholds for complexity warnings
121#[derive(Debug, Clone)]
122pub struct ComplexityThresholds {
123    /// Cyclomatic complexity warning threshold
124    pub cyclomatic_warning: usize,
125
126    /// Nesting depth warning threshold
127    pub nesting_warning: usize,
128
129    /// Function length warning threshold
130    pub function_length_warning: usize,
131
132    /// Maintainability index warning threshold (below this is concerning)
133    pub maintainability_warning: f64,
134}
135
136impl Default for ComplexityConfig {
137    fn default() -> Self {
138        Self {
139            enable_cognitive_complexity: true,
140            enable_maintainability_index: true,
141            enable_language_specific: true,
142            thresholds: ComplexityThresholds::default(),
143        }
144    }
145}
146
147impl Default for ComplexityThresholds {
148    fn default() -> Self {
149        Self {
150            cyclomatic_warning: 10,        // Standard McCabe threshold
151            nesting_warning: 4,            // Deep nesting becomes hard to follow
152            function_length_warning: 50,   // Functions longer than 50 lines
153            maintainability_warning: 20.0, // Below 20 is concerning
154        }
155    }
156}
157
158impl ComplexityAnalyzer {
159    /// Create a new complexity analyzer with default configuration
160    pub fn new() -> Self {
161        Self {
162            config: ComplexityConfig::default(),
163        }
164    }
165
166    /// Create a new complexity analyzer with custom configuration
167    pub fn with_config(config: ComplexityConfig) -> Self {
168        Self { config }
169    }
170
171    /// Analyze complexity of file content
172    pub fn analyze_content(&self, content: &str, language: &str) -> Result<ComplexityMetrics> {
173        let line_metrics = self.analyze_lines(content);
174        let cyclomatic_complexity = self.calculate_cyclomatic_complexity(content, language);
175        let max_nesting_depth = self.calculate_max_nesting_depth(content, language);
176        let function_count = self.count_functions(content, language);
177
178        let cognitive_complexity = if self.config.enable_cognitive_complexity {
179            self.calculate_cognitive_complexity(content, language)
180        } else {
181            0
182        };
183
184        let maintainability_index = if self.config.enable_maintainability_index {
185            self.calculate_maintainability_index(
186                &line_metrics,
187                cyclomatic_complexity,
188                function_count,
189            )
190        } else {
191            0.0
192        };
193
194        let average_function_length = if function_count > 0 {
195            line_metrics.logical_lines as f64 / function_count as f64
196        } else {
197            0.0
198        };
199
200        let code_density = if line_metrics.total_lines > 0 {
201            line_metrics.logical_lines as f64 / line_metrics.total_lines as f64
202        } else {
203            0.0
204        };
205
206        let comment_ratio = if line_metrics.logical_lines > 0 {
207            line_metrics.comment_lines as f64 / line_metrics.logical_lines as f64
208        } else {
209            0.0
210        };
211
212        let language_metrics = if self.config.enable_language_specific {
213            self.analyze_language_specific(content, language)
214        } else {
215            LanguageSpecificMetrics::default(language)
216        };
217
218        Ok(ComplexityMetrics {
219            cyclomatic_complexity,
220            max_nesting_depth,
221            function_count,
222            logical_lines: line_metrics.logical_lines,
223            comment_lines: line_metrics.comment_lines,
224            blank_lines: line_metrics.blank_lines,
225            total_lines: line_metrics.total_lines,
226            cognitive_complexity,
227            maintainability_index,
228            average_function_length,
229            code_density,
230            comment_ratio,
231            language_metrics,
232        })
233    }
234
235    /// Analyze line-based metrics
236    fn analyze_lines(&self, content: &str) -> LineMetrics {
237        let mut logical_lines = 0;
238        let mut comment_lines = 0;
239        let mut blank_lines = 0;
240        let total_lines = content.lines().count();
241
242        for line in content.lines() {
243            let trimmed = line.trim();
244
245            if trimmed.is_empty() {
246                blank_lines += 1;
247            } else if self.is_comment_line(trimmed) {
248                comment_lines += 1;
249            } else {
250                logical_lines += 1;
251            }
252        }
253
254        LineMetrics {
255            logical_lines,
256            comment_lines,
257            blank_lines,
258            total_lines,
259        }
260    }
261
262    /// Check if a line is primarily a comment
263    fn is_comment_line(&self, line: &str) -> bool {
264        let trimmed = line.trim();
265
266        // Common comment patterns
267        trimmed.starts_with("//")
268            || trimmed.starts_with("#")
269            || trimmed.starts_with("/*")
270            || trimmed.starts_with("*")
271            || trimmed.starts_with("*/")
272            || trimmed.starts_with("<!--")
273            || trimmed.starts_with("--")
274            || trimmed.starts_with("%")
275            || trimmed.starts_with(";")
276    }
277
278    /// Calculate cyclomatic complexity (FAST version - no regex, minimal string ops)
279    fn calculate_cyclomatic_complexity(&self, content: &str, language: &str) -> usize {
280        let mut complexity = 1; // Base complexity
281
282        // Use simple byte-based counting instead of expensive string matching
283        match language.to_lowercase().as_str() {
284            "rust" => {
285                // Count key complexity indicators quickly
286                complexity += content.matches(" if ").count();
287                complexity += content.matches(" else ").count();
288                complexity += content.matches(" match ").count();
289                complexity += content.matches(" while ").count();
290                complexity += content.matches(" for ").count();
291                complexity += content.matches("?").count(); // Error handling
292                complexity += content.matches("&&").count();
293                complexity += content.matches("||").count();
294            }
295            "python" => {
296                complexity += content.matches(" if ").count();
297                complexity += content.matches(" elif ").count();
298                complexity += content.matches(" while ").count();
299                complexity += content.matches(" for ").count();
300                complexity += content.matches(" except ").count();
301                complexity += content.matches(" and ").count();
302                complexity += content.matches(" or ").count();
303            }
304            "javascript" | "typescript" => {
305                complexity += content.matches(" if ").count();
306                complexity += content.matches(" while ").count();
307                complexity += content.matches(" for ").count();
308                complexity += content.matches(" catch ").count();
309                complexity += content.matches("&&").count();
310                complexity += content.matches("||").count();
311                complexity += content.matches("?").count();
312            }
313            _ => {
314                // Generic fallback - just count some basic patterns
315                complexity += content.matches(" if ").count();
316                complexity += content.matches(" while ").count();
317                complexity += content.matches(" for ").count();
318            }
319        }
320
321        complexity.max(1) // Ensure minimum complexity of 1
322    }
323
324    /// Get complexity-increasing keywords for a language
325    fn get_complexity_keywords(&self, language: &str) -> Vec<&'static str> {
326        match language.to_lowercase().as_str() {
327            "rust" => vec![
328                "if", "else if", "match", "while", "for", "loop", "catch", "?", "&&", "||",
329                "break", "continue",
330            ],
331            "python" => vec![
332                "if", "elif", "while", "for", "except", "and", "or", "break", "continue", "return",
333                "yield",
334            ],
335            "javascript" | "typescript" => vec![
336                "if", "else if", "while", "for", "catch", "case", "&&", "||", "?", "break",
337                "continue", "return",
338            ],
339            "java" | "c#" => vec![
340                "if", "else if", "while", "for", "foreach", "catch", "case", "&&", "||", "?",
341                "break", "continue", "return",
342            ],
343            "go" => vec![
344                "if", "else if", "for", "switch", "case", "select", "&&", "||", "break",
345                "continue", "return",
346            ],
347            "c" | "cpp" | "c++" => vec![
348                "if", "else if", "while", "for", "switch", "case", "&&", "||", "?", "break",
349                "continue", "return",
350            ],
351            _ => vec![
352                "if", "else", "while", "for", "switch", "case", "&&", "||", "?", "break",
353                "continue", "return",
354            ],
355        }
356    }
357
358    /// Calculate maximum nesting depth (FAST version - no char iteration)
359    fn calculate_max_nesting_depth(&self, content: &str, _language: &str) -> usize {
360        let mut max_depth: usize = 0;
361        let mut current_depth: usize = 0;
362
363        // Simple heuristic: count braces on each line (much faster than char-by-char)
364        for line in content.lines() {
365            // Count opening and closing braces in one pass
366            let opens = line.matches('{').count();
367            let closes = line.matches('}').count();
368
369            current_depth += opens;
370            max_depth = max_depth.max(current_depth);
371            current_depth = current_depth.saturating_sub(closes);
372        }
373
374        max_depth
375    }
376
377    /// Get nesting characters for a language
378    fn get_nesting_chars(&self, language: &str) -> (Vec<char>, Vec<char>) {
379        match language.to_lowercase().as_str() {
380            "python" => {
381                // Python uses indentation, but we can still track some nesting
382                (vec!['{', '[', '('], vec!['}', ']', ')'])
383            }
384            _ => {
385                // Most C-style languages
386                (vec!['{', '[', '('], vec!['}', ']', ')'])
387            }
388        }
389    }
390
391    /// Count functions in the content
392    fn count_functions(&self, content: &str, language: &str) -> usize {
393        let function_keywords = self.get_function_keywords(language);
394        let mut count = 0;
395
396        for line in content.lines() {
397            let line = line.trim();
398
399            for keyword in &function_keywords {
400                if line.starts_with(keyword) || line.contains(&format!(" {}", keyword)) {
401                    count += 1;
402                    break; // Only count once per line
403                }
404            }
405        }
406
407        count
408    }
409
410    /// Get function declaration keywords for a language
411    fn get_function_keywords(&self, language: &str) -> Vec<&'static str> {
412        match language.to_lowercase().as_str() {
413            "rust" => vec!["fn ", "pub fn ", "async fn ", "pub async fn "],
414            "python" => vec!["def ", "async def ", "class "],
415            "javascript" | "typescript" => {
416                vec!["function ", "const ", "let ", "var ", "async function "]
417            }
418            "java" => vec!["public ", "private ", "protected ", "static "],
419            "c#" => vec!["public ", "private ", "protected ", "internal ", "static "],
420            "go" => vec!["func "],
421            "c" | "cpp" | "c++" => vec!["int ", "void ", "char ", "float ", "double ", "static "],
422            _ => vec!["function ", "def ", "fn "],
423        }
424    }
425
426    /// Calculate cognitive complexity (more intuitive than cyclomatic)
427    fn calculate_cognitive_complexity(&self, content: &str, language: &str) -> usize {
428        let mut complexity: usize = 0;
429        let mut nesting_level: usize = 0;
430
431        for line in content.lines() {
432            let line = line.trim().to_lowercase();
433
434            // Track nesting level changes
435            if line.contains('{') {
436                nesting_level += 1;
437            }
438            if line.contains('}') {
439                nesting_level = nesting_level.saturating_sub(1);
440            }
441
442            // Cognitive complexity increments
443            if line.contains("if") || line.contains("while") || line.contains("for") {
444                complexity += 1 + nesting_level; // Base increment + nesting penalty
445            }
446
447            if line.contains("else if") || line.contains("elif") {
448                complexity += 1;
449            }
450
451            if line.contains("catch") || line.contains("except") {
452                complexity += 1 + nesting_level;
453            }
454
455            if line.contains("switch") || line.contains("match") {
456                complexity += 1 + nesting_level;
457            }
458
459            // Recursive calls add complexity
460            if self.has_recursive_call(&line, language) {
461                complexity += 1;
462            }
463        }
464
465        complexity
466    }
467
468    /// Check if a line contains a recursive call
469    fn has_recursive_call(&self, line: &str, _language: &str) -> bool {
470        // Simple heuristic: look for function calls that might be recursive
471        line.contains("self.")
472            || line.contains("this.")
473            || line.contains("recursive")
474            || line.contains("recurse")
475    }
476
477    /// Calculate maintainability index (Halstead-based)
478    fn calculate_maintainability_index(
479        &self,
480        line_metrics: &LineMetrics,
481        cyclomatic: usize,
482        functions: usize,
483    ) -> f64 {
484        // Simplified maintainability index calculation
485        // Real calculation would use Halstead metrics
486
487        let volume = (line_metrics.logical_lines as f64).ln();
488        let complexity = cyclomatic as f64;
489        let lloc = line_metrics.logical_lines as f64;
490
491        // Simplified formula (real one is more complex)
492        let mi = 171.0 - 5.2 * volume - 0.23 * complexity - 16.2 * lloc.ln();
493
494        // Normalize to 0-100 range
495        mi.max(0.0).min(100.0)
496    }
497
498    /// Analyze language-specific metrics
499    fn analyze_language_specific(&self, content: &str, language: &str) -> LanguageSpecificMetrics {
500        let mut complexity_factors = HashMap::new();
501        let import_count = self.count_imports(content, language);
502        let export_count = self.count_exports(content, language);
503        let api_surface_area = self.estimate_api_surface_area(content, language);
504
505        // Language-specific complexity factors
506        match language.to_lowercase().as_str() {
507            "rust" => {
508                complexity_factors.insert(
509                    "ownership_complexity".to_string(),
510                    self.calculate_ownership_complexity(content),
511                );
512                complexity_factors.insert(
513                    "trait_complexity".to_string(),
514                    self.count_trait_usage(content) as f64,
515                );
516                complexity_factors.insert(
517                    "macro_complexity".to_string(),
518                    self.count_macro_usage(content) as f64,
519                );
520            }
521            "python" => {
522                complexity_factors.insert(
523                    "decorator_complexity".to_string(),
524                    self.count_decorators(content) as f64,
525                );
526                complexity_factors.insert(
527                    "comprehension_complexity".to_string(),
528                    self.count_comprehensions(content) as f64,
529                );
530            }
531            "javascript" | "typescript" => {
532                complexity_factors.insert(
533                    "closure_complexity".to_string(),
534                    self.count_closures(content) as f64,
535                );
536                complexity_factors.insert(
537                    "promise_complexity".to_string(),
538                    self.count_async_patterns(content) as f64,
539                );
540            }
541            _ => {
542                // Generic complexity factors
543                complexity_factors.insert("generic_complexity".to_string(), 1.0);
544            }
545        }
546
547        LanguageSpecificMetrics {
548            language: language.to_string(),
549            complexity_factors,
550            import_count,
551            export_count,
552            api_surface_area,
553        }
554    }
555
556    /// Count import statements
557    fn count_imports(&self, content: &str, language: &str) -> usize {
558        let import_patterns = match language.to_lowercase().as_str() {
559            "rust" => vec!["use ", "extern crate "],
560            "python" => vec!["import ", "from "],
561            "javascript" | "typescript" => vec!["import ", "require(", "const ", "let "],
562            "java" => vec!["import "],
563            "go" => vec!["import "],
564            _ => vec!["import ", "include ", "use "],
565        };
566
567        content
568            .lines()
569            .filter(|line| {
570                let trimmed = line.trim();
571                import_patterns
572                    .iter()
573                    .any(|pattern| trimmed.starts_with(pattern))
574            })
575            .count()
576    }
577
578    /// Count export statements
579    fn count_exports(&self, content: &str, language: &str) -> usize {
580        let export_patterns = match language.to_lowercase().as_str() {
581            "rust" => vec!["pub fn ", "pub struct ", "pub enum ", "pub trait "],
582            "python" => vec!["def ", "class "], // Python exports everything by default
583            "javascript" | "typescript" => vec!["export ", "module.exports"],
584            "java" => vec!["public class ", "public interface ", "public enum "],
585            _ => vec!["public ", "export "],
586        };
587
588        content
589            .lines()
590            .filter(|line| {
591                let trimmed = line.trim();
592                export_patterns
593                    .iter()
594                    .any(|pattern| trimmed.contains(pattern))
595            })
596            .count()
597    }
598
599    /// Estimate API surface area
600    fn estimate_api_surface_area(&self, content: &str, language: &str) -> usize {
601        // Simple heuristic: count public functions, classes, etc.
602        let public_items = self.count_exports(content, language);
603        let function_count = self.count_functions(content, language);
604
605        // API surface area is roughly the number of publicly accessible items
606        public_items.min(function_count)
607    }
608
609    // Language-specific complexity calculations
610    fn calculate_ownership_complexity(&self, content: &str) -> f64 {
611        let ownership_keywords = ["&", "&mut", "Box<", "Rc<", "Arc<", "RefCell<", "Mutex<"];
612        let mut complexity = 0.0;
613
614        for line in content.lines() {
615            for keyword in &ownership_keywords {
616                complexity += line.matches(keyword).count() as f64 * 0.5;
617            }
618        }
619
620        complexity
621    }
622
623    fn count_trait_usage(&self, content: &str) -> usize {
624        content
625            .lines()
626            .filter(|line| line.contains("trait ") || line.contains("impl "))
627            .count()
628    }
629
630    fn count_macro_usage(&self, content: &str) -> usize {
631        content
632            .lines()
633            .filter(|line| line.contains("macro_rules!") || line.contains("!"))
634            .count()
635    }
636
637    fn count_decorators(&self, content: &str) -> usize {
638        content
639            .lines()
640            .filter(|line| line.trim().starts_with("@"))
641            .count()
642    }
643
644    fn count_comprehensions(&self, content: &str) -> usize {
645        content
646            .lines()
647            .filter(|line| {
648                line.contains("[") && line.contains("for ") && line.contains("in ")
649                    || line.contains("{") && line.contains("for ") && line.contains("in ")
650            })
651            .count()
652    }
653
654    fn count_closures(&self, content: &str) -> usize {
655        content
656            .lines()
657            .filter(|line| line.contains("=>") || line.contains("function("))
658            .count()
659    }
660
661    fn count_async_patterns(&self, content: &str) -> usize {
662        content
663            .lines()
664            .filter(|line| {
665                line.contains("async")
666                    || line.contains("await")
667                    || line.contains("Promise")
668                    || line.contains(".then(")
669            })
670            .count()
671    }
672}
673
674/// Line-based metrics
675#[derive(Debug)]
676struct LineMetrics {
677    logical_lines: usize,
678    comment_lines: usize,
679    blank_lines: usize,
680    total_lines: usize,
681}
682
683impl Default for LanguageSpecificMetrics {
684    fn default() -> Self {
685        Self::default("unknown")
686    }
687}
688
689impl LanguageSpecificMetrics {
690    fn default(language: &str) -> Self {
691        Self {
692            language: language.to_string(),
693            complexity_factors: HashMap::new(),
694            import_count: 0,
695            export_count: 0,
696            api_surface_area: 0,
697        }
698    }
699}
700
701impl ComplexityMetrics {
702    /// Get a complexity score (0-1, where higher means more complex)
703    pub fn complexity_score(&self) -> f64 {
704        // Normalize various complexity metrics to a 0-1 score
705        let cyclomatic_score = (self.cyclomatic_complexity as f64 / 20.0).min(1.0);
706        let nesting_score = (self.max_nesting_depth as f64 / 8.0).min(1.0);
707        let cognitive_score = (self.cognitive_complexity as f64 / 15.0).min(1.0);
708        let maintainability_score = (100.0 - self.maintainability_index) / 100.0;
709
710        // Weighted average
711        (cyclomatic_score * 0.3
712            + nesting_score * 0.2
713            + cognitive_score * 0.3
714            + maintainability_score * 0.2)
715            .min(1.0)
716    }
717
718    /// Check if any complexity thresholds are exceeded
719    pub fn exceeds_thresholds(&self, thresholds: &ComplexityThresholds) -> Vec<String> {
720        let mut warnings = Vec::new();
721
722        if self.cyclomatic_complexity > thresholds.cyclomatic_warning {
723            warnings.push(format!(
724                "High cyclomatic complexity: {}",
725                self.cyclomatic_complexity
726            ));
727        }
728
729        if self.max_nesting_depth > thresholds.nesting_warning {
730            warnings.push(format!("Deep nesting: {}", self.max_nesting_depth));
731        }
732
733        if self.average_function_length > thresholds.function_length_warning as f64 {
734            warnings.push(format!(
735                "Long functions: avg {:.1} lines",
736                self.average_function_length
737            ));
738        }
739
740        if self.maintainability_index < thresholds.maintainability_warning {
741            warnings.push(format!(
742                "Low maintainability: {:.1}",
743                self.maintainability_index
744            ));
745        }
746
747        warnings
748    }
749
750    /// Get a human-readable summary
751    pub fn summary(&self) -> String {
752        format!(
753            "Complexity: CC={}, Depth={}, Functions={}, MI={:.1}, Cognitive={}",
754            self.cyclomatic_complexity,
755            self.max_nesting_depth,
756            self.function_count,
757            self.maintainability_index,
758            self.cognitive_complexity
759        )
760    }
761}
762
763#[cfg(test)]
764mod tests {
765    use super::*;
766
767    #[test]
768    fn test_analyzer_creation() {
769        let analyzer = ComplexityAnalyzer::new();
770        assert!(analyzer.config.enable_cognitive_complexity);
771        assert!(analyzer.config.enable_maintainability_index);
772    }
773
774    #[test]
775    fn test_simple_rust_analysis() {
776        let analyzer = ComplexityAnalyzer::new();
777        let content = r#"
778fn main() {
779    if x > 0 {
780        println!("positive");
781    } else {
782        println!("negative");
783    }
784}
785"#;
786
787        let metrics = analyzer.analyze_content(content, "rust").unwrap();
788
789        assert!(metrics.cyclomatic_complexity >= 2); // if-else adds complexity
790        assert!(metrics.function_count >= 1);
791        assert!(metrics.max_nesting_depth >= 1);
792        assert!(metrics.total_lines > 0);
793    }
794
795    #[test]
796    fn test_complex_code_analysis() {
797        let analyzer = ComplexityAnalyzer::new();
798        let content = r#"
799fn complex_function() {
800    for i in 0..10 {
801        if i % 2 == 0 {
802            while some_condition() {
803                if another_condition() {
804                    match value {
805                        1 => do_something(),
806                        2 => do_something_else(),
807                        _ => default_action(),
808                    }
809                }
810            }
811        } else {
812            continue;
813        }
814    }
815}
816"#;
817
818        let metrics = analyzer.analyze_content(content, "rust").unwrap();
819
820        assert!(metrics.cyclomatic_complexity > 5); // Multiple branches
821        assert!(metrics.max_nesting_depth > 3); // Deep nesting
822        assert!(metrics.cognitive_complexity > metrics.cyclomatic_complexity); // Cognitive should be higher due to nesting
823    }
824
825    #[test]
826    fn test_line_analysis() {
827        let analyzer = ComplexityAnalyzer::new();
828        let content = r#"
829// This is a comment
830fn test() {
831    // Another comment
832    let x = 5;
833    
834    // More comments
835    println!("Hello");
836}
837"#;
838
839        let metrics = analyzer.analyze_content(content, "rust").unwrap();
840
841        assert!(metrics.comment_lines > 0);
842        assert!(metrics.blank_lines > 0);
843        assert!(metrics.logical_lines > 0);
844        assert!(metrics.comment_ratio > 0.0);
845        assert!(metrics.code_density > 0.0);
846    }
847
848    #[test]
849    fn test_language_specific_analysis() {
850        let analyzer = ComplexityAnalyzer::new();
851
852        // Test Rust-specific features
853        let rust_content = r#"
854use std::collections::HashMap;
855pub fn test() -> Result<(), Box<dyn Error>> {
856    let mut data: Vec<&str> = vec![];
857    Ok(())
858}
859"#;
860
861        let rust_metrics = analyzer.analyze_content(rust_content, "rust").unwrap();
862        assert_eq!(rust_metrics.language_metrics.language, "rust");
863        assert!(rust_metrics.language_metrics.import_count > 0);
864
865        // Test Python-specific features
866        let python_content = r#"
867import os
868from typing import List
869
870@decorator
871def test_function():
872    result = [x for x in range(10) if x % 2 == 0]
873    return result
874"#;
875
876        let python_metrics = analyzer.analyze_content(python_content, "python").unwrap();
877        assert_eq!(python_metrics.language_metrics.language, "python");
878        assert!(python_metrics.language_metrics.import_count > 0);
879    }
880
881    #[test]
882    fn test_complexity_score() {
883        let analyzer = ComplexityAnalyzer::new();
884
885        // Simple code should have low complexity score
886        let simple_content = "fn main() { println!(\"hello\"); }";
887        let simple_metrics = analyzer.analyze_content(simple_content, "rust").unwrap();
888        let simple_score = simple_metrics.complexity_score();
889
890        // Complex code should have higher complexity score
891        let complex_content = r#"
892fn complex() {
893    for i in 0..100 {
894        if i % 2 == 0 {
895            while condition() {
896                match value {
897                    1 => { if nested() { deep(); } },
898                    2 => { if more_nested() { deeper(); } },
899                    _ => { if even_more() { deepest(); } },
900                }
901            }
902        }
903    }
904}
905"#;
906        let complex_metrics = analyzer.analyze_content(complex_content, "rust").unwrap();
907        let complex_score = complex_metrics.complexity_score();
908
909        assert!(complex_score > simple_score);
910        assert!(simple_score >= 0.0 && simple_score <= 1.0);
911        assert!(complex_score >= 0.0 && complex_score <= 1.0);
912    }
913
914    #[test]
915    fn test_threshold_warnings() {
916        let analyzer = ComplexityAnalyzer::new();
917        let thresholds = ComplexityThresholds {
918            cyclomatic_warning: 5,
919            nesting_warning: 2,
920            function_length_warning: 10,
921            maintainability_warning: 50.0,
922        };
923
924        let complex_content = r#"
925fn complex_function() {
926    for i in 0..10 {
927        if i % 2 == 0 {
928            while some_condition() {
929                if another_condition() {
930                    if yet_another() {
931                        do_something();
932                    }
933                }
934            }
935        }
936    }
937}
938"#;
939
940        let metrics = analyzer.analyze_content(complex_content, "rust").unwrap();
941        let warnings = metrics.exceeds_thresholds(&thresholds);
942
943        assert!(!warnings.is_empty());
944        assert!(warnings.iter().any(|w| w.contains("complexity")));
945    }
946
947    #[test]
948    fn test_metrics_summary() {
949        let analyzer = ComplexityAnalyzer::new();
950        let content = "fn test() { if x > 0 { return 1; } else { return 0; } }";
951        let metrics = analyzer.analyze_content(content, "rust").unwrap();
952
953        let summary = metrics.summary();
954        assert!(summary.contains("CC="));
955        assert!(summary.contains("Depth="));
956        assert!(summary.contains("Functions="));
957        assert!(summary.contains("MI="));
958        assert!(summary.contains("Cognitive="));
959    }
960}