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 std::collections::HashMap;
32use serde::{Deserialize, Serialize};
33use scribe_core::Result;
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(&line_metrics, cyclomatic_complexity, function_count)
186        } else {
187            0.0
188        };
189        
190        let average_function_length = if function_count > 0 {
191            line_metrics.logical_lines as f64 / function_count as f64
192        } else {
193            0.0
194        };
195        
196        let code_density = if line_metrics.total_lines > 0 {
197            line_metrics.logical_lines as f64 / line_metrics.total_lines as f64
198        } else {
199            0.0
200        };
201        
202        let comment_ratio = if line_metrics.logical_lines > 0 {
203            line_metrics.comment_lines as f64 / line_metrics.logical_lines as f64
204        } else {
205            0.0
206        };
207        
208        let language_metrics = if self.config.enable_language_specific {
209            self.analyze_language_specific(content, language)
210        } else {
211            LanguageSpecificMetrics::default(language)
212        };
213        
214        Ok(ComplexityMetrics {
215            cyclomatic_complexity,
216            max_nesting_depth,
217            function_count,
218            logical_lines: line_metrics.logical_lines,
219            comment_lines: line_metrics.comment_lines,
220            blank_lines: line_metrics.blank_lines,
221            total_lines: line_metrics.total_lines,
222            cognitive_complexity,
223            maintainability_index,
224            average_function_length,
225            code_density,
226            comment_ratio,
227            language_metrics,
228        })
229    }
230    
231    /// Analyze line-based metrics
232    fn analyze_lines(&self, content: &str) -> LineMetrics {
233        let mut logical_lines = 0;
234        let mut comment_lines = 0;
235        let mut blank_lines = 0;
236        let total_lines = content.lines().count();
237        
238        for line in content.lines() {
239            let trimmed = line.trim();
240            
241            if trimmed.is_empty() {
242                blank_lines += 1;
243            } else if self.is_comment_line(trimmed) {
244                comment_lines += 1;
245            } else {
246                logical_lines += 1;
247            }
248        }
249        
250        LineMetrics {
251            logical_lines,
252            comment_lines,
253            blank_lines,
254            total_lines,
255        }
256    }
257    
258    /// Check if a line is primarily a comment
259    fn is_comment_line(&self, line: &str) -> bool {
260        let trimmed = line.trim();
261        
262        // Common comment patterns
263        trimmed.starts_with("//") ||
264        trimmed.starts_with("#") ||
265        trimmed.starts_with("/*") ||
266        trimmed.starts_with("*") ||
267        trimmed.starts_with("*/") ||
268        trimmed.starts_with("<!--") ||
269        trimmed.starts_with("--") ||
270        trimmed.starts_with("%") ||
271        trimmed.starts_with(";")
272    }
273    
274    /// Calculate cyclomatic complexity (FAST version - no regex, minimal string ops)
275    fn calculate_cyclomatic_complexity(&self, content: &str, language: &str) -> usize {
276        let mut complexity = 1; // Base complexity
277        
278        // Use simple byte-based counting instead of expensive string matching
279        match language.to_lowercase().as_str() {
280            "rust" => {
281                // Count key complexity indicators quickly
282                complexity += content.matches(" if ").count();
283                complexity += content.matches(" else ").count(); 
284                complexity += content.matches(" match ").count();
285                complexity += content.matches(" while ").count();
286                complexity += content.matches(" for ").count();
287                complexity += content.matches("?").count(); // Error handling
288                complexity += content.matches("&&").count();
289                complexity += content.matches("||").count();
290            }
291            "python" => {
292                complexity += content.matches(" if ").count();
293                complexity += content.matches(" elif ").count();
294                complexity += content.matches(" while ").count();
295                complexity += content.matches(" for ").count();
296                complexity += content.matches(" except ").count();
297                complexity += content.matches(" and ").count();
298                complexity += content.matches(" or ").count();
299            }
300            "javascript" | "typescript" => {
301                complexity += content.matches(" if ").count();
302                complexity += content.matches(" while ").count();
303                complexity += content.matches(" for ").count();
304                complexity += content.matches(" catch ").count();
305                complexity += content.matches("&&").count();
306                complexity += content.matches("||").count();
307                complexity += content.matches("?").count();
308            }
309            _ => {
310                // Generic fallback - just count some basic patterns
311                complexity += content.matches(" if ").count();
312                complexity += content.matches(" while ").count();
313                complexity += content.matches(" for ").count();
314            }
315        }
316        
317        complexity.max(1) // Ensure minimum complexity of 1
318    }
319    
320    /// Get complexity-increasing keywords for a language
321    fn get_complexity_keywords(&self, language: &str) -> Vec<&'static str> {
322        match language.to_lowercase().as_str() {
323            "rust" => vec![
324                "if", "else if", "match", "while", "for", "loop", 
325                "catch", "?", "&&", "||", "break", "continue"
326            ],
327            "python" => vec![
328                "if", "elif", "while", "for", "except", "and", "or",
329                "break", "continue", "return", "yield"
330            ],
331            "javascript" | "typescript" => vec![
332                "if", "else if", "while", "for", "catch", "case", 
333                "&&", "||", "?", "break", "continue", "return"
334            ],
335            "java" | "c#" => vec![
336                "if", "else if", "while", "for", "foreach", "catch", 
337                "case", "&&", "||", "?", "break", "continue", "return"
338            ],
339            "go" => vec![
340                "if", "else if", "for", "switch", "case", "select",
341                "&&", "||", "break", "continue", "return"
342            ],
343            "c" | "cpp" | "c++" => vec![
344                "if", "else if", "while", "for", "switch", "case",
345                "&&", "||", "?", "break", "continue", "return"
346            ],
347            _ => vec![
348                "if", "else", "while", "for", "switch", "case",
349                "&&", "||", "?", "break", "continue", "return"
350            ],
351        }
352    }
353    
354    /// Calculate maximum nesting depth (FAST version - no char iteration)
355    fn calculate_max_nesting_depth(&self, content: &str, _language: &str) -> usize {
356        let mut max_depth: usize = 0;
357        let mut current_depth: usize = 0;
358        
359        // Simple heuristic: count braces on each line (much faster than char-by-char)
360        for line in content.lines() {
361            // Count opening and closing braces in one pass
362            let opens = line.matches('{').count();
363            let closes = line.matches('}').count();
364            
365            current_depth += opens;
366            max_depth = max_depth.max(current_depth);
367            current_depth = current_depth.saturating_sub(closes);
368        }
369        
370        max_depth
371    }
372    
373    /// Get nesting characters for a language
374    fn get_nesting_chars(&self, language: &str) -> (Vec<char>, Vec<char>) {
375        match language.to_lowercase().as_str() {
376            "python" => {
377                // Python uses indentation, but we can still track some nesting
378                (vec!['{', '[', '('], vec!['}', ']', ')'])
379            },
380            _ => {
381                // Most C-style languages
382                (vec!['{', '[', '('], vec!['}', ']', ')'])
383            }
384        }
385    }
386    
387    /// Count functions in the content
388    fn count_functions(&self, content: &str, language: &str) -> usize {
389        let function_keywords = self.get_function_keywords(language);
390        let mut count = 0;
391        
392        for line in content.lines() {
393            let line = line.trim();
394            
395            for keyword in &function_keywords {
396                if line.starts_with(keyword) || line.contains(&format!(" {}", keyword)) {
397                    count += 1;
398                    break; // Only count once per line
399                }
400            }
401        }
402        
403        count
404    }
405    
406    /// Get function declaration keywords for a language
407    fn get_function_keywords(&self, language: &str) -> Vec<&'static str> {
408        match language.to_lowercase().as_str() {
409            "rust" => vec!["fn ", "pub fn ", "async fn ", "pub async fn "],
410            "python" => vec!["def ", "async def ", "class "],
411            "javascript" | "typescript" => vec!["function ", "const ", "let ", "var ", "async function "],
412            "java" => vec!["public ", "private ", "protected ", "static "],
413            "c#" => vec!["public ", "private ", "protected ", "internal ", "static "],
414            "go" => vec!["func "],
415            "c" | "cpp" | "c++" => vec!["int ", "void ", "char ", "float ", "double ", "static "],
416            _ => vec!["function ", "def ", "fn "],
417        }
418    }
419    
420    /// Calculate cognitive complexity (more intuitive than cyclomatic)
421    fn calculate_cognitive_complexity(&self, content: &str, language: &str) -> usize {
422        let mut complexity: usize = 0;
423        let mut nesting_level: usize = 0;
424        
425        for line in content.lines() {
426            let line = line.trim().to_lowercase();
427            
428            // Track nesting level changes
429            if line.contains('{') {
430                nesting_level += 1;
431            }
432            if line.contains('}') {
433                nesting_level = nesting_level.saturating_sub(1);
434            }
435            
436            // Cognitive complexity increments
437            if line.contains("if") || line.contains("while") || line.contains("for") {
438                complexity += 1 + nesting_level; // Base increment + nesting penalty
439            }
440            
441            if line.contains("else if") || line.contains("elif") {
442                complexity += 1;
443            }
444            
445            if line.contains("catch") || line.contains("except") {
446                complexity += 1 + nesting_level;
447            }
448            
449            if line.contains("switch") || line.contains("match") {
450                complexity += 1 + nesting_level;
451            }
452            
453            // Recursive calls add complexity
454            if self.has_recursive_call(&line, language) {
455                complexity += 1;
456            }
457        }
458        
459        complexity
460    }
461    
462    /// Check if a line contains a recursive call
463    fn has_recursive_call(&self, line: &str, _language: &str) -> bool {
464        // Simple heuristic: look for function calls that might be recursive
465        line.contains("self.") || line.contains("this.") || 
466        line.contains("recursive") || line.contains("recurse")
467    }
468    
469    /// Calculate maintainability index (Halstead-based)
470    fn calculate_maintainability_index(&self, line_metrics: &LineMetrics, cyclomatic: usize, functions: usize) -> f64 {
471        // Simplified maintainability index calculation
472        // Real calculation would use Halstead metrics
473        
474        let volume = (line_metrics.logical_lines as f64).ln();
475        let complexity = cyclomatic as f64;
476        let lloc = line_metrics.logical_lines as f64;
477        
478        // Simplified formula (real one is more complex)
479        let mi = 171.0 - 5.2 * volume - 0.23 * complexity - 16.2 * lloc.ln();
480        
481        // Normalize to 0-100 range
482        mi.max(0.0).min(100.0)
483    }
484    
485    /// Analyze language-specific metrics
486    fn analyze_language_specific(&self, content: &str, language: &str) -> LanguageSpecificMetrics {
487        let mut complexity_factors = HashMap::new();
488        let import_count = self.count_imports(content, language);
489        let export_count = self.count_exports(content, language);
490        let api_surface_area = self.estimate_api_surface_area(content, language);
491        
492        // Language-specific complexity factors
493        match language.to_lowercase().as_str() {
494            "rust" => {
495                complexity_factors.insert("ownership_complexity".to_string(), self.calculate_ownership_complexity(content));
496                complexity_factors.insert("trait_complexity".to_string(), self.count_trait_usage(content) as f64);
497                complexity_factors.insert("macro_complexity".to_string(), self.count_macro_usage(content) as f64);
498            },
499            "python" => {
500                complexity_factors.insert("decorator_complexity".to_string(), self.count_decorators(content) as f64);
501                complexity_factors.insert("comprehension_complexity".to_string(), self.count_comprehensions(content) as f64);
502            },
503            "javascript" | "typescript" => {
504                complexity_factors.insert("closure_complexity".to_string(), self.count_closures(content) as f64);
505                complexity_factors.insert("promise_complexity".to_string(), self.count_async_patterns(content) as f64);
506            },
507            _ => {
508                // Generic complexity factors
509                complexity_factors.insert("generic_complexity".to_string(), 1.0);
510            }
511        }
512        
513        LanguageSpecificMetrics {
514            language: language.to_string(),
515            complexity_factors,
516            import_count,
517            export_count,
518            api_surface_area,
519        }
520    }
521    
522    /// Count import statements
523    fn count_imports(&self, content: &str, language: &str) -> usize {
524        let import_patterns = match language.to_lowercase().as_str() {
525            "rust" => vec!["use ", "extern crate "],
526            "python" => vec!["import ", "from "],
527            "javascript" | "typescript" => vec!["import ", "require(", "const ", "let "],
528            "java" => vec!["import "],
529            "go" => vec!["import "],
530            _ => vec!["import ", "include ", "use "],
531        };
532        
533        content.lines()
534            .filter(|line| {
535                let trimmed = line.trim();
536                import_patterns.iter().any(|pattern| trimmed.starts_with(pattern))
537            })
538            .count()
539    }
540    
541    /// Count export statements
542    fn count_exports(&self, content: &str, language: &str) -> usize {
543        let export_patterns = match language.to_lowercase().as_str() {
544            "rust" => vec!["pub fn ", "pub struct ", "pub enum ", "pub trait "],
545            "python" => vec!["def ", "class "], // Python exports everything by default
546            "javascript" | "typescript" => vec!["export ", "module.exports"],
547            "java" => vec!["public class ", "public interface ", "public enum "],
548            _ => vec!["public ", "export "],
549        };
550        
551        content.lines()
552            .filter(|line| {
553                let trimmed = line.trim();
554                export_patterns.iter().any(|pattern| trimmed.contains(pattern))
555            })
556            .count()
557    }
558    
559    /// Estimate API surface area
560    fn estimate_api_surface_area(&self, content: &str, language: &str) -> usize {
561        // Simple heuristic: count public functions, classes, etc.
562        let public_items = self.count_exports(content, language);
563        let function_count = self.count_functions(content, language);
564        
565        // API surface area is roughly the number of publicly accessible items
566        public_items.min(function_count)
567    }
568    
569    // Language-specific complexity calculations
570    fn calculate_ownership_complexity(&self, content: &str) -> f64 {
571        let ownership_keywords = ["&", "&mut", "Box<", "Rc<", "Arc<", "RefCell<", "Mutex<"];
572        let mut complexity = 0.0;
573        
574        for line in content.lines() {
575            for keyword in &ownership_keywords {
576                complexity += line.matches(keyword).count() as f64 * 0.5;
577            }
578        }
579        
580        complexity
581    }
582    
583    fn count_trait_usage(&self, content: &str) -> usize {
584        content.lines()
585            .filter(|line| line.contains("trait ") || line.contains("impl "))
586            .count()
587    }
588    
589    fn count_macro_usage(&self, content: &str) -> usize {
590        content.lines()
591            .filter(|line| line.contains("macro_rules!") || line.contains("!"))
592            .count()
593    }
594    
595    fn count_decorators(&self, content: &str) -> usize {
596        content.lines()
597            .filter(|line| line.trim().starts_with("@"))
598            .count()
599    }
600    
601    fn count_comprehensions(&self, content: &str) -> usize {
602        content.lines()
603            .filter(|line| {
604                line.contains("[") && line.contains("for ") && line.contains("in ") ||
605                line.contains("{") && line.contains("for ") && line.contains("in ")
606            })
607            .count()
608    }
609    
610    fn count_closures(&self, content: &str) -> usize {
611        content.lines()
612            .filter(|line| line.contains("=>") || line.contains("function("))
613            .count()
614    }
615    
616    fn count_async_patterns(&self, content: &str) -> usize {
617        content.lines()
618            .filter(|line| {
619                line.contains("async") || line.contains("await") || 
620                line.contains("Promise") || line.contains(".then(")
621            })
622            .count()
623    }
624}
625
626/// Line-based metrics
627#[derive(Debug)]
628struct LineMetrics {
629    logical_lines: usize,
630    comment_lines: usize,
631    blank_lines: usize,
632    total_lines: usize,
633}
634
635impl Default for LanguageSpecificMetrics {
636    fn default() -> Self {
637        Self::default("unknown")
638    }
639}
640
641impl LanguageSpecificMetrics {
642    fn default(language: &str) -> Self {
643        Self {
644            language: language.to_string(),
645            complexity_factors: HashMap::new(),
646            import_count: 0,
647            export_count: 0,
648            api_surface_area: 0,
649        }
650    }
651}
652
653impl ComplexityMetrics {
654    /// Get a complexity score (0-1, where higher means more complex)
655    pub fn complexity_score(&self) -> f64 {
656        // Normalize various complexity metrics to a 0-1 score
657        let cyclomatic_score = (self.cyclomatic_complexity as f64 / 20.0).min(1.0);
658        let nesting_score = (self.max_nesting_depth as f64 / 8.0).min(1.0);
659        let cognitive_score = (self.cognitive_complexity as f64 / 15.0).min(1.0);
660        let maintainability_score = (100.0 - self.maintainability_index) / 100.0;
661        
662        // Weighted average
663        (cyclomatic_score * 0.3 + 
664         nesting_score * 0.2 + 
665         cognitive_score * 0.3 + 
666         maintainability_score * 0.2).min(1.0)
667    }
668    
669    /// Check if any complexity thresholds are exceeded
670    pub fn exceeds_thresholds(&self, thresholds: &ComplexityThresholds) -> Vec<String> {
671        let mut warnings = Vec::new();
672        
673        if self.cyclomatic_complexity > thresholds.cyclomatic_warning {
674            warnings.push(format!("High cyclomatic complexity: {}", self.cyclomatic_complexity));
675        }
676        
677        if self.max_nesting_depth > thresholds.nesting_warning {
678            warnings.push(format!("Deep nesting: {}", self.max_nesting_depth));
679        }
680        
681        if self.average_function_length > thresholds.function_length_warning as f64 {
682            warnings.push(format!("Long functions: avg {:.1} lines", self.average_function_length));
683        }
684        
685        if self.maintainability_index < thresholds.maintainability_warning {
686            warnings.push(format!("Low maintainability: {:.1}", self.maintainability_index));
687        }
688        
689        warnings
690    }
691    
692    /// Get a human-readable summary
693    pub fn summary(&self) -> String {
694        format!(
695            "Complexity: CC={}, Depth={}, Functions={}, MI={:.1}, Cognitive={}",
696            self.cyclomatic_complexity,
697            self.max_nesting_depth,
698            self.function_count,
699            self.maintainability_index,
700            self.cognitive_complexity
701        )
702    }
703}
704
705#[cfg(test)]
706mod tests {
707    use super::*;
708    
709    #[test]
710    fn test_analyzer_creation() {
711        let analyzer = ComplexityAnalyzer::new();
712        assert!(analyzer.config.enable_cognitive_complexity);
713        assert!(analyzer.config.enable_maintainability_index);
714    }
715    
716    #[test]
717    fn test_simple_rust_analysis() {
718        let analyzer = ComplexityAnalyzer::new();
719        let content = r#"
720fn main() {
721    if x > 0 {
722        println!("positive");
723    } else {
724        println!("negative");
725    }
726}
727"#;
728        
729        let metrics = analyzer.analyze_content(content, "rust").unwrap();
730        
731        assert!(metrics.cyclomatic_complexity >= 2); // if-else adds complexity
732        assert!(metrics.function_count >= 1);
733        assert!(metrics.max_nesting_depth >= 1);
734        assert!(metrics.total_lines > 0);
735    }
736    
737    #[test]
738    fn test_complex_code_analysis() {
739        let analyzer = ComplexityAnalyzer::new();
740        let content = r#"
741fn complex_function() {
742    for i in 0..10 {
743        if i % 2 == 0 {
744            while some_condition() {
745                if another_condition() {
746                    match value {
747                        1 => do_something(),
748                        2 => do_something_else(),
749                        _ => default_action(),
750                    }
751                }
752            }
753        } else {
754            continue;
755        }
756    }
757}
758"#;
759        
760        let metrics = analyzer.analyze_content(content, "rust").unwrap();
761        
762        assert!(metrics.cyclomatic_complexity > 5); // Multiple branches
763        assert!(metrics.max_nesting_depth > 3); // Deep nesting
764        assert!(metrics.cognitive_complexity > metrics.cyclomatic_complexity); // Cognitive should be higher due to nesting
765    }
766    
767    #[test]
768    fn test_line_analysis() {
769        let analyzer = ComplexityAnalyzer::new();
770        let content = r#"
771// This is a comment
772fn test() {
773    // Another comment
774    let x = 5;
775    
776    // More comments
777    println!("Hello");
778}
779"#;
780        
781        let metrics = analyzer.analyze_content(content, "rust").unwrap();
782        
783        assert!(metrics.comment_lines > 0);
784        assert!(metrics.blank_lines > 0);
785        assert!(metrics.logical_lines > 0);
786        assert!(metrics.comment_ratio > 0.0);
787        assert!(metrics.code_density > 0.0);
788    }
789    
790    #[test]
791    fn test_language_specific_analysis() {
792        let analyzer = ComplexityAnalyzer::new();
793        
794        // Test Rust-specific features
795        let rust_content = r#"
796use std::collections::HashMap;
797pub fn test() -> Result<(), Box<dyn Error>> {
798    let mut data: Vec<&str> = vec![];
799    Ok(())
800}
801"#;
802        
803        let rust_metrics = analyzer.analyze_content(rust_content, "rust").unwrap();
804        assert_eq!(rust_metrics.language_metrics.language, "rust");
805        assert!(rust_metrics.language_metrics.import_count > 0);
806        
807        // Test Python-specific features
808        let python_content = r#"
809import os
810from typing import List
811
812@decorator
813def test_function():
814    result = [x for x in range(10) if x % 2 == 0]
815    return result
816"#;
817        
818        let python_metrics = analyzer.analyze_content(python_content, "python").unwrap();
819        assert_eq!(python_metrics.language_metrics.language, "python");
820        assert!(python_metrics.language_metrics.import_count > 0);
821    }
822    
823    #[test]
824    fn test_complexity_score() {
825        let analyzer = ComplexityAnalyzer::new();
826        
827        // Simple code should have low complexity score
828        let simple_content = "fn main() { println!(\"hello\"); }";
829        let simple_metrics = analyzer.analyze_content(simple_content, "rust").unwrap();
830        let simple_score = simple_metrics.complexity_score();
831        
832        // Complex code should have higher complexity score
833        let complex_content = r#"
834fn complex() {
835    for i in 0..100 {
836        if i % 2 == 0 {
837            while condition() {
838                match value {
839                    1 => { if nested() { deep(); } },
840                    2 => { if more_nested() { deeper(); } },
841                    _ => { if even_more() { deepest(); } },
842                }
843            }
844        }
845    }
846}
847"#;
848        let complex_metrics = analyzer.analyze_content(complex_content, "rust").unwrap();
849        let complex_score = complex_metrics.complexity_score();
850        
851        assert!(complex_score > simple_score);
852        assert!(simple_score >= 0.0 && simple_score <= 1.0);
853        assert!(complex_score >= 0.0 && complex_score <= 1.0);
854    }
855    
856    #[test]
857    fn test_threshold_warnings() {
858        let analyzer = ComplexityAnalyzer::new();
859        let thresholds = ComplexityThresholds {
860            cyclomatic_warning: 5,
861            nesting_warning: 2,
862            function_length_warning: 10,
863            maintainability_warning: 50.0,
864        };
865        
866        let complex_content = r#"
867fn complex_function() {
868    for i in 0..10 {
869        if i % 2 == 0 {
870            while some_condition() {
871                if another_condition() {
872                    if yet_another() {
873                        do_something();
874                    }
875                }
876            }
877        }
878    }
879}
880"#;
881        
882        let metrics = analyzer.analyze_content(complex_content, "rust").unwrap();
883        let warnings = metrics.exceeds_thresholds(&thresholds);
884        
885        assert!(!warnings.is_empty());
886        assert!(warnings.iter().any(|w| w.contains("complexity")));
887    }
888    
889    #[test]
890    fn test_metrics_summary() {
891        let analyzer = ComplexityAnalyzer::new();
892        let content = "fn test() { if x > 0 { return 1; } else { return 0; } }";
893        let metrics = analyzer.analyze_content(content, "rust").unwrap();
894        
895        let summary = metrics.summary();
896        assert!(summary.contains("CC="));
897        assert!(summary.contains("Depth="));
898        assert!(summary.contains("Functions="));
899        assert!(summary.contains("MI="));
900        assert!(summary.contains("Cognitive="));
901    }
902}