use std::fmt;
#[cfg(feature = "serde")]
use serde::Serialize;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
pub enum Severity {
Bug,
Error,
Warning,
Note,
Help,
}
pub trait DiagnosticCode: fmt::Debug + Clone {
fn code(&self) -> &str;
fn severity(&self) -> Severity;
fn url(&self) -> Option<&'static str> {
None
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct Span {
pub file: String,
pub line: usize,
pub column: usize,
pub length: usize,
}
impl Span {
pub fn new(file: impl Into<String>, line: usize, column: usize, length: usize) -> Self {
Self { file: file.into(), line, column, length }
}
pub fn from_zero_based(
file: impl Into<String>,
line: usize,
column: usize,
length: usize,
) -> Self {
Self { file: file.into(), line: line + 1, column: column + 1, length }
}
pub fn synthetic(file: impl Into<String>) -> Self {
Self { file: file.into(), line: 0, column: 0, length: 0 }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
pub enum LabelStyle {
Primary,
Secondary,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct Label {
pub span: Span,
pub message: Option<String>,
pub style: LabelStyle,
pub note: Option<String>,
}
impl Label {
pub fn primary(span: Span, message: impl Into<Option<String>>) -> Self {
Self { span, message: message.into(), style: LabelStyle::Primary, note: None }
}
pub fn secondary(span: Span, message: impl Into<Option<String>>) -> Self {
Self { span, message: message.into(), style: LabelStyle::Secondary, note: None }
}
pub fn with_note(mut self, note: impl Into<String>) -> Self {
self.note = Some(note.into());
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
pub enum Applicability {
MachineApplicable,
MaybeIncorrect,
HasPlaceholders,
Unspecified,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct Suggestion {
pub span: Span,
pub replacement: String,
pub message: Option<String>,
pub applicability: Applicability,
}
impl Suggestion {
pub fn new(span: Span, replacement: impl Into<String>) -> Self {
Self {
span,
replacement: replacement.into(),
message: None,
applicability: Applicability::Unspecified,
}
}
pub fn with_message(mut self, message: impl Into<String>) -> Self {
self.message = Some(message.into());
self
}
pub fn with_applicability(mut self, app: Applicability) -> Self {
self.applicability = app;
self
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize))]
pub struct Diagnostic<C: DiagnosticCode> {
pub code: C,
pub severity: Severity,
pub message: String,
pub labels: Vec<Label>,
pub notes: Vec<String>,
pub help: Option<String>,
pub suggestions: Vec<Suggestion>,
}
impl<C: DiagnosticCode> Diagnostic<C> {
pub fn new(code: C, message: impl Into<String>) -> Self {
let severity = code.severity();
Self {
code,
severity,
message: message.into(),
labels: Vec::new(),
notes: Vec::new(),
help: None,
suggestions: Vec::new(),
}
}
pub fn with_label(mut self, label: Label) -> Self {
self.labels.push(label);
self
}
pub fn with_note(mut self, note: impl Into<String>) -> Self {
self.notes.push(note.into());
self
}
pub fn with_help(mut self, help: impl Into<String>) -> Self {
self.help = Some(help.into());
self
}
pub fn with_suggestion(mut self, suggestion: Suggestion) -> Self {
self.suggestions.push(suggestion);
self
}
pub fn with_severity(mut self, severity: Severity) -> Self {
self.severity = severity;
self
}
pub fn primary_label(&self) -> Option<&Label> {
self.labels.iter().find(|l| l.style == LabelStyle::Primary).or_else(|| self.labels.first())
}
}