code complexity & repetition analysis tool
at main 117 lines 4.1 kB view raw
1use anyhow::Result; 2use mccabre_core::{ 3 complexity::{CyclomaticMetrics, LocMetrics}, 4 config::Config, 5 loader::FileLoader, 6 reporter::{FileReport, Report}, 7}; 8use owo_colors::OwoColorize; 9use std::path::PathBuf; 10 11pub fn run( 12 path: PathBuf, json: bool, threshold: Option<usize>, config_path: Option<PathBuf>, respect_gitignore: bool, 13) -> Result<()> { 14 let config = if let Some(config_path) = config_path { 15 Config::from_file(config_path)? 16 } else { 17 Config::load_default()? 18 }; 19 20 let config = config.merge_with_cli(threshold, None, Some(respect_gitignore)); 21 let loader = FileLoader::new().with_gitignore(config.files.respect_gitignore); 22 let files = loader.load(&path)?; 23 24 if files.is_empty() { 25 eprintln!("{}", "No supported files found".yellow()); 26 return Ok(()); 27 } 28 29 let mut file_reports = Vec::new(); 30 31 for file in &files { 32 let loc = LocMetrics::calculate(&file.content, file.language)?; 33 let cyclomatic = CyclomaticMetrics::calculate(&file.content, file.language)?; 34 35 file_reports.push(FileReport { path: file.path.clone(), loc, cyclomatic }); 36 } 37 38 let report = Report::new(file_reports, Vec::new()); 39 40 if json { 41 println!("{}", report.to_json()?); 42 } else { 43 print_complexity_report(&report, &config); 44 } 45 46 Ok(()) 47} 48 49fn print_complexity_report(report: &Report, config: &Config) { 50 println!("{}", "=".repeat(80).cyan()); 51 println!("{}", "COMPLEXITY ANALYSIS".cyan().bold()); 52 println!("{}\n", "=".repeat(80).cyan()); 53 54 println!("{}", "SUMMARY".green().bold()); 55 println!("{}", "-".repeat(80).cyan()); 56 println!("Total files analyzed: {}", report.summary.total_files.bold()); 57 println!( 58 "Total physical LOC: {}", 59 report.summary.total_physical_loc.bold() 60 ); 61 println!( 62 "Total logical LOC: {}", 63 report.summary.total_logical_loc.bold() 64 ); 65 println!( 66 "Average complexity: {}", 67 format!("{:.2}", report.summary.avg_complexity).bold() 68 ); 69 println!("Maximum complexity: {}", report.summary.max_complexity.bold()); 70 println!( 71 "High complexity files: {}\n", 72 report.summary.high_complexity_files.bold() 73 ); 74 75 println!("{}", "FILE METRICS".green().bold()); 76 println!("{}", "-".repeat(80).cyan()); 77 78 for file in &report.files { 79 println!("{} {}", "FILE:".blue().bold(), file.path.display().bold()); 80 81 let complexity_value = file.cyclomatic.file_complexity; 82 let complexity_text = format!("Cyclomatic Complexity: {complexity_value}"); 83 84 if complexity_value > config.complexity.error_threshold { 85 println!(" {}", complexity_text.red().bold()); 86 } else if complexity_value > config.complexity.warning_threshold { 87 println!(" {}", complexity_text.yellow()); 88 } else { 89 println!(" {}", complexity_text.green()); 90 } 91 println!(" Physical LOC: {}", file.loc.physical); 92 println!(" Logical LOC: {}", file.loc.logical); 93 println!(" Comment lines: {}", file.loc.comments); 94 println!(" Blank lines: {}\n", file.loc.blank); 95 96 if !file.cyclomatic.functions.is_empty() { 97 println!(" {}:", "Functions".magenta()); 98 for func in &file.cyclomatic.functions { 99 let func_text = format!( 100 " - {} (line {}): complexity {}", 101 func.name, func.line, func.complexity 102 ); 103 104 if func.complexity > config.complexity.error_threshold { 105 println!("{}", func_text.red()); 106 } else if func.complexity > config.complexity.warning_threshold { 107 println!("{}", func_text.yellow()); 108 } else { 109 println!("{func_text}"); 110 } 111 } 112 println!(); 113 } 114 } 115 116 println!("{}", "=".repeat(80).cyan()); 117}