mirror of https://github.com/zkat/miette.git
feat(labels): replace snippet stuff with simpler labels (#62)
This commit is contained in:
parent
92a3150921
commit
f87b158b22
|
|
@ -25,6 +25,7 @@ unicode-width = { version = "0.1.8", optional = true }
|
||||||
supports-hyperlinks = { version = "1.1.0", optional = true }
|
supports-hyperlinks = { version = "1.1.0", optional = true }
|
||||||
supports-color = { version = "1.0.2", optional = true }
|
supports-color = { version = "1.0.2", optional = true }
|
||||||
supports-unicode = { version = "1.0.0", optional = true }
|
supports-unicode = { version = "1.0.0", optional = true }
|
||||||
|
itertools = { version = "0.10.1", optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
semver = "1.0.4"
|
semver = "1.0.4"
|
||||||
|
|
@ -47,7 +48,8 @@ fancy = [
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
"supports-hyperlinks",
|
"supports-hyperlinks",
|
||||||
"supports-color",
|
"supports-color",
|
||||||
"supports-unicode"
|
"supports-unicode",
|
||||||
|
"itertools"
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,9 @@ use crate::code::Code;
|
||||||
use crate::diagnostic_arg::DiagnosticArg;
|
use crate::diagnostic_arg::DiagnosticArg;
|
||||||
use crate::forward::{Forward, WhichFn};
|
use crate::forward::{Forward, WhichFn};
|
||||||
use crate::help::Help;
|
use crate::help::Help;
|
||||||
|
use crate::label::Labels;
|
||||||
use crate::severity::Severity;
|
use crate::severity::Severity;
|
||||||
use crate::snippets::Snippets;
|
use crate::source_code::SourceCode;
|
||||||
use crate::url::Url;
|
use crate::url::Url;
|
||||||
|
|
||||||
pub enum Diagnostic {
|
pub enum Diagnostic {
|
||||||
|
|
@ -32,7 +33,7 @@ pub struct DiagnosticDef {
|
||||||
|
|
||||||
pub enum DiagnosticDefArgs {
|
pub enum DiagnosticDefArgs {
|
||||||
Transparent(Forward),
|
Transparent(Forward),
|
||||||
Concrete(DiagnosticConcreteArgs),
|
Concrete(Box<DiagnosticConcreteArgs>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiagnosticDefArgs {
|
impl DiagnosticDefArgs {
|
||||||
|
|
@ -59,7 +60,8 @@ pub struct DiagnosticConcreteArgs {
|
||||||
pub code: Option<Code>,
|
pub code: Option<Code>,
|
||||||
pub severity: Option<Severity>,
|
pub severity: Option<Severity>,
|
||||||
pub help: Option<Help>,
|
pub help: Option<Help>,
|
||||||
pub snippets: Option<Snippets>,
|
pub labels: Option<Labels>,
|
||||||
|
pub source_code: Option<SourceCode>,
|
||||||
pub url: Option<Url>,
|
pub url: Option<Url>,
|
||||||
pub forward: Option<Forward>,
|
pub forward: Option<Forward>,
|
||||||
}
|
}
|
||||||
|
|
@ -99,14 +101,16 @@ impl DiagnosticConcreteArgs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let snippets = Snippets::from_fields(fields)?;
|
let labels = Labels::from_fields(fields)?;
|
||||||
|
let source_code = SourceCode::from_fields(fields)?;
|
||||||
let concrete = DiagnosticConcreteArgs {
|
let concrete = DiagnosticConcreteArgs {
|
||||||
code,
|
code,
|
||||||
help,
|
help,
|
||||||
severity,
|
severity,
|
||||||
snippets,
|
labels,
|
||||||
url,
|
url,
|
||||||
forward,
|
forward,
|
||||||
|
source_code,
|
||||||
};
|
};
|
||||||
Ok(concrete)
|
Ok(concrete)
|
||||||
}
|
}
|
||||||
|
|
@ -141,7 +145,7 @@ impl DiagnosticDefArgs {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|x| !matches!(x, DiagnosticArg::Transparent));
|
.filter(|x| !matches!(x, DiagnosticArg::Transparent));
|
||||||
let concrete = DiagnosticConcreteArgs::parse(ident, fields, attr, args)?;
|
let concrete = DiagnosticConcreteArgs::parse(ident, fields, attr, args)?;
|
||||||
Ok(DiagnosticDefArgs::Concrete(concrete))
|
Ok(DiagnosticDefArgs::Concrete(Box::new(concrete)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -208,16 +212,18 @@ impl Diagnostic {
|
||||||
let code_method = forward.gen_struct_method(WhichFn::Code);
|
let code_method = forward.gen_struct_method(WhichFn::Code);
|
||||||
let help_method = forward.gen_struct_method(WhichFn::Help);
|
let help_method = forward.gen_struct_method(WhichFn::Help);
|
||||||
let url_method = forward.gen_struct_method(WhichFn::Url);
|
let url_method = forward.gen_struct_method(WhichFn::Url);
|
||||||
|
let labels_method = forward.gen_struct_method(WhichFn::Labels);
|
||||||
|
let source_code_method = forward.gen_struct_method(WhichFn::SourceCode);
|
||||||
let severity_method = forward.gen_struct_method(WhichFn::Severity);
|
let severity_method = forward.gen_struct_method(WhichFn::Severity);
|
||||||
let snippets_method = forward.gen_struct_method(WhichFn::Snippets);
|
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
|
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
|
||||||
#code_method
|
#code_method
|
||||||
#help_method
|
#help_method
|
||||||
#url_method
|
#url_method
|
||||||
|
#labels_method
|
||||||
#severity_method
|
#severity_method
|
||||||
#snippets_method
|
#source_code_method
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -243,24 +249,29 @@ impl Diagnostic {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|x| x.gen_struct())
|
.and_then(|x| x.gen_struct())
|
||||||
.or_else(|| forward(WhichFn::Severity));
|
.or_else(|| forward(WhichFn::Severity));
|
||||||
let snip_body = concrete
|
|
||||||
.snippets
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|x| x.gen_struct(fields))
|
|
||||||
.or_else(|| forward(WhichFn::Snippets));
|
|
||||||
let url_body = concrete
|
let url_body = concrete
|
||||||
.url
|
.url
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|x| x.gen_struct(ident, fields))
|
.and_then(|x| x.gen_struct(ident, fields))
|
||||||
.or_else(|| forward(WhichFn::Url));
|
.or_else(|| forward(WhichFn::Url));
|
||||||
|
let labels_body = concrete
|
||||||
|
.labels
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|x| x.gen_struct(fields))
|
||||||
|
.or_else(|| forward(WhichFn::Labels));
|
||||||
|
let src_body = concrete
|
||||||
|
.source_code
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|x| x.gen_struct(fields))
|
||||||
|
.or_else(|| forward(WhichFn::SourceCode));
|
||||||
quote! {
|
quote! {
|
||||||
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
|
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
|
||||||
#code_body
|
#code_body
|
||||||
#help_body
|
#help_body
|
||||||
#sev_body
|
#sev_body
|
||||||
#snip_body
|
|
||||||
#url_body
|
#url_body
|
||||||
|
#labels_body
|
||||||
|
#src_body
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -275,14 +286,16 @@ impl Diagnostic {
|
||||||
let code_body = Code::gen_enum(variants);
|
let code_body = Code::gen_enum(variants);
|
||||||
let help_body = Help::gen_enum(variants);
|
let help_body = Help::gen_enum(variants);
|
||||||
let sev_body = Severity::gen_enum(variants);
|
let sev_body = Severity::gen_enum(variants);
|
||||||
let snip_body = Snippets::gen_enum(variants);
|
let labels_body = Labels::gen_enum(variants);
|
||||||
|
let src_body = SourceCode::gen_enum(variants);
|
||||||
let url_body = Url::gen_enum(ident, variants);
|
let url_body = Url::gen_enum(ident, variants);
|
||||||
quote! {
|
quote! {
|
||||||
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
|
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
|
||||||
#code_body
|
#code_body
|
||||||
#help_body
|
#help_body
|
||||||
#sev_body
|
#sev_body
|
||||||
#snip_body
|
#labels_body
|
||||||
|
#src_body
|
||||||
#url_body
|
#url_body
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,8 @@ pub enum WhichFn {
|
||||||
Help,
|
Help,
|
||||||
Url,
|
Url,
|
||||||
Severity,
|
Severity,
|
||||||
Snippets,
|
Labels,
|
||||||
|
SourceCode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WhichFn {
|
impl WhichFn {
|
||||||
|
|
@ -45,7 +46,8 @@ impl WhichFn {
|
||||||
Self::Help => quote! { help() },
|
Self::Help => quote! { help() },
|
||||||
Self::Url => quote! { url() },
|
Self::Url => quote! { url() },
|
||||||
Self::Severity => quote! { severity() },
|
Self::Severity => quote! { severity() },
|
||||||
Self::Snippets => quote! { snippets() },
|
Self::Labels => quote! { labels() },
|
||||||
|
Self::SourceCode => quote! { source_code() },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,11 +65,11 @@ impl WhichFn {
|
||||||
Self::Severity => quote! {
|
Self::Severity => quote! {
|
||||||
fn severity(&self) -> std::option::Option<miette::Severity>
|
fn severity(&self) -> std::option::Option<miette::Severity>
|
||||||
},
|
},
|
||||||
Self::Snippets => quote! {
|
Self::Labels => quote! {
|
||||||
fn snippets(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::DiagnosticSnippet> + '_>>
|
fn labels(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::LabeledSpan> + '_>>
|
||||||
},
|
},
|
||||||
Self::Related => quote! {
|
Self::SourceCode => quote! {
|
||||||
fn related<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = &'a dyn miette::Diagnostic> + 'a>>
|
fn source_code(&self) -> std::option::Option<&dyn miette::SourceCode>
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,179 @@
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::{format_ident, quote};
|
||||||
|
use syn::{
|
||||||
|
parenthesized,
|
||||||
|
parse::{Parse, ParseStream},
|
||||||
|
spanned::Spanned,
|
||||||
|
Token,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
|
||||||
|
fmt::{self, Display},
|
||||||
|
forward::WhichFn,
|
||||||
|
utils::{display_pat_members, gen_all_variants_with},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Labels(Vec<Label>);
|
||||||
|
|
||||||
|
struct Label {
|
||||||
|
label: Option<Display>,
|
||||||
|
span: syn::Member,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LabelAttr {
|
||||||
|
label: Option<Display>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Parse for LabelAttr {
|
||||||
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
|
let la = input.lookahead1();
|
||||||
|
let label = if la.peek(syn::token::Paren) {
|
||||||
|
// #[label("{}", x)]
|
||||||
|
let content;
|
||||||
|
parenthesized!(content in input);
|
||||||
|
if content.peek(syn::LitStr) {
|
||||||
|
let fmt = content.parse()?;
|
||||||
|
let args = if content.is_empty() {
|
||||||
|
TokenStream::new()
|
||||||
|
} else {
|
||||||
|
fmt::parse_token_expr(&content, false)?
|
||||||
|
};
|
||||||
|
let display = Display {
|
||||||
|
fmt,
|
||||||
|
args,
|
||||||
|
has_bonus_display: false,
|
||||||
|
};
|
||||||
|
Some(display)
|
||||||
|
} else {
|
||||||
|
return Err(syn::Error::new(input.span(), "Invalid argument to label() attribute. The first argument must be a literal string."));
|
||||||
|
}
|
||||||
|
} else if la.peek(Token![=]) {
|
||||||
|
// #[label = "blabla"]
|
||||||
|
input.parse::<Token![=]>()?;
|
||||||
|
Some(Display {
|
||||||
|
fmt: input.parse()?,
|
||||||
|
args: TokenStream::new(),
|
||||||
|
has_bonus_display: false,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
Ok(LabelAttr { label })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Labels {
|
||||||
|
pub fn from_fields(fields: &syn::Fields) -> syn::Result<Option<Self>> {
|
||||||
|
match fields {
|
||||||
|
syn::Fields::Named(named) => Self::from_fields_vec(named.named.iter().collect()),
|
||||||
|
syn::Fields::Unnamed(unnamed) => {
|
||||||
|
Self::from_fields_vec(unnamed.unnamed.iter().collect())
|
||||||
|
}
|
||||||
|
syn::Fields::Unit => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_fields_vec(fields: Vec<&syn::Field>) -> syn::Result<Option<Self>> {
|
||||||
|
let mut labels = Vec::new();
|
||||||
|
for (i, field) in fields.iter().enumerate() {
|
||||||
|
for attr in &field.attrs {
|
||||||
|
if attr.path.is_ident("label") {
|
||||||
|
let span = if let Some(ident) = field.ident.clone() {
|
||||||
|
syn::Member::Named(ident)
|
||||||
|
} else {
|
||||||
|
syn::Member::Unnamed(syn::Index {
|
||||||
|
index: i as u32,
|
||||||
|
span: field.span(),
|
||||||
|
})
|
||||||
|
};
|
||||||
|
let LabelAttr { label } = syn::parse2::<LabelAttr>(attr.tokens.clone())?;
|
||||||
|
labels.push(Label { label, span });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if labels.is_empty() {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
Ok(Some(Labels(labels)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn gen_struct(&self, fields: &syn::Fields) -> Option<TokenStream> {
|
||||||
|
let (display_pat, display_members) = display_pat_members(fields);
|
||||||
|
let labels = self.0.iter().map(|highlight| {
|
||||||
|
let Label { span, label } = highlight;
|
||||||
|
if let Some(display) = label {
|
||||||
|
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||||
|
quote! {
|
||||||
|
miette::LabeledSpan::new_with_span(
|
||||||
|
std::option::Option::Some(format!(#fmt #args)),
|
||||||
|
self.#span.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
miette::LabeledSpan::new_with_span(
|
||||||
|
std::option::Option::None,
|
||||||
|
self.#span.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Some(quote! {
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn labels(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::LabeledSpan> + '_>> {
|
||||||
|
let Self #display_pat = self;
|
||||||
|
Some(Box::new(vec![
|
||||||
|
#(#labels),*
|
||||||
|
].into_iter()))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream> {
|
||||||
|
gen_all_variants_with(
|
||||||
|
variants,
|
||||||
|
WhichFn::Labels,
|
||||||
|
|ident, fields, DiagnosticConcreteArgs { labels, .. }| {
|
||||||
|
let (display_pat, display_members) = display_pat_members(fields);
|
||||||
|
labels.as_ref().and_then(|labels| {
|
||||||
|
let variant_labels = labels.0.iter().map(|label| {
|
||||||
|
let Label { span, label } = label;
|
||||||
|
let field = match &span {
|
||||||
|
syn::Member::Named(ident) => ident.clone(),
|
||||||
|
syn::Member::Unnamed(syn::Index { index, .. }) => {
|
||||||
|
format_ident!("_{}", index)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Some(display) = label {
|
||||||
|
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||||
|
quote! {
|
||||||
|
miette::LabeledSpan::new_with_span(
|
||||||
|
std::option::Option::Some(format!(#fmt #args)),
|
||||||
|
#field.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
miette::LabeledSpan::new_with_span(
|
||||||
|
std::option::Option::None,
|
||||||
|
#field.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let variant_name = ident.clone();
|
||||||
|
match &fields {
|
||||||
|
syn::Fields::Unit => None,
|
||||||
|
_ => Some(quote! {
|
||||||
|
Self::#variant_name #display_pat => std::option::Option::Some(std::boxed::Box::new(vec![
|
||||||
|
#(#variant_labels),*
|
||||||
|
].into_iter())),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,12 +9,13 @@ mod diagnostic_arg;
|
||||||
mod fmt;
|
mod fmt;
|
||||||
mod forward;
|
mod forward;
|
||||||
mod help;
|
mod help;
|
||||||
|
mod label;
|
||||||
mod severity;
|
mod severity;
|
||||||
mod snippets;
|
mod source_code;
|
||||||
mod url;
|
mod url;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
#[proc_macro_derive(Diagnostic, attributes(diagnostic, snippet, highlight))]
|
#[proc_macro_derive(Diagnostic, attributes(diagnostic, label, source_code))]
|
||||||
pub fn derive_diagnostic(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
pub fn derive_diagnostic(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
let cmd = match Diagnostic::from_derive_input(input) {
|
let cmd = match Diagnostic::from_derive_input(input) {
|
||||||
|
|
|
||||||
|
|
@ -1,375 +0,0 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use proc_macro2::TokenStream;
|
|
||||||
use quote::{format_ident, quote};
|
|
||||||
use syn::{
|
|
||||||
parenthesized,
|
|
||||||
parse::{Parse, ParseStream},
|
|
||||||
spanned::Spanned,
|
|
||||||
Token,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
|
|
||||||
fmt::{self, Display},
|
|
||||||
forward::WhichFn,
|
|
||||||
utils::{display_pat_members, gen_all_variants_with},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Snippets(Vec<Snippet>);
|
|
||||||
|
|
||||||
struct Snippet {
|
|
||||||
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<Display>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct HighlightAttr {
|
|
||||||
label: Option<Display>,
|
|
||||||
snippet: syn::Member,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for SnippetAttr {
|
|
||||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
|
||||||
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 {
|
|
||||||
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(
|
|
||||||
ident.span(),
|
|
||||||
"Invalid sub-attribute. Only `message()` is allowed.",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
Ok(SnippetAttr { source, message })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parse for HighlightAttr {
|
|
||||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
|
||||||
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 {
|
|
||||||
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 })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Snippets {
|
|
||||||
pub fn from_fields(fields: &syn::Fields) -> syn::Result<Option<Self>> {
|
|
||||||
match fields {
|
|
||||||
syn::Fields::Named(named) => Self::from_fields_vec(named.named.iter().collect()),
|
|
||||||
syn::Fields::Unnamed(unnamed) => {
|
|
||||||
Self::from_fields_vec(unnamed.unnamed.iter().collect())
|
|
||||||
}
|
|
||||||
syn::Fields::Unit => Ok(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_fields_vec(fields: Vec<&syn::Field>) -> syn::Result<Option<Self>> {
|
|
||||||
let mut snippets = HashMap::new();
|
|
||||||
// First we collect all the contexts
|
|
||||||
for (i, field) in fields.iter().enumerate() {
|
|
||||||
for attr in &field.attrs {
|
|
||||||
if attr.path.is_ident("snippet") {
|
|
||||||
let snippet = if let Some(ident) = field.ident.clone() {
|
|
||||||
syn::Member::Named(ident)
|
|
||||||
} else {
|
|
||||||
syn::Member::Unnamed(syn::Index {
|
|
||||||
index: i as u32,
|
|
||||||
span: field.span(),
|
|
||||||
})
|
|
||||||
};
|
|
||||||
let SnippetAttr { source, message } = attr.parse_args::<SnippetAttr>()?;
|
|
||||||
// TODO: useful error when source refers to a field that doesn't exist.
|
|
||||||
snippets.insert(
|
|
||||||
snippet.clone(),
|
|
||||||
Snippet {
|
|
||||||
message,
|
|
||||||
highlights: Vec::new(),
|
|
||||||
source,
|
|
||||||
snippet,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Then we loop again looking for highlights
|
|
||||||
for (i, field) in fields.iter().enumerate() {
|
|
||||||
for attr in &field.attrs {
|
|
||||||
if attr.path.is_ident("highlight") {
|
|
||||||
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)
|
|
||||||
} else {
|
|
||||||
syn::Member::Unnamed(syn::Index {
|
|
||||||
index: i as u32,
|
|
||||||
span: field.span(),
|
|
||||||
})
|
|
||||||
};
|
|
||||||
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."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if snippets.is_empty() {
|
|
||||||
Ok(None)
|
|
||||||
} else {
|
|
||||||
Ok(Some(Snippets(snippets.into_values().collect())))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn gen_struct(&self, fields: &syn::Fields) -> Option<TokenStream> {
|
|
||||||
let (display_pat, display_members) = display_pat_members(fields);
|
|
||||||
let snippets = self.0.iter().map(|snippet| {
|
|
||||||
// snippet message
|
|
||||||
let msg = if let Some(display) = &snippet.message {
|
|
||||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
|
||||||
quote! {
|
|
||||||
message: {
|
|
||||||
std::option::Option::Some(format!(#fmt #args))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
quote! {
|
|
||||||
message: std::option::Option::None,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Source field
|
|
||||||
let src_ident = &snippet.source;
|
|
||||||
let src_ident = quote! {
|
|
||||||
source: &self.#src_ident,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Context
|
|
||||||
let context = &snippet.snippet;
|
|
||||||
let context = quote! {
|
|
||||||
context: self.#context.clone().into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Highlights
|
|
||||||
let highlights = snippet.highlights.iter().map(|highlight| {
|
|
||||||
let Highlight { highlight, label } = highlight;
|
|
||||||
if let Some(display) = label {
|
|
||||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
|
||||||
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! {
|
|
||||||
highlights: std::option::Option::Some(vec![
|
|
||||||
#(#highlights),*
|
|
||||||
]),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Generate the snippet itself
|
|
||||||
quote! {
|
|
||||||
miette::DiagnosticSnippet {
|
|
||||||
#msg
|
|
||||||
#src_ident
|
|
||||||
#context
|
|
||||||
#highlights
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Some(quote! {
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
fn snippets(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::DiagnosticSnippet> + '_>> {
|
|
||||||
let Self #display_pat = self;
|
|
||||||
Some(Box::new(vec![
|
|
||||||
#(#snippets),*
|
|
||||||
].into_iter()))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream> {
|
|
||||||
gen_all_variants_with(
|
|
||||||
variants,
|
|
||||||
WhichFn::Snippets,
|
|
||||||
|ident, fields, DiagnosticConcreteArgs { snippets, .. }| {
|
|
||||||
let (display_pat, display_members) = display_pat_members(fields);
|
|
||||||
snippets.as_ref().and_then(|snippets| {
|
|
||||||
let variant_snippets = snippets.0.iter().map(|snippet| {
|
|
||||||
// snippet message
|
|
||||||
let msg = if let Some(display) = &snippet.message {
|
|
||||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
|
||||||
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(),
|
|
||||||
syn::Member::Unnamed(syn::Index { index, .. }) => {
|
|
||||||
format_ident!("_{}", index)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let src_ident = quote! {
|
|
||||||
// TODO: I don't like this. Think about it more and maybe improve protocol?
|
|
||||||
source: #src_ident,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Context
|
|
||||||
let context = match &snippet.snippet {
|
|
||||||
syn::Member::Named(id) => id.clone(),
|
|
||||||
syn::Member::Unnamed(syn::Index { index, .. }) => {
|
|
||||||
format_ident!("_{}", index)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let context = quote! {
|
|
||||||
context: #context.clone().into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Highlights
|
|
||||||
let highlights = snippet.highlights.iter().map(|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)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
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! {
|
|
||||||
highlights: std::option::Option::Some(vec![
|
|
||||||
#(#highlights),*
|
|
||||||
]),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Generate the snippet itself
|
|
||||||
quote! {
|
|
||||||
miette::DiagnosticSnippet {
|
|
||||||
#msg
|
|
||||||
#src_ident
|
|
||||||
#context
|
|
||||||
#highlights
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let variant_name = ident.clone();
|
|
||||||
match &fields {
|
|
||||||
syn::Fields::Unit => None,
|
|
||||||
_ => Some(quote! {
|
|
||||||
Self::#variant_name #display_pat => std::option::Option::Some(std::boxed::Box::new(vec![
|
|
||||||
#(#variant_snippets),*
|
|
||||||
].into_iter())),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::{format_ident, quote};
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
|
||||||
|
forward::WhichFn,
|
||||||
|
utils::{display_pat_members, gen_all_variants_with},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct SourceCode {
|
||||||
|
source_code: syn::Member,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SourceCode {
|
||||||
|
pub fn from_fields(fields: &syn::Fields) -> syn::Result<Option<Self>> {
|
||||||
|
match fields {
|
||||||
|
syn::Fields::Named(named) => Self::from_fields_vec(named.named.iter().collect()),
|
||||||
|
syn::Fields::Unnamed(unnamed) => {
|
||||||
|
Self::from_fields_vec(unnamed.unnamed.iter().collect())
|
||||||
|
}
|
||||||
|
syn::Fields::Unit => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_fields_vec(fields: Vec<&syn::Field>) -> syn::Result<Option<Self>> {
|
||||||
|
for (i, field) in fields.iter().enumerate() {
|
||||||
|
for attr in &field.attrs {
|
||||||
|
if attr.path.is_ident("source_code") {
|
||||||
|
let source_code = if let Some(ident) = field.ident.clone() {
|
||||||
|
syn::Member::Named(ident)
|
||||||
|
} else {
|
||||||
|
syn::Member::Unnamed(syn::Index {
|
||||||
|
index: i as u32,
|
||||||
|
span: field.span(),
|
||||||
|
})
|
||||||
|
};
|
||||||
|
return Ok(Some(SourceCode { source_code }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn gen_struct(&self, fields: &syn::Fields) -> Option<TokenStream> {
|
||||||
|
let (display_pat, _display_members) = display_pat_members(fields);
|
||||||
|
let src = &self.source_code;
|
||||||
|
Some(quote! {
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
fn source_code(&self) -> std::option::Option<&dyn miette::SourceCode> {
|
||||||
|
let Self #display_pat = self;
|
||||||
|
Some(&self.#src)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream> {
|
||||||
|
gen_all_variants_with(
|
||||||
|
variants,
|
||||||
|
WhichFn::SourceCode,
|
||||||
|
|ident, fields, DiagnosticConcreteArgs { source_code, .. }| {
|
||||||
|
let (display_pat, _display_members) = display_pat_members(fields);
|
||||||
|
source_code.as_ref().and_then(|source_code| {
|
||||||
|
let field = match &source_code.source_code {
|
||||||
|
syn::Member::Named(ident) => ident.clone(),
|
||||||
|
syn::Member::Unnamed(syn::Index { index, .. }) => {
|
||||||
|
format_ident!("_{}", index)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let variant_name = ident.clone();
|
||||||
|
match &fields {
|
||||||
|
syn::Fields::Unit => None,
|
||||||
|
_ => Some(quote! {
|
||||||
|
Self::#variant_name #display_pat => std::option::Option::Some(#field),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ use core::fmt::{self, Debug, Display, Write};
|
||||||
|
|
||||||
use std::error::Error as StdError;
|
use std::error::Error as StdError;
|
||||||
|
|
||||||
use crate::Diagnostic;
|
use crate::{Diagnostic, LabeledSpan};
|
||||||
|
|
||||||
mod ext {
|
mod ext {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
@ -141,10 +141,8 @@ where
|
||||||
self.error.url()
|
self.error.url()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn snippets<'a>(
|
fn labels<'a>(&'a self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + 'a>> {
|
||||||
&'a self,
|
self.error.labels()
|
||||||
) -> Option<Box<dyn Iterator<Item = crate::DiagnosticSnippet<'a>> + 'a>> {
|
|
||||||
self.error.snippets()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -168,10 +166,8 @@ where
|
||||||
self.error.inner.diagnostic().url()
|
self.error.inner.diagnostic().url()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn snippets<'a>(
|
fn labels<'a>(&'a self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + 'a>> {
|
||||||
&'a self,
|
self.error.inner.diagnostic().labels()
|
||||||
) -> Option<Box<dyn Iterator<Item = crate::DiagnosticSnippet<'a>> + 'a>> {
|
|
||||||
self.error.inner.diagnostic().snippets()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use core::fmt::{self, Debug, Display};
|
||||||
|
|
||||||
use std::error::Error as StdError;
|
use std::error::Error as StdError;
|
||||||
|
|
||||||
use crate::Diagnostic;
|
use crate::{Diagnostic, LabeledSpan};
|
||||||
|
|
||||||
use crate as miette;
|
use crate as miette;
|
||||||
|
|
||||||
|
|
@ -71,11 +71,31 @@ impl Display for NoneError {
|
||||||
impl StdError for NoneError {}
|
impl StdError for NoneError {}
|
||||||
impl Diagnostic for NoneError {}
|
impl Diagnostic for NoneError {}
|
||||||
|
|
||||||
#[derive(miette_derive::Diagnostic)]
|
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[diagnostic(transparent)]
|
|
||||||
pub(crate) struct BoxedError(pub(crate) Box<dyn Diagnostic + Send + Sync>);
|
pub(crate) struct BoxedError(pub(crate) Box<dyn Diagnostic + Send + Sync>);
|
||||||
|
|
||||||
|
impl Diagnostic for BoxedError {
|
||||||
|
fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
|
||||||
|
self.0.code()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn severity(&self) -> Option<miette::Severity> {
|
||||||
|
self.0.severity()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
|
||||||
|
self.0.help()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
|
||||||
|
self.0.url()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn labels<'a>(&'a self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + 'a>> {
|
||||||
|
self.0.labels()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Debug for BoxedError {
|
impl Debug for BoxedError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
Debug::fmt(&self.0, f)
|
Debug::fmt(&self.0, f)
|
||||||
|
|
|
||||||
|
|
@ -48,9 +48,9 @@ impl DebugReportHandler {
|
||||||
if let Some(help) = diagnostic.help() {
|
if let Some(help) = diagnostic.help() {
|
||||||
diag.field("help", &help.to_string());
|
diag.field("help", &help.to_string());
|
||||||
}
|
}
|
||||||
if let Some(snippets) = diagnostic.snippets() {
|
if let Some(labels) = diagnostic.labels() {
|
||||||
let snippets: Vec<_> = snippets.collect();
|
let labels: Vec<_> = labels.collect();
|
||||||
diag.field("snippets", &format!("{:?}", snippets));
|
diag.field("labels", &format!("{:?}", labels));
|
||||||
}
|
}
|
||||||
diag.finish()?;
|
diag.finish()?;
|
||||||
writeln!(f)?;
|
writeln!(f)?;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
use std::fmt::{self, Write};
|
use std::fmt::{self, Write};
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
use owo_colors::{OwoColorize, Style};
|
use owo_colors::{OwoColorize, Style};
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
use crate::chain::Chain;
|
use crate::chain::Chain;
|
||||||
use crate::handlers::theme::*;
|
use crate::handlers::theme::*;
|
||||||
use crate::protocol::{Diagnostic, DiagnosticSnippet, Severity};
|
use crate::protocol::{Diagnostic, Severity};
|
||||||
use crate::{ReportHandler, SourceSpan, SpanContents};
|
use crate::{LabeledSpan, ReportHandler, SourceCode, SourceSpan, SpanContents};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
A [ReportHandler] that displays a given [crate::Report] in a quasi-graphical
|
A [ReportHandler] that displays a given [crate::Report] in a quasi-graphical
|
||||||
|
|
@ -94,10 +95,14 @@ impl GraphicalReportHandler {
|
||||||
writeln!(f)?;
|
writeln!(f)?;
|
||||||
self.render_causes(f, diagnostic)?;
|
self.render_causes(f, diagnostic)?;
|
||||||
|
|
||||||
if let Some(snippets) = diagnostic.snippets() {
|
if let Some(source) = diagnostic.source_code() {
|
||||||
for snippet in snippets {
|
if let Some(labels) = diagnostic.labels() {
|
||||||
writeln!(f)?;
|
let mut labels = labels.collect::<Vec<_>>();
|
||||||
self.render_snippet(f, &snippet)?;
|
labels.sort_unstable_by_key(|l| l.inner().offset());
|
||||||
|
if !labels.is_empty() {
|
||||||
|
writeln!(f)?;
|
||||||
|
self.render_snippets(f, source, labels)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -228,23 +233,39 @@ impl GraphicalReportHandler {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_snippet(
|
fn render_snippets(
|
||||||
&self,
|
&self,
|
||||||
f: &mut impl fmt::Write,
|
f: &mut impl fmt::Write,
|
||||||
snippet: &DiagnosticSnippet<'_>,
|
source: &dyn SourceCode,
|
||||||
|
labels: Vec<LabeledSpan>,
|
||||||
) -> fmt::Result {
|
) -> fmt::Result {
|
||||||
let (contents, lines) = self.get_lines(snippet)?;
|
// TODO: Actually do the rewrite against the new protocol.
|
||||||
|
let contexts: Vec<_> = labels
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.coalesce(|left, right| {
|
||||||
|
if left.offset() + left.len() >= right.offset() {
|
||||||
|
let left_end = left.offset() + left.len();
|
||||||
|
let right_end = right.offset() + right.len();
|
||||||
|
Ok(LabeledSpan::new(
|
||||||
|
left.label().map(String::from),
|
||||||
|
left.offset(),
|
||||||
|
right_end - left_end,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Err((left, right))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let (contents, lines) = self.get_lines(source, &labels)?;
|
||||||
|
|
||||||
// Highlights are the bits we're going to underline in our overall
|
// sorting is your friend
|
||||||
// snippet, and we need to do some analysis first to come up with
|
let labels = labels
|
||||||
// gutter size.
|
.iter()
|
||||||
let mut highlights = snippet.highlights.clone().unwrap_or_else(Vec::new);
|
|
||||||
// sorting is your friend.
|
|
||||||
highlights.sort_unstable_by_key(|(_, h)| h.offset());
|
|
||||||
let highlights = highlights
|
|
||||||
.into_iter()
|
|
||||||
.zip(self.theme.styles.highlights.iter().cloned().cycle())
|
.zip(self.theme.styles.highlights.iter().cloned().cycle())
|
||||||
.map(|((label, hl), st)| FancySpan::new(label, hl, st))
|
.map(|(label, st)| {
|
||||||
|
FancySpan::new(label.label().map(String::from), label.inner().clone(), st)
|
||||||
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
// The max number of gutter-lines that will be active at any given
|
// The max number of gutter-lines that will be active at any given
|
||||||
|
|
@ -253,7 +274,7 @@ impl GraphicalReportHandler {
|
||||||
let mut max_gutter = 0usize;
|
let mut max_gutter = 0usize;
|
||||||
for line in &lines {
|
for line in &lines {
|
||||||
let mut num_highlights = 0;
|
let mut num_highlights = 0;
|
||||||
for hl in &highlights {
|
for hl in &labels {
|
||||||
if !line.span_line_only(hl) && line.span_applies(hl) {
|
if !line.span_line_only(hl) && line.span_applies(hl) {
|
||||||
num_highlights += 1;
|
num_highlights += 1;
|
||||||
}
|
}
|
||||||
|
|
@ -270,45 +291,26 @@ impl GraphicalReportHandler {
|
||||||
.len();
|
.len();
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
if let Some(msg) = &snippet.message {
|
|
||||||
writeln!(
|
|
||||||
f,
|
|
||||||
"{}{}{}",
|
|
||||||
" ".repeat(linum_width + 2),
|
|
||||||
self.theme.characters.ltop,
|
|
||||||
self.theme.characters.hbar.to_string().repeat(4)
|
|
||||||
)?;
|
|
||||||
writeln!(
|
|
||||||
f,
|
|
||||||
"{}{} error: {}",
|
|
||||||
" ".repeat(linum_width + 2),
|
|
||||||
self.theme.characters.vbar,
|
|
||||||
msg
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"{}{}{}",
|
"{}{}{}",
|
||||||
" ".repeat(linum_width + 2),
|
" ".repeat(linum_width + 2),
|
||||||
if snippet.message.is_some() {
|
self.theme.characters.ltop,
|
||||||
self.theme.characters.lcross
|
|
||||||
} else {
|
|
||||||
self.theme.characters.ltop
|
|
||||||
},
|
|
||||||
self.theme.characters.hbar,
|
self.theme.characters.hbar,
|
||||||
)?;
|
)?;
|
||||||
if let Some(source_name) = snippet.source.name() {
|
// TODO: filenames
|
||||||
let source_name = source_name.style(self.theme.styles.link);
|
// if let Some(source_name) = source.name() {
|
||||||
writeln!(
|
// let source_name = source_name.style(self.theme.styles.link);
|
||||||
f,
|
// writeln!(
|
||||||
"[{}:{}:{}]",
|
// f,
|
||||||
source_name,
|
// "[{}:{}:{}]",
|
||||||
contents.line() + 1,
|
// source_name,
|
||||||
contents.column() + 1
|
// contents.line() + 1,
|
||||||
)?;
|
// contents.column() + 1
|
||||||
} else {
|
// )?;
|
||||||
writeln!(f, "[{}:{}]", contents.line() + 1, contents.column() + 1)?;
|
// } else {
|
||||||
}
|
// writeln!(f, "[{}:{}]", contents.line() + 1, contents.column() + 1)?;
|
||||||
|
// }
|
||||||
|
|
||||||
// Blank line to improve readability
|
// Blank line to improve readability
|
||||||
writeln!(
|
writeln!(
|
||||||
|
|
@ -326,13 +328,13 @@ impl GraphicalReportHandler {
|
||||||
// Then, we need to print the gutter, along with any fly-bys We
|
// Then, we need to print the gutter, along with any fly-bys We
|
||||||
// have separate gutters depending on whether we're on the actual
|
// have separate gutters depending on whether we're on the actual
|
||||||
// line, or on one of the "highlight lines" below it.
|
// line, or on one of the "highlight lines" below it.
|
||||||
self.render_line_gutter(f, max_gutter, line, &highlights)?;
|
self.render_line_gutter(f, max_gutter, line, &labels)?;
|
||||||
|
|
||||||
// And _now_ we can print out the line text itself!
|
// And _now_ we can print out the line text itself!
|
||||||
writeln!(f, "{}", line.text)?;
|
writeln!(f, "{}", line.text)?;
|
||||||
|
|
||||||
// Next, we write all the highlights that apply to this particular line.
|
// Next, we write all the highlights that apply to this particular line.
|
||||||
let (single_line, multi_line): (Vec<_>, Vec<_>) = highlights
|
let (single_line, multi_line): (Vec<_>, Vec<_>) = labels
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|hl| line.span_applies(hl))
|
.filter(|hl| line.span_applies(hl))
|
||||||
.partition(|hl| line.span_line_only(hl));
|
.partition(|hl| line.span_line_only(hl));
|
||||||
|
|
@ -340,14 +342,14 @@ impl GraphicalReportHandler {
|
||||||
// no line number!
|
// no line number!
|
||||||
self.write_no_linum(f, linum_width)?;
|
self.write_no_linum(f, linum_width)?;
|
||||||
// gutter _again_
|
// gutter _again_
|
||||||
self.render_highlight_gutter(f, max_gutter, line, &highlights)?;
|
self.render_highlight_gutter(f, max_gutter, line, &labels)?;
|
||||||
self.render_single_line_highlights(
|
self.render_single_line_highlights(
|
||||||
f,
|
f,
|
||||||
line,
|
line,
|
||||||
linum_width,
|
linum_width,
|
||||||
max_gutter,
|
max_gutter,
|
||||||
&single_line,
|
&single_line,
|
||||||
&highlights,
|
&labels,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
for hl in multi_line {
|
for hl in multi_line {
|
||||||
|
|
@ -355,7 +357,7 @@ impl GraphicalReportHandler {
|
||||||
// no line number!
|
// no line number!
|
||||||
self.write_no_linum(f, linum_width)?;
|
self.write_no_linum(f, linum_width)?;
|
||||||
// gutter _again_
|
// gutter _again_
|
||||||
self.render_highlight_gutter(f, max_gutter, line, &highlights)?;
|
self.render_highlight_gutter(f, max_gutter, line, &labels)?;
|
||||||
self.render_multi_line_end(f, hl)?;
|
self.render_multi_line_end(f, hl)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -571,16 +573,23 @@ impl GraphicalReportHandler {
|
||||||
|
|
||||||
fn get_lines<'a>(
|
fn get_lines<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
snippet: &'a DiagnosticSnippet<'_>,
|
source: &'a dyn SourceCode,
|
||||||
) -> Result<(Box<dyn SpanContents + 'a>, Vec<Line>), fmt::Error> {
|
labels: &'a [LabeledSpan],
|
||||||
let context_data = snippet
|
) -> Result<(Box<dyn SpanContents<'a> + 'a>, Vec<Line>), fmt::Error> {
|
||||||
.source
|
let first = labels.first().expect("MIETTE BUG: This should be safe.");
|
||||||
.read_span(&snippet.context)
|
let last = labels.last().expect("MIETTE BUG: This should be safe.");
|
||||||
|
let context_span = (
|
||||||
|
first.inner().offset(),
|
||||||
|
last.inner().offset() + last.inner().len(),
|
||||||
|
)
|
||||||
|
.into();
|
||||||
|
let context_data = source
|
||||||
|
.read_span(&context_span, 1, 1)
|
||||||
.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();
|
||||||
let mut column = context_data.column();
|
let mut column = context_data.column();
|
||||||
let mut offset = snippet.context.offset();
|
let mut offset = context_span.offset();
|
||||||
let mut line_offset = offset;
|
let mut line_offset = offset;
|
||||||
let mut iter = context.chars().peekable();
|
let mut iter = context.chars().peekable();
|
||||||
let mut line_str = String::new();
|
let mut line_str = String::new();
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use crate::chain::Chain;
|
use crate::chain::Chain;
|
||||||
use crate::protocol::{Diagnostic, DiagnosticSnippet, Severity};
|
use crate::protocol::{Diagnostic, Severity};
|
||||||
use crate::{ReportHandler, SourceSpan, SpanContents};
|
use crate::{ReportHandler, SourceCode, SourceSpan, SpanContents};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
[ReportHandler] that renders plain text and avoids extraneous graphics.
|
[ReportHandler] that renders plain text and avoids extraneous graphics.
|
||||||
|
|
@ -18,7 +18,7 @@ impl NarratableReportHandler {
|
||||||
/// Create a new [NarratableReportHandler]. There are no customization
|
/// Create a new [NarratableReportHandler]. There are no customization
|
||||||
/// options.
|
/// options.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { footer: None}
|
Self { footer: None }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the footer to be displayed at the end of the report.
|
/// Set the footer to be displayed at the end of the report.
|
||||||
|
|
@ -47,12 +47,11 @@ impl NarratableReportHandler {
|
||||||
self.render_header(f, diagnostic)?;
|
self.render_header(f, diagnostic)?;
|
||||||
self.render_causes(f, diagnostic)?;
|
self.render_causes(f, diagnostic)?;
|
||||||
|
|
||||||
if let Some(snippets) = diagnostic.snippets() {
|
// if let Some(labels) = diagnostic.labels() {
|
||||||
for snippet in snippets {
|
// for label in labels {
|
||||||
writeln!(f)?;
|
// self.render_label(f, &label)?;
|
||||||
self.render_snippet(f, &snippet)?;
|
// }
|
||||||
}
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
self.render_footer(f, diagnostic)?;
|
self.render_footer(f, diagnostic)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -92,68 +91,12 @@ impl NarratableReportHandler {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_snippet(
|
/*
|
||||||
&self,
|
|
||||||
f: &mut impl fmt::Write,
|
|
||||||
snippet: &DiagnosticSnippet<'_>,
|
|
||||||
) -> fmt::Result {
|
|
||||||
let (contents, lines) = self.get_lines(snippet)?;
|
|
||||||
|
|
||||||
write!(f, "Begin snippet")?;
|
|
||||||
if let Some(filename) = snippet.source.name() {
|
|
||||||
write!(f, " for {}", filename,)?;
|
|
||||||
}
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
" starting at line {}, column {}",
|
|
||||||
contents.line() + 1,
|
|
||||||
contents.column() + 1
|
|
||||||
)?;
|
|
||||||
if let Some(message) = snippet.message.as_deref() {
|
|
||||||
write!(f, ": {}", message)?;
|
|
||||||
}
|
|
||||||
writeln!(f)?;
|
|
||||||
writeln!(f)?;
|
|
||||||
|
|
||||||
// Highlights are the bits we're going to underline in our overall
|
|
||||||
// snippet, and we need to do some analysis first to come up with
|
|
||||||
// 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());
|
|
||||||
|
|
||||||
// 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 (label, hl) in relevant {
|
|
||||||
let contents = snippet.source.read_span(hl).map_err(|_| fmt::Error)?;
|
|
||||||
if contents.line() + 1 == line.line_number {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
" highlight starting at line {}, column {}",
|
|
||||||
contents.line() + 1,
|
|
||||||
contents.column() + 1
|
|
||||||
)?;
|
|
||||||
if let Some(label) = label {
|
|
||||||
write!(f, ": {}", label)?;
|
|
||||||
}
|
|
||||||
writeln!(f)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writeln!(f)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_lines<'a>(
|
fn get_lines<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
snippet: &'a DiagnosticSnippet<'a>,
|
source: &'a dyn Source,
|
||||||
) -> Result<(Box<dyn SpanContents + 'a>, Vec<Line>), fmt::Error> {
|
) -> Result<(Box<dyn SpanContents + 'a>, Vec<Line>), fmt::Error> {
|
||||||
let context_data = snippet
|
let context_data = source.read_span(&snippet.context).map_err(|_| fmt::Error)?;
|
||||||
.source
|
|
||||||
.read_span(&snippet.context)
|
|
||||||
.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();
|
||||||
let mut column = context_data.column();
|
let mut column = context_data.column();
|
||||||
|
|
@ -200,6 +143,7 @@ impl NarratableReportHandler {
|
||||||
}
|
}
|
||||||
Ok((context_data, lines))
|
Ok((context_data, lines))
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReportHandler for NarratableReportHandler {
|
impl ReportHandler for NarratableReportHandler {
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,52 @@
|
||||||
use crate::Source;
|
use crate::{MietteError, MietteSpanContents, SourceCode, SpanContents};
|
||||||
|
|
||||||
/// Utility struct for when you have a regular [Source] type, such as a String,
|
/// 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()`
|
/// that doesn't implement `name`, or if you want to override the `.name()`
|
||||||
/// returned by the `Source`.
|
/// returned by the `Source`.
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct NamedSource {
|
pub struct NamedSource {
|
||||||
source: Box<dyn Source + Send + Sync + 'static>,
|
source: Box<dyn SourceCode + 'static>,
|
||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for NamedSource {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("NamedSource")
|
||||||
|
.field("name", &self.name)
|
||||||
|
.field("source", &"<redacted>");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl NamedSource {
|
impl NamedSource {
|
||||||
/// Create a new [NamedSource] using a regular [Source] and giving it a [Source::name].
|
/// Create a new [NamedSource] using a regular [SourceCode] and giving its returned [SpanContents] a name.
|
||||||
pub fn new(name: impl AsRef<str>, source: impl Source + Send + Sync + 'static) -> Self {
|
pub fn new(name: impl AsRef<str>, source: impl SourceCode + Send + Sync + 'static) -> Self {
|
||||||
Self {
|
Self {
|
||||||
source: Box::new(source),
|
source: Box::new(source),
|
||||||
name: name.as_ref().to_string(),
|
name: name.as_ref().to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference the inner [Source] type for this [NamedSource].
|
/// Returns a reference the inner [SourceCode] type for this [NamedSource].
|
||||||
pub fn inner(&self) -> &(dyn Source + Send + Sync + 'static) {
|
pub fn inner(&self) -> &(dyn SourceCode + 'static) {
|
||||||
&*self.source
|
&*self.source
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Source for NamedSource {
|
impl SourceCode for NamedSource {
|
||||||
fn read_span<'a>(
|
fn read_span<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
span: &crate::SourceSpan,
|
span: &crate::SourceSpan,
|
||||||
) -> Result<Box<dyn crate::SpanContents + 'a>, crate::MietteError> {
|
context_lines_before: usize,
|
||||||
self.source.read_span(span)
|
context_lines_after: usize,
|
||||||
}
|
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
|
||||||
|
let contents = self
|
||||||
fn name(&self) -> Option<String> {
|
.inner()
|
||||||
Some(self.name.clone())
|
.read_span(span, context_lines_before, context_lines_after)?;
|
||||||
|
Ok(Box::new(MietteSpanContents::new_named(
|
||||||
|
self.name.clone(),
|
||||||
|
contents.data(),
|
||||||
|
contents.line(),
|
||||||
|
contents.column(),
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
145
src/protocol.rs
145
src/protocol.rs
|
|
@ -45,9 +45,13 @@ pub trait Diagnostic: std::error::Error {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Additional contextual snippets. This is typically used for adding
|
/// Source code to apply this Diagnostic's [Diagnostic::labels] to.
|
||||||
/// marked-up source file output the way compilers often do.
|
fn source_code(&self) -> Option<&dyn SourceCode> {
|
||||||
fn snippets<'a>(&'a self) -> Option<Box<dyn Iterator<Item = DiagnosticSnippet<'a>> + 'a>> {
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Labels to apply to this Diagnostic's [Diagnostic::source_code]
|
||||||
|
fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -158,7 +162,7 @@ pub enum Severity {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Represents a readable source of some sort.
|
Represents readable source code of some sort.
|
||||||
|
|
||||||
This trait is able to support simple Source types like [String]s, as well
|
This trait is able to support simple Source types like [String]s, as well
|
||||||
as more involved types like indexes into centralized `SourceMap`-like types,
|
as more involved types like indexes into centralized `SourceMap`-like types,
|
||||||
|
|
@ -166,32 +170,86 @@ file handles, and even network streams.
|
||||||
|
|
||||||
If you can read it, you can source it,
|
If you can read it, you can source it,
|
||||||
and it's not necessary to read the whole thing--meaning you should be able to
|
and it's not necessary to read the whole thing--meaning you should be able to
|
||||||
support Sources which are gigabytes or larger in size.
|
support SourceCodes which are gigabytes or larger in size.
|
||||||
*/
|
*/
|
||||||
pub trait Source: std::fmt::Debug + Send + Sync {
|
pub trait SourceCode {
|
||||||
/// Read the bytes for a specific span from this Source.
|
/// Read the bytes for a specific span from this SourceCode, keeping a
|
||||||
|
/// certain number of lines before and after the span as context.
|
||||||
fn read_span<'a>(
|
fn read_span<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
span: &SourceSpan,
|
span: &SourceSpan,
|
||||||
) -> Result<Box<dyn SpanContents + 'a>, MietteError>;
|
context_lines_before: usize,
|
||||||
|
context_lines_after: usize,
|
||||||
|
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError>;
|
||||||
|
}
|
||||||
|
|
||||||
/// Optional name, usually a filename, for this source.
|
/**
|
||||||
fn name(&self) -> Option<String> {
|
A labeled [SourceSpan].
|
||||||
None
|
*/
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct LabeledSpan {
|
||||||
|
label: Option<String>,
|
||||||
|
span: SourceSpan,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LabeledSpan {
|
||||||
|
/// Makes a new labeled span.
|
||||||
|
pub fn new(label: Option<String>, offset: ByteOffset, len: ByteOffset) -> Self {
|
||||||
|
Self {
|
||||||
|
label,
|
||||||
|
span: (offset, len).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes a new labeled span using an existing span.
|
||||||
|
pub fn new_with_span(label: Option<String>, span: impl Into<SourceSpan>) -> Self {
|
||||||
|
Self {
|
||||||
|
label,
|
||||||
|
span: span.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the (optional) label string for this LabeledSpan.
|
||||||
|
pub fn label(&self) -> Option<&str> {
|
||||||
|
self.label.as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the inner [SourceSpan].
|
||||||
|
pub fn inner(&self) -> &SourceSpan {
|
||||||
|
&self.span
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the 0-based starting byte offset.
|
||||||
|
pub fn offset(&self) -> usize {
|
||||||
|
self.span.offset()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of bytes this LabeledSpan spans.
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.span.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// True if this LabeledSpan is empty.
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.span.is_empty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Contents of a [Source] covered by [SourceSpan].
|
Contents of a [SourceCode] covered by [SourceSpan].
|
||||||
|
|
||||||
Includes line and column information to optimize highlight calculations.
|
Includes line and column information to optimize highlight calculations.
|
||||||
*/
|
*/
|
||||||
pub trait SpanContents {
|
pub trait SpanContents<'a> {
|
||||||
/// Reference to the data inside the associated span, in bytes.
|
/// Reference to the data inside the associated span, in bytes.
|
||||||
fn data(&self) -> &[u8];
|
fn data(&self) -> &'a [u8];
|
||||||
/// The 0-indexed line in the associated [Source] where the data begins.
|
/// An optional (file?) name for the container of this SpanContents.
|
||||||
|
fn name(&self) -> Option<&'a str> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
/// The 0-indexed line in the associated [SourceCode] where the data begins.
|
||||||
fn line(&self) -> usize;
|
fn line(&self) -> usize;
|
||||||
/// The 0-indexed column in the associated [Source] where the data begins,
|
/// The 0-indexed column in the associated [SourceCode] where the data begins,
|
||||||
/// relative to `line`.
|
/// relative to `line`.
|
||||||
fn column(&self) -> usize;
|
fn column(&self) -> usize;
|
||||||
}
|
}
|
||||||
|
|
@ -201,23 +259,45 @@ Basic implementation of the [SpanContents] trait, for convenience.
|
||||||
*/
|
*/
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct MietteSpanContents<'a> {
|
pub struct MietteSpanContents<'a> {
|
||||||
/// Data from a [Source], in bytes.
|
/// Data from a [SourceCode], in bytes.
|
||||||
data: &'a [u8],
|
data: &'a [u8],
|
||||||
// The 0-indexed line where the associated [SourceSpan] _starts_.
|
// The 0-indexed line where the associated [SourceSpan] _starts_.
|
||||||
line: usize,
|
line: usize,
|
||||||
// The 0-indexed column where the associated [SourceSpan] _starts_.
|
// The 0-indexed column where the associated [SourceSpan] _starts_.
|
||||||
column: usize,
|
column: usize,
|
||||||
|
// Optional filename
|
||||||
|
name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> MietteSpanContents<'a> {
|
impl<'a> MietteSpanContents<'a> {
|
||||||
/// Make a new [MietteSpanContents] object.
|
/// Make a new [MietteSpanContents] object.
|
||||||
pub fn new(data: &'a [u8], line: usize, column: usize) -> MietteSpanContents<'a> {
|
pub fn new(data: &'a [u8], line: usize, column: usize) -> MietteSpanContents<'a> {
|
||||||
MietteSpanContents { data, line, column }
|
MietteSpanContents {
|
||||||
|
data,
|
||||||
|
line,
|
||||||
|
column,
|
||||||
|
name: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Make a new [MietteSpanContents] object, with a name for its "file".
|
||||||
|
pub fn new_named(
|
||||||
|
name: String,
|
||||||
|
data: &'a [u8],
|
||||||
|
line: usize,
|
||||||
|
column: usize,
|
||||||
|
) -> MietteSpanContents<'a> {
|
||||||
|
MietteSpanContents {
|
||||||
|
data,
|
||||||
|
line,
|
||||||
|
column,
|
||||||
|
name: Some(name),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> SpanContents for MietteSpanContents<'a> {
|
impl<'a> SpanContents<'a> for MietteSpanContents<'a> {
|
||||||
fn data(&self) -> &[u8] {
|
fn data(&self) -> &'a [u8] {
|
||||||
self.data
|
self.data
|
||||||
}
|
}
|
||||||
fn line(&self) -> usize {
|
fn line(&self) -> usize {
|
||||||
|
|
@ -229,24 +309,7 @@ impl<'a> SpanContents for MietteSpanContents<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
A snippet from a [Source] to be displayed with a message and possibly some highlights.
|
Span within a [SourceCode] with an associated message.
|
||||||
*/
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct DiagnosticSnippet<'a> {
|
|
||||||
/// Explanation of this specific diagnostic snippet.
|
|
||||||
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.
|
|
||||||
pub context: SourceSpan,
|
|
||||||
/// 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<(Option<String>, SourceSpan)>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Span within a [Source] with an associated message.
|
|
||||||
*/
|
*/
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct SourceSpan {
|
pub struct SourceSpan {
|
||||||
|
|
@ -265,7 +328,7 @@ impl SourceSpan {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The absolute offset, in bytes, from the beginning of a [Source].
|
/// The absolute offset, in bytes, from the beginning of a [SourceCode].
|
||||||
pub fn offset(&self) -> usize {
|
pub fn offset(&self) -> usize {
|
||||||
self.offset.offset()
|
self.offset.offset()
|
||||||
}
|
}
|
||||||
|
|
@ -301,12 +364,12 @@ impl From<(SourceOffset, SourceOffset)> for SourceSpan {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
"Raw" type for the byte offset from the beginning of a [Source].
|
"Raw" type for the byte offset from the beginning of a [SourceCode].
|
||||||
*/
|
*/
|
||||||
pub type ByteOffset = usize;
|
pub type ByteOffset = usize;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Newtype that represents the [ByteOffset] from the beginning of a [Source]
|
Newtype that represents the [ByteOffset] from the beginning of a [SourceCode]
|
||||||
*/
|
*/
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct SourceOffset(ByteOffset);
|
pub struct SourceOffset(ByteOffset);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*!
|
/*!
|
||||||
Default trait implementations for [Source].
|
Default trait implementations for [SourceCode].
|
||||||
*/
|
*/
|
||||||
use std::{
|
use std::{
|
||||||
borrow::{Cow, ToOwned},
|
borrow::{Cow, ToOwned},
|
||||||
|
|
@ -7,98 +7,175 @@ use std::{
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{MietteError, MietteSpanContents, Source, SourceSpan, SpanContents};
|
use crate::{MietteError, MietteSpanContents, SourceCode, SourceSpan, SpanContents};
|
||||||
|
|
||||||
fn start_line_column(string: &str, span: &SourceSpan) -> Result<(usize, usize), MietteError> {
|
fn context_info<'a>(
|
||||||
|
input: &'a [u8],
|
||||||
|
span: &SourceSpan,
|
||||||
|
context_lines_before: usize,
|
||||||
|
context_lines_after: usize,
|
||||||
|
) -> Result<(&'a [u8], usize, usize), MietteError> {
|
||||||
let mut offset = 0usize;
|
let mut offset = 0usize;
|
||||||
let mut start_line = 0usize;
|
let mut start_line = 0usize;
|
||||||
let mut start_column = 0usize;
|
let mut start_column = 0usize;
|
||||||
let mut iter = string.chars().peekable();
|
let mut before_lines_starts = Vec::new();
|
||||||
|
let mut current_line_start = 0usize;
|
||||||
|
let mut end_lines = 0usize;
|
||||||
|
let mut post_span = false;
|
||||||
|
let mut iter = input.iter().copied().peekable();
|
||||||
while let Some(char) = iter.next() {
|
while let Some(char) = iter.next() {
|
||||||
if offset < span.offset() {
|
if matches!(char, b'\r' | b'\n') {
|
||||||
match char {
|
if char == b'\r' && iter.next_if_eq(&b'\n').is_some() {
|
||||||
'\r' => {
|
offset += 1;
|
||||||
if iter.next_if_eq(&'\n').is_some() {
|
}
|
||||||
|
if offset < span.offset() {
|
||||||
|
// We're before the start of the span.
|
||||||
|
start_column = 0;
|
||||||
|
before_lines_starts.push(current_line_start);
|
||||||
|
if before_lines_starts.len() > context_lines_before {
|
||||||
|
start_line += 1;
|
||||||
|
before_lines_starts.remove(0);
|
||||||
|
}
|
||||||
|
} else if offset >= span.offset() + span.len() - 1 {
|
||||||
|
// We're after the end of the span, but haven't necessarily
|
||||||
|
// started collecting end lines yet (we might still be
|
||||||
|
// collecting context lines).
|
||||||
|
if post_span {
|
||||||
|
end_lines += 1;
|
||||||
|
start_column = 0;
|
||||||
|
if end_lines > context_lines_after {
|
||||||
offset += 1;
|
offset += 1;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
start_line += 1;
|
|
||||||
start_column = 0;
|
|
||||||
}
|
|
||||||
'\n' => {
|
|
||||||
start_line += 1;
|
|
||||||
start_column = 0;
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
start_column += 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
current_line_start = offset + 1;
|
||||||
|
} else if offset < span.offset() {
|
||||||
|
start_column += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if offset >= span.offset() + span.len() - 1 {
|
if offset >= span.offset() + span.len() - 1 {
|
||||||
return Ok((start_line, start_column));
|
post_span = true;
|
||||||
|
if end_lines >= context_lines_after {
|
||||||
|
offset += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
offset += char.len_utf8();
|
offset += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if offset >= span.offset() + span.len() - 1 {
|
||||||
|
Ok((
|
||||||
|
&input[before_lines_starts
|
||||||
|
.get(0)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or_else(|| span.offset())..offset],
|
||||||
|
start_line,
|
||||||
|
if context_lines_before == 0 {
|
||||||
|
start_column
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
},
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Err(MietteError::OutOfBounds)
|
||||||
}
|
}
|
||||||
Err(MietteError::OutOfBounds)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The basic impl here is on str (not &str), because otherwise String's impl cannot reuse it
|
impl SourceCode for [u8] {
|
||||||
// without creating a temporary &str inside its read_span implementation, and then returning data
|
|
||||||
// that refers to that temporary.
|
|
||||||
impl Source for str {
|
|
||||||
fn read_span<'a>(
|
fn read_span<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
span: &SourceSpan,
|
span: &SourceSpan,
|
||||||
) -> Result<Box<dyn SpanContents + 'a>, MietteError> {
|
context_lines_before: usize,
|
||||||
let (start_line, start_column) = start_line_column(self, span)?;
|
context_lines_after: usize,
|
||||||
|
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
|
||||||
|
let (data, start_line, start_column) =
|
||||||
|
context_info(self, span, context_lines_before, context_lines_after)?;
|
||||||
return Ok(Box::new(MietteSpanContents::new(
|
return Ok(Box::new(MietteSpanContents::new(
|
||||||
&self.as_bytes()[span.offset()..span.offset() + span.len()],
|
data,
|
||||||
start_line,
|
start_line,
|
||||||
start_column,
|
start_column,
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'src> SourceCode for &'src [u8] {
|
||||||
|
fn read_span<'a>(
|
||||||
|
&'a self,
|
||||||
|
span: &SourceSpan,
|
||||||
|
context_lines_before: usize,
|
||||||
|
context_lines_after: usize,
|
||||||
|
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
|
||||||
|
<[u8] as SourceCode>::read_span(self, span, context_lines_before, context_lines_after)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SourceCode for str {
|
||||||
|
fn read_span<'a>(
|
||||||
|
&'a self,
|
||||||
|
span: &SourceSpan,
|
||||||
|
context_lines_before: usize,
|
||||||
|
context_lines_after: usize,
|
||||||
|
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
|
||||||
|
<[u8] as SourceCode>::read_span(
|
||||||
|
self.as_bytes(),
|
||||||
|
span,
|
||||||
|
context_lines_before,
|
||||||
|
context_lines_after,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Makes `src: &'static str` or `struct S<'a> { src: &'a str }` usable.
|
/// Makes `src: &'static str` or `struct S<'a> { src: &'a str }` usable.
|
||||||
impl<'s> Source for &'s str {
|
impl<'s> SourceCode for &'s str {
|
||||||
fn read_span<'a>(
|
fn read_span<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
span: &SourceSpan,
|
span: &SourceSpan,
|
||||||
) -> Result<Box<dyn SpanContents + 'a>, MietteError> {
|
context_lines_before: usize,
|
||||||
<str as Source>::read_span(self, span)
|
context_lines_after: usize,
|
||||||
|
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
|
||||||
|
<str as SourceCode>::read_span(self, span, context_lines_before, context_lines_after)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Source for String {
|
impl SourceCode for String {
|
||||||
fn read_span<'a>(
|
fn read_span<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
span: &SourceSpan,
|
span: &SourceSpan,
|
||||||
) -> Result<Box<dyn SpanContents + 'a>, MietteError> {
|
context_lines_before: usize,
|
||||||
<str as Source>::read_span(self, span)
|
context_lines_after: usize,
|
||||||
|
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
|
||||||
|
<str as SourceCode>::read_span(self, span, context_lines_before, context_lines_after)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Source> Source for Arc<T> {
|
impl<T: SourceCode> SourceCode for Arc<T> {
|
||||||
fn read_span<'a>(
|
fn read_span<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
span: &SourceSpan,
|
span: &SourceSpan,
|
||||||
) -> Result<Box<dyn SpanContents + 'a>, MietteError> {
|
context_lines_before: usize,
|
||||||
self.as_ref().read_span(span)
|
context_lines_after: usize,
|
||||||
|
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
|
||||||
|
self.as_ref()
|
||||||
|
.read_span(span, context_lines_before, context_lines_after)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ?Sized + Source + ToOwned> Source for Cow<'_, T>
|
impl<T: ?Sized + SourceCode + ToOwned> SourceCode for Cow<'_, T>
|
||||||
where
|
where
|
||||||
// The minimal bounds are used here. `T::Owned` need not be `Source`, because `&T` can always
|
// The minimal bounds are used here. `T::Owned` need not be `SourceCode`,
|
||||||
// be obtained from `Cow<'_, T>`.
|
// because `&T` can always be obtained from `Cow<'_, T>`.
|
||||||
T::Owned: Debug + Send + Sync,
|
T::Owned: Debug + Send + Sync,
|
||||||
{
|
{
|
||||||
fn read_span<'a>(
|
fn read_span<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
span: &SourceSpan,
|
span: &SourceSpan,
|
||||||
) -> Result<Box<dyn SpanContents + 'a>, MietteError> {
|
context_lines_before: usize,
|
||||||
self.as_ref().read_span(span)
|
context_lines_after: usize,
|
||||||
|
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
|
||||||
|
self.as_ref()
|
||||||
|
.read_span(span, context_lines_before, context_lines_after)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,24 +186,53 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn basic() -> Result<(), MietteError> {
|
fn basic() -> Result<(), MietteError> {
|
||||||
let src = String::from("foo\n");
|
let src = String::from("foo\n");
|
||||||
let contents = src.read_span(&(0, 4).into())?;
|
let contents = src.read_span(&(0, 4).into(), 0, 0)?;
|
||||||
assert_eq!("foo\n", std::str::from_utf8(contents.data()).unwrap());
|
assert_eq!("foo\n", std::str::from_utf8(contents.data()).unwrap());
|
||||||
|
assert_eq!(0, contents.line());
|
||||||
|
assert_eq!(0, contents.column());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn middle() -> Result<(), MietteError> {
|
fn middle() -> Result<(), MietteError> {
|
||||||
let src = String::from("foo\nbar\nbaz\n");
|
let src = String::from("foo\nbar\nbaz\n");
|
||||||
let contents = src.read_span(&(4, 4).into())?;
|
let contents = src.read_span(&(4, 4).into(), 0, 0)?;
|
||||||
assert_eq!("bar\n", std::str::from_utf8(contents.data()).unwrap());
|
assert_eq!("bar\n", std::str::from_utf8(contents.data()).unwrap());
|
||||||
|
assert_eq!(1, contents.line());
|
||||||
|
assert_eq!(0, contents.column());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn middle_of_line() -> Result<(), MietteError> {
|
||||||
|
let src = String::from("foo\nbarbar\nbaz\n");
|
||||||
|
let contents = src.read_span(&(7, 4).into(), 0, 0)?;
|
||||||
|
assert_eq!("bar\n", std::str::from_utf8(contents.data()).unwrap());
|
||||||
|
assert_eq!(1, contents.line());
|
||||||
|
assert_eq!(3, contents.column());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn with_crlf() -> Result<(), MietteError> {
|
fn with_crlf() -> Result<(), MietteError> {
|
||||||
let src = String::from("foo\r\nbar\r\nbaz\r\n");
|
let src = String::from("foo\r\nbar\r\nbaz\r\n");
|
||||||
let contents = src.read_span(&(5, 5).into())?;
|
let contents = src.read_span(&(5, 5).into(), 0, 0)?;
|
||||||
assert_eq!("bar\r\n", std::str::from_utf8(contents.data()).unwrap());
|
assert_eq!("bar\r\n", std::str::from_utf8(contents.data()).unwrap());
|
||||||
|
assert_eq!(1, contents.line());
|
||||||
|
assert_eq!(0, contents.column());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn with_context() -> Result<(), MietteError> {
|
||||||
|
let src = String::from("xxx\nfoo\nbar\nbaz\n\nyyy\n");
|
||||||
|
let contents = src.read_span(&(8, 4).into(), 1, 2)?;
|
||||||
|
assert_eq!(
|
||||||
|
"foo\nbar\nbaz\n\n",
|
||||||
|
std::str::from_utf8(contents.data()).unwrap()
|
||||||
|
);
|
||||||
|
assert_eq!(1, contents.line());
|
||||||
|
assert_eq!(0, contents.column());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
121
tests/derive.rs
121
tests/derive.rs
|
|
@ -202,41 +202,15 @@ fn test_snippet_named_struct() {
|
||||||
#[error("welp")]
|
#[error("welp")]
|
||||||
#[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
|
#[source_code]
|
||||||
// reused by multiple contexts, and just needs to implement
|
|
||||||
// miette::Source!
|
|
||||||
src: String,
|
src: String,
|
||||||
|
#[label("var 1")]
|
||||||
// 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
|
|
||||||
// reasonable context, but still somewhat compact.
|
|
||||||
//
|
|
||||||
// You can have as many of these #[snippet] fields as you want, and
|
|
||||||
// even feed them from different sources!
|
|
||||||
//
|
|
||||||
// Example display:
|
|
||||||
// / [my_snippet]: hi this is where the thing went wrong.
|
|
||||||
// 1 | hello
|
|
||||||
// 2 | world
|
|
||||||
#[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.
|
|
||||||
// These will be used to underline/point to specific sections of the
|
|
||||||
// #[snippet] they refer to. As such, these SourceSpans must be within
|
|
||||||
// the bounds of their referenced snippet.
|
|
||||||
//
|
|
||||||
// Example display:
|
|
||||||
// 1 | var1 + var2
|
|
||||||
// | ^^^^ ^^^^ - var 2
|
|
||||||
// | |
|
|
||||||
// | var 1
|
|
||||||
#[highlight(snip)]
|
|
||||||
// label from SourceSpan is used, if any.
|
|
||||||
var1: SourceSpan,
|
var1: SourceSpan,
|
||||||
#[highlight(snip)]
|
#[label = "var 2"]
|
||||||
// Anything that's Clone + Into<SourceSpan> can be used here.
|
// Anything that's Clone + Into<SourceSpan> can be used here.
|
||||||
var2: (usize, usize),
|
var2: (usize, usize),
|
||||||
|
#[label]
|
||||||
|
var3: (usize, usize),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -246,15 +220,10 @@ fn test_snippet_unnamed_struct() {
|
||||||
#[error("welp")]
|
#[error("welp")]
|
||||||
#[diagnostic(code(foo::bar::baz))]
|
#[diagnostic(code(foo::bar::baz))]
|
||||||
struct Foo(
|
struct Foo(
|
||||||
String,
|
#[source_code] String,
|
||||||
#[snippet(0, message("hi"))] SourceSpan,
|
#[label("{0}")] SourceSpan,
|
||||||
#[highlight(1)] SourceSpan,
|
#[label = "idk"] SourceSpan,
|
||||||
#[highlight(1)] SourceSpan,
|
#[label] SourceSpan,
|
||||||
// referenced source name
|
|
||||||
String,
|
|
||||||
#[snippet(0, message("{}", self.4))] SourceSpan,
|
|
||||||
#[highlight(5)] SourceSpan,
|
|
||||||
#[highlight(5)] SourceSpan,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -266,24 +235,23 @@ fn test_snippet_enum() {
|
||||||
enum Foo {
|
enum Foo {
|
||||||
#[diagnostic(code(foo::a))]
|
#[diagnostic(code(foo::a))]
|
||||||
A {
|
A {
|
||||||
|
#[source_code]
|
||||||
src: String,
|
src: String,
|
||||||
#[snippet(src, message("hi this is where the thing went wrong"))]
|
msg: String,
|
||||||
snip: SourceSpan,
|
#[label("hi this is where the thing went wrong ({msg})")]
|
||||||
#[highlight(snip)]
|
var0: SourceSpan,
|
||||||
|
#[label = "blorp"]
|
||||||
var1: SourceSpan,
|
var1: SourceSpan,
|
||||||
#[highlight(snip)]
|
#[label]
|
||||||
var2: SourceSpan,
|
var2: SourceSpan,
|
||||||
},
|
},
|
||||||
#[diagnostic(code(foo::b))]
|
#[diagnostic(code(foo::b))]
|
||||||
B(
|
B(
|
||||||
|
#[source_code] String,
|
||||||
String,
|
String,
|
||||||
#[snippet(0, message("hi"))] SourceSpan,
|
#[label("{1}")] SourceSpan,
|
||||||
#[highlight(1)] SourceSpan,
|
#[label = "blorp"] SourceSpan,
|
||||||
#[highlight(1, label("var 2"))] SourceSpan,
|
#[label] SourceSpan,
|
||||||
// referenced source name
|
|
||||||
#[snippet(0)] SourceSpan,
|
|
||||||
#[highlight(4)] SourceSpan,
|
|
||||||
#[highlight(4)] SourceSpan,
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -329,19 +297,17 @@ const SNIPPET_TEXT: &str = "hello from miette";
|
||||||
severity(Warning)
|
severity(Warning)
|
||||||
)]
|
)]
|
||||||
struct ForwardsTo {
|
struct ForwardsTo {
|
||||||
|
#[source_code]
|
||||||
src: String,
|
src: String,
|
||||||
#[snippet(src, message("snippet text"))]
|
#[label("highlight text")]
|
||||||
snip: miette::SourceSpan,
|
label: miette::SourceSpan,
|
||||||
#[highlight(snip, label("highlight text"))]
|
|
||||||
highlight: miette::SourceSpan,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ForwardsTo {
|
impl ForwardsTo {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
ForwardsTo {
|
ForwardsTo {
|
||||||
src: SNIPPET_TEXT.into(),
|
src: SNIPPET_TEXT.into(),
|
||||||
snip: SourceSpan::new(0.into(), SNIPPET_TEXT.len().into()),
|
label: SourceSpan::new(11.into(), 6.into()),
|
||||||
highlight: SourceSpan::new(11.into(), 6.into()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -352,39 +318,6 @@ fn check_all(diag: &impl Diagnostic) {
|
||||||
assert_eq!(diag.url().unwrap().to_string(), "https://example.com");
|
assert_eq!(diag.url().unwrap().to_string(), "https://example.com");
|
||||||
assert_eq!(diag.help().unwrap().to_string(), "help");
|
assert_eq!(diag.help().unwrap().to_string(), "help");
|
||||||
assert_eq!(diag.severity().unwrap(), miette::Severity::Warning);
|
assert_eq!(diag.severity().unwrap(), miette::Severity::Warning);
|
||||||
check_snippets(diag);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_snippets(diag: &impl Diagnostic) {
|
|
||||||
type Snip = (Option<String>, usize, usize);
|
|
||||||
let snips: Vec<(Snip, Vec<Snip>)> = diag
|
|
||||||
.snippets()
|
|
||||||
.unwrap()
|
|
||||||
.map(
|
|
||||||
|miette::DiagnosticSnippet {
|
|
||||||
message,
|
|
||||||
context,
|
|
||||||
highlights,
|
|
||||||
..
|
|
||||||
}| {
|
|
||||||
(
|
|
||||||
(message, context.offset(), context.len()),
|
|
||||||
highlights
|
|
||||||
.into_iter()
|
|
||||||
.flatten()
|
|
||||||
.map(|(msg, span)| (msg, span.offset(), span.len()))
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.collect();
|
|
||||||
assert_eq!(
|
|
||||||
&snips[..],
|
|
||||||
&[(
|
|
||||||
(Some("snippet text".into()), 0, SNIPPET_TEXT.len()),
|
|
||||||
vec![(Some("highlight text".into()), 11, 6)]
|
|
||||||
)]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -475,8 +408,6 @@ fn test_forward_struct_named() {
|
||||||
assert_eq!(diag.code().unwrap().to_string(), "foo::bar::overridden");
|
assert_eq!(diag.code().unwrap().to_string(), "foo::bar::overridden");
|
||||||
assert_eq!(diag.help().unwrap().to_string(), "overridden help please");
|
assert_eq!(diag.help().unwrap().to_string(), "overridden help please");
|
||||||
assert_eq!(diag.severity(), Some(Severity::Advice));
|
assert_eq!(diag.severity(), Some(Severity::Advice));
|
||||||
// this comes from <ForwardsTo as Diagnostic>::snippets()
|
|
||||||
check_snippets(&diag);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -490,8 +421,6 @@ fn test_forward_struct_unnamed() {
|
||||||
let diag = Struct(ForwardsTo::new(), "url here");
|
let diag = Struct(ForwardsTo::new(), "url here");
|
||||||
assert_eq!(diag.code().unwrap().to_string(), "foo::bar::overridden");
|
assert_eq!(diag.code().unwrap().to_string(), "foo::bar::overridden");
|
||||||
assert_eq!(diag.url().unwrap().to_string(), "url here");
|
assert_eq!(diag.url().unwrap().to_string(), "url here");
|
||||||
// this comes from <ForwardsTo as Diagnostic>::snippets()
|
|
||||||
check_snippets(&diag);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -515,8 +444,6 @@ fn test_forward_enum_named() {
|
||||||
variant.help().unwrap().to_string(),
|
variant.help().unwrap().to_string(),
|
||||||
"overridden help please"
|
"overridden help please"
|
||||||
);
|
);
|
||||||
// this comes from <ForwardsTo as Diagnostic>::snippets()
|
|
||||||
check_snippets(&variant);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -534,8 +461,6 @@ fn test_forward_enum_unnamed() {
|
||||||
variant.help().unwrap().to_string(),
|
variant.help().unwrap().to_string(),
|
||||||
"overridden help please"
|
"overridden help please"
|
||||||
);
|
);
|
||||||
// this comes from <ForwardsTo as Diagnostic>::snippets()
|
|
||||||
check_snippets(&variant);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
use miette::{
|
use miette::{Diagnostic, MietteError, NamedSource, NarratableReportHandler, Report, SourceSpan};
|
||||||
Diagnostic, MietteError, NamedSource,
|
|
||||||
NarratableReportHandler, Report, SourceSpan,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(feature = "fancy")]
|
#[cfg(feature = "fancy")]
|
||||||
use miette::{GraphicalReportHandler, GraphicalTheme};
|
use miette::{GraphicalReportHandler, GraphicalTheme};
|
||||||
|
|
@ -30,19 +27,17 @@ fn single_line_highlight() -> Result<(), MietteError> {
|
||||||
#[error("oops!")]
|
#[error("oops!")]
|
||||||
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
struct MyBad {
|
struct MyBad {
|
||||||
|
#[source_code]
|
||||||
src: NamedSource,
|
src: NamedSource,
|
||||||
#[snippet(src, message("This is the part that broke"))]
|
#[label("this bit here")]
|
||||||
ctx: SourceSpan,
|
bad_thing: SourceSpan,
|
||||||
#[highlight(ctx, label = "this bit here")]
|
|
||||||
highlight: SourceSpan,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
src: NamedSource::new("bad_file.rs", src),
|
src: NamedSource::new("bad_file.rs", src),
|
||||||
ctx: (0, len).into(),
|
bad_thing: (9, 4).into(),
|
||||||
highlight: (9, 4).into(),
|
|
||||||
};
|
};
|
||||||
let out = fmt_report(err.into());
|
let out = fmt_report(err.into());
|
||||||
println!("{}", out);
|
println!("{}", out);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue