pub async fn handle_analyze_satd(config: SatdAnalysisConfig) -> Result<()> {
eprintln!("🔍 Analyzing Self-Admitted Technical Debt (SATD)...");
log_filter_info(&config);
let result = execute_satd_analysis(&config).await?;
let filtered_result = apply_analysis_filters(result, &config)?;
write_satd_output(&filtered_result, &config).await?;
Ok(())
}
fn log_filter_info(config: &SatdAnalysisConfig) {
if !config.include.is_empty() || !config.exclude.is_empty() {
eprintln!("🔍 Applying file filters...");
if !config.include.is_empty() {
eprintln!(" Include patterns: {:?}", config.include);
}
if !config.exclude.is_empty() {
eprintln!(" Exclude patterns: {:?}", config.exclude);
}
}
}
async fn execute_satd_analysis(config: &SatdAnalysisConfig) -> Result<SatdAnalysisResult> {
let registry = Arc::new(ServiceRegistry::new());
let facade = SatdFacade::new(registry);
if config.extended {
eprintln!("📋 Extended mode: detecting euphemisms (placeholder, stub, for now...)");
}
let request = SatdAnalysisRequest {
path: config.path.clone(),
strict_mode: config.strict,
include_tests: config.include_tests,
extended: config.extended,
};
facade.analyze_project(request).await
}
fn apply_analysis_filters(
mut result: SatdAnalysisResult,
config: &SatdAnalysisConfig,
) -> Result<SatdAnalysisResult> {
if !config.include.is_empty() || !config.exclude.is_empty() {
use crate::utils::file_filter::FileFilter;
let filter = FileFilter::new(config.include.clone(), config.exclude.clone())?;
if filter.has_filters() {
result.violations.retain(|violation| {
let path = std::path::Path::new(&violation.file_path);
filter.should_include(path)
});
let unique_files: std::collections::HashSet<_> =
result.violations.iter().map(|v| &v.file_path).collect();
result.total_files = unique_files.len();
}
}
Ok(apply_filters(
result,
config.severity.clone(),
config.critical_only,
))
}
async fn write_satd_output(
filtered_result: &SatdAnalysisResult,
config: &SatdAnalysisConfig,
) -> Result<()> {
let content = format_output(
filtered_result,
config.format.clone(),
config.evolution,
config.days,
config.metrics,
);
if let Some(output_path) = &config.output {
tokio::fs::write(output_path, &content).await?;
eprintln!("✅ SATD analysis written to: {}", output_path.display());
} else {
println!("{content}");
}
if config.metrics {
print_metrics(filtered_result);
}
Ok(())
}
fn apply_filters(
mut result: SatdAnalysisResult,
severity: Option<SatdSeverity>,
critical_only: bool,
) -> SatdAnalysisResult {
if let Some(min_severity) = severity {
result.violations.retain(|v| match min_severity {
SatdSeverity::Critical => matches!(
v.severity,
crate::services::facades::satd_facade::SatdSeverity::Critical
),
SatdSeverity::High => matches!(
v.severity,
crate::services::facades::satd_facade::SatdSeverity::Critical
| crate::services::facades::satd_facade::SatdSeverity::High
),
SatdSeverity::Medium => !matches!(
v.severity,
crate::services::facades::satd_facade::SatdSeverity::Low
),
SatdSeverity::Low => true,
});
}
if critical_only {
result.violations.retain(|v| {
matches!(
v.severity,
crate::services::facades::satd_facade::SatdSeverity::Critical
| crate::services::facades::satd_facade::SatdSeverity::High
)
});
}
result
}