use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Severity {
Warning,
Info,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DiagnosticKind {
MalformedTable,
UnclosedCodeBlock,
Other,
}
#[derive(Debug, Clone)]
pub struct Diagnostic {
pub severity: Severity,
pub kind: DiagnosticKind,
pub line: usize,
pub message: String,
pub snippet: Option<String>,
}
impl Diagnostic {
pub fn new(
severity: Severity,
kind: DiagnosticKind,
line: usize,
message: impl Into<String>,
) -> Self {
Self {
severity,
kind,
line,
message: message.into(),
snippet: None,
}
}
pub fn with_snippet(mut self, snippet: impl Into<String>) -> Self {
self.snippet = Some(snippet.into());
self
}
}
impl fmt::Display for Diagnostic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let severity_icon = match self.severity {
Severity::Warning => "⚠️",
Severity::Info => "ℹ️",
};
write!(f, "{} Line {}: {}", severity_icon, self.line, self.message)?;
if let Some(snippet) = &self.snippet {
write!(f, "\n │ {}", snippet)?;
}
Ok(())
}
}
#[derive(Debug, Default, Clone)]
pub struct Diagnostics {
messages: Vec<Diagnostic>,
}
impl Diagnostics {
pub fn new() -> Self {
Self {
messages: Vec::new(),
}
}
pub fn add(&mut self, diagnostic: Diagnostic) {
self.messages.push(diagnostic);
}
pub fn warn(&mut self, kind: DiagnosticKind, line: usize, message: impl Into<String>) {
self.add(Diagnostic::new(Severity::Warning, kind, line, message));
}
pub fn info(&mut self, kind: DiagnosticKind, line: usize, message: impl Into<String>) {
self.add(Diagnostic::new(Severity::Info, kind, line, message));
}
pub fn is_empty(&self) -> bool {
self.messages.is_empty()
}
pub fn len(&self) -> usize {
self.messages.len()
}
pub fn messages(&self) -> &[Diagnostic] {
&self.messages
}
pub fn by_severity(&self, severity: Severity) -> Vec<&Diagnostic> {
self.messages
.iter()
.filter(|d| d.severity == severity)
.collect()
}
pub fn print_to_stderr(&self) {
if self.is_empty() {
return;
}
eprintln!("\n{} issues found:", self.len());
for diagnostic in &self.messages {
eprintln!("{}", diagnostic);
}
eprintln!();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_diagnostic_creation() {
let diag = Diagnostic::new(
Severity::Warning,
DiagnosticKind::MalformedTable,
42,
"Test message",
);
assert_eq!(diag.line, 42);
assert_eq!(diag.message, "Test message");
}
#[test]
fn test_diagnostics_collection() {
let mut diags = Diagnostics::new();
assert!(diags.is_empty());
diags.warn(DiagnosticKind::MalformedTable, 10, "Test");
assert_eq!(diags.len(), 1);
let warnings = diags.by_severity(Severity::Warning);
assert_eq!(warnings.len(), 1);
}
}