use std::fmt;
use crate::{
error::{Severity, error_code::ErrorCode, label::Label},
span::Span,
};
#[derive(Debug, Clone)]
pub struct Diagnostic {
severity: Severity,
code: Option<ErrorCode>,
message: String,
labels: Vec<Label>,
help: Option<String>,
}
impl Diagnostic {
pub fn error(message: impl Into<String>) -> Self {
Self::new(Severity::Error, message)
}
pub fn warning(message: impl Into<String>) -> Self {
Self::new(Severity::Warning, message)
}
pub fn severity(&self) -> Severity {
self.severity
}
pub fn code(&self) -> Option<ErrorCode> {
self.code
}
pub fn message(&self) -> &str {
&self.message
}
pub fn labels(&self) -> &[Label] {
&self.labels
}
pub fn help(&self) -> Option<&str> {
self.help.as_deref()
}
pub fn with_code(mut self, code: ErrorCode) -> Self {
self.code = Some(code);
self
}
pub fn with_label(mut self, span: Span, message: impl Into<String>) -> Self {
self.labels.push(Label::primary(span, message));
self
}
pub fn with_secondary_label(mut self, span: Span, message: impl Into<String>) -> Self {
self.labels.push(Label::secondary(span, message));
self
}
pub fn with_help(mut self, help: impl Into<String>) -> Self {
self.help = Some(help.into());
self
}
fn new(severity: Severity, message: impl Into<String>) -> Self {
Self {
severity,
code: None,
message: message.into(),
labels: Vec::new(),
help: None,
}
}
}
impl fmt::Display for Diagnostic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.severity)?;
if let Some(code) = self.code {
write!(f, "[{}]", code)?;
}
write!(f, ": {}", self.message)
}
}
impl std::error::Error for Diagnostic {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_diagnostic_new() {
let diag = Diagnostic::new(Severity::Error, "test error");
assert!(diag.severity().is_error());
assert!(!diag.severity().is_warning());
assert_eq!(diag.message(), "test error");
assert!(diag.code().is_none());
assert!(diag.labels().is_empty());
assert!(diag.help().is_none());
}
#[test]
fn test_diagnostic_with_code() {
let diag = Diagnostic::new(Severity::Error, "undefined type").with_code(ErrorCode::E300);
assert_eq!(diag.code(), Some(ErrorCode::E300));
}
#[test]
fn test_diagnostic_with_label() {
let diag = Diagnostic::new(Severity::Error, "test error")
.with_label(Span::new(10..20), "error here");
assert_eq!(diag.labels().len(), 1);
assert!(diag.labels()[0].is_primary());
assert_eq!(diag.labels()[0].message(), "error here");
}
#[test]
fn test_diagnostic_with_secondary_label() {
let diag = Diagnostic::new(Severity::Error, "duplicate definition")
.with_label(Span::new(10..20), "duplicate here")
.with_secondary_label(Span::new(5..15), "first defined here");
assert_eq!(diag.labels().len(), 2);
assert!(diag.labels()[0].is_primary());
assert!(diag.labels()[1].is_secondary());
}
#[test]
fn test_diagnostic_with_help() {
let diag = Diagnostic::new(Severity::Warning, "unused variable")
.with_help("consider removing or prefixing with underscore");
assert_eq!(
diag.help(),
Some("consider removing or prefixing with underscore")
);
}
#[test]
fn test_diagnostic_display_with_code() {
let diag =
Diagnostic::new(Severity::Error, "undefined type `Foo`").with_code(ErrorCode::E300);
assert_eq!(diag.to_string(), "error[E300]: undefined type `Foo`");
}
#[test]
fn test_diagnostic_display_without_code() {
let diag = Diagnostic::new(Severity::Warning, "unused import");
assert_eq!(diag.to_string(), "warning: unused import");
}
#[test]
fn test_diagnostic_builder_chain() {
let diag = Diagnostic::new(Severity::Error, "type `User` is defined multiple times")
.with_code(ErrorCode::E301)
.with_label(Span::new(100..120), "duplicate definition")
.with_secondary_label(Span::new(50..70), "first defined here")
.with_help("remove the duplicate or use a different name");
assert!(diag.severity().is_error());
assert_eq!(diag.code(), Some(ErrorCode::E301));
assert_eq!(diag.message(), "type `User` is defined multiple times");
assert_eq!(diag.labels().len(), 2);
assert_eq!(
diag.help(),
Some("remove the duplicate or use a different name")
);
}
}