mirror of https://github.com/zkat/miette.git
Add support for primary_label in specifying line/col information
This commit is contained in:
parent
a9c2bae9dc
commit
4c970fb4cf
|
|
@ -20,6 +20,7 @@ struct Label {
|
||||||
label: Option<Display>,
|
label: Option<Display>,
|
||||||
ty: syn::Type,
|
ty: syn::Type,
|
||||||
span: syn::Member,
|
span: syn::Member,
|
||||||
|
primary: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LabelAttr {
|
struct LabelAttr {
|
||||||
|
|
@ -90,7 +91,16 @@ impl Labels {
|
||||||
let mut labels = Vec::new();
|
let mut labels = Vec::new();
|
||||||
for (i, field) in fields.iter().enumerate() {
|
for (i, field) in fields.iter().enumerate() {
|
||||||
for attr in &field.attrs {
|
for attr in &field.attrs {
|
||||||
if attr.path().is_ident("label") {
|
let is_label = attr.path().is_ident("label");
|
||||||
|
let is_primary_label = attr.path().is_ident("primary_label");
|
||||||
|
if is_label || is_primary_label {
|
||||||
|
if is_primary_label && labels.iter().any(|l: &Label| l.primary) {
|
||||||
|
return Err(syn::Error::new(
|
||||||
|
field.span(),
|
||||||
|
"Cannot have more than one primary label.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let span = if let Some(ident) = field.ident.clone() {
|
let span = if let Some(ident) = field.ident.clone() {
|
||||||
syn::Member::Named(ident)
|
syn::Member::Named(ident)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -106,6 +116,7 @@ impl Labels {
|
||||||
label,
|
label,
|
||||||
span,
|
span,
|
||||||
ty: field.ty.clone(),
|
ty: field.ty.clone(),
|
||||||
|
primary: is_primary_label,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -120,13 +131,23 @@ impl Labels {
|
||||||
pub(crate) fn gen_struct(&self, fields: &syn::Fields) -> Option<TokenStream> {
|
pub(crate) fn gen_struct(&self, fields: &syn::Fields) -> Option<TokenStream> {
|
||||||
let (display_pat, display_members) = display_pat_members(fields);
|
let (display_pat, display_members) = display_pat_members(fields);
|
||||||
let labels = self.0.iter().map(|highlight| {
|
let labels = self.0.iter().map(|highlight| {
|
||||||
let Label { span, label, ty } = highlight;
|
let Label {
|
||||||
|
span,
|
||||||
|
label,
|
||||||
|
ty,
|
||||||
|
primary,
|
||||||
|
} = highlight;
|
||||||
let var = quote! { __miette_internal_var };
|
let var = quote! { __miette_internal_var };
|
||||||
|
let ctor = if *primary {
|
||||||
|
quote! { miette::LabeledSpan::new_primary_with_span }
|
||||||
|
} else {
|
||||||
|
quote! { miette::LabeledSpan::new_with_span }
|
||||||
|
};
|
||||||
if let Some(display) = label {
|
if let Some(display) = label {
|
||||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||||
quote! {
|
quote! {
|
||||||
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
|
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
|
||||||
.map(|#var| miette::LabeledSpan::new_with_span(
|
.map(|#var| #ctor(
|
||||||
std::option::Option::Some(format!(#fmt #args)),
|
std::option::Option::Some(format!(#fmt #args)),
|
||||||
#var.clone(),
|
#var.clone(),
|
||||||
))
|
))
|
||||||
|
|
@ -134,7 +155,7 @@ impl Labels {
|
||||||
} else {
|
} else {
|
||||||
quote! {
|
quote! {
|
||||||
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
|
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
|
||||||
.map(|#var| miette::LabeledSpan::new_with_span(
|
.map(|#var| #ctor(
|
||||||
std::option::Option::None,
|
std::option::Option::None,
|
||||||
#var.clone(),
|
#var.clone(),
|
||||||
))
|
))
|
||||||
|
|
@ -161,7 +182,7 @@ impl Labels {
|
||||||
let (display_pat, display_members) = display_pat_members(fields);
|
let (display_pat, display_members) = display_pat_members(fields);
|
||||||
labels.as_ref().and_then(|labels| {
|
labels.as_ref().and_then(|labels| {
|
||||||
let variant_labels = labels.0.iter().map(|label| {
|
let variant_labels = labels.0.iter().map(|label| {
|
||||||
let Label { span, label, ty } = label;
|
let Label { span, label, ty, primary } = label;
|
||||||
let field = match &span {
|
let field = match &span {
|
||||||
syn::Member::Named(ident) => ident.clone(),
|
syn::Member::Named(ident) => ident.clone(),
|
||||||
syn::Member::Unnamed(syn::Index { index, .. }) => {
|
syn::Member::Unnamed(syn::Index { index, .. }) => {
|
||||||
|
|
@ -169,11 +190,16 @@ impl Labels {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let var = quote! { __miette_internal_var };
|
let var = quote! { __miette_internal_var };
|
||||||
|
let ctor = if *primary {
|
||||||
|
quote! { miette::LabeledSpan::new_primary_with_span }
|
||||||
|
} else {
|
||||||
|
quote! { miette::LabeledSpan::new_with_span }
|
||||||
|
};
|
||||||
if let Some(display) = label {
|
if let Some(display) = label {
|
||||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||||
quote! {
|
quote! {
|
||||||
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(#field)
|
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(#field)
|
||||||
.map(|#var| miette::LabeledSpan::new_with_span(
|
.map(|#var| #ctor(
|
||||||
std::option::Option::Some(format!(#fmt #args)),
|
std::option::Option::Some(format!(#fmt #args)),
|
||||||
#var.clone(),
|
#var.clone(),
|
||||||
))
|
))
|
||||||
|
|
@ -181,7 +207,7 @@ impl Labels {
|
||||||
} else {
|
} else {
|
||||||
quote! {
|
quote! {
|
||||||
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(#field)
|
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(#field)
|
||||||
.map(|#var| miette::LabeledSpan::new_with_span(
|
.map(|#var| #ctor(
|
||||||
std::option::Option::None,
|
std::option::Option::None,
|
||||||
#var.clone(),
|
#var.clone(),
|
||||||
))
|
))
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,15 @@ mod utils;
|
||||||
|
|
||||||
#[proc_macro_derive(
|
#[proc_macro_derive(
|
||||||
Diagnostic,
|
Diagnostic,
|
||||||
attributes(diagnostic, source_code, label, related, help, diagnostic_source)
|
attributes(
|
||||||
|
diagnostic,
|
||||||
|
source_code,
|
||||||
|
label,
|
||||||
|
primary_label,
|
||||||
|
related,
|
||||||
|
help,
|
||||||
|
diagnostic_source
|
||||||
|
)
|
||||||
)]
|
)]
|
||||||
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);
|
||||||
|
|
|
||||||
|
|
@ -391,6 +391,8 @@ impl GraphicalReportHandler {
|
||||||
) -> fmt::Result {
|
) -> fmt::Result {
|
||||||
let (contents, lines) = self.get_lines(source, context.inner())?;
|
let (contents, lines) = self.get_lines(source, context.inner())?;
|
||||||
|
|
||||||
|
let primary_label = labels.iter().find(|label| label.primary());
|
||||||
|
|
||||||
// sorting is your friend
|
// sorting is your friend
|
||||||
let labels = labels
|
let labels = labels
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -431,19 +433,33 @@ impl GraphicalReportHandler {
|
||||||
self.theme.characters.hbar,
|
self.theme.characters.hbar,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
if let Some(source_name) = contents.name() {
|
// If there is a primary label, then use its span
|
||||||
|
// as the reference point for line/column information.
|
||||||
|
let primary_contents = match primary_label {
|
||||||
|
Some(label) => source
|
||||||
|
.read_span(label.inner(), 0, 0)
|
||||||
|
.map_err(|_| fmt::Error)?,
|
||||||
|
None => contents,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(source_name) = primary_contents.name() {
|
||||||
let source_name = source_name.style(self.theme.styles.link);
|
let source_name = source_name.style(self.theme.styles.link);
|
||||||
writeln!(
|
writeln!(
|
||||||
f,
|
f,
|
||||||
"[{}:{}:{}]",
|
"[{}:{}:{}]",
|
||||||
source_name,
|
source_name,
|
||||||
contents.line() + 1,
|
primary_contents.line() + 1,
|
||||||
contents.column() + 1
|
primary_contents.column() + 1
|
||||||
)?;
|
)?;
|
||||||
} else if lines.len() <= 1 {
|
} else if lines.len() <= 1 {
|
||||||
writeln!(f, "{}", self.theme.characters.hbar.to_string().repeat(3))?;
|
writeln!(f, "{}", self.theme.characters.hbar.to_string().repeat(3))?;
|
||||||
} else {
|
} else {
|
||||||
writeln!(f, "[{}:{}]", contents.line() + 1, contents.column() + 1)?;
|
writeln!(
|
||||||
|
f,
|
||||||
|
"[{}:{}]",
|
||||||
|
primary_contents.line() + 1,
|
||||||
|
primary_contents.column() + 1
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now it's time for the fun part--actually rendering everything!
|
// Now it's time for the fun part--actually rendering everything!
|
||||||
|
|
|
||||||
|
|
@ -292,14 +292,16 @@ fn test_serialize_miette_diagnostic() {
|
||||||
"offset": 0,
|
"offset": 0,
|
||||||
"length": 0
|
"length": 0
|
||||||
},
|
},
|
||||||
"label": "label1"
|
"label": "label1",
|
||||||
|
"primary": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"span": {
|
"span": {
|
||||||
"offset": 1,
|
"offset": 1,
|
||||||
"length": 2
|
"length": 2
|
||||||
},
|
},
|
||||||
"label": "label2"
|
"label": "label2",
|
||||||
|
"primary": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
@ -350,14 +352,16 @@ fn test_deserialize_miette_diagnostic() {
|
||||||
"offset": 0,
|
"offset": 0,
|
||||||
"length": 0
|
"length": 0
|
||||||
},
|
},
|
||||||
"label": "label1"
|
"label": "label1",
|
||||||
|
"primary": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"span": {
|
"span": {
|
||||||
"offset": 1,
|
"offset": 1,
|
||||||
"length": 2
|
"length": 2
|
||||||
},
|
},
|
||||||
"label": "label2"
|
"label": "label2",
|
||||||
|
"primary": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -249,6 +249,7 @@ pub struct LabeledSpan {
|
||||||
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
|
||||||
label: Option<String>,
|
label: Option<String>,
|
||||||
span: SourceSpan,
|
span: SourceSpan,
|
||||||
|
primary: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LabeledSpan {
|
impl LabeledSpan {
|
||||||
|
|
@ -257,6 +258,7 @@ impl LabeledSpan {
|
||||||
Self {
|
Self {
|
||||||
label,
|
label,
|
||||||
span: SourceSpan::new(SourceOffset(offset), SourceOffset(len)),
|
span: SourceSpan::new(SourceOffset(offset), SourceOffset(len)),
|
||||||
|
primary: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -265,6 +267,16 @@ impl LabeledSpan {
|
||||||
Self {
|
Self {
|
||||||
label,
|
label,
|
||||||
span: span.into(),
|
span: span.into(),
|
||||||
|
primary: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes a new labeled primary span using an existing span.
|
||||||
|
pub fn new_primary_with_span(label: Option<String>, span: impl Into<SourceSpan>) -> Self {
|
||||||
|
Self {
|
||||||
|
label,
|
||||||
|
span: span.into(),
|
||||||
|
primary: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -340,6 +352,11 @@ impl LabeledSpan {
|
||||||
pub const fn is_empty(&self) -> bool {
|
pub const fn is_empty(&self) -> bool {
|
||||||
self.span.is_empty()
|
self.span.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// True if this `LabeledSpan` is a primary span.
|
||||||
|
pub const fn primary(&self) -> bool {
|
||||||
|
self.primary
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
|
|
@ -350,7 +367,8 @@ fn test_serialize_labeled_span() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
json!(LabeledSpan::new(None, 0, 0)),
|
json!(LabeledSpan::new(None, 0, 0)),
|
||||||
json!({
|
json!({
|
||||||
"span": { "offset": 0, "length": 0 }
|
"span": { "offset": 0, "length": 0, },
|
||||||
|
"primary": false,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -358,7 +376,8 @@ fn test_serialize_labeled_span() {
|
||||||
json!(LabeledSpan::new(Some("label".to_string()), 0, 0)),
|
json!(LabeledSpan::new(Some("label".to_string()), 0, 0)),
|
||||||
json!({
|
json!({
|
||||||
"label": "label",
|
"label": "label",
|
||||||
"span": { "offset": 0, "length": 0 }
|
"span": { "offset": 0, "length": 0, },
|
||||||
|
"primary": false,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -370,20 +389,23 @@ fn test_deserialize_labeled_span() {
|
||||||
|
|
||||||
let span: LabeledSpan = serde_json::from_value(json!({
|
let span: LabeledSpan = serde_json::from_value(json!({
|
||||||
"label": null,
|
"label": null,
|
||||||
"span": { "offset": 0, "length": 0 }
|
"span": { "offset": 0, "length": 0, },
|
||||||
|
"primary": false,
|
||||||
}))
|
}))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(span, LabeledSpan::new(None, 0, 0));
|
assert_eq!(span, LabeledSpan::new(None, 0, 0));
|
||||||
|
|
||||||
let span: LabeledSpan = serde_json::from_value(json!({
|
let span: LabeledSpan = serde_json::from_value(json!({
|
||||||
"span": { "offset": 0, "length": 0 }
|
"span": { "offset": 0, "length": 0, },
|
||||||
|
"primary": false
|
||||||
}))
|
}))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(span, LabeledSpan::new(None, 0, 0));
|
assert_eq!(span, LabeledSpan::new(None, 0, 0));
|
||||||
|
|
||||||
let span: LabeledSpan = serde_json::from_value(json!({
|
let span: LabeledSpan = serde_json::from_value(json!({
|
||||||
"label": "label",
|
"label": "label",
|
||||||
"span": { "offset": 0, "length": 0 }
|
"span": { "offset": 0, "length": 0, },
|
||||||
|
"primary": false
|
||||||
}))
|
}))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(span, LabeledSpan::new(Some("label".to_string()), 0, 0))
|
assert_eq!(span, LabeledSpan::new(Some("label".to_string()), 0, 0))
|
||||||
|
|
|
||||||
|
|
@ -1212,3 +1212,33 @@ fn zero_length_eol_span() {
|
||||||
|
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn primary_label() {
|
||||||
|
#[derive(Error, Debug, Diagnostic)]
|
||||||
|
#[error("oops!")]
|
||||||
|
struct MyBad {
|
||||||
|
#[source_code]
|
||||||
|
src: NamedSource,
|
||||||
|
#[primary_label("The root cause")]
|
||||||
|
bad_bit: SourceSpan,
|
||||||
|
}
|
||||||
|
let err = MyBad {
|
||||||
|
src: NamedSource::new("issue", "this is the first line\nthis is the second line"),
|
||||||
|
bad_bit: (24, 4).into(),
|
||||||
|
};
|
||||||
|
let out = fmt_report(err.into());
|
||||||
|
println!("Error: {}", out);
|
||||||
|
|
||||||
|
let expected = r#" × oops!
|
||||||
|
╭─[issue:2:2]
|
||||||
|
1 │ this is the first line
|
||||||
|
2 │ this is the second line
|
||||||
|
· ──┬─
|
||||||
|
· ╰── The root cause
|
||||||
|
╰────
|
||||||
|
"#
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue