1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
//! Cognitive complexity command - Calculate SonarQube cognitive complexity
//!
//! Analyzes cognitive complexity for functions in a file or directory,
//! with threshold checking.
//! Extends the basic complexity command with detailed cognitive metrics.
use std::path::PathBuf;
use anyhow::Result;
use clap::Args;
use tldr_core::{
metrics::{
analyze_cognitive, merge_cognitive_reports, walk_source_files, CognitiveOptions,
WalkOptions,
},
validate_file_path, Language,
};
use crate::output::{format_cognitive_text, OutputFormat, OutputWriter};
/// Calculate cognitive complexity for functions (SonarQube algorithm)
#[derive(Debug, Args)]
pub struct CognitiveArgs {
/// File or directory to analyze
#[arg(default_value = ".")]
pub path: PathBuf,
/// Specific function to analyze (analyzes all if not specified)
/// Note: --function is the long form; -f short flag is NOT used to avoid collision with --format
#[arg(long)]
pub function: Option<String>,
/// Programming language (auto-detect if not specified)
#[arg(long, short = 'l')]
pub lang: Option<Language>,
/// Complexity threshold for violations (default: 15)
#[arg(long, default_value = "15")]
pub threshold: u32,
/// High threshold for severe violations (default: 25)
#[arg(long, default_value = "25")]
pub high_threshold: u32,
/// Show line-by-line complexity contributors
#[arg(long)]
pub show_contributors: bool,
/// Include cyclomatic complexity comparison
#[arg(long)]
pub include_cyclomatic: bool,
/// Maximum functions to report (0 = all)
#[arg(long, default_value = "50")]
pub top: usize,
/// Exclude patterns (glob syntax), can be specified multiple times
#[arg(long, short = 'e')]
pub exclude: Vec<String>,
/// Include hidden files (dotfiles)
#[arg(long)]
pub include_hidden: bool,
/// Maximum files to process (0 = unlimited)
#[arg(long, default_value = "0")]
pub max_files: usize,
}
impl CognitiveArgs {
/// Run the cognitive complexity command
pub fn run(&self, format: OutputFormat, quiet: bool) -> Result<()> {
let writer = OutputWriter::new(format, quiet);
// Build options
let options = CognitiveOptions::new()
.with_function(self.function.clone())
.with_threshold(self.threshold)
.with_high_threshold(self.high_threshold)
.with_contributors(self.show_contributors)
.with_cyclomatic(self.include_cyclomatic)
.with_top(self.top);
let report = if self.path.is_file() {
// Single file: preserve exact current behavior
let validated_path = validate_file_path(self.path.to_str().unwrap_or_default(), None)?;
writer.progress(&format!(
"Calculating cognitive complexity for {}...",
validated_path.display()
));
analyze_cognitive(&validated_path, &options)?
} else if self.path.is_dir() {
// Directory: walk -> analyze each -> merge
let walk_options = WalkOptions {
lang: self.lang,
exclude: self.exclude.clone(),
include_hidden: self.include_hidden,
gitignore: true,
max_files: self.max_files,
};
let (files, walk_warnings) = walk_source_files(&self.path, &walk_options)?;
writer.progress(&format!(
"Analyzing {} files in {}...",
files.len(),
self.path.display()
));
let mut reports = Vec::new();
let mut extra_warnings = walk_warnings;
for file in &files {
match analyze_cognitive(file, &options) {
Ok(report) => reports.push(report),
Err(e) => {
extra_warnings.push(format!("Failed to analyze {}: {}", file.display(), e));
}
}
}
let mut merged = merge_cognitive_reports(reports, &options);
// Prepend walk warnings and per-file error warnings
let mut all_warnings = extra_warnings;
all_warnings.append(&mut merged.warnings);
merged.warnings = all_warnings;
merged
} else {
// Path does not exist (or is a special file)
return Err(anyhow::anyhow!(
"Path does not exist: {}",
self.path.display()
));
};
// Output based on format
if writer.is_text() {
writer.write_text(&format_cognitive_text(&report))?;
} else {
writer.write(&report)?;
}
Ok(())
}
}