#[cfg(test)]
mod tests;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Severity {
Error = 1,
Warning = 2,
Information = 3,
#[default]
Hint = 4,
}
impl std::fmt::Display for Severity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Severity::Error => write!(f, "error"),
Severity::Warning => write!(f, "warning"),
Severity::Information => write!(f, "info"),
Severity::Hint => write!(f, "hint"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Diagnostic {
pub file: PathBuf,
pub line: u32,
pub column: u32,
pub end_line: Option<u32>,
pub end_column: Option<u32>,
pub severity: Severity,
pub message: String,
pub code: Option<String>,
pub source: String,
pub url: Option<String>,
}
impl Diagnostic {
pub fn dedupe_key(&self) -> String {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
self.message.hash(&mut hasher);
let msg_hash = hasher.finish();
format!(
"{}:{}:{}:{:x}",
self.file.display(),
self.line,
self.column,
msg_hash
)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct DiagnosticsReport {
pub diagnostics: Vec<Diagnostic>,
pub summary: DiagnosticsSummary,
pub tools_run: Vec<ToolResult>,
pub files_analyzed: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct DiagnosticsSummary {
pub errors: usize,
pub warnings: usize,
pub info: usize,
pub hints: usize,
pub total: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolResult {
pub name: String,
pub version: Option<String>,
pub success: bool,
pub duration_ms: u64,
pub diagnostic_count: usize,
pub error: Option<String>,
}
#[derive(Debug, Clone)]
pub struct ToolConfig {
pub name: &'static str,
pub binary: &'static str,
pub args: Vec<String>,
pub is_type_checker: bool,
pub is_linter: bool,
}
#[derive(Debug, Clone, Default)]
pub struct DiagnosticsOptions {
pub min_severity: Severity,
pub no_typecheck: bool,
pub no_lint: bool,
pub tools: Vec<String>,
pub timeout_secs: u64,
pub project_mode: bool,
pub filter_files: Vec<String>,
pub ignore_codes: Vec<String>,
}
pub fn filter_diagnostics_by_severity(
diagnostics: &[Diagnostic],
min_severity: Severity,
) -> Vec<Diagnostic> {
diagnostics
.iter()
.filter(|d| d.severity <= min_severity)
.cloned()
.collect()
}
pub fn compute_summary(diagnostics: &[Diagnostic]) -> DiagnosticsSummary {
let mut summary = DiagnosticsSummary::default();
for diag in diagnostics {
match diag.severity {
Severity::Error => summary.errors += 1,
Severity::Warning => summary.warnings += 1,
Severity::Information => summary.info += 1,
Severity::Hint => summary.hints += 1,
}
}
summary.total = diagnostics.len();
summary
}
pub fn dedupe_diagnostics(diagnostics: Vec<Diagnostic>) -> Vec<Diagnostic> {
use std::collections::HashSet;
let mut seen = HashSet::new();
diagnostics
.into_iter()
.filter(|d| seen.insert(d.dedupe_key()))
.collect()
}
pub fn compute_exit_code(summary: &DiagnosticsSummary, strict: bool) -> i32 {
if summary.errors > 0 || (strict && summary.warnings > 0) {
1
} else {
0
}
}
pub mod parsers;
pub mod runner;
pub use parsers::*;
pub use runner::*;