#![cfg_attr(coverage_nightly, coverage(off))]
pub mod clippy;
pub mod metrics;
pub mod output;
pub mod types;
pub use types::{
EnforcementMetadata, FileSummary, LintHotspot, LintHotspotParams, LintHotspotResult,
QualityGateStatus, QualityViolation, RefactorChain, RefactorStep, SeverityDistribution,
ViolationDetail,
};
pub use output::format_summary;
use crate::cli::LintHotspotOutputFormat;
use anyhow::Result;
use metrics::{calculate_enforcement_metadata, check_quality_gates, generate_refactor_chain};
use output::format_output;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
#[allow(clippy::too_many_arguments)]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub async fn handle_analyze_lint_hotspot(
project_path: PathBuf,
file: Option<PathBuf>,
format: LintHotspotOutputFormat,
max_density: f64,
min_confidence: f64,
enforce: bool,
dry_run: bool,
enforcement_metadata: bool,
output: Option<PathBuf>,
perf: bool,
clippy_flags: String,
top_files: usize,
include: Vec<String>,
exclude: Vec<String>,
) -> Result<()> {
if !include.is_empty() || !exclude.is_empty() {
eprintln!("🔍 Applying file filters...");
if !include.is_empty() {
eprintln!(" Include patterns: {include:?}");
}
if !exclude.is_empty() {
eprintln!(" Exclude patterns: {exclude:?}");
}
}
let params = LintHotspotParams {
project_path,
file,
format,
max_density,
min_confidence,
enforce,
dry_run,
enforcement_metadata,
output,
perf,
clippy_flags,
top_files,
include,
exclude,
};
handle_analyze_lint_hotspot_with_params(params).await
}
async fn handle_analyze_lint_hotspot_with_params(params: LintHotspotParams) -> Result<()> {
let start_time = std::time::Instant::now();
log_analysis_start(¶ms.format);
let result = run_analysis_by_mode(¶ms).await;
let mut result = match result {
Ok(r) => r,
Err(e) if e.to_string().contains("No lint violations found") => {
use crate::cli::colors as c;
eprintln!("{}", c::pass("No lint violations found — project is clean"));
return Ok(());
}
Err(e) => return Err(e),
};
apply_file_filters(&mut result, ¶ms)?;
let final_result = build_final_result(result, ¶ms)?;
output_results(&final_result, ¶ms, start_time.elapsed()).await?;
execute_enforcement_if_needed(&final_result, ¶ms);
check_exit_conditions(&final_result, ¶ms);
Ok(())
}
fn log_analysis_start(format: &LintHotspotOutputFormat) {
if *format != LintHotspotOutputFormat::Json {
eprintln!("🔍 Running Clippy analysis...");
}
}
async fn run_analysis_by_mode(params: &LintHotspotParams) -> Result<LintHotspotResult> {
if let Some(ref file_path) = params.file {
log_single_file_mode(file_path, ¶ms.format);
clippy::run_clippy_analysis_single_file(
¶ms.project_path,
file_path,
¶ms.clippy_flags,
)
.await
} else {
clippy::run_clippy_analysis(¶ms.project_path, ¶ms.clippy_flags).await
}
}
fn log_single_file_mode(file_path: &Path, format: &LintHotspotOutputFormat) {
if *format != LintHotspotOutputFormat::Json {
eprintln!("📄 Analyzing single file: {}", file_path.display());
}
}
fn apply_file_filters(result: &mut LintHotspotResult, params: &LintHotspotParams) -> Result<()> {
if params.include.is_empty() && params.exclude.is_empty() {
return Ok(());
}
use crate::utils::file_filter::FileFilter;
let filter = FileFilter::new(params.include.clone(), params.exclude.clone())?;
if !filter.has_filters() {
return Ok(());
}
filter_violations(result, &filter);
recalculate_hotspot_metrics(result);
Ok(())
}
fn filter_violations(
result: &mut LintHotspotResult,
filter: &crate::utils::file_filter::FileFilter,
) {
result.hotspot.detailed_violations.retain(|violation| {
let path = std::path::Path::new(&violation.file);
filter.should_include(path)
});
result.all_violations.retain(|violation| {
let path = std::path::Path::new(&violation.file);
filter.should_include(path)
});
let filtered_summary: HashMap<PathBuf, FileSummary> = result
.summary_by_file
.drain()
.filter(|(path, _summary)| filter.should_include(path))
.collect();
result.summary_by_file = filtered_summary;
}
fn recalculate_hotspot_metrics(result: &mut LintHotspotResult) {
result.hotspot.total_violations = result.hotspot.detailed_violations.len();
if result.hotspot.sloc > 0 {
result.hotspot.defect_density =
result.hotspot.total_violations as f64 / result.hotspot.sloc as f64;
}
}
fn build_final_result(
mut result: LintHotspotResult,
params: &LintHotspotParams,
) -> Result<LintHotspotResult> {
let enforcement = generate_enforcement_metadata_if_needed(&result.hotspot, params);
let refactor_chain = generate_refactor_chain_if_needed(&result.hotspot, params, &enforcement);
let quality_gate = check_quality_gates(&result.hotspot, params.max_density);
result.enforcement = enforcement;
result.refactor_chain = refactor_chain;
result.quality_gate = quality_gate;
Ok(result)
}
fn generate_enforcement_metadata_if_needed(
hotspot: &LintHotspot,
params: &LintHotspotParams,
) -> Option<EnforcementMetadata> {
if params.enforcement_metadata || params.enforce {
Some(calculate_enforcement_metadata(
hotspot,
params.min_confidence,
))
} else {
None
}
}
fn generate_refactor_chain_if_needed(
hotspot: &LintHotspot,
params: &LintHotspotParams,
enforcement: &Option<EnforcementMetadata>,
) -> Option<RefactorChain> {
if params.enforce || enforcement.as_ref().is_some_and(|e| e.requires_enforcement) {
Some(generate_refactor_chain(hotspot, params.min_confidence))
} else {
None
}
}
async fn output_results(
final_result: &LintHotspotResult,
params: &LintHotspotParams,
elapsed: std::time::Duration,
) -> Result<()> {
let output_content = format_output(
final_result,
params.format.clone(),
params.perf,
elapsed,
params.top_files,
)?;
if let Some(output_path) = ¶ms.output {
tokio::fs::write(output_path, &output_content).await?;
} else {
println!("{output_content}");
}
Ok(())
}
fn execute_enforcement_if_needed(final_result: &LintHotspotResult, params: &LintHotspotParams) {
if params.enforce && !params.dry_run && final_result.quality_gate.blocking {
eprintln!("🚨 Enforcement required - executing refactor chain...");
eprintln!("⚠️ Enforcement execution not yet implemented");
}
}
fn check_exit_conditions(final_result: &LintHotspotResult, params: &LintHotspotParams) {
if should_exit_with_error(final_result, params) {
log_enforcement_failure_if_needed(final_result, params);
std::process::exit(1);
}
}
fn should_exit_with_error(final_result: &LintHotspotResult, params: &LintHotspotParams) -> bool {
!final_result.quality_gate.passed
|| (params.enforce && final_result.total_project_violations > 0)
}
fn log_enforcement_failure_if_needed(final_result: &LintHotspotResult, params: &LintHotspotParams) {
if params.enforce
&& final_result.total_project_violations > 0
&& final_result.quality_gate.passed
{
eprintln!(
"\n❌ Enforcement failed: {} violations found",
final_result.total_project_violations
);
}
}
#[cfg(all(test, feature = "broken-tests"))]
#[path = "../lint_hotspot_handlers_tests.rs"]
mod tests;
#[cfg(test)]
mod pure_helper_tests {
use super::*;
use crate::cli::LintHotspotOutputFormat;
use std::collections::HashMap;
fn make_violation(file: &str, line: u32, severity: &str) -> ViolationDetail {
ViolationDetail {
file: PathBuf::from(file),
line,
column: 0,
end_line: line,
end_column: 0,
lint_name: "test_lint".to_string(),
message: "test".to_string(),
severity: severity.to_string(),
suggestion: None,
machine_applicable: false,
}
}
fn make_hotspot(file: &str, sloc: usize, total: usize) -> LintHotspot {
LintHotspot {
file: PathBuf::from(file),
defect_density: total as f64 / sloc.max(1) as f64,
total_violations: total,
sloc,
severity_distribution: SeverityDistribution::default(),
top_lints: vec![],
detailed_violations: (0..total)
.map(|i| make_violation(file, i as u32, "warning"))
.collect(),
}
}
fn make_result(hotspot: LintHotspot, all: Vec<ViolationDetail>) -> LintHotspotResult {
LintHotspotResult {
hotspot,
all_violations: all,
summary_by_file: HashMap::new(),
total_project_violations: 0,
enforcement: None,
refactor_chain: None,
quality_gate: QualityGateStatus {
passed: true,
violations: vec![],
blocking: false,
},
}
}
fn make_params(include: Vec<String>, exclude: Vec<String>) -> LintHotspotParams {
LintHotspotParams {
project_path: PathBuf::from("/tmp"),
file: None,
format: LintHotspotOutputFormat::Json,
max_density: 0.1,
min_confidence: 0.5,
enforce: false,
dry_run: false,
enforcement_metadata: false,
output: None,
perf: false,
clippy_flags: String::new(),
top_files: 10,
include,
exclude,
}
}
#[test]
fn test_apply_file_filters_empty_include_exclude_short_circuit() {
let mut result = make_result(make_hotspot("src/foo.rs", 100, 5), vec![]);
let params = make_params(vec![], vec![]);
let result_ok = apply_file_filters(&mut result, ¶ms);
assert!(result_ok.is_ok());
assert_eq!(result.hotspot.detailed_violations.len(), 5);
}
#[test]
fn test_apply_file_filters_invalid_pattern_returns_err() {
let mut result = make_result(make_hotspot("src/foo.rs", 100, 5), vec![]);
let params = make_params(vec!["[invalid".to_string()], vec![]);
let r = apply_file_filters(&mut result, ¶ms);
assert!(r.is_err());
}
#[test]
fn test_recalculate_hotspot_metrics_recomputes_density() {
let mut result = make_result(make_hotspot("src/foo.rs", 100, 5), vec![]);
result.hotspot.detailed_violations.pop();
recalculate_hotspot_metrics(&mut result);
assert_eq!(result.hotspot.total_violations, 4);
assert!((result.hotspot.defect_density - 0.04).abs() < 1e-9);
}
#[test]
fn test_recalculate_hotspot_metrics_zero_sloc_defect_density_unchanged() {
let mut result = make_result(make_hotspot("src/foo.rs", 0, 5), vec![]);
let original_density = result.hotspot.defect_density;
result.hotspot.detailed_violations.clear();
recalculate_hotspot_metrics(&mut result);
assert_eq!(result.hotspot.total_violations, 0);
assert_eq!(result.hotspot.defect_density, original_density);
}
#[test]
fn test_should_exit_quality_gate_failed() {
let mut result = make_result(make_hotspot("src/foo.rs", 100, 5), vec![]);
result.quality_gate.passed = false;
let params = make_params(vec![], vec![]);
assert!(should_exit_with_error(&result, ¶ms));
}
#[test]
fn test_should_exit_quality_gate_passed_no_enforce() {
let result = make_result(make_hotspot("src/foo.rs", 100, 5), vec![]);
let params = make_params(vec![], vec![]);
assert!(!should_exit_with_error(&result, ¶ms));
}
#[test]
fn test_should_exit_enforce_with_violations() {
let mut result = make_result(make_hotspot("src/foo.rs", 100, 5), vec![]);
result.total_project_violations = 3;
let mut params = make_params(vec![], vec![]);
params.enforce = true;
assert!(should_exit_with_error(&result, ¶ms));
}
#[test]
fn test_should_exit_enforce_with_no_violations_passes() {
let mut result = make_result(make_hotspot("src/foo.rs", 100, 5), vec![]);
result.total_project_violations = 0;
let mut params = make_params(vec![], vec![]);
params.enforce = true;
assert!(!should_exit_with_error(&result, ¶ms));
}
#[test]
fn test_generate_enforcement_metadata_none_when_neither_flag_set() {
let hotspot = make_hotspot("src/foo.rs", 100, 5);
let params = make_params(vec![], vec![]);
assert!(generate_enforcement_metadata_if_needed(&hotspot, ¶ms).is_none());
}
#[test]
fn test_generate_enforcement_metadata_some_when_metadata_flag() {
let hotspot = make_hotspot("src/foo.rs", 100, 5);
let mut params = make_params(vec![], vec![]);
params.enforcement_metadata = true;
assert!(generate_enforcement_metadata_if_needed(&hotspot, ¶ms).is_some());
}
#[test]
fn test_generate_enforcement_metadata_some_when_enforce_flag() {
let hotspot = make_hotspot("src/foo.rs", 100, 5);
let mut params = make_params(vec![], vec![]);
params.enforce = true;
assert!(generate_enforcement_metadata_if_needed(&hotspot, ¶ms).is_some());
}
}