use std::cmp::Ordering;
use std::fmt;
use rowan::TextRange;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Span {
start: usize,
end: usize,
}
impl Span {
pub const 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
}
pub fn contains(&self, offset: usize) -> bool {
offset >= self.start && offset < self.end
}
#[inline]
pub fn intersect(self, other: Self) -> Option<Self> {
let start = self.start.max(other.start);
let end = self.end.min(other.end);
if end < start {
return None;
}
Some(Self { start, 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())
}
}
impl From<TextRange> for Span {
fn from(value: TextRange) -> Self {
let start = usize::from(value.start());
Self::new(start, usize::from(value.end()) - start)
}
}
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd, serde::Deserialize, serde::Serialize,
)]
pub enum Severity {
Error,
Warning,
Note,
}
impl Severity {
#[must_use]
pub fn is_error(&self) -> bool {
matches!(self, Self::Error)
}
#[must_use]
pub fn is_warning(&self) -> bool {
matches!(self, Self::Warning)
}
#[must_use]
pub fn is_note(&self) -> bool {
matches!(self, Self::Note)
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Diagnostic {
rule: Option<String>,
severity: Severity,
message: String,
fix: Option<String>,
labels: Vec<Label>,
}
impl Ord for Diagnostic {
fn cmp(&self, other: &Self) -> Ordering {
match self.labels.cmp(&other.labels) {
Ordering::Equal => {}
ord => return ord,
}
match self.rule.cmp(&other.rule) {
Ordering::Equal => {}
ord => return ord,
}
match self.severity.cmp(&other.severity) {
Ordering::Equal => {}
ord => return ord,
}
match self.message.cmp(&other.message) {
Ordering::Equal => {}
ord => return ord,
}
self.fix.cmp(&other.fix)
}
}
impl PartialOrd for Diagnostic {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
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 Into<Span>) -> Self {
self.labels.push(Label::new(String::new(), span.into()));
self
}
pub fn with_label(mut self, message: impl Into<String>, span: impl Into<Span>) -> Self {
self.labels.push(Label::new(message, span.into()));
self
}
pub fn with_severity(mut self, severity: Severity) -> Self {
self.severity = severity;
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()
}
pub fn labels_mut(&mut self) -> impl Iterator<Item = &mut Label> {
self.labels.iter_mut()
}
pub fn to_codespan<FileId: Copy>(
&self,
file_id: FileId,
) -> codespan_reporting::diagnostic::Diagnostic<FileId> {
use codespan_reporting::diagnostic as codespan;
let mut diagnostic: codespan::Diagnostic<FileId> = 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}"));
}
if self.labels.is_empty() {
diagnostic.labels.push(codespan::Label::new(
codespan::LabelStyle::Primary,
file_id,
usize::MAX - 1..usize::MAX,
))
} else {
for (i, label) in self.labels.iter().enumerate() {
diagnostic.labels.push(
codespan::Label::new(
if i == 0 {
codespan::LabelStyle::Primary
} else {
codespan::LabelStyle::Secondary
},
file_id,
label.span.start..label.span.end,
)
.with_message(&label.message),
);
}
}
diagnostic
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Label {
message: String,
span: Span,
}
impl Ord for Label {
fn cmp(&self, other: &Self) -> Ordering {
match self.span.cmp(&other.span) {
Ordering::Equal => {}
ord => return ord,
}
self.message.cmp(&other.message)
}
}
impl PartialOrd for Label {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Label {
pub fn new(message: impl Into<String>, span: impl Into<Span>) -> Self {
Self {
message: message.into(),
span: span.into(),
}
}
pub fn message(&self) -> &str {
&self.message
}
pub fn span(&self) -> Span {
self.span
}
pub fn set_span(&mut self, span: impl Into<Span>) {
self.span = span.into();
}
}