fix(zero length, no context): Add Option to distinguish between "current line" and "just the span"

Fixes #355

Change the number of context lines from usize to Option<usize> to allow
choosing between "just the span" (as implemented previously when using
0 context line) using `None`, and displaying the error line without
extra context (not possible before) using `Some(0)`.
This commit is contained in:
Nahor 2024-03-11 12:15:31 -07:00
parent f7fa50d8de
commit d63b2bb638
9 changed files with 343 additions and 57 deletions

View File

@ -283,7 +283,7 @@ mod tests {
report
.source_code()
.unwrap()
.read_span(&(0..5).into(), 0, 0)
.read_span(&(0..5).into(), None, None)
.unwrap()
.data()
.to_vec(),

View File

@ -28,7 +28,7 @@ pub struct GraphicalReportHandler {
pub(crate) termwidth: usize,
pub(crate) theme: GraphicalTheme,
pub(crate) footer: Option<String>,
pub(crate) context_lines: usize,
pub(crate) context_lines: Option<usize>,
pub(crate) tab_width: usize,
pub(crate) with_cause_chain: bool,
pub(crate) wrap_lines: bool,
@ -55,7 +55,7 @@ impl GraphicalReportHandler {
termwidth: 200,
theme: GraphicalTheme::default(),
footer: None,
context_lines: 1,
context_lines: Some(1),
tab_width: 4,
with_cause_chain: true,
wrap_lines: true,
@ -74,7 +74,7 @@ impl GraphicalReportHandler {
termwidth: 200,
theme,
footer: None,
context_lines: 1,
context_lines: Some(1),
tab_width: 4,
wrap_lines: true,
with_cause_chain: true,
@ -159,7 +159,7 @@ impl GraphicalReportHandler {
self
}
/// Sets the word splitter to usewhen wrapping.
/// Sets the word splitter to use when wrapping.
pub fn with_word_splitter(mut self, word_splitter: textwrap::WordSplitter) -> Self {
self.word_splitter = Some(word_splitter);
self
@ -172,7 +172,22 @@ impl GraphicalReportHandler {
}
/// Sets the number of lines of context to show around each error.
pub fn with_context_lines(mut self, lines: usize) -> Self {
///
/// If `0`, then only the span content will be shown (equivalent to
/// `with_opt_context_lines(None)`).\
/// Use `with_opt_context_lines(Some(0))` if you want the whole line
/// containing the error without extra context.
pub fn with_context_lines(self, lines: usize) -> Self {
self.with_opt_context_lines((lines != 0).then_some(lines))
}
/// Sets the number of lines of context to show around each error.
///
/// `None` means only the span content (and possibly the content in between
/// multiple adjacent labels) will be shown.\
/// `Some(0)` will show the whole line containing the label.\
/// `Some(n)` will show the whole line plus n line before and after the label.
pub fn with_opt_context_lines(mut self, lines: Option<usize>) -> Self {
self.context_lines = lines;
self
}
@ -559,7 +574,7 @@ impl GraphicalReportHandler {
// as the reference point for line/column information.
let primary_contents = match primary_label {
Some(label) => source
.read_span(label.inner(), 0, 0)
.read_span(label.inner(), None, None)
.map_err(|_| fmt::Error)?,
None => contents,
};

View File

@ -159,7 +159,7 @@ impl JSONReportHandler {
) -> fmt::Result {
if let Some(mut labels) = diagnostic.labels() {
if let Some(label) = labels.next() {
if let Ok(span_content) = source.read_span(label.inner(), 0, 0) {
if let Ok(span_content) = source.read_span(label.inner(), None, None) {
let filename = span_content.name().unwrap_or_default();
return write!(f, r#""filename": "{}","#, escape(filename));
}

View File

@ -13,7 +13,7 @@ non-graphical environments, such as non-TTY output.
*/
#[derive(Debug, Clone)]
pub struct NarratableReportHandler {
context_lines: usize,
context_lines: Option<usize>,
with_cause_chain: bool,
footer: Option<String>,
}
@ -24,7 +24,7 @@ impl NarratableReportHandler {
pub const fn new() -> Self {
Self {
footer: None,
context_lines: 1,
context_lines: Some(1),
with_cause_chain: true,
}
}
@ -49,7 +49,22 @@ impl NarratableReportHandler {
}
/// Sets the number of lines of context to show around each error.
pub const fn with_context_lines(mut self, lines: usize) -> Self {
///
/// If `0`, then only the span content will be shown (equivalent to
/// `with_opt_context_lines(None)`).\
/// Use `with_opt_context_lines(Some(0))` if you want the whole line
/// containing the error without extra context.
pub fn with_context_lines(self, lines: usize) -> Self {
self.with_opt_context_lines((lines != 0).then_some(lines))
}
/// Sets the number of lines of context to show around each error.
///
/// `None` means only the span content (and possibly the content in between
/// multiple adjacent labels) will be shown.\
/// `Some(0)` will show the whole line containing the label.\
/// `Some(n)` will show the whole line plus n line before and after the label.
pub fn with_opt_context_lines(mut self, lines: Option<usize>) -> Self {
self.context_lines = lines;
self
}

View File

@ -56,8 +56,8 @@ impl<S: SourceCode + 'static> SourceCode for NamedSource<S> {
fn read_span<'a>(
&'a self,
span: &crate::SourceSpan,
context_lines_before: usize,
context_lines_after: usize,
context_lines_before: Option<usize>,
context_lines_after: Option<usize>,
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
let inner_contents =
self.inner()

View File

@ -247,11 +247,16 @@ gigabytes or larger in size.
pub trait SourceCode: Send + Sync {
/// Read the bytes for a specific span from this `SourceCode`, keeping a
/// certain number of lines before and after the span as context.
/// When the `context_lines_before` (resp. `context_lines_after`) is set to
/// `None`, the span content must start (resp. stop) at the **span** boundary.
/// When set to `Some(0)`, the content should start (resp. stop) at the
/// **line** boundary. When set to `Some(n)`, the content should include the
/// _n_ lines before (resp. after) the span.
fn read_span<'a>(
&'a self,
span: &SourceSpan,
context_lines_before: usize,
context_lines_after: usize,
context_lines_before: Option<usize>,
context_lines_after: Option<usize>,
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError>;
}

View File

@ -15,8 +15,8 @@ struct LineInfo {
fn context_info<'a>(
input: &'a [u8],
span: &SourceSpan,
context_lines_before: usize,
context_lines_after: usize,
context_lines_before: Option<usize>,
context_lines_after: Option<usize>,
) -> Result<MietteSpanContents<'a>, MietteError> {
let mut iter = input
.split_inclusive(|b| *b == b'\n')
@ -56,14 +56,14 @@ fn context_info<'a>(
Some(info) => info,
};
if line_starts.len() > context_lines_before {
if line_starts.len() > context_lines_before.unwrap_or(0) {
line_starts.pop_front();
}
line_starts.push_back(line_info);
}
let (start_lineno, start_offset, start_column) = {
let start_info = line_starts.pop_front().unwrap();
if context_lines_before > 0 {
if context_lines_before.is_some() {
(start_info.line_no, start_info.start, 0)
} else {
(
@ -83,13 +83,13 @@ fn context_info<'a>(
}
// Get the "after" lines
if let Some(last) = iter.take(context_lines_after).last() {
if let Some(last) = iter.take(context_lines_after.unwrap_or(0)).last() {
line_info = last;
}
if span.offset() + span.len() > line_info.end {
return Err(MietteError::OutOfBounds);
}
let (end_lineno, end_offset) = if context_lines_after > 0 {
let (end_lineno, end_offset) = if context_lines_after.is_some() {
(line_info.line_no, line_info.end)
} else {
(line_info.line_no, span.offset() + span.len())
@ -108,8 +108,8 @@ impl SourceCode for [u8] {
fn read_span<'a>(
&'a self,
span: &SourceSpan,
context_lines_before: usize,
context_lines_after: usize,
context_lines_before: Option<usize>,
context_lines_after: Option<usize>,
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
let contents = context_info(self, span, context_lines_before, context_lines_after)?;
Ok(Box::new(contents))
@ -120,8 +120,8 @@ impl<'src> SourceCode for &'src [u8] {
fn read_span<'a>(
&'a self,
span: &SourceSpan,
context_lines_before: usize,
context_lines_after: usize,
context_lines_before: Option<usize>,
context_lines_after: Option<usize>,
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
<[u8] as SourceCode>::read_span(self, span, context_lines_before, context_lines_after)
}
@ -131,8 +131,8 @@ impl SourceCode for Vec<u8> {
fn read_span<'a>(
&'a self,
span: &SourceSpan,
context_lines_before: usize,
context_lines_after: usize,
context_lines_before: Option<usize>,
context_lines_after: Option<usize>,
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
<[u8] as SourceCode>::read_span(self, span, context_lines_before, context_lines_after)
}
@ -142,8 +142,8 @@ impl SourceCode for str {
fn read_span<'a>(
&'a self,
span: &SourceSpan,
context_lines_before: usize,
context_lines_after: usize,
context_lines_before: Option<usize>,
context_lines_after: Option<usize>,
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
<[u8] as SourceCode>::read_span(
self.as_bytes(),
@ -159,8 +159,8 @@ impl<'s> SourceCode for &'s str {
fn read_span<'a>(
&'a self,
span: &SourceSpan,
context_lines_before: usize,
context_lines_after: usize,
context_lines_before: Option<usize>,
context_lines_after: Option<usize>,
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
<str as SourceCode>::read_span(self, span, context_lines_before, context_lines_after)
}
@ -170,8 +170,8 @@ impl SourceCode for String {
fn read_span<'a>(
&'a self,
span: &SourceSpan,
context_lines_before: usize,
context_lines_after: usize,
context_lines_before: Option<usize>,
context_lines_after: Option<usize>,
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
<str as SourceCode>::read_span(self, span, context_lines_before, context_lines_after)
}
@ -181,8 +181,8 @@ impl<T: ?Sized + SourceCode> SourceCode for Arc<T> {
fn read_span<'a>(
&'a self,
span: &SourceSpan,
context_lines_before: usize,
context_lines_after: usize,
context_lines_before: Option<usize>,
context_lines_after: Option<usize>,
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
self.as_ref()
.read_span(span, context_lines_before, context_lines_after)
@ -201,8 +201,8 @@ where
fn read_span<'a>(
&'a self,
span: &SourceSpan,
context_lines_before: usize,
context_lines_after: usize,
context_lines_before: Option<usize>,
context_lines_after: Option<usize>,
) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
self.as_ref()
.read_span(span, context_lines_before, context_lines_after)
@ -216,7 +216,7 @@ mod tests {
#[test]
fn basic() -> Result<(), MietteError> {
let src = String::from("foo\n");
let contents = src.read_span(&(0, 4).into(), 0, 0)?;
let contents = src.read_span(&(0, 4).into(), None, None)?;
assert_eq!("foo\n", std::str::from_utf8(contents.data()).unwrap());
assert_eq!(SourceSpan::from((0, 4)), *contents.span());
assert_eq!(0, contents.line());
@ -228,7 +228,7 @@ mod tests {
#[test]
fn shifted() -> Result<(), MietteError> {
let src = String::from("foobar");
let contents = src.read_span(&(3, 3).into(), 1, 1)?;
let contents = src.read_span(&(3, 3).into(), Some(1), Some(1))?;
assert_eq!("foobar", std::str::from_utf8(contents.data()).unwrap());
assert_eq!(SourceSpan::from((0, 6)), *contents.span());
assert_eq!(0, contents.line());
@ -240,7 +240,7 @@ mod tests {
#[test]
fn middle() -> Result<(), MietteError> {
let src = String::from("foo\nbar\nbaz\n");
let contents = src.read_span(&(4, 4).into(), 0, 0)?;
let contents = src.read_span(&(4, 4).into(), None, None)?;
assert_eq!("bar\n", std::str::from_utf8(contents.data()).unwrap());
assert_eq!(SourceSpan::from((4, 4)), *contents.span());
assert_eq!(1, contents.line());
@ -252,7 +252,7 @@ mod tests {
#[test]
fn middle_of_line() -> Result<(), MietteError> {
let src = String::from("foo\nbarbar\nbaz\n");
let contents = src.read_span(&(7, 4).into(), 0, 0)?;
let contents = src.read_span(&(7, 4).into(), None, None)?;
assert_eq!("bar\n", std::str::from_utf8(contents.data()).unwrap());
assert_eq!(SourceSpan::from((7, 4)), *contents.span());
assert_eq!(1, contents.line());
@ -264,7 +264,7 @@ mod tests {
#[test]
fn end_of_line_before_newline() -> Result<(), MietteError> {
let src = String::from("foo\nbar\nbaz\n");
let contents = src.read_span(&(7, 0).into(), 0, 0)?;
let contents = src.read_span(&(7, 0).into(), None, None)?;
assert_eq!("", std::str::from_utf8(contents.data()).unwrap());
assert_eq!(SourceSpan::from((7, 0)), *contents.span());
assert_eq!(1, contents.line());
@ -276,7 +276,7 @@ mod tests {
#[test]
fn end_of_line_after_newline() -> Result<(), MietteError> {
let src = String::from("foo\nbar\nbaz\n");
let contents = src.read_span(&(8, 0).into(), 0, 0)?;
let contents = src.read_span(&(8, 0).into(), None, None)?;
assert_eq!("", std::str::from_utf8(contents.data()).unwrap());
assert_eq!(SourceSpan::from((8, 0)), *contents.span());
assert_eq!(2, contents.line());
@ -288,7 +288,7 @@ mod tests {
#[test]
fn end_of_file_with_newline() -> Result<(), MietteError> {
let src = String::from("foo\nbar\nbaz\n");
let contents = src.read_span(&(12, 0).into(), 0, 0)?;
let contents = src.read_span(&(12, 0).into(), None, None)?;
assert_eq!("", std::str::from_utf8(contents.data()).unwrap());
assert_eq!(SourceSpan::from((12, 0)), *contents.span());
assert_eq!(2, contents.line());
@ -300,7 +300,7 @@ mod tests {
#[test]
fn end_of_file_without_newline() -> Result<(), MietteError> {
let src = String::from("foo\nbar\nbaz");
let contents = src.read_span(&(11, 0).into(), 0, 0)?;
let contents = src.read_span(&(11, 0).into(), None, None)?;
assert_eq!("", std::str::from_utf8(contents.data()).unwrap());
assert_eq!(SourceSpan::from((11, 0)), *contents.span());
assert_eq!(2, contents.line());
@ -312,7 +312,7 @@ mod tests {
#[test]
fn with_crlf() -> Result<(), MietteError> {
let src = String::from("foo\r\nbar\r\nbaz\r\n");
let contents = src.read_span(&(5, 5).into(), 0, 0)?;
let contents = src.read_span(&(5, 5).into(), None, None)?;
assert_eq!("bar\r\n", std::str::from_utf8(contents.data()).unwrap());
assert_eq!(SourceSpan::from((5, 5)), *contents.span());
assert_eq!(1, contents.line());
@ -324,7 +324,7 @@ mod tests {
#[test]
fn with_context() -> Result<(), MietteError> {
let src = String::from("xxx\nfoo\nbar\nbaz\n\nyyy\n");
let contents = src.read_span(&(8, 3).into(), 1, 1)?;
let contents = src.read_span(&(8, 3).into(), Some(1), Some(1))?;
assert_eq!(
"foo\nbar\nbaz\n",
std::str::from_utf8(contents.data()).unwrap()
@ -339,7 +339,7 @@ mod tests {
#[test]
fn multiline_with_context() -> Result<(), MietteError> {
let src = String::from("aaa\nxxx\n\nfoo\nbar\nbaz\n\nyyy\nbbb\n");
let contents = src.read_span(&(9, 11).into(), 1, 1)?;
let contents = src.read_span(&(9, 11).into(), Some(1), Some(1))?;
assert_eq!(
"\nfoo\nbar\nbaz\n\n",
std::str::from_utf8(contents.data()).unwrap()
@ -356,7 +356,7 @@ mod tests {
#[test]
fn multiline_with_context_line_start() -> Result<(), MietteError> {
let src = String::from("one\ntwo\n\nthree\nfour\nfive\n\nsix\nseven\n");
let contents = src.read_span(&(2, 0).into(), 2, 2)?;
let contents = src.read_span(&(2, 0).into(), Some(2), Some(2))?;
assert_eq!(
"one\ntwo\n\n",
std::str::from_utf8(contents.data()).unwrap()
@ -374,7 +374,7 @@ mod tests {
fn empty_source() -> Result<(), MietteError> {
let src = String::from("");
let contents = src.read_span(&(0, 0).into(), 0, 0)?;
let contents = src.read_span(&(0, 0).into(), None, None)?;
assert_eq!("", std::str::from_utf8(contents.data()).unwrap());
assert_eq!(SourceSpan::from((0, 0)), *contents.span());
assert_eq!(0, contents.line());
@ -388,22 +388,22 @@ mod tests {
fn empty_source_out_of_bounds() {
let src = String::from("");
let contents = src.read_span(&(0, 1).into(), 0, 0);
let contents = src.read_span(&(0, 1).into(), None, None);
assert!(matches!(contents, Err(MietteError::OutOfBounds)));
let contents = src.read_span(&(0, 2).into(), 0, 0);
let contents = src.read_span(&(0, 2).into(), None, None);
assert!(matches!(contents, Err(MietteError::OutOfBounds)));
let contents = src.read_span(&(1, 0).into(), 0, 0);
let contents = src.read_span(&(1, 0).into(), None, None);
assert!(matches!(contents, Err(MietteError::OutOfBounds)));
let contents = src.read_span(&(1, 1).into(), 0, 0);
let contents = src.read_span(&(1, 1).into(), None, None);
assert!(matches!(contents, Err(MietteError::OutOfBounds)));
let contents = src.read_span(&(2, 0).into(), 0, 0);
let contents = src.read_span(&(2, 0).into(), None, None);
assert!(matches!(contents, Err(MietteError::OutOfBounds)));
let contents = src.read_span(&(2, 1).into(), 0, 0);
let contents = src.read_span(&(2, 1).into(), None, None);
assert!(matches!(contents, Err(MietteError::OutOfBounds)));
}
}

View File

@ -2196,3 +2196,254 @@ Error: oops::my::inner
assert_eq!(expected, &out);
Ok(())
}
#[test]
fn zero_length_no_context() -> Result<(), MietteError> {
#[derive(Debug, Diagnostic, Error)]
#[error("oops!")]
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
struct MyBad {
#[source_code]
src: NamedSource<String>,
#[label("this bit here")]
highlight: SourceSpan,
}
let src = "one\ntwoo\nthree".to_string();
let err = MyBad {
src: NamedSource::new("bad_file.rs", src),
highlight: (6, 0).into(),
};
let out = fmt_report_with_settings(err.into(), |handler| {
handler
.with_opt_context_lines(None)
.without_syntax_highlighting()
});
let expected = r#"oops::my::bad
× oops!
[bad_file.rs:2:3]
help: try doing it better next time?
"#
.trim_start()
.to_string();
assert_eq!(expected, out);
Ok(())
}
#[test]
fn multi_adjacent_zero_length_no_context() -> Result<(), MietteError> {
#[derive(Debug, Diagnostic, Error)]
#[error("oops!")]
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
struct MyBad {
#[source_code]
src: NamedSource<String>,
#[label("this bit here")]
highlight: SourceSpan,
#[label("and here")]
highlight2: SourceSpan,
}
let src = "one\ntwoo\nthree\nfour".to_string();
let err = MyBad {
src: NamedSource::new("bad_file.rs", src),
highlight: (6, 0).into(),
highlight2: (12, 0).into(),
};
let out = fmt_report_with_settings(err.into(), |handler| {
handler
.with_opt_context_lines(None)
.without_syntax_highlighting()
});
let expected = r#"oops::my::bad
× oops!
[bad_file.rs:2:3]
2 oo
·
· this bit here
3 thr
help: try doing it better next time?
"#
.trim_start()
.to_string();
assert_eq!(expected, out);
Ok(())
}
#[test]
fn multi_separated_zero_length_no_context() -> Result<(), MietteError> {
#[derive(Debug, Diagnostic, Error)]
#[error("oops!")]
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
struct MyBad {
#[source_code]
src: NamedSource<String>,
#[label("this bit here")]
highlight: SourceSpan,
#[label("and here")]
highlight2: SourceSpan,
}
let src = "one\ntwoo\nthree\nfour".to_string();
let err = MyBad {
src: NamedSource::new("bad_file.rs", src),
highlight: (6, 0).into(),
highlight2: (17, 0).into(),
};
let out = fmt_report_with_settings(err.into(), |handler| {
handler
.with_opt_context_lines(None)
.without_syntax_highlighting()
});
let expected = r#"oops::my::bad
× oops!
[bad_file.rs:2:3]
[bad_file.rs:4:3]
help: try doing it better next time?
"#
.trim_start()
.to_string();
assert_eq!(expected, out);
Ok(())
}
#[test]
fn zero_length_zero_context() -> Result<(), MietteError> {
#[derive(Debug, Diagnostic, Error)]
#[error("oops!")]
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
struct MyBad {
#[source_code]
src: NamedSource<String>,
#[label("this bit here")]
highlight: SourceSpan,
}
let src = "one\ntwoo\nthree".to_string();
let err = MyBad {
src: NamedSource::new("bad_file.rs", src),
highlight: (6, 0).into(),
};
let out = fmt_report_with_settings(err.into(), |handler| {
handler
.with_opt_context_lines(Some(0))
.without_syntax_highlighting()
});
let expected = r#"oops::my::bad
× oops!
[bad_file.rs:2:3]
2 twoo
·
· this bit here
help: try doing it better next time?
"#
.trim_start()
.to_string();
assert_eq!(expected, out);
Ok(())
}
#[test]
fn multi_adjacent_zero_length_zero_context() -> Result<(), MietteError> {
#[derive(Debug, Diagnostic, Error)]
#[error("oops!")]
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
struct MyBad {
#[source_code]
src: NamedSource<String>,
#[label("this bit here")]
highlight: SourceSpan,
#[label("and here")]
highlight2: SourceSpan,
}
let src = "one\ntwoo\nthree\nfour".to_string();
let err = MyBad {
src: NamedSource::new("bad_file.rs", src),
highlight: (6, 0).into(),
highlight2: (12, 0).into(),
};
let out = fmt_report_with_settings(err.into(), |handler| {
handler
.with_opt_context_lines(Some(0))
.without_syntax_highlighting()
});
let expected = r#"oops::my::bad
× oops!
[bad_file.rs:2:3]
2 twoo
·
· this bit here
3 three
·
· and here
help: try doing it better next time?
"#
.trim_start()
.to_string();
assert_eq!(expected, out);
Ok(())
}
#[test]
fn multi_separated_zero_length_zero_context() -> Result<(), MietteError> {
#[derive(Debug, Diagnostic, Error)]
#[error("oops!")]
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
struct MyBad {
#[source_code]
src: NamedSource<String>,
#[label("this bit here")]
highlight: SourceSpan,
#[label("and here")]
highlight2: SourceSpan,
}
let src = "one\ntwoo\nthree\nfour".to_string();
let err = MyBad {
src: NamedSource::new("bad_file.rs", src),
highlight: (6, 0).into(),
highlight2: (17, 0).into(),
};
let out = fmt_report_with_settings(err.into(), |handler| {
handler
.with_opt_context_lines(Some(0))
.without_syntax_highlighting()
});
let expected = r#"oops::my::bad
× oops!
[bad_file.rs:2:3]
2 twoo
·
· this bit here
[bad_file.rs:4:3]
4 four
·
· and here
help: try doing it better next time?
"#
.trim_start()
.to_string();
assert_eq!(expected, out);
Ok(())
}

View File

@ -188,7 +188,7 @@ fn test_boxed_custom_diagnostic() {
let span = SourceSpan::from(0..CustomDiagnostic::SOURCE_CODE.len());
assert_eq!(
report.source_code().map(|source_code| source_code
.read_span(&span, 0, 0)
.read_span(&span, None, None)
.expect("read data from source code successfully")
.data()
.to_owned()),