use crate::diagnostics::{Diagnostic, Severity};
use crate::error::TldrError;
use serde::Deserialize;
use std::path::PathBuf;
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct EslintFileResult {
file_path: String,
messages: Vec<EslintMessage>,
#[allow(dead_code)]
error_count: Option<u32>,
#[allow(dead_code)]
warning_count: Option<u32>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct EslintMessage {
rule_id: Option<String>,
severity: u8,
message: String,
line: u32,
column: u32,
end_line: Option<u32>,
end_column: Option<u32>,
}
pub fn parse_eslint_output(output: &str) -> Result<Vec<Diagnostic>, TldrError> {
if output.trim().is_empty() {
return Ok(Vec::new());
}
if output.trim() == "[]" {
return Ok(Vec::new());
}
let parsed: Vec<EslintFileResult> =
serde_json::from_str(output).map_err(|e| TldrError::ParseError {
file: std::path::PathBuf::from("<eslint-output>"),
line: None,
message: format!("Failed to parse eslint JSON: {}", e),
})?;
let mut diagnostics = Vec::new();
for file_result in parsed {
for msg in file_result.messages {
let severity = match msg.severity {
2 => Severity::Error,
1 => Severity::Warning,
_ => Severity::Warning,
};
diagnostics.push(Diagnostic {
file: PathBuf::from(&file_result.file_path),
line: msg.line,
column: msg.column,
end_line: msg.end_line,
end_column: msg.end_column,
severity,
message: msg.message,
code: msg.rule_id,
source: "eslint".to_string(),
url: None,
});
}
}
Ok(diagnostics)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_simple() {
let json = r#"[
{
"filePath": "/project/src/auth.ts",
"messages": [
{
"ruleId": "no-unused-vars",
"severity": 2,
"message": "'x' is defined but never used.",
"line": 10,
"column": 5,
"endLine": 10,
"endColumn": 6
}
],
"errorCount": 1,
"warningCount": 0
}
]"#;
let result = parse_eslint_output(json).unwrap();
assert_eq!(result.len(), 1);
let d = &result[0];
assert_eq!(d.file, PathBuf::from("/project/src/auth.ts"));
assert_eq!(d.line, 10);
assert_eq!(d.column, 5);
assert_eq!(d.severity, Severity::Error);
assert_eq!(d.code, Some("no-unused-vars".to_string()));
assert_eq!(d.source, "eslint");
}
#[test]
fn test_severity_mapping() {
let json = r#"[
{
"filePath": "test.ts",
"messages": [
{"ruleId": "a", "severity": 2, "message": "error", "line": 1, "column": 1},
{"ruleId": "b", "severity": 1, "message": "warning", "line": 2, "column": 1}
]
}
]"#;
let result = parse_eslint_output(json).unwrap();
assert_eq!(result.len(), 2);
assert_eq!(result[0].severity, Severity::Error);
assert_eq!(result[1].severity, Severity::Warning);
}
#[test]
fn test_empty_messages() {
let json = r#"[
{
"filePath": "/project/src/clean.ts",
"messages": [],
"errorCount": 0,
"warningCount": 0
}
]"#;
let result = parse_eslint_output(json).unwrap();
assert!(result.is_empty());
}
#[test]
fn test_empty_output() {
let result = parse_eslint_output("").unwrap();
assert!(result.is_empty());
}
#[test]
fn test_empty_array() {
let result = parse_eslint_output("[]").unwrap();
assert!(result.is_empty());
}
}