Skip to content

Commit 7d9dfc6

Browse files
authoredFeb 21, 2024··
fix(invalid span): skip the snippet when read_span fails (#347)
Fixes: #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, ...)
1 parent 75fea09 commit 7d9dfc6

File tree

2 files changed

+245
-3
lines changed

2 files changed

+245
-3
lines changed
 

‎src/handlers/graphical.rs

+19-3
Original file line numberDiff line numberDiff line change
@@ -436,9 +436,25 @@ impl GraphicalReportHandler {
436436

437437
let mut contexts = Vec::with_capacity(labels.len());
438438
for right in labels.iter().cloned() {
439-
let right_conts = source
440-
.read_span(right.inner(), self.context_lines, self.context_lines)
441-
.map_err(|_| fmt::Error)?;
439+
let right_conts =
440+
match source.read_span(right.inner(), self.context_lines, self.context_lines) {
441+
Ok(cont) => cont,
442+
Err(err) => {
443+
writeln!(
444+
f,
445+
" [{} `{}` (offset: {}, length: {}): {:?}]",
446+
"Failed to read contents for label".style(self.theme.styles.error),
447+
right
448+
.label()
449+
.unwrap_or("<none>")
450+
.style(self.theme.styles.link),
451+
right.offset().style(self.theme.styles.link),
452+
right.len().style(self.theme.styles.link),
453+
err.style(self.theme.styles.warning)
454+
)?;
455+
return Ok(());
456+
}
457+
};
442458

443459
if contexts.is_empty() {
444460
contexts.push((right, right_conts));

‎tests/graphical.rs

+226
Original file line numberDiff line numberDiff line change
@@ -1970,3 +1970,229 @@ fn non_adjacent_highlight() -> Result<(), MietteError> {
19701970
assert_eq!(expected, &out);
19711971
Ok(())
19721972
}
1973+
1974+
#[test]
1975+
fn invalid_span_bad_offset() -> Result<(), MietteError> {
1976+
#[derive(Debug, Diagnostic, Error)]
1977+
#[error("oops!")]
1978+
#[diagnostic(code(oops::my::bad), help("help info"))]
1979+
struct MyBad {
1980+
#[source_code]
1981+
src: NamedSource<String>,
1982+
#[label = "1st"]
1983+
highlight1: SourceSpan,
1984+
}
1985+
1986+
let src = "blabla blibli".to_string();
1987+
let err = MyBad {
1988+
src: NamedSource::new("bad_file.rs", src),
1989+
highlight1: (50, 6).into(),
1990+
};
1991+
let out = fmt_report(err.into());
1992+
println!("Error: {}", out);
1993+
let expected = "oops::my::bad
1994+
1995+
× oops!
1996+
[Failed to read contents for label `1st` (offset: 50, length: 6): OutOfBounds]
1997+
help: help info
1998+
";
1999+
assert_eq!(expected, &out);
2000+
Ok(())
2001+
}
2002+
2003+
#[test]
2004+
fn invalid_span_bad_length() -> Result<(), MietteError> {
2005+
#[derive(Debug, Diagnostic, Error)]
2006+
#[error("oops!")]
2007+
#[diagnostic(code(oops::my::bad), help("help info"))]
2008+
struct MyBad {
2009+
#[source_code]
2010+
src: NamedSource<String>,
2011+
#[label = "1st"]
2012+
highlight1: SourceSpan,
2013+
}
2014+
2015+
let src = "blabla blibli".to_string();
2016+
let err = MyBad {
2017+
src: NamedSource::new("bad_file.rs", src),
2018+
highlight1: (0, 50).into(),
2019+
};
2020+
let out = fmt_report(err.into());
2021+
println!("Error: {}", out);
2022+
let expected = "oops::my::bad
2023+
2024+
× oops!
2025+
[Failed to read contents for label `1st` (offset: 0, length: 50): OutOfBounds]
2026+
help: help info
2027+
";
2028+
assert_eq!(expected, &out);
2029+
Ok(())
2030+
}
2031+
2032+
#[test]
2033+
fn invalid_span_no_label() -> Result<(), MietteError> {
2034+
#[derive(Debug, Diagnostic, Error)]
2035+
#[error("oops!")]
2036+
#[diagnostic(code(oops::my::bad), help("help info"))]
2037+
struct MyBad {
2038+
#[source_code]
2039+
src: NamedSource<String>,
2040+
#[label]
2041+
highlight1: SourceSpan,
2042+
}
2043+
2044+
let src = "blabla blibli".to_string();
2045+
let err = MyBad {
2046+
src: NamedSource::new("bad_file.rs", src),
2047+
highlight1: (50, 6).into(),
2048+
};
2049+
let out = fmt_report(err.into());
2050+
println!("Error: {}", out);
2051+
let expected = "oops::my::bad
2052+
2053+
× oops!
2054+
[Failed to read contents for label `<none>` (offset: 50, length: 6): OutOfBounds]
2055+
help: help info
2056+
";
2057+
assert_eq!(expected, &out);
2058+
Ok(())
2059+
}
2060+
2061+
#[test]
2062+
fn invalid_span_2nd_label() -> Result<(), MietteError> {
2063+
#[derive(Debug, Diagnostic, Error)]
2064+
#[error("oops!")]
2065+
#[diagnostic(code(oops::my::bad), help("help info"))]
2066+
struct MyBad {
2067+
#[source_code]
2068+
src: NamedSource<String>,
2069+
#[label("1st")]
2070+
highlight1: SourceSpan,
2071+
#[label("2nd")]
2072+
highlight2: SourceSpan,
2073+
}
2074+
2075+
let src = "blabla blibli".to_string();
2076+
let err = MyBad {
2077+
src: NamedSource::new("bad_file.rs", src),
2078+
highlight1: (0, 6).into(),
2079+
highlight2: (50, 6).into(),
2080+
};
2081+
let out = fmt_report(err.into());
2082+
println!("Error: {}", out);
2083+
let expected = "oops::my::bad
2084+
2085+
× oops!
2086+
[Failed to read contents for label `2nd` (offset: 50, length: 6): OutOfBounds]
2087+
help: help info
2088+
";
2089+
assert_eq!(expected, &out);
2090+
Ok(())
2091+
}
2092+
2093+
#[test]
2094+
fn invalid_span_inner() -> Result<(), MietteError> {
2095+
#[derive(Debug, Diagnostic, Error)]
2096+
#[error("oops inside!")]
2097+
#[diagnostic(code(oops::my::inner), help("help info"))]
2098+
struct MyInner {
2099+
#[source_code]
2100+
src: NamedSource<String>,
2101+
#[label("inner label")]
2102+
inner_label: SourceSpan,
2103+
}
2104+
2105+
#[derive(Debug, Diagnostic, Error)]
2106+
#[error("oops outside!")]
2107+
#[diagnostic(code(oops::my::outer), help("help info"))]
2108+
struct MyBad {
2109+
#[source_code]
2110+
src: NamedSource<String>,
2111+
#[label("outer label")]
2112+
outer_label: SourceSpan,
2113+
#[source]
2114+
inner: MyInner,
2115+
}
2116+
2117+
let src_outer = "outer source".to_string();
2118+
let src_inner = "inner source".to_string();
2119+
let err = MyBad {
2120+
src: NamedSource::new("bad_file.rs", src_outer),
2121+
outer_label: (0, 6).into(),
2122+
inner: MyInner {
2123+
src: NamedSource::new("bad_file2.rs", src_inner),
2124+
inner_label: (60, 6).into(),
2125+
},
2126+
};
2127+
let out = fmt_report(err.into());
2128+
println!("Error: {}", out);
2129+
let expected = "oops::my::outer
2130+
2131+
× oops outside!
2132+
╰─▶ oops inside!
2133+
╭─[bad_file.rs:1:1]
2134+
1 │ outer source
2135+
· ───┬──
2136+
· ╰── outer label
2137+
╰────
2138+
help: help info
2139+
";
2140+
assert_eq!(expected, &out);
2141+
Ok(())
2142+
}
2143+
2144+
#[test]
2145+
fn invalid_span_related() -> Result<(), MietteError> {
2146+
#[derive(Debug, Diagnostic, Error)]
2147+
#[error("oops inside!")]
2148+
#[diagnostic(code(oops::my::inner), help("help info"))]
2149+
struct MyRelated {
2150+
#[source_code]
2151+
src: NamedSource<String>,
2152+
#[label("inner label")]
2153+
inner_label: SourceSpan,
2154+
}
2155+
2156+
#[derive(Debug, Diagnostic, Error)]
2157+
#[error("oops outside!")]
2158+
#[diagnostic(code(oops::my::outer), help("help info"))]
2159+
struct MyBad {
2160+
#[source_code]
2161+
src: NamedSource<String>,
2162+
#[label("outer label")]
2163+
outer_label: SourceSpan,
2164+
#[related]
2165+
inner: Vec<MyRelated>,
2166+
}
2167+
2168+
let src_outer = "outer source".to_string();
2169+
let src_inner = "related source".to_string();
2170+
let err = MyBad {
2171+
src: NamedSource::new("bad_file.rs", src_outer),
2172+
outer_label: (0, 6).into(),
2173+
inner: vec![MyRelated {
2174+
src: NamedSource::new("bad_file2.rs", src_inner),
2175+
inner_label: (60, 6).into(),
2176+
}],
2177+
};
2178+
let out = fmt_report(err.into());
2179+
println!("Error: {}", out);
2180+
let expected = "oops::my::outer
2181+
2182+
× oops outside!
2183+
╭─[bad_file.rs:1:1]
2184+
1 │ outer source
2185+
· ───┬──
2186+
· ╰── outer label
2187+
╰────
2188+
help: help info
2189+
2190+
Error: oops::my::inner
2191+
2192+
× oops inside!
2193+
[Failed to read contents for label `inner label` (offset: 60, length: 6): OutOfBounds]
2194+
help: help info
2195+
";
2196+
assert_eq!(expected, &out);
2197+
Ok(())
2198+
}

0 commit comments

Comments
 (0)
Please sign in to comment.