use std::path::PathBuf;
use super::super::tools::{L1Finding, ToolCategory};
use super::ParseError;
pub fn parse_ruff_output(stdout: &str) -> Result<Vec<L1Finding>, ParseError> {
let stdout = stdout.trim();
if stdout.is_empty() || stdout == "[]" {
return Ok(Vec::new());
}
let items: Vec<serde_json::Value> = serde_json::from_str(stdout)?;
let mut findings = Vec::new();
for item in &items {
let file = item
.get("filename")
.and_then(|v| v.as_str())
.unwrap_or("");
let row = item
.pointer("/location/row")
.and_then(|v| v.as_u64())
.unwrap_or(0);
let col = item
.pointer("/location/column")
.and_then(|v| v.as_u64())
.unwrap_or(0);
let code = item.get("code").and_then(|v| v.as_str()).unwrap_or("");
let message = item
.get("message")
.and_then(|v| v.as_str())
.unwrap_or("");
let severity = ruff_code_to_severity(code);
findings.push(L1Finding {
tool: String::new(), category: ToolCategory::Linter,
file: PathBuf::from(file),
line: row as u32,
column: col as u32,
native_severity: "warning".to_string(),
severity,
message: message.to_string(),
code: if code.is_empty() {
None
} else {
Some(code.to_string())
},
});
}
Ok(findings)
}
fn ruff_code_to_severity(code: &str) -> String {
match code.chars().next() {
Some('E') | Some('F') => "medium".to_string(),
Some('W') => "low".to_string(),
Some('S') => "high".to_string(),
Some('B') => "medium".to_string(),
_ => "low".to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_ruff_empty() {
assert!(parse_ruff_output("").unwrap().is_empty());
assert!(parse_ruff_output("[]").unwrap().is_empty());
}
#[test]
fn test_parse_ruff_finding() {
let json = r#"[{
"filename": "main.py",
"location": {"row": 13, "column": 9},
"end_location": {"row": 13, "column": 20},
"code": "F841",
"message": "Local variable `transformed` is assigned to but never used",
"url": "https://docs.astral.sh/ruff/rules/unused-variable"
}]"#;
let findings = parse_ruff_output(json).unwrap();
assert_eq!(findings.len(), 1);
assert_eq!(findings[0].file, PathBuf::from("main.py"));
assert_eq!(findings[0].line, 13);
assert_eq!(findings[0].column, 9);
assert_eq!(findings[0].code, Some("F841".to_string()));
assert_eq!(findings[0].severity, "medium");
}
#[test]
fn test_ruff_severity_mapping() {
assert_eq!(ruff_code_to_severity("F841"), "medium");
assert_eq!(ruff_code_to_severity("E501"), "medium");
assert_eq!(ruff_code_to_severity("W291"), "low");
assert_eq!(ruff_code_to_severity("S101"), "high");
assert_eq!(ruff_code_to_severity("B006"), "medium");
}
}