use crate::source::FileId;
use crate::span::Span;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Severity {
Error,
Warning,
}
impl Severity {
pub fn as_str(self) -> &'static str {
match self {
Severity::Error => "error",
Severity::Warning => "warning",
}
}
}
#[derive(Clone, Debug)]
pub struct Label {
pub span: Span,
pub file: Option<FileId>,
pub message: String,
}
impl Label {
pub fn new(span: Span, message: impl Into<String>) -> Self {
Self {
span,
file: None,
message: message.into(),
}
}
pub fn in_file(file: FileId, span: Span, message: impl Into<String>) -> Self {
Self {
span,
file: Some(file),
message: message.into(),
}
}
pub fn file_or(&self, diagnostic: FileId) -> FileId {
self.file.unwrap_or(diagnostic)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Applicability {
MachineApplicable,
MaybeIncorrect,
}
impl Applicability {
pub fn as_str(self) -> &'static str {
match self {
Applicability::MachineApplicable => "machine-applicable",
Applicability::MaybeIncorrect => "maybe-incorrect",
}
}
}
#[derive(Clone, Debug)]
pub struct SuggestedEdit {
pub span: Span,
pub replacement: String,
}
#[derive(Clone, Debug)]
pub struct Fix {
pub message: String,
pub edits: Vec<SuggestedEdit>,
pub applicability: Applicability,
}
#[derive(Clone, Debug)]
pub struct Diagnostic {
pub code: &'static str,
pub severity: Severity,
pub message: String,
pub file: FileId,
pub primary: Label,
pub secondary: Vec<Label>,
pub notes: Vec<String>,
pub fix: Option<Fix>,
}
impl Diagnostic {
pub fn error(code: &'static str, file: FileId, span: Span, message: impl Into<String>) -> Self {
let message = message.into();
Self {
code,
severity: Severity::Error,
primary: Label::new(span, message.clone()),
message,
file,
secondary: Vec::new(),
notes: Vec::new(),
fix: None,
}
}
pub fn warning(
code: &'static str,
file: FileId,
span: Span,
message: impl Into<String>,
) -> Self {
let mut diagnostic = Self::error(code, file, span, message);
diagnostic.severity = Severity::Warning;
diagnostic
}
#[must_use]
pub fn with_primary_label(mut self, message: impl Into<String>) -> Self {
self.primary.message = message.into();
self
}
#[must_use]
pub fn with_secondary(mut self, span: Span, message: impl Into<String>) -> Self {
self.secondary.push(Label::new(span, message));
self
}
#[must_use]
pub fn with_secondary_in(
mut self,
file: FileId,
span: Span,
message: impl Into<String>,
) -> Self {
self.secondary.push(Label::in_file(file, span, message));
self
}
#[must_use]
pub fn with_note(mut self, note: impl Into<String>) -> Self {
self.notes.push(note.into());
self
}
#[must_use]
pub fn with_fix(
mut self,
message: impl Into<String>,
span: Span,
replacement: impl Into<String>,
applicability: Applicability,
) -> Self {
self.fix = Some(Fix {
message: message.into(),
edits: vec![SuggestedEdit {
span,
replacement: replacement.into(),
}],
applicability,
});
self
}
#[must_use]
pub fn with_edits(
mut self,
message: impl Into<String>,
edits: Vec<SuggestedEdit>,
applicability: Applicability,
) -> Self {
self.fix = Some(Fix {
message: message.into(),
edits,
applicability,
});
self
}
pub fn is_error(&self) -> bool {
self.severity == Severity::Error
}
}
#[cfg(test)]
mod tests {
use super::{Applicability, Diagnostic, Severity};
use crate::source::SourceMap;
use crate::span::Span;
#[test]
fn primary_label_defaults_to_the_message() {
let mut map = SourceMap::new();
let file = map.add("t.deed", "let x = 1");
let d = Diagnostic::error("DEED0001", file, Span::new(0, 3), "something went wrong");
assert_eq!(d.primary.message, "something went wrong");
assert!(d.is_error());
}
#[test]
fn builders_compose() {
let mut map = SourceMap::new();
let file = map.add("t.deed", "let x = 1");
let d = Diagnostic::warning("DEED0002", file, Span::new(0, 3), "headline")
.with_primary_label("here")
.with_secondary(Span::new(4, 5), "related")
.with_note("a note")
.with_fix(
"try this",
Span::new(0, 3),
"val",
Applicability::MachineApplicable,
);
assert_eq!(d.severity, Severity::Warning);
assert_eq!(d.primary.message, "here");
assert_eq!(d.secondary.len(), 1);
assert_eq!(d.notes, vec!["a note".to_string()]);
assert_eq!(d.fix.unwrap().edits[0].replacement, "val");
}
}