use pomsky_syntax::{
Span,
diagnose::{ParseDiagnostic, ParseDiagnosticKind, ParseErrorKind, ParseWarningKind},
};
use super::{
CompileError, CompileErrorKind, DiagnosticKind,
diagnostic_code::DiagnosticCode,
help::{get_compiler_help, get_parse_warning_help},
};
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Diagnostic {
pub severity: Severity,
pub msg: String,
pub code: Option<DiagnosticCode>,
pub help: Option<String>,
pub span: Span,
pub kind: DiagnosticKind,
}
#[cfg(feature = "miette")]
impl std::error::Error for Diagnostic {}
#[cfg(feature = "miette")]
impl core::fmt::Display for Diagnostic {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.msg.fmt(f)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Severity {
Error,
Warning,
}
impl From<Severity> for &'static str {
fn from(value: Severity) -> Self {
match value {
Severity::Error => "error",
Severity::Warning => "warning",
}
}
}
impl Diagnostic {
pub(crate) fn from_parse_error(
error_span: Span,
kind: &ParseErrorKind,
source_code: &str,
) -> Self {
let range = error_span.range().unwrap_or(0..source_code.len());
let slice = &source_code[range.clone()];
let mut span = Span::from(range);
let help = super::help::get_parser_help(kind, slice, &mut span);
let code = Some(DiagnosticCode::from(kind));
Diagnostic {
severity: Severity::Error,
code,
msg: kind.to_string(),
help,
span,
kind: DiagnosticKind::from(kind),
}
}
pub(crate) fn from_compile_error(err: &CompileError, source_code: &str) -> Self {
let CompileError { kind, span: error_span } = err;
match kind {
CompileErrorKind::ParseError(kind) => {
Diagnostic::from_parse_error(*error_span, kind, source_code)
}
_ => {
let range = error_span.range().unwrap_or(0..source_code.len());
let span = Span::from(range);
let help = get_compiler_help(kind, span);
Diagnostic {
severity: Severity::Error,
code: Some(DiagnosticCode::from(kind)),
msg: kind.to_string(),
help,
span,
kind: DiagnosticKind::from(kind),
}
}
}
}
pub(crate) fn from_warning(span: Span, kind: &ParseWarningKind, source_code: &str) -> Self {
let range = span.range().unwrap_or(0..source_code.len());
let span = Span::from(range);
Diagnostic {
severity: Severity::Warning,
code: Some(DiagnosticCode::from(kind)),
msg: kind.to_string(),
help: get_parse_warning_help(kind),
span,
kind: DiagnosticKind::from(kind),
}
}
pub(crate) fn from_parser(diagnostic: &ParseDiagnostic, source_code: &str) -> Self {
let span = diagnostic.span;
match &diagnostic.kind {
ParseDiagnosticKind::Error(e) => Diagnostic::from_parse_error(span, e, source_code),
ParseDiagnosticKind::Warning(w) => Diagnostic::from_warning(span, w, source_code),
}
}
#[must_use]
pub fn test_failure(span: Span, code: DiagnosticCode, actual_value: Option<&str>) -> Self {
let (msg, help) = match code {
DiagnosticCode::TestNoExactMatch => {
("The regex does not exactly match the test string".into(), None)
}
DiagnosticCode::TestMissingSubstringMatch => {
("The regex did not find this match within the test string".into(), None)
}
DiagnosticCode::TestUnexpectedSubstringMatch => (
"The regex found an unexpected match within the test string".into(),
Some(format!("The regex matched the substring {:?}", actual_value.unwrap())),
),
DiagnosticCode::TestWrongSubstringMatch => (
"The regex found a different match in the test string".into(),
Some(format!("The actual match is {:?}", actual_value.unwrap())),
),
DiagnosticCode::TestUnexpectedExactMatch => (
"The regex exactly matches the test string, but no match was expected".into(),
None,
),
DiagnosticCode::TestMissingCaptureGroup => {
("The regex match does not have the expected capture group".into(), None)
}
DiagnosticCode::TestWrongCaptureGroup => (
"The capture group does not have the expected content".into(),
Some(format!("The actual content is {:?}", actual_value.unwrap())),
),
_ => unreachable!("An unexpected diagnostic code was passed to `test_failure`"),
};
Diagnostic {
severity: Severity::Error,
code: Some(code),
msg,
help,
span,
kind: DiagnosticKind::Test,
}
}
#[must_use]
pub fn ad_hoc(
severity: Severity,
code: Option<DiagnosticCode>,
msg: String,
help: Option<String>,
) -> Self {
Diagnostic { severity, code, msg, help, span: Span::empty(), kind: DiagnosticKind::Other }
}
#[cfg(feature = "miette")]
#[must_use]
pub fn display_ascii<'a>(
&'a self,
source_code: Option<&'a str>,
) -> impl std::fmt::Display + 'a {
use miette::GraphicalTheme;
use std::fmt;
#[derive(Debug)]
struct MietteDiagnostic<'a> {
diagnostic: &'a Diagnostic,
source_code: Option<&'a str>,
}
impl fmt::Display for MietteDiagnostic<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.diagnostic.fmt(f)
}
}
impl std::error::Error for MietteDiagnostic<'_> {}
impl miette::Diagnostic for MietteDiagnostic<'_> {
fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
self.diagnostic.help.as_deref().map(|h| Box::new(h) as Box<dyn fmt::Display + 'a>)
}
fn source_code(&self) -> Option<&dyn miette::SourceCode> {
self.source_code.as_ref().map(|s| s as &dyn miette::SourceCode)
}
fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
if let Some(std::ops::Range { start, end }) = self.diagnostic.span.range() {
let label = match self.diagnostic.severity {
Severity::Error => "error occurred here",
Severity::Warning => "warning originated here",
};
Some(Box::new(std::iter::once(miette::LabeledSpan::new(
Some(label.into()),
start,
end - start,
))))
} else {
None
}
}
fn severity(&self) -> Option<miette::Severity> {
Some(match self.diagnostic.severity {
Severity::Error => miette::Severity::Error,
Severity::Warning => miette::Severity::Warning,
})
}
}
struct Handler<'a>(MietteDiagnostic<'a>);
impl fmt::Display for Handler<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
miette::GraphicalReportHandler::new()
.with_width(100)
.with_theme(GraphicalTheme::unicode_nocolor())
.render_report(f, &self.0)
}
}
Handler(MietteDiagnostic { diagnostic: self, source_code })
}
}