fix(invalid span): skip the snippet when read_span fails (#347)

Fixes: https://github.com/zkat/miette/issues/219

When a snippet couldn't be read (typically because the span didn't fit
within the source code), it and the rest of the diagnostic were silently
dropped, which was confusing to the developer.
Now, in place of the snippet, print an error message with the name of
the failed label and the error it triggered, then proceed with the rest
of the diagnostic (footer, related, ...)
This commit is contained in:
Nahor 2024-02-21 13:46:58 -08:00 committed by GitHub
parent 75fea0935e
commit 7d9dfc6e8e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 245 additions and 3 deletions

View File

@ -436,9 +436,25 @@ impl GraphicalReportHandler {
let mut contexts = Vec::with_capacity(labels.len());
for right in labels.iter().cloned() {
let right_conts = source
.read_span(right.inner(), self.context_lines, self.context_lines)
.map_err(|_| fmt::Error)?;
let right_conts =
match source.read_span(right.inner(), self.context_lines, self.context_lines) {
Ok(cont) => cont,
Err(err) => {
writeln!(
f,
" [{} `{}` (offset: {}, length: {}): {:?}]",
"Failed to read contents for label".style(self.theme.styles.error),
right
.label()
.unwrap_or("<none>")
.style(self.theme.styles.link),
right.offset().style(self.theme.styles.link),
right.len().style(self.theme.styles.link),
err.style(self.theme.styles.warning)
)?;
return Ok(());
}
};
if contexts.is_empty() {
contexts.push((right, right_conts));

View File

@ -1970,3 +1970,229 @@ fn non_adjacent_highlight() -> Result<(), MietteError> {
assert_eq!(expected, &out);
Ok(())
}
#[test]
fn invalid_span_bad_offset() -> Result<(), MietteError> {
#[derive(Debug, Diagnostic, Error)]
#[error("oops!")]
#[diagnostic(code(oops::my::bad), help("help info"))]
struct MyBad {
#[source_code]
src: NamedSource<String>,
#[label = "1st"]
highlight1: SourceSpan,
}
let src = "blabla blibli".to_string();
let err = MyBad {
src: NamedSource::new("bad_file.rs", src),
highlight1: (50, 6).into(),
};
let out = fmt_report(err.into());
println!("Error: {}", out);
let expected = "oops::my::bad
× oops!
[Failed to read contents for label `1st` (offset: 50, length: 6): OutOfBounds]
help: help info
";
assert_eq!(expected, &out);
Ok(())
}
#[test]
fn invalid_span_bad_length() -> Result<(), MietteError> {
#[derive(Debug, Diagnostic, Error)]
#[error("oops!")]
#[diagnostic(code(oops::my::bad), help("help info"))]
struct MyBad {
#[source_code]
src: NamedSource<String>,
#[label = "1st"]
highlight1: SourceSpan,
}
let src = "blabla blibli".to_string();
let err = MyBad {
src: NamedSource::new("bad_file.rs", src),
highlight1: (0, 50).into(),
};
let out = fmt_report(err.into());
println!("Error: {}", out);
let expected = "oops::my::bad
× oops!
[Failed to read contents for label `1st` (offset: 0, length: 50): OutOfBounds]
help: help info
";
assert_eq!(expected, &out);
Ok(())
}
#[test]
fn invalid_span_no_label() -> Result<(), MietteError> {
#[derive(Debug, Diagnostic, Error)]
#[error("oops!")]
#[diagnostic(code(oops::my::bad), help("help info"))]
struct MyBad {
#[source_code]
src: NamedSource<String>,
#[label]
highlight1: SourceSpan,
}
let src = "blabla blibli".to_string();
let err = MyBad {
src: NamedSource::new("bad_file.rs", src),
highlight1: (50, 6).into(),
};
let out = fmt_report(err.into());
println!("Error: {}", out);
let expected = "oops::my::bad
× oops!
[Failed to read contents for label `<none>` (offset: 50, length: 6): OutOfBounds]
help: help info
";
assert_eq!(expected, &out);
Ok(())
}
#[test]
fn invalid_span_2nd_label() -> Result<(), MietteError> {
#[derive(Debug, Diagnostic, Error)]
#[error("oops!")]
#[diagnostic(code(oops::my::bad), help("help info"))]
struct MyBad {
#[source_code]
src: NamedSource<String>,
#[label("1st")]
highlight1: SourceSpan,
#[label("2nd")]
highlight2: SourceSpan,
}
let src = "blabla blibli".to_string();
let err = MyBad {
src: NamedSource::new("bad_file.rs", src),
highlight1: (0, 6).into(),
highlight2: (50, 6).into(),
};
let out = fmt_report(err.into());
println!("Error: {}", out);
let expected = "oops::my::bad
× oops!
[Failed to read contents for label `2nd` (offset: 50, length: 6): OutOfBounds]
help: help info
";
assert_eq!(expected, &out);
Ok(())
}
#[test]
fn invalid_span_inner() -> Result<(), MietteError> {
#[derive(Debug, Diagnostic, Error)]
#[error("oops inside!")]
#[diagnostic(code(oops::my::inner), help("help info"))]
struct MyInner {
#[source_code]
src: NamedSource<String>,
#[label("inner label")]
inner_label: SourceSpan,
}
#[derive(Debug, Diagnostic, Error)]
#[error("oops outside!")]
#[diagnostic(code(oops::my::outer), help("help info"))]
struct MyBad {
#[source_code]
src: NamedSource<String>,
#[label("outer label")]
outer_label: SourceSpan,
#[source]
inner: MyInner,
}
let src_outer = "outer source".to_string();
let src_inner = "inner source".to_string();
let err = MyBad {
src: NamedSource::new("bad_file.rs", src_outer),
outer_label: (0, 6).into(),
inner: MyInner {
src: NamedSource::new("bad_file2.rs", src_inner),
inner_label: (60, 6).into(),
},
};
let out = fmt_report(err.into());
println!("Error: {}", out);
let expected = "oops::my::outer
× oops outside!
oops inside!
[bad_file.rs:1:1]
1 outer source
·
· outer label
help: help info
";
assert_eq!(expected, &out);
Ok(())
}
#[test]
fn invalid_span_related() -> Result<(), MietteError> {
#[derive(Debug, Diagnostic, Error)]
#[error("oops inside!")]
#[diagnostic(code(oops::my::inner), help("help info"))]
struct MyRelated {
#[source_code]
src: NamedSource<String>,
#[label("inner label")]
inner_label: SourceSpan,
}
#[derive(Debug, Diagnostic, Error)]
#[error("oops outside!")]
#[diagnostic(code(oops::my::outer), help("help info"))]
struct MyBad {
#[source_code]
src: NamedSource<String>,
#[label("outer label")]
outer_label: SourceSpan,
#[related]
inner: Vec<MyRelated>,
}
let src_outer = "outer source".to_string();
let src_inner = "related source".to_string();
let err = MyBad {
src: NamedSource::new("bad_file.rs", src_outer),
outer_label: (0, 6).into(),
inner: vec![MyRelated {
src: NamedSource::new("bad_file2.rs", src_inner),
inner_label: (60, 6).into(),
}],
};
let out = fmt_report(err.into());
println!("Error: {}", out);
let expected = "oops::my::outer
× oops outside!
[bad_file.rs:1:1]
1 outer source
·
· outer label
help: help info
Error: oops::my::inner
× oops inside!
[Failed to read contents for label `inner label` (offset: 60, length: 6): OutOfBounds]
help: help info
";
assert_eq!(expected, &out);
Ok(())
}