use crate::services::clippy_fix::{ClippyDiagnostic, ClippyFixEngine, ConfidenceLevel};
use anyhow::Result;
use pmcp::ToolResult;
use serde_json::{json, Value};
pub async fn auto_clippy_fix(
project_path: Option<String>,
confidence_level: Option<String>,
dry_run: Option<bool>,
fix_specific_codes: Option<Vec<String>>,
) -> Result<ToolResult> {
let path = project_path.unwrap_or_else(|| ".".to_string());
let min_confidence = parse_confidence_level(&confidence_level)?;
let is_dry_run = dry_run.unwrap_or(false);
let diagnostics = run_clippy_analysis(&path).await?;
let engine = ClippyFixEngine::new();
let filtered = filter_diagnostics(&engine, diagnostics, min_confidence, &fix_specific_codes);
let results = if is_dry_run {
simulate_fixes(&engine, filtered).await?
} else {
apply_fixes(&engine, filtered).await?
};
Ok(create_fix_response(results, is_dry_run))
}
fn parse_confidence_level(level: &Option<String>) -> Result<ConfidenceLevel> {
match level.as_deref() {
Some("high") => Ok(ConfidenceLevel::High),
Some("medium") => Ok(ConfidenceLevel::Medium),
Some("low") => Ok(ConfidenceLevel::Low),
None => Ok(ConfidenceLevel::High), Some(other) => Err(anyhow::anyhow!("Invalid confidence level: {other}")),
}
}
async fn run_clippy_analysis(path: &str) -> Result<Vec<ClippyDiagnostic>> {
use tokio::process::Command;
let output = Command::new("cargo")
.args(["clippy", "--message-format=json"])
.current_dir(path)
.output()
.await?;
if !output.status.success() {
return Err(anyhow::anyhow!(
"Clippy failed: {}",
String::from_utf8_lossy(&output.stderr)
));
}
parse_clippy_output(&String::from_utf8_lossy(&output.stdout))
}
fn parse_clippy_output(output: &str) -> Result<Vec<ClippyDiagnostic>> {
let mut diagnostics = Vec::new();
for line in output.lines() {
if line.trim().is_empty() {
continue;
}
if let Ok(diagnostic) = ClippyDiagnostic::from_json(line) {
diagnostics.push(diagnostic);
}
}
Ok(diagnostics)
}
fn filter_diagnostics(
engine: &ClippyFixEngine,
diagnostics: Vec<ClippyDiagnostic>,
min_confidence: ConfidenceLevel,
specific_codes: &Option<Vec<String>>,
) -> Vec<ClippyDiagnostic> {
diagnostics
.into_iter()
.filter(|d| {
let confidence = engine.calculate_confidence(d);
confidence_meets_minimum(confidence, min_confidence.clone())
})
.filter(|d| {
if let Some(codes) = specific_codes {
codes.contains(&d.code)
} else {
true
}
})
.collect()
}
fn confidence_meets_minimum(actual: ConfidenceLevel, minimum: ConfidenceLevel) -> bool {
matches!(
(actual, minimum),
(ConfidenceLevel::High, _)
| (ConfidenceLevel::Medium, ConfidenceLevel::Low)
| (ConfidenceLevel::Medium, ConfidenceLevel::Medium)
| (ConfidenceLevel::Low, ConfidenceLevel::Low)
)
}
async fn simulate_fixes(
engine: &ClippyFixEngine,
diagnostics: Vec<ClippyDiagnostic>,
) -> Result<Value> {
let mut fixes = Vec::new();
for diagnostic in diagnostics {
let confidence = engine.calculate_confidence(&diagnostic);
fixes.push(json!({
"file": diagnostic.file,
"line": diagnostic.line_start,
"code": diagnostic.code,
"message": diagnostic.message,
"confidence": format!("{:?}", confidence),
"would_fix": true,
}));
}
Ok(json!({
"dry_run": true,
"total_fixes": fixes.len(),
"fixes": fixes,
}))
}
async fn apply_fixes(
engine: &ClippyFixEngine,
diagnostics: Vec<ClippyDiagnostic>,
) -> Result<Value> {
let results = engine.apply_batch_fixes(&diagnostics).await?;
let report = engine.generate_report(results.clone());
let detailed_results: Vec<Value> = results
.iter()
.map(|r| {
json!({
"file": r.diagnostic.file,
"line": r.diagnostic.line_start,
"code": r.diagnostic.code,
"success": r.success,
"error": r.error,
"duration_ms": r.duration.as_millis(),
})
})
.collect();
Ok(json!({
"dry_run": false,
"report": {
"total_diagnostics": report.total_diagnostics,
"successful_fixes": report.successful_fixes,
"failed_fixes": report.failed_fixes,
"success_rate": report.success_rate,
"total_duration_ms": report.total_duration.as_millis(),
"fixed_files": report.fixed_files,
},
"detailed_results": detailed_results,
}))
}
fn create_fix_response(results: Value, is_dry_run: bool) -> ToolResult {
let action = if is_dry_run { "analyzed" } else { "applied" };
let response = json!({
"action": action,
"results": results,
"message": format!("🔧 Clippy fixes {} successfully", action)
});
ToolResult {
content: vec![pmcp::Content::Text {
text: serde_json::to_string_pretty(&response).unwrap_or_else(|_| response.to_string()),
}],
is_error: false,
}
}
#[cfg(test)]
mod property_tests {
use proptest::prelude::*;
proptest! {
#[test]
fn basic_property_stability(_input in ".*") {
prop_assert!(true);
}
#[test]
fn module_consistency_check(_x in 0u32..1000) {
prop_assert!(_x < 1001);
}
}
}