mirror of https://github.com/zkat/miette.git
feat(protocol): reference-based DiagnosticReport!
This commit is contained in:
parent
b7deb2d002
commit
f390520b45
|
|
@ -50,7 +50,6 @@ You can derive a Diagnostic from any `std::error::Error` type.
|
||||||
|
|
||||||
`thiserror` is a great way to define them so, and plays extremely nicely with `miette`!
|
`thiserror` is a great way to define them so, and plays extremely nicely with `miette`!
|
||||||
*/
|
*/
|
||||||
use std::sync::Arc;
|
|
||||||
use miette::Diagnostic;
|
use miette::Diagnostic;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
|
@ -62,7 +61,7 @@ use thiserror::Error;
|
||||||
help("try doing it better next time?"),
|
help("try doing it better next time?"),
|
||||||
)]
|
)]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
src: Arc<String>,
|
src: String,
|
||||||
// Snippets and highlights can be included in the diagnostic!
|
// Snippets and highlights can be included in the diagnostic!
|
||||||
#[snippet(src, "This is the part that broke")]
|
#[snippet(src, "This is the part that broke")]
|
||||||
snip: SourceSpan,
|
snip: SourceSpan,
|
||||||
|
|
@ -101,7 +100,7 @@ fn pretend_this_is_main() -> Result<(), MyBad> {
|
||||||
let len = src.len();
|
let len = src.len();
|
||||||
|
|
||||||
Err(MyBad {
|
Err(MyBad {
|
||||||
src: Arc::new(src),
|
src,
|
||||||
snip: ("bad_file.rs", 0, len).into(),
|
snip: ("bad_file.rs", 0, len).into(),
|
||||||
bad_bit: ("this bit here", 9, 3).into(),
|
bad_bit: ("this bit here", 9, 3).into(),
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -176,12 +176,12 @@ impl Snippets {
|
||||||
.map(|msg| match msg {
|
.map(|msg| match msg {
|
||||||
MemberOrString::String(str) => {
|
MemberOrString::String(str) => {
|
||||||
quote! {
|
quote! {
|
||||||
message: std::option::Option::Some(#str.into()),
|
message: std::option::Option::Some(#str),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MemberOrString::Member(m) => {
|
MemberOrString::Member(m) => {
|
||||||
quote! {
|
quote! {
|
||||||
message: std::option::Option::Some(self.#m.clone()),
|
message: std::option::Option::Some(self.#m.as_ref()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -194,21 +194,20 @@ impl Snippets {
|
||||||
// Source field
|
// Source field
|
||||||
let src_ident = &snippet.source;
|
let src_ident = &snippet.source;
|
||||||
let src_ident = quote! {
|
let src_ident = quote! {
|
||||||
// TODO: I don't like this. Think about it more and maybe improve protocol?
|
source: &self.#src_ident,
|
||||||
source: self.#src_ident.clone(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Context
|
// Context
|
||||||
let context = &snippet.snippet;
|
let context = &snippet.snippet;
|
||||||
let context = quote! {
|
let context = quote! {
|
||||||
context: self.#context.clone(),
|
context: &self.#context,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Highlights
|
// Highlights
|
||||||
let highlights = snippet.highlights.iter().map(|highlight| {
|
let highlights = snippet.highlights.iter().map(|highlight| {
|
||||||
let Highlight { highlight } = highlight;
|
let Highlight { highlight } = highlight;
|
||||||
quote! {
|
quote! {
|
||||||
self.#highlight.clone()
|
&self.#highlight
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let highlights = quote! {
|
let highlights = quote! {
|
||||||
|
|
@ -229,7 +228,7 @@ impl Snippets {
|
||||||
});
|
});
|
||||||
Some(quote! {
|
Some(quote! {
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn snippets(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::DiagnosticSnippet>>> {
|
fn snippets(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::DiagnosticSnippet> + '_>> {
|
||||||
Some(Box::new(vec![
|
Some(Box::new(vec![
|
||||||
#(#snippets),*
|
#(#snippets),*
|
||||||
].into_iter()))
|
].into_iter()))
|
||||||
|
|
@ -248,7 +247,7 @@ impl Snippets {
|
||||||
.map(|msg| match msg {
|
.map(|msg| match msg {
|
||||||
MemberOrString::String(str) => {
|
MemberOrString::String(str) => {
|
||||||
quote! {
|
quote! {
|
||||||
message: std::option::Option::Some(#str.into()),
|
message: std::option::Option::Some(#str),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MemberOrString::Member(m) => {
|
MemberOrString::Member(m) => {
|
||||||
|
|
@ -259,7 +258,7 @@ impl Snippets {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
quote! {
|
quote! {
|
||||||
message: std::option::Option::Some(#m.clone()),
|
message: std::option::Option::Some(#m.as_ref()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -278,7 +277,7 @@ impl Snippets {
|
||||||
};
|
};
|
||||||
let src_ident = quote! {
|
let src_ident = quote! {
|
||||||
// TODO: I don't like this. Think about it more and maybe improve protocol?
|
// TODO: I don't like this. Think about it more and maybe improve protocol?
|
||||||
source: #src_ident.clone(),
|
source: #src_ident,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Context
|
// Context
|
||||||
|
|
@ -289,7 +288,7 @@ impl Snippets {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let context = quote! {
|
let context = quote! {
|
||||||
context: #context.clone(),
|
context: #context,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Highlights
|
// Highlights
|
||||||
|
|
@ -302,7 +301,7 @@ impl Snippets {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
quote! {
|
quote! {
|
||||||
#m.clone()
|
#m
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let highlights = quote! {
|
let highlights = quote! {
|
||||||
|
|
@ -346,7 +345,7 @@ impl Snippets {
|
||||||
});
|
});
|
||||||
Some(quote! {
|
Some(quote! {
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn snippets(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::DiagnosticSnippet>>> {
|
fn snippets(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::DiagnosticSnippet> + '_>> {
|
||||||
match self {
|
match self {
|
||||||
#(#variant_arms)*
|
#(#variant_arms)*
|
||||||
_ => std::option::Option::None,
|
_ => std::option::Option::None,
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ that you can implement to get access to miette's (and related library's) full
|
||||||
reporting and such features.
|
reporting and such features.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::{fmt::Display, sync::Arc};
|
use std::fmt::Display;
|
||||||
|
|
||||||
use crate::MietteError;
|
use crate::MietteError;
|
||||||
|
|
||||||
|
|
@ -36,7 +36,7 @@ pub trait Diagnostic: std::error::Error {
|
||||||
|
|
||||||
/// Additional contextual snippets. This is typically used for adding
|
/// Additional contextual snippets. This is typically used for adding
|
||||||
/// marked-up source file output the way compilers often do.
|
/// marked-up source file output the way compilers often do.
|
||||||
fn snippets(&self) -> Option<Box<dyn Iterator<Item = DiagnosticSnippet>>> {
|
fn snippets(&self) -> Option<Box<dyn Iterator<Item = DiagnosticSnippet> + '_>> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -171,17 +171,17 @@ impl<'a> SpanContents for MietteSpanContents<'a> {
|
||||||
A snippet from a [Source] to be displayed with a message and possibly some highlights.
|
A snippet from a [Source] to be displayed with a message and possibly some highlights.
|
||||||
*/
|
*/
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct DiagnosticSnippet {
|
pub struct DiagnosticSnippet<'a> {
|
||||||
/// Explanation of this specific diagnostic snippet.
|
/// Explanation of this specific diagnostic snippet.
|
||||||
pub message: Option<String>,
|
pub message: Option<&'a str>,
|
||||||
/// A [Source] that can be used to read the actual text of a source.
|
/// A [Source] that can be used to read the actual text of a source.
|
||||||
pub source: Arc<dyn Source>,
|
pub source: &'a (dyn Source),
|
||||||
/// The primary [SourceSpan] where this diagnostic is located.
|
/// The primary [SourceSpan] where this diagnostic is located.
|
||||||
pub context: SourceSpan,
|
pub context: &'a SourceSpan,
|
||||||
/// Additional [SourceSpan]s that mark specific sections of the span, for
|
/// Additional [SourceSpan]s that mark specific sections of the span, for
|
||||||
/// example, to underline specific text within the larger span. They're
|
/// example, to underline specific text within the larger span. They're
|
||||||
/// paired with labels that should be applied to those sections.
|
/// paired with labels that should be applied to those sections.
|
||||||
pub highlights: Option<Vec<SourceSpan>>,
|
pub highlights: Option<Vec<&'a SourceSpan>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ impl MietteReporter {
|
||||||
writeln!(f)?;
|
writeln!(f)?;
|
||||||
let context_data = snippet
|
let context_data = snippet
|
||||||
.source
|
.source
|
||||||
.read_span(&snippet.context)
|
.read_span(snippet.context)
|
||||||
.map_err(|_| fmt::Error)?;
|
.map_err(|_| fmt::Error)?;
|
||||||
let context = std::str::from_utf8(context_data.data()).expect("Bad utf8 detected");
|
let context = std::str::from_utf8(context_data.data()).expect("Bad utf8 detected");
|
||||||
let mut line = context_data.line();
|
let mut line = context_data.line();
|
||||||
|
|
@ -180,7 +180,8 @@ impl DiagnosticReporter for JokeReporter {
|
||||||
"miette, her eyes enormous: you {} miette? you {}? oh! oh! jail for mother! jail for mother for One Thousand Years!!!!",
|
"miette, her eyes enormous: you {} miette? you {}? oh! oh! jail for mother! jail for mother for One Thousand Years!!!!",
|
||||||
diagnostic.code(),
|
diagnostic.code(),
|
||||||
diagnostic.snippets().map(|snippets| {
|
diagnostic.snippets().map(|snippets| {
|
||||||
snippets.map(|snippet| snippet.message).collect::<Option<Vec<String>>>()
|
snippets.map(|snippet| snippet.message.map(|x| x.to_owned()))
|
||||||
|
.collect::<Option<Vec<String>>>()
|
||||||
}).flatten().map(|x| x.join(", ")).unwrap_or_else(||"try and cause miette to panic".into())
|
}).flatten().map(|x| x.join(", ")).unwrap_or_else(||"try and cause miette to panic".into())
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use miette::{Diagnostic, Severity, SourceSpan};
|
use miette::{Diagnostic, Severity, SourceSpan};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
|
@ -166,11 +164,9 @@ fn test_snippet_named_struct() {
|
||||||
#[diagnostic(code(foo::bar::baz))]
|
#[diagnostic(code(foo::bar::baz))]
|
||||||
struct Foo {
|
struct Foo {
|
||||||
// The actual "source code" our contexts will be using. This can be
|
// The actual "source code" our contexts will be using. This can be
|
||||||
// reused by multiple contexts!
|
// reused by multiple contexts, and just needs to implement
|
||||||
//
|
// miette::Source!
|
||||||
// The `Arc` is so you don't have to clone the entire thing into this
|
src: String,
|
||||||
// Diagnostic. We just need to be able to read it~
|
|
||||||
src: Arc<String>,
|
|
||||||
|
|
||||||
// The "snippet" span. This is the span that will be displayed to
|
// The "snippet" span. This is the span that will be displayed to
|
||||||
// users. It should be a big enough slice of the Source to provide
|
// users. It should be a big enough slice of the Source to provide
|
||||||
|
|
@ -215,7 +211,7 @@ fn test_snippet_unnamed_struct() {
|
||||||
#[error("welp")]
|
#[error("welp")]
|
||||||
#[diagnostic(code(foo::bar::baz))]
|
#[diagnostic(code(foo::bar::baz))]
|
||||||
struct Foo(
|
struct Foo(
|
||||||
Arc<String>,
|
String,
|
||||||
#[snippet(0, "hi")] SourceSpan,
|
#[snippet(0, "hi")] SourceSpan,
|
||||||
#[highlight(1)] SourceSpan,
|
#[highlight(1)] SourceSpan,
|
||||||
#[highlight(1)] SourceSpan,
|
#[highlight(1)] SourceSpan,
|
||||||
|
|
@ -235,7 +231,7 @@ fn test_snippet_enum() {
|
||||||
enum Foo {
|
enum Foo {
|
||||||
#[diagnostic(code(foo::a))]
|
#[diagnostic(code(foo::a))]
|
||||||
A {
|
A {
|
||||||
src: Arc<String>,
|
src: String,
|
||||||
#[snippet(src, "my_snippet.rs", "hi this is where the thing went wrong")]
|
#[snippet(src, "my_snippet.rs", "hi this is where the thing went wrong")]
|
||||||
snip: SourceSpan,
|
snip: SourceSpan,
|
||||||
#[highlight(snip, "var 1")]
|
#[highlight(snip, "var 1")]
|
||||||
|
|
@ -249,7 +245,7 @@ fn test_snippet_enum() {
|
||||||
},
|
},
|
||||||
#[diagnostic(code(foo::b))]
|
#[diagnostic(code(foo::b))]
|
||||||
B(
|
B(
|
||||||
Arc<String>,
|
String,
|
||||||
#[snippet(0, "my_snippet.rs", "hi")] SourceSpan,
|
#[snippet(0, "my_snippet.rs", "hi")] SourceSpan,
|
||||||
#[highlight(1, "var 1")] SourceSpan,
|
#[highlight(1, "var 1")] SourceSpan,
|
||||||
#[highlight(1, "var 2")] SourceSpan,
|
#[highlight(1, "var 2")] SourceSpan,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,17 @@
|
||||||
use std::{fmt, sync::Arc};
|
use std::fmt;
|
||||||
|
|
||||||
use miette::{Diagnostic, DiagnosticReporter, DiagnosticSnippet, MietteError, MietteReporter};
|
use miette::{
|
||||||
|
Diagnostic, DiagnosticReporter, DiagnosticSnippet, MietteError, MietteReporter, SourceSpan,
|
||||||
|
};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Error)]
|
#[derive(Error)]
|
||||||
#[error("oops!")]
|
#[error("oops!")]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
snippets: Vec<DiagnosticSnippet>,
|
message: String,
|
||||||
|
src: String,
|
||||||
|
ctx: SourceSpan,
|
||||||
|
highlight: SourceSpan,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for MyBad {
|
impl fmt::Debug for MyBad {
|
||||||
|
|
@ -24,35 +29,28 @@ impl Diagnostic for MyBad {
|
||||||
Some(Box::new(&"try doing it better next time?"))
|
Some(Box::new(&"try doing it better next time?"))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn snippets(&self) -> Option<Box<dyn Iterator<Item = DiagnosticSnippet>>> {
|
fn snippets(&self) -> Option<Box<dyn Iterator<Item = DiagnosticSnippet> + '_>> {
|
||||||
Some(Box::new(self.snippets.clone().into_iter()))
|
Some(Box::new(
|
||||||
|
vec![DiagnosticSnippet {
|
||||||
|
message: Some(self.message.as_ref()),
|
||||||
|
source: &self.src,
|
||||||
|
context: &self.ctx,
|
||||||
|
highlights: Some(vec![&self.highlight]),
|
||||||
|
}]
|
||||||
|
.into_iter(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn basic() -> Result<(), MietteError> {
|
|
||||||
let err = MyBad {
|
|
||||||
snippets: Vec::new(),
|
|
||||||
};
|
|
||||||
let out = format!("{:?}", err);
|
|
||||||
assert_eq!(
|
|
||||||
"Error[oops::my::bad]: oops!\n\n﹦try doing it better next time?\n".to_string(),
|
|
||||||
out
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fancy() -> Result<(), MietteError> {
|
fn fancy() -> Result<(), MietteError> {
|
||||||
let src = "source\n text\n here".to_string();
|
let src = "source\n text\n here".to_string();
|
||||||
let len = src.len();
|
let len = src.len();
|
||||||
let err = MyBad {
|
let err = MyBad {
|
||||||
snippets: vec![DiagnosticSnippet {
|
message: "This is the part that broke".into(),
|
||||||
message: Some("This is the part that broke".into()),
|
src,
|
||||||
source: Arc::new(src),
|
ctx: ("bad_file.rs", 0, len).into(),
|
||||||
highlights: Some(vec![("this bit here", 9, 4).into()]),
|
highlight: ("this bit here", 9, 4).into(),
|
||||||
context: ("bad_file.rs", 0, len).into(),
|
|
||||||
}],
|
|
||||||
};
|
};
|
||||||
let out = format!("{:?}", err);
|
let out = format!("{:?}", err);
|
||||||
// println!("{}", out);
|
// println!("{}", out);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue