use std::ops::Range;
pub mod codes;
pub mod render;
pub use render::to_human;
pub type Span = Range<usize>;
pub type Diagnostics = Vec<Diagnostic>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Severity {
Error,
Warning,
Note,
}
impl Severity {
pub fn as_str(self) -> &'static str {
match self {
Severity::Error => "error",
Severity::Warning => "warning",
Severity::Note => "note",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DiagCode(&'static str);
impl DiagCode {
pub const fn new(code: &'static str) -> Self {
Self(code)
}
pub const fn as_str(self) -> &'static str {
self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Label {
pub span: Span,
pub text: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Edit {
pub span: Span,
pub text: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Fix {
pub title: String,
pub edits: Vec<Edit>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Diagnostic {
pub code: DiagCode,
pub severity: Severity,
pub message: String,
pub primary: Span,
pub labels: Vec<Label>,
pub help: Option<String>,
pub fixes: Vec<Fix>,
}
impl Diagnostic {
pub fn error(
code: DiagCode,
primary: Span,
message: impl Into<String>,
label: impl Into<String>,
) -> Self {
Self {
code,
severity: Severity::Error,
message: message.into(),
labels: vec![Label {
span: primary.clone(),
text: label.into(),
}],
primary,
help: None,
fixes: Vec::new(),
}
}
pub fn warning(
code: DiagCode,
primary: Span,
message: impl Into<String>,
label: impl Into<String>,
) -> Self {
Self {
severity: Severity::Warning,
..Self::error(code, primary, message, label)
}
}
pub fn with_label(mut self, span: Span, text: impl Into<String>) -> Self {
self.labels.push(Label {
span,
text: text.into(),
});
self
}
pub fn with_help(mut self, help: impl Into<String>) -> Self {
self.help = Some(help.into());
self
}
pub fn with_fix(mut self, fix: Fix) -> Self {
self.fixes.push(fix);
self
}
pub fn primary_label(&self) -> Option<&Label> {
self.labels.iter().find(|label| label.span == self.primary)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_diagnostic_has_one_primary_label() {
let d = Diagnostic::error(codes::CORE_UNKNOWN_ELEMENT, 4..8, "bad", "no such thing")
.with_label(20..24, "declared here");
assert_eq!(d.severity, Severity::Error);
assert_eq!(d.primary_label().unwrap().text, "no such thing");
assert_eq!(d.labels.len(), 2);
}
#[test]
fn codes_are_data_not_prefixes() {
let d = Diagnostic::error(DiagCode::new("E0701"), 0..1, "boom", "here");
assert_eq!(d.code.as_str(), "E0701");
}
#[test]
fn the_machine_severity_spelling_is_stable() {
assert_eq!(Severity::Error.as_str(), "error");
assert_eq!(Severity::Warning.as_str(), "warning");
assert_eq!(Severity::Note.as_str(), "note");
}
}