use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Severity {
Error,
Warning,
Info,
Hint,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolDiagnostic {
pub tool: String,
pub file: String,
pub line: u32,
pub col: u32,
pub severity: Severity,
pub code: Option<String>,
pub message: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct DiagnosticsReport {
pub tools_run: Vec<String>,
pub tools_unavailable: Vec<String>,
pub diagnostics: Vec<ToolDiagnostic>,
}
impl DiagnosticsReport {
pub fn empty() -> Self {
Self {
tools_run: Vec::new(),
tools_unavailable: Vec::new(),
diagnostics: Vec::new(),
}
}
}
pub trait StaticTool: Send + Sync {
fn name(&self) -> &str;
fn language(&self) -> &str;
fn aliases(&self) -> &[&str] {
&[]
}
fn is_available(&self) -> bool;
fn run(&self, file: &Path, content: &str) -> anyhow::Result<Vec<ToolDiagnostic>>;
fn is_project_scoped(&self) -> bool {
false
}
fn run_project(&self, files: &[PathBuf]) -> anyhow::Result<Vec<ToolDiagnostic>> {
let mut out = Vec::new();
for f in files {
match self.run(f, "") {
Ok(diags) => out.extend(diags),
Err(e) => {
tracing::warn!(
tool = self.name(),
file = %f.display(),
"run_project default: per-file run failed, skipping: {e:#}"
);
}
}
}
Ok(out)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn severity_serializes_lowercase() {
assert_eq!(
serde_json::to_string(&Severity::Error).expect("serialize"),
"\"error\""
);
assert_eq!(
serde_json::to_string(&Severity::Hint).expect("serialize"),
"\"hint\""
);
}
#[test]
fn diagnostic_round_trips() {
let d = ToolDiagnostic {
tool: "clippy".into(),
file: "src/main.rs".into(),
line: 12,
col: 4,
severity: Severity::Warning,
code: Some("clippy::needless_return".into()),
message: "unneeded return statement".into(),
};
let json = serde_json::to_string(&d).expect("serialize");
let back: ToolDiagnostic = serde_json::from_str(&json).expect("deserialize");
assert_eq!(back.tool, "clippy");
assert_eq!(back.line, 12);
assert_eq!(back.severity, Severity::Warning);
}
}