code complexity & repetition analysis tool
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}