mirror of https://github.com/zkat/miette.git
parent
3e5ee0ee4d
commit
f6e6acf2d2
|
|
@ -6,7 +6,7 @@ use syn::{
|
|||
Token,
|
||||
};
|
||||
|
||||
use crate::diagnostic::{Diagnostic, DiagnosticVariant};
|
||||
use crate::diagnostic::DiagnosticVariant;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Code(pub String);
|
||||
|
|
@ -44,10 +44,7 @@ impl Parse for Code {
|
|||
}
|
||||
|
||||
impl Code {
|
||||
pub(crate) fn gen_enum(
|
||||
_diag: &Diagnostic,
|
||||
variants: &[DiagnosticVariant],
|
||||
) -> Option<TokenStream> {
|
||||
pub(crate) fn gen_enum(variants: &[DiagnosticVariant]) -> Option<TokenStream> {
|
||||
let code_pairs = variants.iter().map(
|
||||
|DiagnosticVariant {
|
||||
ref ident,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use crate::code::Code;
|
|||
use crate::diagnostic_arg::DiagnosticArg;
|
||||
use crate::help::Help;
|
||||
use crate::severity::Severity;
|
||||
use crate::snippets::Snippets;
|
||||
|
||||
pub enum Diagnostic {
|
||||
Struct {
|
||||
|
|
@ -14,6 +15,7 @@ pub enum Diagnostic {
|
|||
code: Code,
|
||||
severity: Option<Severity>,
|
||||
help: Option<Help>,
|
||||
snippets: Option<Snippets>,
|
||||
},
|
||||
Enum {
|
||||
ident: syn::Ident,
|
||||
|
|
@ -28,12 +30,13 @@ pub struct DiagnosticVariant {
|
|||
pub code: Code,
|
||||
pub severity: Option<Severity>,
|
||||
pub help: Option<Help>,
|
||||
pub snippets: Option<Snippets>,
|
||||
}
|
||||
|
||||
impl Diagnostic {
|
||||
pub fn from_derive_input(input: DeriveInput) -> Result<Self, syn::Error> {
|
||||
Ok(match input.data {
|
||||
syn::Data::Struct(_) => {
|
||||
syn::Data::Struct(data_struct) => {
|
||||
if let Some(attr) = input.attrs.iter().find(|x| x.path.is_ident("diagnostic")) {
|
||||
let args = attr.parse_args_with(
|
||||
Punctuated::<DiagnosticArg, Token![,]>::parse_terminated,
|
||||
|
|
@ -53,6 +56,7 @@ impl Diagnostic {
|
|||
DiagnosticArg::Help(hl) => help = Some(hl),
|
||||
}
|
||||
}
|
||||
let snippets = Snippets::from_fields(&data_struct.fields)?;
|
||||
let ident = input.ident.clone();
|
||||
Diagnostic::Struct {
|
||||
ident: input.ident,
|
||||
|
|
@ -62,6 +66,7 @@ impl Diagnostic {
|
|||
})?,
|
||||
help,
|
||||
severity,
|
||||
snippets,
|
||||
}
|
||||
} else {
|
||||
// Also handle when there's multiple `#[diagnostic]` attrs?
|
||||
|
|
@ -95,6 +100,7 @@ impl Diagnostic {
|
|||
}
|
||||
}
|
||||
}
|
||||
let snippets = Snippets::from_fields(&var.fields)?;
|
||||
let ident = input.ident.clone();
|
||||
vars.push(DiagnosticVariant {
|
||||
ident: var.ident,
|
||||
|
|
@ -104,6 +110,7 @@ impl Diagnostic {
|
|||
})?,
|
||||
help,
|
||||
severity,
|
||||
snippets,
|
||||
});
|
||||
} else {
|
||||
// Also handle when there's multiple `#[diagnostic]` attrs?
|
||||
|
|
@ -136,17 +143,20 @@ impl Diagnostic {
|
|||
code,
|
||||
severity,
|
||||
help,
|
||||
snippets,
|
||||
} => {
|
||||
let (impl_generics, ty_generics, where_clause) = &generics.split_for_impl();
|
||||
let code_body = code.gen_struct();
|
||||
let help_body = help.as_ref().and_then(|x| x.gen_struct());
|
||||
let sev_body = severity.as_ref().and_then(|x| x.gen_struct());
|
||||
let snip_body = snippets.as_ref().and_then(|x| x.gen_struct());
|
||||
|
||||
quote! {
|
||||
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
|
||||
#code_body
|
||||
#help_body
|
||||
#sev_body
|
||||
#snip_body
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -156,15 +166,17 @@ impl Diagnostic {
|
|||
variants,
|
||||
} => {
|
||||
let (impl_generics, ty_generics, where_clause) = &generics.split_for_impl();
|
||||
let code_body = Code::gen_enum(self, variants);
|
||||
let help_body = Help::gen_enum(self, variants);
|
||||
let sev_body = Severity::gen_enum(self, variants);
|
||||
let code_body = Code::gen_enum(variants);
|
||||
let help_body = Help::gen_enum(variants);
|
||||
let sev_body = Severity::gen_enum(variants);
|
||||
let snip_body = Snippets::gen_enum(variants);
|
||||
|
||||
quote! {
|
||||
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
|
||||
#code_body
|
||||
#help_body
|
||||
#sev_body
|
||||
#snip_body
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use syn::{
|
|||
Token,
|
||||
};
|
||||
|
||||
use crate::diagnostic::{Diagnostic, DiagnosticVariant};
|
||||
use crate::diagnostic::DiagnosticVariant;
|
||||
|
||||
pub struct Help {
|
||||
pub fmt: String,
|
||||
|
|
@ -65,10 +65,7 @@ impl Parse for Help {
|
|||
}
|
||||
}
|
||||
impl Help {
|
||||
pub(crate) fn gen_enum(
|
||||
_diag: &Diagnostic,
|
||||
variants: &[DiagnosticVariant],
|
||||
) -> Option<TokenStream> {
|
||||
pub(crate) fn gen_enum(variants: &[DiagnosticVariant]) -> Option<TokenStream> {
|
||||
let help_pairs = variants
|
||||
.iter()
|
||||
.filter(|v| v.help.is_some())
|
||||
|
|
|
|||
|
|
@ -8,8 +8,9 @@ mod diagnostic;
|
|||
mod diagnostic_arg;
|
||||
mod help;
|
||||
mod severity;
|
||||
mod snippets;
|
||||
|
||||
#[proc_macro_derive(Diagnostic, attributes(diagnostic))]
|
||||
#[proc_macro_derive(Diagnostic, attributes(diagnostic, snippet, highlight))]
|
||||
pub fn derive_diagnostic(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let cmd = match Diagnostic::from_derive_input(input) {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use syn::{
|
|||
Token,
|
||||
};
|
||||
|
||||
use crate::diagnostic::{Diagnostic, DiagnosticVariant};
|
||||
use crate::diagnostic::DiagnosticVariant;
|
||||
|
||||
pub struct Severity(pub syn::Path);
|
||||
|
||||
|
|
@ -36,10 +36,7 @@ impl Parse for Severity {
|
|||
}
|
||||
}
|
||||
impl Severity {
|
||||
pub(crate) fn gen_enum(
|
||||
_diag: &Diagnostic,
|
||||
variants: &[DiagnosticVariant],
|
||||
) -> Option<TokenStream> {
|
||||
pub(crate) fn gen_enum(variants: &[DiagnosticVariant]) -> Option<TokenStream> {
|
||||
let sev_pairs = variants
|
||||
.iter()
|
||||
.filter(|v| v.severity.is_some())
|
||||
|
|
|
|||
|
|
@ -0,0 +1,409 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote, ToTokens};
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
punctuated::Punctuated,
|
||||
spanned::Spanned,
|
||||
Token,
|
||||
};
|
||||
|
||||
use crate::diagnostic::DiagnosticVariant;
|
||||
|
||||
pub struct Snippets(Vec<Snippet>);
|
||||
|
||||
struct Snippet {
|
||||
message: Option<MemberOrString>,
|
||||
highlights: Vec<Highlight>,
|
||||
source_name: MemberOrString,
|
||||
source: syn::Member,
|
||||
snippet: syn::Member,
|
||||
}
|
||||
|
||||
struct Highlight {
|
||||
highlight: syn::Member,
|
||||
label: Option<MemberOrString>,
|
||||
}
|
||||
|
||||
struct SnippetAttr {
|
||||
source: syn::Member,
|
||||
source_name: MemberOrString,
|
||||
message: Option<MemberOrString>,
|
||||
}
|
||||
|
||||
struct HighlightAttr {
|
||||
snippet: syn::Member,
|
||||
label: Option<MemberOrString>,
|
||||
}
|
||||
|
||||
enum MemberOrString {
|
||||
Member(syn::Member),
|
||||
String(syn::LitStr),
|
||||
}
|
||||
|
||||
impl ToTokens for MemberOrString {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
use MemberOrString::*;
|
||||
match self {
|
||||
Member(member) => member.to_tokens(tokens),
|
||||
String(string) => string.to_tokens(tokens),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for MemberOrString {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let lookahead = input.lookahead1();
|
||||
if lookahead.peek(syn::Ident) || lookahead.peek(syn::LitInt) {
|
||||
Ok(MemberOrString::Member(input.parse()?))
|
||||
} else if lookahead.peek(syn::LitStr) {
|
||||
Ok(MemberOrString::String(input.parse()?))
|
||||
} else {
|
||||
Err(syn::Error::new(
|
||||
input.span(),
|
||||
"Expected a string or a field reference.",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
_ => {
|
||||
return Err(syn::Error::new(
|
||||
span,
|
||||
"Source must be an identifier that refers to a Source for this snippet.",
|
||||
))
|
||||
}
|
||||
};
|
||||
let src_name = iter
|
||||
.next()
|
||||
.ok_or_else(|| syn::Error::new(span, "Expected a source name."))?;
|
||||
let message = iter.next();
|
||||
Ok(SnippetAttr {
|
||||
source,
|
||||
source_name: src_name,
|
||||
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.",
|
||||
)),
|
||||
};
|
||||
let label = iter.next();
|
||||
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,
|
||||
source_name,
|
||||
} = 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_name,
|
||||
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 {
|
||||
label,
|
||||
highlight: member,
|
||||
});
|
||||
} 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) -> 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.into()),
|
||||
}
|
||||
}
|
||||
MemberOrString::Member(m) => {
|
||||
quote! {
|
||||
message: std::option::Option::Some(self.#m.clone()),
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
quote! {
|
||||
message: std::option::Option::None,
|
||||
}
|
||||
});
|
||||
|
||||
// Source field
|
||||
let src_ident = &snippet.source;
|
||||
let src_ident = quote! {
|
||||
// TODO: I don't like this. Think about it more and maybe improve protocol?
|
||||
source: self.#src_ident.clone(),
|
||||
};
|
||||
|
||||
// Source name
|
||||
let src_name = match &snippet.source_name {
|
||||
MemberOrString::String(str) => {
|
||||
quote! {
|
||||
source_name: #str.into(),
|
||||
}
|
||||
}
|
||||
MemberOrString::Member(member) => quote! {
|
||||
source_name: self.#member.clone(),
|
||||
},
|
||||
};
|
||||
|
||||
// Context
|
||||
let context = &snippet.snippet;
|
||||
let context = quote! {
|
||||
context: self.#context.clone(),
|
||||
};
|
||||
|
||||
// Highlights
|
||||
let highlights = snippet.highlights.iter().map(|highlight| {
|
||||
let Highlight { highlight, label } = highlight;
|
||||
quote! {
|
||||
(#label.into(), self.#highlight.clone())
|
||||
}
|
||||
});
|
||||
let highlights = quote! {
|
||||
highlights: std::option::Option::Some(vec![
|
||||
#(#highlights),*
|
||||
]),
|
||||
};
|
||||
|
||||
// Generate the snippet itself
|
||||
quote! {
|
||||
miette::DiagnosticSnippet {
|
||||
#msg
|
||||
#src_name
|
||||
#src_ident
|
||||
#context
|
||||
#highlights
|
||||
}
|
||||
}
|
||||
});
|
||||
Some(quote! {
|
||||
fn snippets(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::DiagnosticSnippet>>> {
|
||||
Some(Box::new(vec![
|
||||
#(#snippets),*
|
||||
].into_iter()))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn gen_enum(variants: &[DiagnosticVariant]) -> Option<TokenStream> {
|
||||
let variant_arms = variants.iter().map(|variant| {
|
||||
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.into()),
|
||||
}
|
||||
}
|
||||
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.clone()),
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap_or_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.clone(),
|
||||
};
|
||||
|
||||
// Source name
|
||||
let src_name = match &snippet.source_name {
|
||||
MemberOrString::String(str) => {
|
||||
quote! {
|
||||
source_name: #str.into(),
|
||||
}
|
||||
}
|
||||
MemberOrString::Member(m) => {
|
||||
let m = match m {
|
||||
syn::Member::Named(id) => id.clone(),
|
||||
syn::Member::Unnamed(syn::Index { index, .. }) => {
|
||||
format_ident!("_{}", index)
|
||||
}
|
||||
};
|
||||
quote! {
|
||||
source_name: #m.clone(),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 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(),
|
||||
};
|
||||
|
||||
// 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)
|
||||
}
|
||||
};
|
||||
quote! {
|
||||
(#label.into(), #m.clone())
|
||||
}
|
||||
});
|
||||
let highlights = quote! {
|
||||
highlights: std::option::Option::Some(vec![
|
||||
#(#highlights),*
|
||||
]),
|
||||
};
|
||||
|
||||
// Generate the snippet itself
|
||||
quote! {
|
||||
miette::DiagnosticSnippet {
|
||||
#msg
|
||||
#src_name
|
||||
#src_ident
|
||||
#context
|
||||
#highlights
|
||||
}
|
||||
}
|
||||
});
|
||||
let variant_name = variant.ident.clone();
|
||||
let members = variant.fields.iter().enumerate().map(|(i, field)| {
|
||||
field
|
||||
.ident
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.unwrap_or_else(|| format_ident!("_{}", i))
|
||||
});
|
||||
match &variant.fields {
|
||||
syn::Fields::Unit => None,
|
||||
syn::Fields::Named(_) => Some(quote! {
|
||||
Self::#variant_name { #(#members),* } => std::option::Option::Some(std::boxed::Box::new(vec![
|
||||
#(#variant_snippets),*
|
||||
].into_iter())),
|
||||
}),
|
||||
syn::Fields::Unnamed(_) => Some(quote! {
|
||||
Self::#variant_name(#(#members),*) => std::option::Option::Some(Box::new(vec![
|
||||
#(#variant_snippets),*
|
||||
].into_iter())),
|
||||
}),
|
||||
}
|
||||
})
|
||||
});
|
||||
Some(quote! {
|
||||
fn snippets(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::DiagnosticSnippet>>> {
|
||||
match self {
|
||||
#(#variant_arms)*
|
||||
_ => std::option::Option::None,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
109
tests/derive.rs
109
tests/derive.rs
|
|
@ -1,4 +1,6 @@
|
|||
use miette::{Diagnostic, Severity};
|
||||
use std::sync::Arc;
|
||||
|
||||
use miette::{Diagnostic, Severity, SourceSpan};
|
||||
use thiserror::Error;
|
||||
|
||||
#[test]
|
||||
|
|
@ -156,3 +158,108 @@ fn fmt_help() {
|
|||
|
||||
assert_eq!("1 bar".to_string(), FooEnum::X.help().unwrap().to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_snippet_named_struct() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("welp")]
|
||||
#[diagnostic(code(foo::bar::baz))]
|
||||
struct Foo {
|
||||
// The actual "source code" our contexts will be using. This can be
|
||||
// reused by multiple contexts!
|
||||
//
|
||||
// The `Arc` is so you don't have to clone the entire thing into this
|
||||
// Diagnostic. We just need to be able to read it~
|
||||
src: Arc<String>,
|
||||
|
||||
// The "snippet" span. This is the span that will be displayed to
|
||||
// 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, "my_snippet.rs", "hi this is where the thing went wrong")]
|
||||
snip: SourceSpan,
|
||||
|
||||
// "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, "var 1")]
|
||||
var1: SourceSpan,
|
||||
#[highlight(snip, "var 2")]
|
||||
var2: SourceSpan,
|
||||
|
||||
// Now with member source names
|
||||
filename: String,
|
||||
second_message: String,
|
||||
#[snippet(src, filename, second_message)]
|
||||
snip2: SourceSpan,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_snippet_unnamed_struct() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("welp")]
|
||||
#[diagnostic(code(foo::bar::baz))]
|
||||
struct Foo(
|
||||
Arc<String>,
|
||||
#[snippet(0, "my_snippet.rs", "hi")] SourceSpan,
|
||||
#[highlight(1, "var 1")] SourceSpan,
|
||||
#[highlight(1, "var 2")] SourceSpan,
|
||||
// referenced source name
|
||||
String,
|
||||
String,
|
||||
#[snippet(0, 4, 5)] SourceSpan,
|
||||
#[highlight(6, "var 3")] SourceSpan,
|
||||
#[highlight(6, "var 4")] SourceSpan,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_snippet_enum() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("welp")]
|
||||
#[allow(dead_code)]
|
||||
enum Foo {
|
||||
#[diagnostic(code(foo::a))]
|
||||
A {
|
||||
src: Arc<String>,
|
||||
#[snippet(src, "my_snippet.rs", "hi this is where the thing went wrong")]
|
||||
snip: SourceSpan,
|
||||
#[highlight(snip, "var 1")]
|
||||
var1: SourceSpan,
|
||||
#[highlight(snip, "var 2")]
|
||||
var2: SourceSpan,
|
||||
filename: String,
|
||||
second_message: String,
|
||||
#[snippet(src, filename, second_message)]
|
||||
snip2: SourceSpan,
|
||||
},
|
||||
#[diagnostic(code(foo::b))]
|
||||
B(
|
||||
Arc<String>,
|
||||
#[snippet(0, "my_snippet.rs", "hi")] SourceSpan,
|
||||
#[highlight(1, "var 1")] SourceSpan,
|
||||
#[highlight(1, "var 2")] SourceSpan,
|
||||
// referenced source name
|
||||
String,
|
||||
String,
|
||||
#[snippet(0, 4, 5)] SourceSpan,
|
||||
#[highlight(6, "var 3")] SourceSpan,
|
||||
#[highlight(6, "var 4")] SourceSpan,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue