use ariadne::{Color, Config, Label, Report, ReportKind};
use crate::span::Span;
#[derive(Debug, Clone)]
pub struct CompileError {
pub category: &'static str,
pub span: Span,
pub message: String,
pub labels: Vec<(Span, String)>,
pub notes: Vec<String>,
pub suggestions: Vec<Suggestion>,
}
#[derive(Debug, Clone)]
pub struct Suggestion {
pub message: String,
pub edits: Vec<(Span, String)>,
pub applicability: Applicability,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Applicability {
MachineApplicable,
HasPlaceholders,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Severity {
Error,
Warning,
}
impl Severity {
pub fn for_error(err: &CompileError) -> Severity {
match err.category {
"bynk.parse.orphan_doc_block"
| "bynk.given.unused_capability"
| "bynk.list.deprecated_function"
| "bynk.index.missing"
| "bynk.index.unused" => Severity::Warning,
_ => Severity::Error,
}
}
}
pub fn partition_by_severity(
diagnostics: Vec<CompileError>,
) -> (Vec<CompileError>, Vec<CompileError>) {
diagnostics
.into_iter()
.partition(|d| Severity::for_error(d) == Severity::Error)
}
impl CompileError {
pub fn new(category: &'static str, span: Span, message: impl Into<String>) -> Self {
Self {
category,
span,
message: message.into(),
labels: Vec::new(),
notes: Vec::new(),
suggestions: Vec::new(),
}
}
pub fn with_label(mut self, span: Span, label: impl Into<String>) -> Self {
self.labels.push((span, label.into()));
self
}
pub fn with_note(mut self, note: impl Into<String>) -> Self {
self.notes.push(note.into());
self
}
pub fn with_suggestion(
mut self,
message: impl Into<String>,
edits: Vec<(Span, String)>,
applicability: Applicability,
) -> Self {
self.suggestions.push(Suggestion {
message: message.into(),
edits,
applicability,
});
self
}
pub fn report<'a>(
&'a self,
filename: &'a str,
) -> Report<'a, (&'a str, std::ops::Range<usize>)> {
self.report_with_config(filename, Config::default())
}
pub fn report_plain<'a>(
&'a self,
filename: &'a str,
) -> Report<'a, (&'a str, std::ops::Range<usize>)> {
self.report_with_config(filename, Config::default().with_color(false))
}
fn report_with_config<'a>(
&'a self,
filename: &'a str,
config: Config,
) -> Report<'a, (&'a str, std::ops::Range<usize>)> {
let primary_span = (filename, self.span.range());
let mut builder = Report::build(ReportKind::Error, primary_span.clone())
.with_config(config)
.with_code(self.category)
.with_message(&self.message)
.with_label(
Label::new(primary_span)
.with_message(&self.message)
.with_color(Color::Red),
);
for (span, label) in &self.labels {
builder = builder.with_label(
Label::new((filename, span.range()))
.with_message(label)
.with_color(Color::Yellow),
);
}
for note in &self.notes {
builder = builder.with_note(note);
}
builder.finish()
}
}
#[cfg(test)]
mod warning_channel_tests {
use super::*;
use crate::span::Span;
#[test]
fn partition_splits_by_severity() {
let warn = CompileError::new("bynk.given.unused_capability", Span::default(), "unused");
let err = CompileError::new("bynk.types.argument_mismatch", Span::default(), "bad");
let (errors, warnings) = partition_by_severity(vec![warn, err]);
assert_eq!(errors.len(), 1);
assert_eq!(errors[0].category, "bynk.types.argument_mismatch");
assert_eq!(warnings.len(), 1);
assert_eq!(warnings[0].category, "bynk.given.unused_capability");
}
}