feat(snippets): Overhauled how snippets handle labels, sources, and messages, including the derive macro

BREAKING CHANGE: this will probably break a lot of your stuff. hopefully rustc helps
This commit is contained in:
Kat Marchán 2021-08-22 19:06:26 -07:00
parent 2d886c06a3
commit 61283e9efe
No known key found for this signature in database
GPG Key ID: AEB529C08A3C7E9E
11 changed files with 392 additions and 347 deletions

View File

@ -30,7 +30,7 @@ diagnostic error code: ruget::api::bad_json
- [Features](#features)
- [Installing](#installing)
- [Example](#example)
- [Usage](#usage)
- [Using](#using)
- [... in libraries](#-in-libraries)
- [... in application code](#-in-application-code)
- [... in `main()`](#-in-main)
@ -85,11 +85,12 @@ use thiserror::Error;
)]
struct MyBad {
// The Source that we're gonna be printing snippets out of.
src: String,
// This can be a String if you don't have or care about file names.
src: NamedSource,
// Snippets and highlights can be included in the diagnostic!
#[snippet(src, "This is the part that broke")]
#[snippet(src, message("This is the part that broke"))]
snip: SourceSpan,
#[highlight(snip)]
#[highlight(snip, label("This bit here"))]
bad_bit: SourceSpan,
}
@ -100,7 +101,7 @@ Use this DiagnosticResult type (or its expanded version) as the return type
throughout your app (but NOT your libraries! Those should always return concrete
types!).
*/
use miette::DiagnosticResult;
use miette::{DiagnosticResult, NamedSource};
fn this_fails() -> DiagnosticResult<()> {
// You can use plain strings as a `Source`, or anything that implements
// the one-method `Source` trait.
@ -108,9 +109,9 @@ fn this_fails() -> DiagnosticResult<()> {
let len = src.len();
Err(MyBad {
src,
snip: ("bad_file.rs", 0, len).into(),
bad_bit: ("this bit here", 9, 4).into(),
src: NamedSource::new("bad_file.rs", src),
snip: (0, len).into(),
bad_bit: (9, 4).into(),
})?;
Ok(())
@ -149,7 +150,7 @@ diagnostic help: try doing it better next time?
diagnostic error code: oops::my::bad
">
## Usage
## Using
### ... in libraries
@ -244,6 +245,7 @@ use miette::Diagnostic;
use thiserror::Error;
#[derive(Error, Diagnostic, Debug)]
#[error("kaboom")]
#[diagnostic(
code(my_app::my_error),
// You can do formatting!
@ -268,6 +270,7 @@ use thiserror::Error;
// Will link users to https://docs.rs/my_crate/0.0.0/my_crate/struct.MyErr.html
url(docsrs)
)]
#[error("kaboom")]
struct MyErr;
```
@ -297,17 +300,25 @@ pub struct MyErrorType {
// The `Source` that miette will use.
src: String,
// A snippet that points to `src`, our `Source`. The filename can be
// provided at the callsite.
#[snippet(src, "This is the snippet")]
// A snippet that points to `src`, our `Source`.
#[snippet(
src,
message("This is the snippet")
)]
snip: SourceSpan,
// A highlight for the `snip` snippet we defined above. This will
// underline/mark the specific code inside the larger snippet context.
//
// The label is provided using `SourceSpan`'s label.
#[highlight(snip)]
#[highlight(snip, label("This is the highlight"))]
err_span: SourceSpan,
// You can add as many snippets as you want against the same Source.
// They'll be rendered sequentially.
#[snippet(
src,
message("This is a warning")
)]
snip2: SourceSpan,
}
```

View File

@ -166,7 +166,7 @@ impl Diagnostic {
let code_body = code.gen_struct();
let help_body = help.as_ref().and_then(|x| x.gen_struct(fields));
let sev_body = severity.as_ref().and_then(|x| x.gen_struct());
let snip_body = snippets.as_ref().and_then(|x| x.gen_struct());
let snip_body = snippets.as_ref().and_then(|x| x.gen_struct(fields));
let url_body = url.as_ref().and_then(|x| x.gen_struct(ident, fields));
quote! {

View File

@ -1,72 +1,136 @@
use std::collections::HashMap;
use std::collections::{HashMap, HashSet};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{
parenthesized,
parse::{Parse, ParseStream},
punctuated::Punctuated,
spanned::Spanned,
Token,
};
use crate::diagnostic::DiagnosticVariant;
use crate::utils::MemberOrString;
use crate::fmt;
use crate::{diagnostic::DiagnosticVariant, fmt::Display};
pub struct Snippets(Vec<Snippet>);
struct Snippet {
message: Option<MemberOrString>,
message: Option<Display>,
highlights: Vec<Highlight>,
source: syn::Member,
snippet: syn::Member,
}
struct Highlight {
label: Option<Display>,
highlight: syn::Member,
}
struct SnippetAttr {
source: syn::Member,
message: Option<MemberOrString>,
message: Option<Display>,
}
struct HighlightAttr {
label: Option<Display>,
snippet: syn::Member,
}
impl Parse for SnippetAttr {
fn parse(input: ParseStream) -> syn::Result<Self> {
let punc = Punctuated::<MemberOrString, Token![,]>::parse_terminated(input)?;
let span = input.span();
let mut iter = punc.into_iter();
let source = match iter.next() {
Some(MemberOrString::Member(member)) => member,
_ => {
let source = input.parse::<syn::Member>()?;
let message = if input.peek(Token![,]) {
input.parse::<Token![,]>()?;
let ident = input.parse::<syn::Ident>()?;
if ident == "message" {
let la = input.lookahead1();
if la.peek(syn::token::Paren) {
let content;
parenthesized!(content in input);
if content.peek(syn::LitStr) {
let fmt = content.parse()?;
let args = if content.is_empty() {
TokenStream::new()
} else {
content.parse::<Token![,]>()?;
fmt::parse_token_expr(&content, false)?
};
let display = Display {
fmt,
args,
has_bonus_display: false,
};
Some(display)
} else {
return Err(syn::Error::new(ident.span(), "Invalid argument to message() sub-attribute. The first argument must be a literal string."));
}
} else {
input.parse::<Token![=]>()?;
Some(Display {
fmt: input.parse()?,
args: TokenStream::new(),
has_bonus_display: false,
})
}
} else {
return Err(syn::Error::new(
span,
"Source must be an identifier that refers to a Source for this snippet.",
))
ident.span(),
"Invalid sub-attribute. Only `message()` is allowed.",
));
}
} else {
None
};
let message = iter.next();
Ok(SnippetAttr { source, message })
}
}
impl Parse for HighlightAttr {
fn parse(input: ParseStream) -> syn::Result<Self> {
let punc = Punctuated::<MemberOrString, Token![,]>::parse_terminated(input)?;
let span = input.span();
let mut iter = punc.into_iter();
let snippet =
match iter.next() {
Some(MemberOrString::Member(member)) => member,
_ => return Err(syn::Error::new(
span,
"must be an identifier that refers to something with a #[snippet] attribute.",
)),
};
Ok(HighlightAttr { snippet })
let snippet = input.parse::<syn::Member>()?;
let label = if input.peek(Token![,]) {
input.parse::<Token![,]>()?;
let ident = input.parse::<syn::Ident>()?;
if ident == "label" {
let la = input.lookahead1();
if la.peek(syn::token::Paren) {
let content;
parenthesized!(content in input);
if content.peek(syn::LitStr) {
let fmt = content.parse()?;
let args = if content.is_empty() {
TokenStream::new()
} else {
content.parse::<Token![,]>()?;
fmt::parse_token_expr(&content, false)?
};
let display = Display {
fmt,
args,
has_bonus_display: false,
};
Some(display)
} else {
return Err(syn::Error::new(ident.span(), "Invalid argument to label() sub-attribute. The first argument must be a literal string."));
}
} else {
input.parse::<Token![=]>()?;
Some(Display {
fmt: input.parse()?,
args: TokenStream::new(),
has_bonus_display: false,
})
}
} else {
return Err(syn::Error::new(
ident.span(),
"Invalid sub-attribute. Only `label()` is allowed.",
));
}
} else {
None
};
Ok(HighlightAttr { snippet, label })
}
}
@ -113,7 +177,7 @@ impl Snippets {
for (i, field) in fields.iter().enumerate() {
for attr in &field.attrs {
if attr.path.is_ident("highlight") {
let HighlightAttr { snippet } = attr.parse_args::<HighlightAttr>()?;
let HighlightAttr { snippet, label } = attr.parse_args::<HighlightAttr>()?;
if let Some(snippet) = snippets.get_mut(&snippet) {
let member = if let Some(ident) = field.ident.clone() {
syn::Member::Named(ident)
@ -123,7 +187,10 @@ impl Snippets {
span: field.span(),
})
};
snippet.highlights.push(Highlight { highlight: member });
snippet.highlights.push(Highlight {
highlight: member,
label,
});
} else {
return Err(syn::Error::new(snippet.span(), "Highlight must refer to an existing field with a #[snippet(...)] attribute."));
}
@ -137,29 +204,35 @@ impl Snippets {
}
}
pub(crate) fn gen_struct(&self) -> Option<TokenStream> {
pub(crate) fn gen_struct(&self, fields: &syn::Fields) -> Option<TokenStream> {
let snippets = self.0.iter().map(|snippet| {
// snippet message
let msg = snippet
.message
.as_ref()
.map(|msg| match msg {
MemberOrString::String(str) => {
quote! {
message: std::option::Option::Some(#str),
let msg = if let Some(display) = &snippet.message {
let members: HashSet<syn::Member> = fields
.iter()
.enumerate()
.map(|(i, field)| {
if let Some(ident) = field.ident.as_ref().cloned() {
syn::Member::Named(ident)
} else {
syn::Member::Unnamed(syn::Index {
index: i as u32,
span: field.span(),
})
}
}
MemberOrString::Member(m) => {
quote! {
message: std::option::Option::Some(self.#m.as_ref()),
}
}
})
.unwrap_or_else(|| {
quote! {
message: std::option::Option::None,
}
});
})
.collect();
let mut display = display.clone();
display.expand_shorthand(&members);
let Display { fmt, args, .. } = display;
quote! {
message: std::option::Option::Some(format!(#fmt, #args)),
}
} else {
quote! {
message: std::option::Option::None,
}
};
// Source field
let src_ident = &snippet.source;
@ -175,9 +248,20 @@ impl Snippets {
// Highlights
let highlights = snippet.highlights.iter().map(|highlight| {
let Highlight { highlight } = highlight;
quote! {
self.#highlight.clone().into()
let Highlight { highlight, label } = highlight;
if let Some(Display { fmt, args, .. }) = label {
quote! {
(
std::option::Option::Some(
format!(#fmt, #args)
),
self.#highlight.clone().into()
)
}
} else {
quote! {
(std::option::Option::None, self.#highlight.clone().into())
}
}
});
let highlights = quote! {
@ -211,33 +295,25 @@ impl Snippets {
variant.snippets.as_ref().map(|snippets| {
let variant_snippets = snippets.0.iter().map(|snippet| {
// snippet message
let msg = snippet
.message
.as_ref()
.map(|msg| match msg {
MemberOrString::String(str) => {
quote! {
message: std::option::Option::Some(#str),
}
}
MemberOrString::Member(m) => {
let m = match m {
syn::Member::Named(id) => id.clone(),
syn::Member::Unnamed(syn::Index { index, .. }) => {
format_ident!("_{}", index)
}
};
quote! {
message: std::option::Option::Some(#m.as_ref()),
}
}
})
.unwrap_or_else(|| {
quote! {
message: std::option::Option::None,
}
});
let msg = if let Some(display) = &snippet.message {
let members: HashSet<syn::Member> = variant.fields.iter().enumerate().map(|(i, field)| {
if let Some(ident) = field.ident.as_ref().cloned() {
syn::Member::Named(ident)
} else {
syn::Member::Unnamed(syn::Index { index: i as u32, span: field.span() })
}
}).collect();
let mut display = display.clone();
display.expand_shorthand(&members);
let Display { fmt, args, .. } = display;
quote! {
message: std::option::Option::Some(format!(#fmt, #args)),
}
} else {
quote! {
message: std::option::Option::None,
}
};
// Source field
let src_ident = match &snippet.source {
syn::Member::Named(id) => id.clone(),
@ -263,15 +339,24 @@ impl Snippets {
// Highlights
let highlights = snippet.highlights.iter().map(|highlight| {
let Highlight { highlight } = highlight;
let Highlight { highlight, label } = highlight;
let m = match highlight {
syn::Member::Named(id) => id.clone(),
syn::Member::Unnamed(syn::Index { index, .. }) => {
format_ident!("_{}", index)
}
};
quote! {
#m.clone().into()
if let Some(Display { fmt, args, ..}) = label {
quote! {
(
std::option::Option::Some(format!(#fmt, #args)),
#m.clone().into()
)
}
} else {
quote! {
(std::option::Option::None, #m.clone().into())
}
}
});
let highlights = quote! {

View File

@ -159,11 +159,11 @@ impl GraphicalReportPrinter {
// gutter size.
let mut highlights = snippet.highlights.clone().unwrap_or_else(Vec::new);
// sorting is your friend.
highlights.sort_unstable_by_key(|h| h.offset());
highlights.sort_unstable_by_key(|(_, h)| h.offset());
let highlights = highlights
.into_iter()
.zip(self.theme.styles.highlights.iter().cloned().cycle())
.map(|(hl, st)| FancySpan::new(hl, st))
.map(|((label, hl), st)| FancySpan::new(label, hl, st))
.collect::<Vec<_>>();
// The max number of gutter-lines that will be active at any given
@ -189,7 +189,7 @@ impl GraphicalReportPrinter {
.len();
// Header
if let Some(source_name) = snippet.context.label() {
if let Some(source_name) = snippet.source.name() {
let source_name = source_name.style(self.theme.styles.filename);
write!(
f,
@ -558,13 +558,14 @@ impl Line {
}
struct FancySpan {
label: Option<String>,
span: SourceSpan,
style: Style,
}
impl FancySpan {
fn new(span: SourceSpan, style: Style) -> Self {
FancySpan { span, style }
fn new(label: Option<String>, span: SourceSpan, style: Style) -> Self {
FancySpan { label, span, style }
}
fn style(&self) -> Style {
@ -572,7 +573,9 @@ impl FancySpan {
}
fn label(&self) -> Option<String> {
self.span.label().map(|l| l.style(self.style()).to_string())
self.label
.as_ref()
.map(|l| l.style(self.style()).to_string())
}
fn offset(&self) -> usize {

View File

@ -1,12 +1,10 @@
/*!
Reporters included with `miette`.
*/
use std::fmt;
use atty::Stream;
use once_cell::sync::OnceCell;
use crate::protocol::{Diagnostic, DiagnosticReportPrinter, Severity};
use crate::protocol::DiagnosticReportPrinter;
use crate::MietteError;
// NOTE(zkat): I don't understand why these three are "unreachable" when
@ -55,41 +53,3 @@ fn get_default_printer() -> Box<dyn DiagnosticReportPrinter + Send + Sync + 'sta
Box::new(NarratableReportPrinter)
}
}
/// Literally what it says on the tin.
#[derive(Debug, Clone)]
pub struct JokePrinter;
impl DiagnosticReportPrinter for JokePrinter {
fn debug(&self, diagnostic: &(dyn Diagnostic), f: &mut fmt::Formatter<'_>) -> fmt::Result {
if f.alternate() {
return fmt::Debug::fmt(diagnostic, f);
}
let sev = match diagnostic.severity() {
Some(Severity::Error) | None => "error",
Some(Severity::Warning) => "warning",
Some(Severity::Advice) => "advice",
};
writeln!(
f,
"me, with {} {}: {}",
sev,
diagnostic,
diagnostic
.help()
.unwrap_or_else(|| Box::new(&"have you tried not failing?"))
)?;
writeln!(
f,
"miette, her eyes enormous: you {} miette? you {}? oh! oh! jail for mother! jail for mother for One Thousand Years!!!!",
diagnostic.code(),
diagnostic.snippets().map(|snippets| {
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())
)?;
Ok(())
}
}

View File

@ -90,7 +90,7 @@ impl NarratableReportPrinter {
let (contents, lines) = self.get_lines(snippet)?;
write!(f, "Begin snippet")?;
if let Some(filename) = snippet.context.label() {
if let Some(filename) = snippet.source.name() {
write!(f, " for {}", filename,)?;
}
writeln!(
@ -106,13 +106,13 @@ impl NarratableReportPrinter {
// gutter size.
let mut highlights = snippet.highlights.clone().unwrap_or_else(Vec::new);
// sorting is your friend.
highlights.sort_unstable_by_key(|h| h.offset());
highlights.sort_unstable_by_key(|(_, h)| h.offset());
// Now it's time for the fun part--actually rendering everything!
for line in &lines {
writeln!(f, "snippet line {}: {}", line.line_number, line.text)?;
let relevant = highlights.iter().filter(|hl| line.span_starts(hl));
for hl in relevant {
let relevant = highlights.iter().filter(|(_, hl)| line.span_starts(hl));
for (label, hl) in relevant {
let contents = snippet.source.read_span(hl).map_err(|_| fmt::Error)?;
if contents.line() + 1 == line.line_number {
write!(
@ -121,7 +121,7 @@ impl NarratableReportPrinter {
contents.line() + 1,
contents.column() + 1
)?;
if let Some(label) = hl.label() {
if let Some(label) = label {
write!(f, ": {}", label)?;
}
writeln!(f)?;

View File

@ -45,7 +45,7 @@ pub trait Diagnostic: std::error::Error {
/// Additional contextual snippets. This is typically used for adding
/// marked-up source file output the way compilers often do.
fn snippets(&self) -> Option<Box<dyn Iterator<Item = DiagnosticSnippet<'_>> + '_>> {
fn snippets<'a>(&'a self) -> Option<Box<dyn Iterator<Item = DiagnosticSnippet<'a>> + 'a>> {
None
}
}
@ -157,6 +157,11 @@ pub trait Source: std::fmt::Debug + Send + Sync {
&'a self,
span: &SourceSpan,
) -> Result<Box<dyn SpanContents + 'a>, MietteError>;
/// Optional name, usually a filename, for this source.
fn name(&self) -> Option<String> {
None
}
}
/**
@ -212,7 +217,7 @@ A snippet from a [Source] to be displayed with a message and possibly some highl
#[derive(Clone, Debug)]
pub struct DiagnosticSnippet<'a> {
/// Explanation of this specific diagnostic snippet.
pub message: Option<&'a str>,
pub message: Option<String>,
/// A [Source] that can be used to read the actual text of a source.
pub source: &'a (dyn Source),
/// The primary [SourceSpan] where this diagnostic is located.
@ -220,7 +225,7 @@ pub struct DiagnosticSnippet<'a> {
/// Additional [SourceSpan]s that mark specific sections of the span, for
/// example, to underline specific text within the larger span. They're
/// paired with labels that should be applied to those sections.
pub highlights: Option<Vec<SourceSpan>>,
pub highlights: Option<Vec<(Option<String>, SourceSpan)>>,
}
/**
@ -228,9 +233,6 @@ Span within a [Source] with an associated message.
*/
#[derive(Clone, Debug)]
pub struct SourceSpan {
/// An optional label for this span. Rendered differently depending on
/// context.
label: Option<String>,
/// The start of the span.
offset: SourceOffset,
/// The total length of the span. Think of this as an offset from `start`.
@ -241,16 +243,6 @@ impl SourceSpan {
/// Create a new [SourceSpan].
pub fn new(start: SourceOffset, length: SourceOffset) -> Self {
Self {
label: None,
offset: start,
length,
}
}
/// Create a new [SourceSpan] with a label.
pub fn new_labeled(label: impl AsRef<str>, start: SourceOffset, length: SourceOffset) -> Self {
Self {
label: Some(label.as_ref().into()),
offset: start,
length,
}
@ -261,15 +253,6 @@ impl SourceSpan {
self.offset.offset()
}
/// Returns a reference to this [SourceSpan]'s label. This label may be
/// used for different things in different contexts. In highlights, it
/// will be interpreted as the text on the other end of an underscored
/// section of text. In snippet spans, this will be treated as the source
/// name.
pub fn label(&self) -> Option<&str> {
self.label.as_ref().map(|x| &x[..])
}
/// Total length of the [SourceSpan], in bytes.
pub fn len(&self) -> usize {
self.length.offset()
@ -285,17 +268,6 @@ impl SourceSpan {
impl From<(ByteOffset, ByteOffset)> for SourceSpan {
fn from((start, len): (ByteOffset, ByteOffset)) -> Self {
Self {
label: None,
offset: start.into(),
length: len.into(),
}
}
}
impl<T: AsRef<str>> From<(T, ByteOffset, ByteOffset)> for SourceSpan {
fn from((label, start, len): (T, ByteOffset, ByteOffset)) -> Self {
Self {
label: Some(label.as_ref().into()),
offset: start.into(),
length: len.into(),
}
@ -305,17 +277,6 @@ impl<T: AsRef<str>> From<(T, ByteOffset, ByteOffset)> for SourceSpan {
impl From<(SourceOffset, SourceOffset)> for SourceSpan {
fn from((start, len): (SourceOffset, SourceOffset)) -> Self {
Self {
label: None,
offset: start,
length: len,
}
}
}
impl<T: AsRef<str>> From<(T, SourceOffset, SourceOffset)> for SourceSpan {
fn from((label, start, len): (T, SourceOffset, SourceOffset)) -> Self {
Self {
label: Some(label.as_ref().into()),
offset: start,
length: len,
}

View File

@ -2,7 +2,7 @@ use std::fmt;
use thiserror::Error;
use crate::{Diagnostic, DiagnosticReport};
use crate::{Diagnostic, DiagnosticReport, Source};
/// Convenience alias. This is intended to be used as the return type for `main()`
pub type DiagnosticResult<T> = Result<T, DiagnosticReport>;
@ -47,3 +47,40 @@ impl<T, E: std::error::Error + Send + Sync + 'static> IntoDiagnostic<T, E> for R
})
}
}
/// Utility struct for when you have a regular [Source] type, such as a String,
/// that doesn't implement `name`, or if you want to override the `.name()`
/// returned by the `Source`.
#[derive(Debug)]
pub struct NamedSource {
source: Box<dyn Source + Send + Sync + 'static>,
name: String,
}
impl NamedSource {
/// Create a new [NamedSource] using a regular [Source] and giving it a [Source::name].
pub fn new(name: impl AsRef<str>, source: impl Source + Send + Sync + 'static) -> Self {
Self {
source: Box::new(source),
name: name.as_ref().to_string(),
}
}
/// Returns a reference the inner [Source] type for this [NamedSource].
pub fn inner(&self) -> &(dyn Source + Send + Sync + 'static) {
&*self.source
}
}
impl Source for NamedSource {
fn read_span<'a>(
&'a self,
span: &crate::SourceSpan,
) -> Result<Box<dyn crate::SpanContents + 'a>, crate::MietteError> {
self.source.read_span(span)
}
fn name(&self) -> Option<String> {
Some(self.name.clone())
}
}

View File

@ -212,7 +212,7 @@ fn test_snippet_named_struct() {
// / [my_snippet]: hi this is where the thing went wrong.
// 1 | hello
// 2 | world
#[snippet(src, "hi this is where the thing went wrong")]
#[snippet(src, message("hi this is where the thing went wrong"))]
snip: SourceSpan, // Defines filename using `label`
// "Highlights" are the specific highlights _inside_ the snippet.
@ -230,13 +230,7 @@ fn test_snippet_named_struct() {
var1: SourceSpan,
#[highlight(snip)]
// Anything that's Clone + Into<SourceSpan> can be used here.
var2: (String, usize, usize),
// Now with member source names
filename: String,
second_message: String,
#[snippet(src, filename, second_message)]
snip2: SourceSpan,
var2: (usize, usize),
}
}
@ -247,12 +241,12 @@ fn test_snippet_unnamed_struct() {
#[diagnostic(code(foo::bar::baz))]
struct Foo(
String,
#[snippet(0, "hi")] SourceSpan,
#[snippet(0, message("hi"))] SourceSpan,
#[highlight(1)] SourceSpan,
#[highlight(1)] SourceSpan,
// referenced source name
String,
#[snippet(0, 4)] SourceSpan,
#[snippet(0, message("{}", self.4))] SourceSpan,
#[highlight(5)] SourceSpan,
#[highlight(5)] SourceSpan,
);
@ -267,29 +261,23 @@ fn test_snippet_enum() {
#[diagnostic(code(foo::a))]
A {
src: String,
#[snippet(src, "hi this is where the thing went wrong")]
#[snippet(src, message("hi this is where the thing went wrong"))]
snip: SourceSpan,
#[highlight(snip)]
var1: SourceSpan,
#[highlight(snip)]
var2: SourceSpan,
filename: String,
second_message: String,
#[snippet(src, filename, second_message)]
snip2: SourceSpan,
},
#[diagnostic(code(foo::b))]
B(
String,
#[snippet(0, "hi")] SourceSpan,
#[snippet(0, message("hi"))] SourceSpan,
#[highlight(1)] SourceSpan,
#[highlight(1, "var 2")] SourceSpan,
#[highlight(1, label("var 2"))] SourceSpan,
// referenced source name
String,
String,
#[snippet(0, 4, 5)] SourceSpan,
#[highlight(6)] SourceSpan,
#[highlight(6)] SourceSpan,
#[snippet(0)] SourceSpan,
#[highlight(4)] SourceSpan,
#[highlight(4)] SourceSpan,
),
}
}

View File

@ -1,5 +1,5 @@
use miette::{
Diagnostic, DiagnosticReport, GraphicalReportPrinter, GraphicalTheme, MietteError,
Diagnostic, DiagnosticReport, GraphicalReportPrinter, GraphicalTheme, MietteError, NamedSource,
NarratableReportPrinter, SourceSpan,
};
use thiserror::Error;
@ -25,19 +25,19 @@ fn single_line_highlight() -> Result<(), MietteError> {
#[error("oops!")]
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
struct MyBad {
src: String,
#[snippet(src, "This is the part that broke")]
src: NamedSource,
#[snippet(src, message("This is the part that broke"))]
ctx: SourceSpan,
#[highlight(ctx)]
#[highlight(ctx, label = "this bit here")]
highlight: SourceSpan,
}
let src = "source\n text\n here".to_string();
let len = src.len();
let err = MyBad {
src,
ctx: ("bad_file.rs", 0, len).into(),
highlight: ("this bit here", 9, 4).into(),
src: NamedSource::new("bad_file.rs", src),
ctx: (0, len).into(),
highlight: (9, 4).into(),
};
let out = fmt_report(err.into());
println!("{}", out);
@ -67,8 +67,8 @@ fn single_line_highlight_no_label() -> Result<(), MietteError> {
#[error("oops!")]
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
struct MyBad {
src: String,
#[snippet(src, "This is the part that broke")]
src: NamedSource,
#[snippet(src, message("This is the part that broke"))]
ctx: SourceSpan,
#[highlight(ctx)]
highlight: SourceSpan,
@ -77,8 +77,8 @@ fn single_line_highlight_no_label() -> Result<(), MietteError> {
let src = "source\n text\n here".to_string();
let len = src.len();
let err = MyBad {
src,
ctx: ("bad_file.rs", 0, len).into(),
src: NamedSource::new("bad_file.rs", src),
ctx: (0, len).into(),
highlight: (9, 4).into(),
};
let out = fmt_report(err.into());
@ -109,22 +109,22 @@ fn multiple_same_line_highlights() -> Result<(), MietteError> {
#[error("oops!")]
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
struct MyBad {
src: String,
#[snippet(src, "This is the part that broke")]
src: NamedSource,
#[snippet(src, message("This is the part that broke"))]
ctx: SourceSpan,
#[highlight(ctx)]
#[highlight(ctx, label = "this bit here")]
highlight1: SourceSpan,
#[highlight(ctx)]
#[highlight(ctx, label = "also this bit")]
highlight2: SourceSpan,
}
let src = "source\n text text text text text\n here".to_string();
let len = src.len();
let err = MyBad {
src,
ctx: ("bad_file.rs", 0, len).into(),
highlight1: ("this bit here", 9, 4).into(),
highlight2: ("also this bit", 14, 4).into(),
src: NamedSource::new("bad_file.rs", src),
ctx: (0, len).into(),
highlight1: (9, 4).into(),
highlight2: (14, 4).into(),
};
let out = fmt_report(err.into());
println!("{}", out);
@ -155,19 +155,19 @@ fn multiline_highlight_adjacent() -> Result<(), MietteError> {
#[error("oops!")]
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
struct MyBad {
src: String,
#[snippet(src, "This is the part that broke")]
src: NamedSource,
#[snippet(src, message("This is the part that broke"))]
ctx: SourceSpan,
#[highlight(ctx)]
#[highlight(ctx, label = "these two lines")]
highlight: SourceSpan,
}
let src = "source\n text\n here".to_string();
let len = src.len();
let err = MyBad {
src,
ctx: ("bad_file.rs", 0, len).into(),
highlight: ("these two lines", 9, 11).into(),
src: NamedSource::new("bad_file.rs", src),
ctx: (0, len).into(),
highlight: (9, 11).into(),
};
let out = fmt_report(err.into());
println!("{}", out);
@ -197,12 +197,12 @@ fn multiline_highlight_flyby() -> Result<(), MietteError> {
#[error("oops!")]
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
struct MyBad {
src: String,
#[snippet(src, "This is the part that broke")]
src: NamedSource,
#[snippet(src, message("This is the part that broke"))]
ctx: SourceSpan,
#[highlight(ctx)]
#[highlight(ctx, label = "block 1")]
highlight1: SourceSpan,
#[highlight(ctx)]
#[highlight(ctx, label = "block 2")]
highlight2: SourceSpan,
}
@ -215,10 +215,10 @@ line5
.to_string();
let len = src.len();
let err = MyBad {
src,
ctx: ("bad_file.rs", 0, len).into(),
highlight1: ("block 1", 0, len).into(),
highlight2: ("block 2", 10, 9).into(),
src: NamedSource::new("bad_file.rs", src),
ctx: (0, len).into(),
highlight1: (0, len).into(),
highlight2: (10, 9).into(),
};
let out = fmt_report(err.into());
println!("{}", out);
@ -251,10 +251,10 @@ fn multiline_highlight_no_label() -> Result<(), MietteError> {
#[error("oops!")]
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
struct MyBad {
src: String,
#[snippet(src, "This is the part that broke")]
src: NamedSource,
#[snippet(src, message("This is the part that broke"))]
ctx: SourceSpan,
#[highlight(ctx)]
#[highlight(ctx, label = "block 1")]
highlight1: SourceSpan,
#[highlight(ctx)]
highlight2: SourceSpan,
@ -269,9 +269,9 @@ line5
.to_string();
let len = src.len();
let err = MyBad {
src,
ctx: ("bad_file.rs", 0, len).into(),
highlight1: ("block 1", 0, len).into(),
src: NamedSource::new("bad_file.rs", src),
ctx: (0, len).into(),
highlight1: (0, len).into(),
highlight2: (10, 9).into(),
};
let out = fmt_report(err.into());
@ -305,22 +305,22 @@ fn multiple_multiline_highlights_adjacent() -> Result<(), MietteError> {
#[error("oops!")]
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
struct MyBad {
src: String,
#[snippet(src, "This is the part that broke")]
src: NamedSource,
#[snippet(src, message("This is the part that broke"))]
ctx: SourceSpan,
#[highlight(ctx)]
#[highlight(ctx, label = "this bit here")]
highlight1: SourceSpan,
#[highlight(ctx)]
#[highlight(ctx, label = "also this bit")]
highlight2: SourceSpan,
}
let src = "source\n text\n here\nmore here".to_string();
let len = src.len();
let err = MyBad {
src,
ctx: ("bad_file.rs", 0, len).into(),
highlight1: ("this bit here", 0, 10).into(),
highlight2: ("also this bit", 20, 6).into(),
src: NamedSource::new("bad_file.rs", src),
ctx: (0, len).into(),
highlight1: (0, 10).into(),
highlight2: (20, 6).into(),
};
let out = fmt_report(err.into());
println!("{}", out);
@ -354,22 +354,22 @@ fn multiple_multiline_highlights_overlapping_lines() -> Result<(), MietteError>
#[error("oops!")]
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
struct MyBad {
src: String,
#[snippet(src, "This is the part that broke")]
src: NamedSource,
#[snippet(src, message("This is the part that broke"))]
ctx: SourceSpan,
#[highlight(ctx)]
#[highlight(ctx, label = "this bit here")]
highlight1: SourceSpan,
#[highlight(ctx)]
#[highlight(ctx, label = "also this bit")]
highlight2: SourceSpan,
}
let src = "source\n text\n here".to_string();
let len = src.len();
let err = MyBad {
src,
ctx: ("bad_file.rs", 0, len).into(),
highlight1: ("this bit here", 0, 8).into(),
highlight2: ("also this bit", 9, 10).into(),
src: NamedSource::new("bad_file.rs", src),
ctx: (0, len).into(),
highlight1: (0, 8).into(),
highlight2: (9, 10).into(),
};
let out = fmt_report(err.into());
println!("{}", out);
@ -402,22 +402,22 @@ fn multiple_multiline_highlights_overlapping_offsets() -> Result<(), MietteError
#[error("oops!")]
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
struct MyBad {
src: String,
#[snippet(src, "This is the part that broke")]
src: NamedSource,
#[snippet(src, message("This is the part that broke"))]
ctx: SourceSpan,
#[highlight(ctx)]
#[highlight(ctx, label = "this bit here")]
highlight1: SourceSpan,
#[highlight(ctx)]
#[highlight(ctx, label = "also this bit")]
highlight2: SourceSpan,
}
let src = "source\n text\n here".to_string();
let len = src.len();
let err = MyBad {
src,
ctx: ("bad_file.rs", 0, len).into(),
highlight1: ("this bit here", 0, 8).into(),
highlight2: ("also this bit", 10, 10).into(),
src: NamedSource::new("bad_file.rs", src),
ctx: (0, len).into(),
highlight1: (0, 8).into(),
highlight2: (10, 10).into(),
};
let out = fmt_report(err.into());
println!("{}", out);

View File

@ -1,5 +1,5 @@
use miette::{
Diagnostic, DiagnosticReport, GraphicalReportPrinter, GraphicalTheme, MietteError,
Diagnostic, DiagnosticReport, GraphicalReportPrinter, GraphicalTheme, MietteError, NamedSource,
NarratableReportPrinter, SourceSpan,
};
use thiserror::Error;
@ -29,19 +29,19 @@ fn single_line_highlight() -> Result<(), MietteError> {
#[error("oops!")]
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
struct MyBad {
src: String,
#[snippet(src, "This is the part that broke")]
src: NamedSource,
#[snippet(src, message("This is the part that broke"))]
ctx: SourceSpan,
#[highlight(ctx)]
#[highlight(ctx, label = "this bit here")]
highlight: SourceSpan,
}
let src = "source\n text\n here".to_string();
let len = src.len();
let err = MyBad {
src,
ctx: ("bad_file.rs", 0, len).into(),
highlight: ("this bit here", 9, 4).into(),
src: NamedSource::new("bad_file.rs", src),
ctx: (0, len).into(),
highlight: (9, 4).into(),
};
let out = fmt_report(err.into());
println!("{}", out);
@ -71,8 +71,8 @@ fn single_line_highlight_no_label() -> Result<(), MietteError> {
#[error("oops!")]
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
struct MyBad {
src: String,
#[snippet(src, "This is the part that broke")]
src: NamedSource,
#[snippet(src, message("This is the part that broke"))]
ctx: SourceSpan,
#[highlight(ctx)]
highlight: SourceSpan,
@ -81,8 +81,8 @@ fn single_line_highlight_no_label() -> Result<(), MietteError> {
let src = "source\n text\n here".to_string();
let len = src.len();
let err = MyBad {
src,
ctx: ("bad_file.rs", 0, len).into(),
src: NamedSource::new("bad_file.rs", src),
ctx: (0, len).into(),
highlight: (9, 4).into(),
};
let out = fmt_report(err.into());
@ -112,22 +112,22 @@ fn multiple_same_line_highlights() -> Result<(), MietteError> {
#[error("oops!")]
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
struct MyBad {
src: String,
#[snippet(src, "This is the part that broke")]
src: NamedSource,
#[snippet(src, message("This is the part that broke"))]
ctx: SourceSpan,
#[highlight(ctx)]
#[highlight(ctx, label = "this bit here")]
highlight1: SourceSpan,
#[highlight(ctx)]
#[highlight(ctx, label = "also this bit")]
highlight2: SourceSpan,
}
let src = "source\n text text text text text\n here".to_string();
let len = src.len();
let err = MyBad {
src,
ctx: ("bad_file.rs", 0, len).into(),
highlight1: ("this bit here", 9, 4).into(),
highlight2: ("also this bit", 14, 4).into(),
src: NamedSource::new("bad_file.rs", src),
ctx: (0, len).into(),
highlight1: (9, 4).into(),
highlight2: (14, 4).into(),
};
let out = fmt_report(err.into());
println!("{}", out);
@ -158,19 +158,19 @@ fn multiline_highlight_adjacent() -> Result<(), MietteError> {
#[error("oops!")]
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
struct MyBad {
src: String,
#[snippet(src, "This is the part that broke")]
src: NamedSource,
#[snippet(src, message("This is the part that broke"))]
ctx: SourceSpan,
#[highlight(ctx)]
#[highlight(ctx, label = "these two lines")]
highlight: SourceSpan,
}
let src = "source\n text\n here".to_string();
let len = src.len();
let err = MyBad {
src,
ctx: ("bad_file.rs", 0, len).into(),
highlight: ("these two lines", 9, 11).into(),
src: NamedSource::new("bad_file.rs", src),
ctx: (0, len).into(),
highlight: (9, 11).into(),
};
let out = fmt_report(err.into());
println!("{}", out);
@ -199,12 +199,12 @@ fn multiline_highlight_flyby() -> Result<(), MietteError> {
#[error("oops!")]
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
struct MyBad {
src: String,
#[snippet(src, "This is the part that broke")]
src: NamedSource,
#[snippet(src, message("This is the part that broke"))]
ctx: SourceSpan,
#[highlight(ctx)]
#[highlight(ctx, label = "block 1")]
highlight1: SourceSpan,
#[highlight(ctx)]
#[highlight(ctx, label = "block 2")]
highlight2: SourceSpan,
}
@ -217,10 +217,10 @@ line5
.to_string();
let len = src.len();
let err = MyBad {
src,
ctx: ("bad_file.rs", 0, len).into(),
highlight1: ("block 1", 0, len).into(),
highlight2: ("block 2", 10, 9).into(),
src: NamedSource::new("bad_file.rs", src),
ctx: (0, len).into(),
highlight1: (0, len).into(),
highlight2: (10, 9).into(),
};
let out = fmt_report(err.into());
println!("{}", out);
@ -252,10 +252,10 @@ fn multiline_highlight_no_label() -> Result<(), MietteError> {
#[error("oops!")]
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
struct MyBad {
src: String,
#[snippet(src, "This is the part that broke")]
src: NamedSource,
#[snippet(src, message("This is the part that broke"))]
ctx: SourceSpan,
#[highlight(ctx)]
#[highlight(ctx, label = "block 1")]
highlight1: SourceSpan,
#[highlight(ctx)]
highlight2: SourceSpan,
@ -270,9 +270,9 @@ line5
.to_string();
let len = src.len();
let err = MyBad {
src,
ctx: ("bad_file.rs", 0, len).into(),
highlight1: ("block 1", 0, len).into(),
src: NamedSource::new("bad_file.rs", src),
ctx: (0, len).into(),
highlight1: (0, len).into(),
highlight2: (10, 9).into(),
};
let out = fmt_report(err.into());
@ -304,22 +304,22 @@ fn multiple_multiline_highlights_adjacent() -> Result<(), MietteError> {
#[error("oops!")]
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
struct MyBad {
src: String,
#[snippet(src, "This is the part that broke")]
src: NamedSource,
#[snippet(src, message("This is the part that broke"))]
ctx: SourceSpan,
#[highlight(ctx)]
#[highlight(ctx, label = "this bit here")]
highlight1: SourceSpan,
#[highlight(ctx)]
#[highlight(ctx, label = "also this bit")]
highlight2: SourceSpan,
}
let src = "source\n text\n here\nmore here".to_string();
let len = src.len();
let err = MyBad {
src,
ctx: ("bad_file.rs", 0, len).into(),
highlight1: ("this bit here", 0, 10).into(),
highlight2: ("also this bit", 20, 6).into(),
src: NamedSource::new("bad_file.rs", src),
ctx: (0, len).into(),
highlight1: (0, 10).into(),
highlight2: (20, 6).into(),
};
let out = fmt_report(err.into());
println!("{}", out);
@ -354,22 +354,22 @@ fn multiple_multiline_highlights_overlapping_lines() -> Result<(), MietteError>
#[error("oops!")]
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
struct MyBad {
src: String,
#[snippet(src, "This is the part that broke")]
src: NamedSource,
#[snippet(src, message("This is the part that broke"))]
ctx: SourceSpan,
#[highlight(ctx)]
#[highlight(ctx, label = "this bit here")]
highlight1: SourceSpan,
#[highlight(ctx)]
#[highlight(ctx, label = "also this bit")]
highlight2: SourceSpan,
}
let src = "source\n text\n here".to_string();
let len = src.len();
let err = MyBad {
src,
ctx: ("bad_file.rs", 0, len).into(),
highlight1: ("this bit here", 0, 8).into(),
highlight2: ("also this bit", 9, 10).into(),
src: NamedSource::new("bad_file.rs", src),
ctx: (0, len).into(),
highlight1: (0, 8).into(),
highlight2: (9, 10).into(),
};
let out = fmt_report(err.into());
println!("{}", out);
@ -385,22 +385,22 @@ fn multiple_multiline_highlights_overlapping_offsets() -> Result<(), MietteError
#[error("oops!")]
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
struct MyBad {
src: String,
#[snippet(src, "This is the part that broke")]
src: NamedSource,
#[snippet(src, message("This is the part that broke"))]
ctx: SourceSpan,
#[highlight(ctx)]
#[highlight(ctx, label = "this bit here")]
highlight1: SourceSpan,
#[highlight(ctx)]
#[highlight(ctx, label = "also this bit")]
highlight2: SourceSpan,
}
let src = "source\n text\n here".to_string();
let len = src.len();
let err = MyBad {
src,
ctx: ("bad_file.rs", 0, len).into(),
highlight1: ("this bit here", 0, 8).into(),
highlight2: ("also this bit", 10, 10).into(),
src: NamedSource::new("bad_file.rs", src),
ctx: (0, len).into(),
highlight1: (0, 8).into(),
highlight2: (10, 10).into(),
};
let out = fmt_report(err.into());
println!("{}", out);