use std::fmt;
use rowan::TextRange;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Span {
start: usize,
end: usize,
}
impl Span {
pub fn new(start: usize, len: usize) -> Self {
Self {
start,
end: start + len,
}
}
pub fn start(&self) -> usize {
self.start
}
pub fn end(&self) -> usize {
self.end
}
pub fn len(&self) -> usize {
self.end - self.start
}
pub fn is_empty(&self) -> bool {
self.start == self.end
}
}
impl fmt::Display for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{start}..{end}", start = self.start, end = self.end)
}
}
impl From<logos::Span> for Span {
fn from(value: logos::Span) -> Self {
Self::new(value.start, value.len())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Severity {
Error,
Warning,
Note,
}
#[derive(Debug, Clone)]
pub struct Diagnostic {
rule: Option<String>,
severity: Severity,
message: String,
fix: Option<String>,
labels: Vec<Label>,
}
impl Diagnostic {
pub fn error(message: impl Into<String>) -> Self {
Self {
rule: None,
severity: Severity::Error,
message: message.into(),
fix: None,
labels: Default::default(),
}
}
pub fn warning(message: impl Into<String>) -> Self {
Self {
rule: None,
severity: Severity::Warning,
message: message.into(),
fix: None,
labels: Default::default(),
}
}
pub fn note(message: impl Into<String>) -> Self {
Self {
rule: None,
severity: Severity::Note,
message: message.into(),
fix: None,
labels: Default::default(),
}
}
pub fn with_rule(mut self, rule: impl Into<String>) -> Self {
self.rule = Some(rule.into());
self
}
pub fn with_fix(mut self, fix: impl Into<String>) -> Self {
self.fix = Some(fix.into());
self
}
pub fn with_highlight(mut self, span: impl ToSpan) -> Self {
self.labels.push(Label::new(String::new(), span));
self
}
pub fn with_label(mut self, message: impl Into<String>, span: impl ToSpan) -> Self {
self.labels.push(Label::new(message, span));
self
}
pub fn rule(&self) -> Option<&str> {
self.rule.as_deref()
}
pub fn severity(&self) -> Severity {
self.severity
}
pub fn message(&self) -> &str {
&self.message
}
pub fn fix(&self) -> Option<&str> {
self.fix.as_deref()
}
pub fn labels(&self) -> impl Iterator<Item = &Label> {
self.labels.iter()
}
#[cfg(feature = "codespan")]
pub fn to_codespan(&self) -> codespan_reporting::diagnostic::Diagnostic<()> {
use codespan_reporting::diagnostic as codespan;
let mut diagnostic = match self.severity {
Severity::Error => codespan::Diagnostic::error(),
Severity::Warning => codespan::Diagnostic::warning(),
Severity::Note => codespan::Diagnostic::note(),
};
if let Some(rule) = &self.rule {
diagnostic.code = Some(rule.clone());
}
diagnostic.message.clone_from(&self.message);
if let Some(fix) = &self.fix {
diagnostic.notes.push(format!("fix: {fix}"));
}
for (i, secondary) in self.labels.iter().enumerate() {
diagnostic.labels.push(
codespan::Label::new(
if i == 0 {
codespan::LabelStyle::Primary
} else {
codespan::LabelStyle::Secondary
},
(),
secondary.span.start..secondary.span.end,
)
.with_message(&secondary.message),
);
}
diagnostic
}
}
#[derive(Debug, Clone)]
pub struct Label {
message: String,
span: Span,
}
impl Label {
pub fn new(message: impl Into<String>, span: impl ToSpan) -> Self {
Self {
message: message.into(),
span: span.to_span(),
}
}
pub fn message(&self) -> &str {
&self.message
}
pub fn span(&self) -> Span {
self.span
}
}
pub trait ToSpan {
fn to_span(&self) -> Span;
}
impl ToSpan for TextRange {
fn to_span(&self) -> Span {
let start = usize::from(self.start());
Span::new(start, usize::from(self.end()) - start)
}
}
impl ToSpan for Span {
fn to_span(&self) -> Span {
*self
}
}