use std::fmt::Display;
use std::ops::Range;
use std::sync::Arc;
pub mod handler;
pub mod render;
pub mod source;
pub use crate::handler::*;
pub use crate::render::*;
pub use crate::source::*;
pub type Error = Box<dyn Diagnostic + Send + Sync>;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum Severity {
#[default]
Error,
Warning,
Info,
Note,
Help,
}
impl std::fmt::Display for Severity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Severity::Error => f.write_str("error"),
Severity::Warning => f.write_str("warning"),
Severity::Info => f.write_str("info"),
Severity::Note => f.write_str("note"),
Severity::Help => f.write_str("help"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SpanRange(pub Range<usize>);
impl From<Range<usize>> for SpanRange {
fn from(range: Range<usize>) -> SpanRange {
SpanRange(Range {
start: range.start,
end: range.end,
})
}
}
impl From<SpanRange> for Range<usize> {
fn from(span: SpanRange) -> Range<usize> {
span.0
}
}
impl std::fmt::Display for SpanRange {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.0)
}
}
#[derive(Debug, Clone)]
pub struct SourceLocation {
source: Arc<dyn Source>,
offset: usize,
}
impl SourceLocation {
pub fn new(source: Arc<dyn Source>, offset: usize) -> Self {
Self { source, offset }
}
}
impl PartialEq for SourceLocation {
fn eq(&self, other: &Self) -> bool {
self.source.name() == other.source.name()
&& self.source.content() == other.source.content()
&& self.offset == other.offset
}
}
impl std::cmp::Eq for SourceLocation {}
impl PartialOrd for SourceLocation {
fn partial_cmp(&self, other: &SourceLocation) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl std::cmp::Ord for SourceLocation {
fn cmp(&self, other: &SourceLocation) -> std::cmp::Ordering {
let other_offset = other.offset;
match self.offset {
v if v < other_offset => std::cmp::Ordering::Less,
v if v > other_offset => std::cmp::Ordering::Greater,
_ => std::cmp::Ordering::Equal,
}
}
}
#[derive(Debug, Clone)]
pub struct SourceRange {
source: Arc<dyn Source>,
span: SpanRange,
}
impl SourceRange {
pub fn new(source: Arc<dyn Source>, span: impl Into<SpanRange>) -> Self {
Self {
source,
span: span.into(),
}
}
}
impl PartialEq for SourceRange {
fn eq(&self, other: &Self) -> bool {
self.source.name() == other.source.name()
&& self.source.content() == other.source.content()
&& self.span == other.span
}
}
impl std::cmp::Eq for SourceRange {}
impl PartialOrd for SourceRange {
fn partial_cmp(&self, other: &SourceRange) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl std::cmp::Ord for SourceRange {
fn cmp(&self, other: &SourceRange) -> std::cmp::Ordering {
let other_start = other.span.0.start;
match self.span.0.start {
v if v < other_start => std::cmp::Ordering::Less,
v if v > other_start => std::cmp::Ordering::Greater,
_ => std::cmp::Ordering::Equal,
}
}
}
#[derive(Debug, Clone)]
pub struct Label {
message: String,
source: Option<Arc<dyn Source>>,
range: SpanRange,
severity: Option<Severity>,
}
impl PartialEq for Label {
fn eq(&self, other: &Label) -> bool {
self.message == other.message && self.range == other.range
}
}
impl Eq for Label {}
impl Label {
pub fn new(source: Option<Arc<dyn Source>>, range: impl Into<SpanRange>, message: impl Into<String>) -> Self {
Self {
source,
range: range.into(),
message: message.into(),
severity: None,
}
}
pub fn error(source: Option<Arc<dyn Source>>, range: impl Into<SpanRange>, label: impl Into<String>) -> Self {
Self {
source,
range: range.into(),
message: label.into(),
severity: Some(Severity::Error),
}
}
pub fn warning(source: Option<Arc<dyn Source>>, range: impl Into<SpanRange>, label: impl Into<String>) -> Self {
Self {
source,
range: range.into(),
message: label.into(),
severity: Some(Severity::Warning),
}
}
pub fn info(source: Option<Arc<dyn Source>>, range: impl Into<SpanRange>, label: impl Into<String>) -> Self {
Self {
source,
range: range.into(),
message: label.into(),
severity: Some(Severity::Info),
}
}
pub fn note(source: Option<Arc<dyn Source>>, range: impl Into<SpanRange>, label: impl Into<String>) -> Self {
Self {
source,
range: range.into(),
message: label.into(),
severity: Some(Severity::Note),
}
}
pub fn help(source: Option<Arc<dyn Source>>, range: impl Into<SpanRange>, label: impl Into<String>) -> Self {
Self {
source,
range: range.into(),
message: label.into(),
severity: Some(Severity::Help),
}
}
pub fn message(&self) -> &str {
&self.message
}
pub fn range(&self) -> &SpanRange {
&self.range
}
pub fn source(&self) -> Option<Arc<dyn Source>> {
self.source.clone()
}
pub fn severity(&self) -> Option<Severity> {
self.severity
}
pub fn with_severity(mut self, severity: Severity) -> Self {
self.severity = Some(severity);
self
}
pub fn read_span(&self, diagnostic: Option<&dyn Diagnostic>, context_lines: usize) -> Option<LabelSpan> {
let diag_source = diagnostic.and_then(|d| d.source_code());
let source = self.source.clone().or(diag_source)?;
let content = source.content();
let range = self.range().0.clone();
let mut line_start = 0;
let mut line_spans = Vec::new();
for line in content.lines() {
let line_len = line.len();
let span = line_start..(line_start + line_len);
line_spans.push(span);
line_start += line_len + 1;
}
let mut matching_lines = Vec::new();
for (i, span) in line_spans.iter().enumerate() {
if span.end > range.start && span.start < range.end {
matching_lines.push(i);
}
}
if matching_lines.is_empty() {
let last_line_span = line_spans.get(context_lines * 2 + 1).or_else(|| line_spans.last());
let last_line_idx = last_line_span.map(|s| s.end).unwrap_or_default();
return Some(LabelSpan {
data: content[0..last_line_idx].to_string(),
start_line: context_lines,
line: 0,
});
}
let first_matching_line = *matching_lines.first().unwrap();
let first_match = first_matching_line.saturating_sub(context_lines);
let last_match = (matching_lines.last().unwrap() + context_lines).min(line_spans.len() - 1);
let start_byte = line_spans[first_match].start;
let end_byte = line_spans[last_match].end;
Some(LabelSpan {
data: content[start_byte..end_byte].to_string(),
start_line: first_matching_line,
line: first_match,
})
}
}
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct LabelSpan {
pub data: String,
pub line: usize,
pub start_line: usize,
}
impl LabelSpan {
pub fn line_count(&self) -> usize {
self.data.lines().count()
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Suggestion {
Deletion { range: SourceRange },
Insertion { location: SourceLocation, value: String },
Replacement { range: SourceRange, replacement: String },
}
impl Suggestion {
pub fn delete(range: SourceRange) -> Self {
Self::Deletion { range }
}
pub fn insert(location: SourceLocation, value: impl Into<String>) -> Self {
Self::Insertion {
location,
value: value.into(),
}
}
pub fn replace(range: SourceRange, replacement: impl Into<String>) -> Self {
Self::Replacement {
range,
replacement: replacement.into(),
}
}
pub fn source(&self) -> Arc<dyn Source> {
match self {
Suggestion::Deletion { range, .. } => range.source.clone(),
Suggestion::Insertion { location, .. } => location.source.clone(),
Suggestion::Replacement { range, .. } => range.source.clone(),
}
}
pub fn span(&self) -> Range<usize> {
match self {
Suggestion::Replacement { range, .. } => range.span.0.clone(),
Suggestion::Deletion { range, .. } => range.span.0.clone(),
Suggestion::Insertion { location, .. } => location.offset..location.offset + 1,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Help {
pub message: String,
pub suggestions: Vec<Suggestion>,
}
impl Help {
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
suggestions: Vec::new(),
}
}
pub fn with_suggestion(mut self, suggestion: impl Into<Suggestion>) -> Self {
self.suggestions.push(suggestion.into());
self
}
pub fn with_suggestions(mut self, suggestions: impl IntoIterator<Item = Suggestion>) -> Self {
self.suggestions.extend(suggestions);
self
}
}
impl From<&str> for Help {
fn from(value: &str) -> Self {
Help::new(value)
}
}
impl From<String> for Help {
fn from(value: String) -> Self {
Help::new(value)
}
}
impl From<&String> for Help {
fn from(value: &String) -> Self {
Help::new(value)
}
}
pub trait Diagnostic: std::fmt::Debug {
fn message(&self) -> String;
fn severity(&self) -> Severity {
Severity::default()
}
fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
None
}
fn source_code(&self) -> Option<Arc<dyn Source>> {
None
}
fn labels(&self) -> Option<Box<dyn Iterator<Item = Label> + '_>> {
None
}
fn causes(&self) -> Box<dyn Iterator<Item = &(dyn Diagnostic + Send + Sync)> + '_> {
Box::new(std::iter::empty())
}
fn related(&self) -> Box<dyn Iterator<Item = &(dyn Diagnostic + Send + Sync)> + '_> {
Box::new(std::iter::empty())
}
fn help(&self) -> Option<Box<dyn Iterator<Item = Help> + '_>> {
None
}
}
impl std::fmt::Display for Box<dyn Diagnostic + Send + Sync + 'static> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message())
}
}
impl<T: Diagnostic + Send + Sync + 'static> From<T> for Box<dyn Diagnostic + Send + Sync + 'static> {
fn from(value: T) -> Self {
Box::new(value)
}
}
impl<T: Diagnostic + Send + Sync + 'static> From<T> for Box<dyn Diagnostic + Send + 'static> {
fn from(value: T) -> Self {
Box::<dyn Diagnostic + Send + Sync>::from(value)
}
}
impl<T: Diagnostic + Send + Sync + 'static> From<T> for Box<dyn Diagnostic + 'static> {
fn from(value: T) -> Self {
Box::<dyn Diagnostic + Send + Sync>::from(value)
}
}
impl From<Box<dyn std::error::Error + Send + Sync>> for Box<dyn Diagnostic + Send + Sync> {
fn from(err: Box<dyn std::error::Error + Send + Sync>) -> Self {
err.into_diagnostic()
}
}
impl From<std::io::Error> for Box<dyn Diagnostic + Send + Sync> {
fn from(s: std::io::Error) -> Self {
From::<Box<dyn std::error::Error + Send + Sync>>::from(Box::new(s))
}
}
impl std::cmp::PartialEq for Box<dyn Diagnostic + Send + Sync> {
fn eq(&self, other: &Self) -> bool {
self.message() == other.message()
}
}
impl std::cmp::Eq for Box<dyn Diagnostic + Send + Sync> {}
pub trait IntoDiagnostic {
fn into_diagnostic(self) -> Box<dyn Diagnostic + Send + Sync>;
}
impl<T: std::error::Error + Send + Sync> IntoDiagnostic for T {
fn into_diagnostic(self) -> Box<dyn Diagnostic + Send + Sync> {
Box::new(SimpleDiagnostic::new(self.to_string()))
}
}
#[derive(Default, Debug)]
pub struct SimpleDiagnostic {
pub message: String,
pub code: Option<String>,
pub severity: Severity,
pub help: Vec<Help>,
pub labels: Option<Vec<Label>>,
pub causes: Vec<Box<dyn Diagnostic + Send + Sync>>,
pub related: Vec<Box<dyn Diagnostic + Send + Sync>>,
}
impl SimpleDiagnostic {
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
..Self::default()
}
}
pub fn with_severity(mut self, severity: impl Into<Severity>) -> Self {
self.severity = severity.into();
self
}
pub fn with_code(mut self, code: impl Into<String>) -> Self {
self.code = Some(code.into());
self
}
pub fn with_help(mut self, help: impl Into<Help>) -> Self {
self.help.push(help.into());
self
}
pub fn set_help(mut self, help: impl Into<Help>) -> Self {
self.help = vec![help.into()];
self
}
pub fn with_label(mut self, label: impl Into<Label>) -> Self {
let mut labels = self.labels.unwrap_or_default();
labels.push(label.into());
self.labels = Some(labels);
self
}
pub fn with_labels(mut self, labels: impl IntoIterator<Item = impl Into<Label>>) -> Self {
let labels = labels
.into_iter()
.map(|r| Into::<Label>::into(r))
.collect::<Vec<Label>>();
let mut all_labels = self.labels.unwrap_or_default();
all_labels.extend(labels);
self.labels = Some(all_labels);
self
}
pub fn add_related(mut self, related: impl Into<Box<dyn Diagnostic + Send + Sync>>) -> Self {
self.related.push(related.into());
self
}
pub fn append_related(
mut self,
related: impl IntoIterator<Item = impl Into<Box<dyn Diagnostic + Send + Sync>>>,
) -> Self {
let related = related
.into_iter()
.map(|r| Into::<Box<dyn Diagnostic + Send + Sync>>::into(r))
.collect::<Vec<Box<dyn Diagnostic + Send + Sync>>>();
self.related.extend(related);
self
}
pub fn add_cause(mut self, cause: impl Into<Box<dyn Diagnostic + Send + Sync>>) -> Self {
self.causes.push(cause.into());
self
}
pub fn add_causes(
mut self,
causes: impl IntoIterator<Item = impl Into<Box<dyn Diagnostic + Send + Sync>>>,
) -> Self {
let causes = causes
.into_iter()
.map(|r| Into::<Box<dyn Diagnostic + Send + Sync>>::into(r))
.collect::<Vec<Box<dyn Diagnostic + Send + Sync>>>();
self.causes.extend(causes);
self
}
}
impl Diagnostic for SimpleDiagnostic {
fn message(&self) -> String {
self.message.clone()
}
fn severity(&self) -> Severity {
self.severity
}
fn code(&self) -> Option<Box<dyn Display + '_>> {
self.code.as_ref().map(|c| Box::new(c) as Box<dyn Display>)
}
fn help(&self) -> Option<Box<dyn Iterator<Item = Help> + '_>> {
Some(Box::new(self.help.clone().into_iter()))
}
fn labels(&self) -> Option<Box<dyn Iterator<Item = Label> + '_>> {
self.labels
.as_ref()
.map(|ls| ls.iter().cloned())
.map(Box::new)
.map(|b| b as Box<dyn Iterator<Item = Label>>)
}
fn related(&self) -> Box<dyn Iterator<Item = &(dyn Diagnostic + Send + Sync)> + '_> {
Box::new(self.related.iter().map(|b| b.as_ref()))
}
fn causes(&self) -> Box<dyn Iterator<Item = &(dyn Diagnostic + Send + Sync)> + '_> {
Box::new(self.causes.iter().map(|b| b.as_ref()))
}
}
impl std::fmt::Display for SimpleDiagnostic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", &self.message)
}
}
#[derive(Debug)]
pub struct SourceWrapped {
pub(crate) diagnostic: Box<dyn Diagnostic + Send + Sync>,
pub(crate) source: Arc<dyn Source + Send + Sync>,
}
impl Diagnostic for SourceWrapped {
fn message(&self) -> String {
self.diagnostic.message()
}
fn severity(&self) -> Severity {
self.diagnostic.severity()
}
fn code(&self) -> Option<Box<dyn Display + '_>> {
self.diagnostic.code()
}
fn help(&self) -> Option<Box<dyn Iterator<Item = Help> + '_>> {
self.diagnostic.help()
}
fn labels(&self) -> Option<Box<dyn Iterator<Item = Label> + '_>> {
self.diagnostic.labels()
}
fn related(&self) -> Box<dyn Iterator<Item = &(dyn Diagnostic + Send + Sync)> + '_> {
self.diagnostic.related()
}
fn causes(&self) -> Box<dyn Iterator<Item = &(dyn Diagnostic + Send + Sync)> + '_> {
self.diagnostic.causes()
}
fn source_code(&self) -> Option<Arc<dyn Source>> {
self.diagnostic.source_code().or_else(|| Some(self.source.clone()))
}
}
impl std::fmt::Display for SourceWrapped {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", &self.message())
}
}
pub trait WithSource {
fn with_source(self, source: Arc<dyn Source>) -> impl Diagnostic;
}
impl<T: Diagnostic + Send + Sync + 'static> WithSource for T {
fn with_source(self, source: Arc<dyn Source>) -> impl Diagnostic {
SourceWrapped {
diagnostic: Box::new(self),
source,
}
}
}