From 1142e2b8f6a2b2133a63339431c82c760b18bfca Mon Sep 17 00:00:00 2001 From: Nahor Date: Wed, 21 Feb 2024 12:45:10 -0800 Subject: [PATCH] fix(invalid span): skip the snippet when read_span fails 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, ...) --- src/handlers/graphical.rs | 22 +++- tests/graphical.rs | 226 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 245 insertions(+), 3 deletions(-) diff --git a/src/handlers/graphical.rs b/src/handlers/graphical.rs index 3f74227..a4474f9 100644 --- a/src/handlers/graphical.rs +++ b/src/handlers/graphical.rs @@ -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("") + .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)); diff --git a/tests/graphical.rs b/tests/graphical.rs index b5fa4d8..4763117 100644 --- a/tests/graphical.rs +++ b/tests/graphical.rs @@ -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, + #[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, + #[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, + #[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 `` (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, + #[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, + #[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, + #[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, + #[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, + #[label("outer label")] + outer_label: SourceSpan, + #[related] + inner: Vec, + } + + 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(()) +}