From 679fc128583e7831caa2e7826707f390628d6d94 Mon Sep 17 00:00:00 2001 From: Juan Aguilar Santillana <mhpoin@gmail.com> Date: Tue, 12 May 2020 21:26:38 +0200 Subject: [PATCH 001/455] Fix unnecessary mutable at convert snippet::Slice --- src/display_list/from_snippet.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/display_list/from_snippet.rs b/src/display_list/from_snippet.rs index b3ba5a3d..e75a8613 100644 --- a/src/display_list/from_snippet.rs +++ b/src/display_list/from_snippet.rs @@ -104,15 +104,15 @@ fn format_annotation(annotation: snippet::Annotation<'_>) -> Vec<DisplayLine<'_> } fn format_slice( - mut slice: snippet::Slice<'_>, + slice: snippet::Slice<'_>, is_first: bool, has_footer: bool, ) -> Vec<DisplayLine<'_>> { let main_range = slice.annotations.get(0).map(|x| x.range.0); - let row = slice.line_start; - let origin = slice.origin.take(); + let origin = slice.origin; + let line_start = slice.line_start; let mut body = format_body(slice, has_footer); - let header = format_header(origin, main_range, row, &body, is_first); + let header = format_header(origin, main_range, line_start, &body, is_first); let mut result = vec![]; if let Some(header) = header { From 147a823196f1c34114beae47a1c8be38402dc374 Mon Sep 17 00:00:00 2001 From: Juan Aguilar Santillana <mhpoin@gmail.com> Date: Tue, 12 May 2020 23:26:40 +0200 Subject: [PATCH 002/455] Removing duplicate Slice origins and surrounding empty lines --- src/display_list/from_snippet.rs | 51 ++++++++++++++++++---------- tests/fixtures/no-color/issue_9.toml | 28 +++++++++++++++ tests/fixtures/no-color/issue_9.txt | 12 +++++++ tests/snippet/mod.rs | 2 ++ 4 files changed, 75 insertions(+), 18 deletions(-) create mode 100644 tests/fixtures/no-color/issue_9.toml create mode 100644 tests/fixtures/no-color/issue_9.txt diff --git a/src/display_list/from_snippet.rs b/src/display_list/from_snippet.rs index e75a8613..da0deea2 100644 --- a/src/display_list/from_snippet.rs +++ b/src/display_list/from_snippet.rs @@ -111,7 +111,8 @@ fn format_slice( let main_range = slice.annotations.get(0).map(|x| x.range.0); let origin = slice.origin; let line_start = slice.line_start; - let mut body = format_body(slice, has_footer); + let need_empty_header = origin.is_some() || is_first; + let mut body = format_body(slice, need_empty_header, has_footer); let header = format_header(origin, main_range, line_start, &body, is_first); let mut result = vec![]; @@ -122,6 +123,12 @@ fn format_slice( result } +#[inline] +// TODO: option_zip +fn zip_opt<A, B>(a: Option<A>, b: Option<B>) -> Option<(A, B)> { + a.and_then(|a| b.map(|b| (a, b))) +} + fn format_header<'a>( origin: Option<&'a str>, main_range: Option<usize>, @@ -135,7 +142,7 @@ fn format_header<'a>( DisplayHeaderType::Continuation }; - if let Some(main_range) = main_range { + if let Some((main_range, path)) = zip_opt(main_range, origin) { let mut col = 1; for item in body { @@ -151,14 +158,14 @@ fn format_header<'a>( row += 1; } } - if let Some(path) = origin { - return Some(DisplayLine::Raw(DisplayRawLine::Origin { - path, - pos: Some((row, col)), - header_type: display_header, - })); - } + + return Some(DisplayLine::Raw(DisplayRawLine::Origin { + path, + pos: Some((row, col)), + header_type: display_header, + })); } + if let Some(path) = origin { return Some(DisplayLine::Raw(DisplayRawLine::Origin { path, @@ -166,6 +173,7 @@ fn format_header<'a>( header_type: display_header, })); } + None } @@ -261,7 +269,11 @@ fn fold_body(mut body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> { new_body } -fn format_body(slice: snippet::Slice<'_>, has_footer: bool) -> Vec<DisplayLine<'_>> { +fn format_body( + slice: snippet::Slice<'_>, + need_empty_header: bool, + has_footer: bool, +) -> Vec<DisplayLine<'_>> { let source_len = slice.source.chars().count(); if let Some(bigger) = slice.annotations.iter().find_map(|x| { if source_len < x.range.1 { @@ -445,14 +457,17 @@ fn format_body(slice: snippet::Slice<'_>, has_footer: bool) -> Vec<DisplayLine<' body = fold_body(body); } - body.insert( - 0, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }, - ); + if need_empty_header { + body.insert( + 0, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + ); + } + if has_footer { body.push(DisplayLine::Source { lineno: None, diff --git a/tests/fixtures/no-color/issue_9.toml b/tests/fixtures/no-color/issue_9.toml new file mode 100644 index 00000000..a30563b2 --- /dev/null +++ b/tests/fixtures/no-color/issue_9.toml @@ -0,0 +1,28 @@ +[title] +label = "expected one of `.`, `;`, `?`, or an operator, found `for`" +annotation_type = "Error" + +[[slices]] +source = "let x = vec![1];" +line_start = 4 +origin = "/code/rust/src/test/ui/annotate-snippet/suggestion.rs" +[[slices.annotations]] +label = "move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait" +annotation_type = "Warning" +range = [4, 5] + +[[slices]] +source = "let y = x;" +line_start = 7 +[[slices.annotations]] +label = "value moved here" +annotation_type = "Warning" +range = [8, 9] + +[[slices]] +source = "x;" +line_start = 9 +[[slices.annotations]] +label = "value used here after move" +annotation_type = "Error" +range = [0, 1] diff --git a/tests/fixtures/no-color/issue_9.txt b/tests/fixtures/no-color/issue_9.txt new file mode 100644 index 00000000..affe6bc4 --- /dev/null +++ b/tests/fixtures/no-color/issue_9.txt @@ -0,0 +1,12 @@ +error: expected one of `.`, `;`, `?`, or an operator, found `for` + --> /code/rust/src/test/ui/annotate-snippet/suggestion.rs:4:5 + | +4 | let x = vec![1]; + | - move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait + | +7 | let y = x; + | - value moved here + | +9 | x; + | ^ value used here after move + | \ No newline at end of file diff --git a/tests/snippet/mod.rs b/tests/snippet/mod.rs index c3348741..cc747a48 100644 --- a/tests/snippet/mod.rs +++ b/tests/snippet/mod.rs @@ -53,7 +53,9 @@ where #[derive(Deserialize)] #[serde(remote = "FormatOptions")] pub struct FormatOptionsDef { + #[serde(default)] pub color: bool, + #[serde(default)] pub anonymized_line_numbers: bool, } From e36400016d51ec1074d2d6394c741d983f3e4256 Mon Sep 17 00:00:00 2001 From: Juan Aguilar Santillana <mhpoin@gmail.com> Date: Sat, 13 Jun 2020 12:05:31 +0000 Subject: [PATCH 003/455] Add strip code to the left and right for long lines --- Cargo.toml | 1 + src/display_list/from_snippet.rs | 26 +++- src/display_list/structs.rs | 118 ++++++++++++++++++ src/formatter/mod.rs | 51 +++++++- tests/dl_from_snippet.rs | 6 + tests/fixtures/no-color/strip_line.toml | 25 ++++ tests/fixtures/no-color/strip_line.txt | 6 + .../fixtures/no-color/strip_line_non_ws.toml | 25 ++++ tests/fixtures/no-color/strip_line_non_ws.txt | 6 + tests/snippet/mod.rs | 41 +++++- 10 files changed, 299 insertions(+), 6 deletions(-) create mode 100644 tests/fixtures/no-color/strip_line.toml create mode 100644 tests/fixtures/no-color/strip_line.txt create mode 100644 tests/fixtures/no-color/strip_line_non_ws.toml create mode 100644 tests/fixtures/no-color/strip_line_non_ws.txt diff --git a/Cargo.toml b/Cargo.toml index 28a773bf..55a45f88 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ coveralls = { repository = "rust-lang/annotate-snippets-rs", branch = "master", maintenance = { status = "actively-developed" } [dependencies] +unicode-width = "0.1" yansi-term = { version = "0.1", optional = true } [dev-dependencies] diff --git a/src/display_list/from_snippet.rs b/src/display_list/from_snippet.rs index da0deea2..d32d1e01 100644 --- a/src/display_list/from_snippet.rs +++ b/src/display_list/from_snippet.rs @@ -107,12 +107,13 @@ fn format_slice( slice: snippet::Slice<'_>, is_first: bool, has_footer: bool, + margin: Option<Margin>, ) -> Vec<DisplayLine<'_>> { let main_range = slice.annotations.get(0).map(|x| x.range.0); let origin = slice.origin; let line_start = slice.line_start; let need_empty_header = origin.is_some() || is_first; - let mut body = format_body(slice, need_empty_header, has_footer); + let mut body = format_body(slice, need_empty_header, has_footer, margin); let header = format_header(origin, main_range, line_start, &body, is_first); let mut result = vec![]; @@ -273,6 +274,7 @@ fn format_body( slice: snippet::Slice<'_>, need_empty_header: bool, has_footer: bool, + margin: Option<Margin>, ) -> Vec<DisplayLine<'_>> { let source_len = slice.source.chars().count(); if let Some(bigger) = slice.annotations.iter().find_map(|x| { @@ -312,6 +314,9 @@ fn format_body( let mut annotation_line_count = 0; let mut annotations = slice.annotations; for (idx, (line_start, line_end)) in line_index_ranges.into_iter().enumerate() { + let margin_left = margin + .map(|m| m.left(line_end - line_start)) + .unwrap_or_default(); // It would be nice to use filter_drain here once it's stable. annotations = annotations .into_iter() @@ -328,7 +333,10 @@ fn format_body( if start >= line_start && end <= line_end || start == line_end && end - start <= 1 => { - let range = (start - line_start, end - line_start); + let range = ( + (start - line_start) - margin_left, + (end - line_start) - margin_left, + ); body.insert( body_idx + 1, DisplayLine::Source { @@ -419,7 +427,10 @@ fn format_body( }); } - let range = (end - line_start, end - line_start + 1); + let range = ( + (end - line_start) - margin_left, + (end - line_start + 1) - margin_left, + ); body.insert( body_idx + 1, DisplayLine::Source { @@ -499,7 +510,12 @@ impl<'a> From<snippet::Snippet<'a>> for DisplayList<'a> { } for (idx, slice) in slices.into_iter().enumerate() { - body.append(&mut format_slice(slice, idx == 0, !footer.is_empty())); + body.append(&mut format_slice( + slice, + idx == 0, + !footer.is_empty(), + opt.margin, + )); } for annotation in footer { @@ -509,12 +525,14 @@ impl<'a> From<snippet::Snippet<'a>> for DisplayList<'a> { let FormatOptions { color, anonymized_line_numbers, + margin, } = opt; Self { body, stylesheet: get_term_style(color), anonymized_line_numbers, + margin, } } } diff --git a/src/display_list/structs.rs b/src/display_list/structs.rs index 8f6d8fcf..b4bbedc7 100644 --- a/src/display_list/structs.rs +++ b/src/display_list/structs.rs @@ -1,3 +1,4 @@ +use std::cmp::{max, min}; use std::fmt; use crate::formatter::{get_term_style, style::Stylesheet}; @@ -7,6 +8,7 @@ pub struct DisplayList<'a> { pub body: Vec<DisplayLine<'a>>, pub stylesheet: Box<dyn Stylesheet>, pub anonymized_line_numbers: bool, + pub margin: Option<Margin>, } impl<'a> From<Vec<DisplayLine<'a>>> for DisplayList<'a> { @@ -15,6 +17,7 @@ impl<'a> From<Vec<DisplayLine<'a>>> for DisplayList<'a> { body, anonymized_line_numbers: false, stylesheet: get_term_style(false), + margin: None, } } } @@ -38,6 +41,121 @@ impl<'a> fmt::Debug for DisplayList<'a> { pub struct FormatOptions { pub color: bool, pub anonymized_line_numbers: bool, + pub margin: Option<Margin>, +} + +#[derive(Clone, Copy, Debug)] +pub struct Margin { + /// The available whitespace in the left that can be consumed when centering. + pub whitespace_left: usize, + /// The column of the beginning of left-most span. + pub span_left: usize, + /// The column of the end of right-most span. + pub span_right: usize, + /// The beginning of the line to be displayed. + pub computed_left: usize, + /// The end of the line to be displayed. + pub computed_right: usize, + /// The current width of the terminal. 140 by default and in tests. + pub column_width: usize, + /// The end column of a span label, including the span. Doesn't account for labels not in the + /// same line as the span. + pub label_right: usize, +} + +impl Margin { + pub fn new( + whitespace_left: usize, + span_left: usize, + span_right: usize, + label_right: usize, + column_width: usize, + max_line_len: usize, + ) -> Self { + // The 6 is padding to give a bit of room for `...` when displaying: + // ``` + // error: message + // --> file.rs:16:58 + // | + // 16 | ... fn foo(self) -> Self::Bar { + // | ^^^^^^^^^ + // ``` + + let mut m = Margin { + whitespace_left: whitespace_left.saturating_sub(6), + span_left: span_left.saturating_sub(6), + span_right: span_right + 6, + computed_left: 0, + computed_right: 0, + column_width, + label_right: label_right + 6, + }; + m.compute(max_line_len); + m + } + + pub(crate) fn was_cut_left(&self) -> bool { + self.computed_left > 0 + } + + pub(crate) fn was_cut_right(&self, line_len: usize) -> bool { + let right = + if self.computed_right == self.span_right || self.computed_right == self.label_right { + // Account for the "..." padding given above. Otherwise we end up with code lines that + // do fit but end in "..." as if they were trimmed. + self.computed_right - 6 + } else { + self.computed_right + }; + right < line_len && self.computed_left + self.column_width < line_len + } + + fn compute(&mut self, max_line_len: usize) { + // When there's a lot of whitespace (>20), we want to trim it as it is useless. + self.computed_left = if self.whitespace_left > 20 { + self.whitespace_left - 16 // We want some padding. + } else { + 0 + }; + // We want to show as much as possible, max_line_len is the right-most boundary for the + // relevant code. + self.computed_right = max(max_line_len, self.computed_left); + + if self.computed_right - self.computed_left > self.column_width { + // Trimming only whitespace isn't enough, let's get craftier. + if self.label_right - self.whitespace_left <= self.column_width { + // Attempt to fit the code window only trimming whitespace. + self.computed_left = self.whitespace_left; + self.computed_right = self.computed_left + self.column_width; + } else if self.label_right - self.span_left <= self.column_width { + // Attempt to fit the code window considering only the spans and labels. + let padding_left = (self.column_width - (self.label_right - self.span_left)) / 2; + self.computed_left = self.span_left.saturating_sub(padding_left); + self.computed_right = self.computed_left + self.column_width; + } else if self.span_right - self.span_left <= self.column_width { + // Attempt to fit the code window considering the spans and labels plus padding. + let padding_left = (self.column_width - (self.span_right - self.span_left)) / 5 * 2; + self.computed_left = self.span_left.saturating_sub(padding_left); + self.computed_right = self.computed_left + self.column_width; + } else { + // Mostly give up but still don't show the full line. + self.computed_left = self.span_left; + self.computed_right = self.span_right; + } + } + } + + pub(crate) fn left(&self, line_len: usize) -> usize { + min(self.computed_left, line_len) + } + + pub(crate) fn right(&self, line_len: usize) -> usize { + if line_len.saturating_sub(self.computed_left) <= self.column_width { + line_len + } else { + min(line_len, self.computed_right) + } + } } /// Inline annotation which can be used in either Raw or Source line. diff --git a/src/formatter/mod.rs b/src/formatter/mod.rs index e93aeb32..ed31b86b 100644 --- a/src/formatter/mod.rs +++ b/src/formatter/mod.rs @@ -198,7 +198,56 @@ impl<'a> DisplayList<'a> { DisplaySourceLine::Empty => Ok(()), DisplaySourceLine::Content { text, .. } => { f.write_char(' ')?; - text.fmt(f) + if let Some(margin) = self.margin { + let line_len = text.chars().count(); + let mut left = margin.left(line_len); + let right = margin.right(line_len); + + if margin.was_cut_left() { + // We have stripped some code/whitespace from the beginning, make it clear. + "...".fmt(f)?; + left += 3; + } + + // On long lines, we strip the source line, accounting for unicode. + let mut taken = 0; + let cut_right = if margin.was_cut_right(line_len) { + taken += 3; + true + } else { + false + }; + let range = text + .char_indices() + .skip(left) + .take_while(|(_, ch)| { + // Make sure that the trimming on the right will fall within the terminal width. + // FIXME: `unicode_width` sometimes disagrees with terminals on how wide a `char` is. + // For now, just accept that sometimes the code line will be longer than desired. + taken += unicode_width::UnicodeWidthChar::width(*ch).unwrap_or(1); + if taken > right - left { + return false; + } + true + }) + .fold((None, 0), |acc, (i, _)| { + if acc.0.is_some() { + (acc.0, i) + } else { + (Some(i), i) + } + }); + + text[range.0.expect("One character at line")..=range.1].fmt(f)?; + + if cut_right { + // We have stripped some code after the right-most span end, make it clear we did so. + "...".fmt(f)?; + } + Ok(()) + } else { + text.fmt(f) + } } DisplaySourceLine::Annotation { range, diff --git a/tests/dl_from_snippet.rs b/tests/dl_from_snippet.rs index 2ed99026..0dcfcfa3 100644 --- a/tests/dl_from_snippet.rs +++ b/tests/dl_from_snippet.rs @@ -28,6 +28,7 @@ fn test_format_title() { })], stylesheet: get_term_style(input.opt.color), anonymized_line_numbers: input.opt.anonymized_line_numbers, + margin: None, }; assert_eq!(dl::DisplayList::from(input), output); } @@ -80,6 +81,7 @@ fn test_format_slice() { ], stylesheet: get_term_style(input.opt.color), anonymized_line_numbers: input.opt.anonymized_line_numbers, + margin: None, }; assert_eq!(dl::DisplayList::from(input), output); } @@ -162,6 +164,7 @@ fn test_format_slices_continuation() { ], stylesheet: get_term_style(input.opt.color), anonymized_line_numbers: input.opt.anonymized_line_numbers, + margin: None, }; assert_eq!(dl::DisplayList::from(input), output); } @@ -237,6 +240,7 @@ fn test_format_slice_annotation_standalone() { ], stylesheet: get_term_style(input.opt.color), anonymized_line_numbers: input.opt.anonymized_line_numbers, + margin: None, }; assert_eq!(dl::DisplayList::from(input), output); } @@ -278,6 +282,7 @@ fn test_format_label() { })], stylesheet: get_term_style(input.opt.color), anonymized_line_numbers: input.opt.anonymized_line_numbers, + margin: None, }; assert_eq!(dl::DisplayList::from(input), output); } @@ -395,6 +400,7 @@ fn test_i_29() { ], stylesheet: get_term_style(false), anonymized_line_numbers: false, + margin: None, }; assert_eq!(DisplayList::from(snippets), expected); diff --git a/tests/fixtures/no-color/strip_line.toml b/tests/fixtures/no-color/strip_line.toml new file mode 100644 index 00000000..76d9519b --- /dev/null +++ b/tests/fixtures/no-color/strip_line.toml @@ -0,0 +1,25 @@ +[title] +id = "E0308" +label = "mismatched types" +annotation_type = "Error" + +[[slices]] +source = " let _: () = 42;" +line_start = 4 +origin = "$DIR/whitespace-trimming.rs" + +[[slices.annotations]] +label = "expected (), found integer" +annotation_type = "Error" +range = [192, 194] + +[opt] +color = false +anonymized_line_numbers = true +[opt.margin] +whitespace_left = 180 +span_left = 192 +span_right = 194 +label_right = 221 +column_width = 140 +max_line_len = 195 diff --git a/tests/fixtures/no-color/strip_line.txt b/tests/fixtures/no-color/strip_line.txt new file mode 100644 index 00000000..65b05384 --- /dev/null +++ b/tests/fixtures/no-color/strip_line.txt @@ -0,0 +1,6 @@ +error[E0308]: mismatched types + --> $DIR/whitespace-trimming.rs:4:193 + | +LL | ... let _: () = 42; + | ^^ expected (), found integer + | diff --git a/tests/fixtures/no-color/strip_line_non_ws.toml b/tests/fixtures/no-color/strip_line_non_ws.toml new file mode 100644 index 00000000..5129f5ce --- /dev/null +++ b/tests/fixtures/no-color/strip_line_non_ws.toml @@ -0,0 +1,25 @@ +[title] +id = "E0308" +label = "mismatched types" +annotation_type = "Error" + +[[slices]] +source = " let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = ();" +line_start = 4 +origin = "$DIR/non-whitespace-trimming.rs" + +[[slices.annotations]] +label = "expected (), found integer" +annotation_type = "Error" +range = [240, 242] + +[opt] +color = false +anonymized_line_numbers = true +[opt.margin] +whitespace_left = 4 +span_left = 240 +span_right = 242 +label_right = 271 +column_width = 140 +max_line_len = 371 diff --git a/tests/fixtures/no-color/strip_line_non_ws.txt b/tests/fixtures/no-color/strip_line_non_ws.txt new file mode 100644 index 00000000..850619ad --- /dev/null +++ b/tests/fixtures/no-color/strip_line_non_ws.txt @@ -0,0 +1,6 @@ +error[E0308]: mismatched types + --> $DIR/non-whitespace-trimming.rs:4:241 + | +LL | ... = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = ();... + | ^^ expected (), found integer + | diff --git a/tests/snippet/mod.rs b/tests/snippet/mod.rs index cc747a48..247c26d7 100644 --- a/tests/snippet/mod.rs +++ b/tests/snippet/mod.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Deserializer, Serialize}; use annotate_snippets::{ - display_list::FormatOptions, + display_list::{FormatOptions, Margin}, snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, }; @@ -57,6 +57,45 @@ pub struct FormatOptionsDef { pub color: bool, #[serde(default)] pub anonymized_line_numbers: bool, + #[serde(deserialize_with = "deserialize_margin")] + #[serde(default)] + pub margin: Option<Margin>, +} + +fn deserialize_margin<'de, D>(deserializer: D) -> Result<Option<Margin>, D::Error> +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + struct Wrapper { + whitespace_left: usize, + span_left: usize, + span_right: usize, + label_right: usize, + column_width: usize, + max_line_len: usize, + }; + + Option::<Wrapper>::deserialize(deserializer).map(|opt_wrapped: Option<Wrapper>| { + opt_wrapped.map(|wrapped: Wrapper| { + let Wrapper { + whitespace_left, + span_left, + span_right, + label_right, + column_width, + max_line_len, + } = wrapped; + Margin::new( + whitespace_left, + span_left, + span_right, + label_right, + column_width, + max_line_len, + ) + }) + }) } fn deserialize_slices<'de, D>(deserializer: D) -> Result<Vec<Slice<'de>>, D::Error> From e3d5e65eba7aad5557aa6697bc665c76047a6f36 Mon Sep 17 00:00:00 2001 From: Juan Aguilar Santillana <mhpoin@gmail.com> Date: Sat, 13 Jun 2020 12:30:36 +0000 Subject: [PATCH 004/455] Remove references to non existent `DisplayListFormatter` --- README.md | 58 ++++++++++++++++++------------------- src/display_list/structs.rs | 3 +- src/formatter/style.rs | 3 +- src/lib.rs | 4 +-- src/stylesheets/mod.rs | 2 +- 5 files changed, 33 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 592a51e1..f80d6597 100644 --- a/README.md +++ b/README.md @@ -35,49 +35,47 @@ Usage ```rust use annotate_snippets::{ - display_list::DisplayList, - formatter::DisplayListFormatter, + display_list::{DisplayList, FormatOptions}, snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, }; fn main() { let snippet = Snippet { title: Some(Annotation { - label: Some("expected type, found `22`".to_string()), + label: Some("expected type, found `22`"), id: None, annotation_type: AnnotationType::Error, }), footer: vec![], - slices: vec![ - Slice { - source: r#" -This is an example -content of the slice -which will be annotated -with the list of annotations below. - "#.to_string(), - line_start: 26, - origin: Some("examples/example.txt".to_string()), - fold: false, - annotations: vec![ - SourceAnnotation { - label: "Example error annotation".to_string(), - annotation_type: AnnotationType::Error, - range: (13, 18), - }, - SourceAnnotation { - label: "and here's a warning".to_string(), - annotation_type: AnnotationType::Warning, - range: (34, 50), - }, - ], - }, - ], + slices: vec![Slice { + source: r#" annotations: vec![SourceAnnotation { + label: "expected struct `annotate_snippets::snippet::Slice`, found reference" + , + range: <22, 25>,"#, + line_start: 26, + origin: Some("examples/footer.rs"), + fold: true, + annotations: vec![ + SourceAnnotation { + label: "", + annotation_type: AnnotationType::Error, + range: (205, 207), + }, + SourceAnnotation { + label: "while parsing this struct", + annotation_type: AnnotationType::Info, + range: (34, 50), + }, + ], + }], + opt: FormatOptions { + color: true, + ..Default::default() + }, }; let dl = DisplayList::from(snippet); - let dlf = DisplayListFormatter::new(true, false); - println!("{}", dlf.format(&dl)); + println!("{}", dl); } ``` diff --git a/src/display_list/structs.rs b/src/display_list/structs.rs index b4bbedc7..d7719fc0 100644 --- a/src/display_list/structs.rs +++ b/src/display_list/structs.rs @@ -280,8 +280,7 @@ pub enum DisplayMarkType { /// A type of the `Annotation` which may impact the sigils, style or text displayed. /// -/// There are several ways in which the `DisplayListFormatter` uses this information -/// when formatting the `DisplayList`: +/// There are several ways to uses this information when formatting the `DisplayList`: /// /// * An annotation may display the name of the type like `error` or `info`. /// * An underline for `Error` may be `^^^` while for `Warning` it coule be `---`. diff --git a/src/formatter/style.rs b/src/formatter/style.rs index f76e6b0a..3fc01c19 100644 --- a/src/formatter/style.rs +++ b/src/formatter/style.rs @@ -39,8 +39,7 @@ pub trait Style { c: Box<dyn FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result + 'a>, f: &mut fmt::Formatter<'_>, ) -> fmt::Result; - /// The method used by the DisplayListFormatter to display the message - /// in bold font. + /// The method used by the `Formatter` to display the message in bold font. fn bold(&self) -> Box<dyn Style>; } diff --git a/src/lib.rs b/src/lib.rs index 46b25e15..d5813672 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,8 +38,7 @@ //! of lines containing semantic information about each line. //! This structure is the easiest to manipulate and organize. //! -//! Finally, [DisplayListFormatter](self::formatter::DisplayListFormatter) is -//! used to format the `DisplayList` using a `Stylesheet` into a final `String` output. +//! Finally, `impl Display` into a final `String` output. //! //! A user of the crate may choose to provide their own equivalent of the input //! structure with an `Into<DisplayList>` trait. @@ -47,6 +46,7 @@ //! A user of the crate may also choose to provide their own formatter logic, //! to convert a `DisplayList` into a `String`, or just a `Stylesheet` to //! use the crate's formatting logic, but with a custom stylesheet. +// TODO: check documentation pub mod display_list; pub mod formatter; diff --git a/src/stylesheets/mod.rs b/src/stylesheets/mod.rs index 49f6ea04..4648852a 100644 --- a/src/stylesheets/mod.rs +++ b/src/stylesheets/mod.rs @@ -1,4 +1,4 @@ -//! List of stylesheets that can be used by the `DisplayListFormatter`. +//! List of stylesheets //! //! The list depends on what optional dependencies the crate has been //! compiled with. From dae1a97e99d2d800c1a54b5da77b1be2e16c21db Mon Sep 17 00:00:00 2001 From: Juan Aguilar Santillana <mhpoin@gmail.com> Date: Sat, 13 Jun 2020 14:46:37 +0000 Subject: [PATCH 005/455] Fix visibility of Margin fields --- src/display_list/structs.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/display_list/structs.rs b/src/display_list/structs.rs index d7719fc0..7941d5fc 100644 --- a/src/display_list/structs.rs +++ b/src/display_list/structs.rs @@ -47,20 +47,20 @@ pub struct FormatOptions { #[derive(Clone, Copy, Debug)] pub struct Margin { /// The available whitespace in the left that can be consumed when centering. - pub whitespace_left: usize, + whitespace_left: usize, /// The column of the beginning of left-most span. - pub span_left: usize, + span_left: usize, /// The column of the end of right-most span. - pub span_right: usize, + span_right: usize, /// The beginning of the line to be displayed. - pub computed_left: usize, + computed_left: usize, /// The end of the line to be displayed. - pub computed_right: usize, + computed_right: usize, /// The current width of the terminal. 140 by default and in tests. - pub column_width: usize, + column_width: usize, /// The end column of a span label, including the span. Doesn't account for labels not in the /// same line as the span. - pub label_right: usize, + label_right: usize, } impl Margin { From 14b3b1c5f042e04599e56464ec2e6d4c017fa501 Mon Sep 17 00:00:00 2001 From: Zibi Braniecki <zibi@braniecki.net> Date: Sun, 28 Jun 2020 00:40:01 -0700 Subject: [PATCH 006/455] Fix an example --- examples/expected_type.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/expected_type.rs b/examples/expected_type.rs index 89f0ddc1..6f2a0d9a 100644 --- a/examples/expected_type.rs +++ b/examples/expected_type.rs @@ -23,7 +23,7 @@ fn main() { SourceAnnotation { label: "", annotation_type: AnnotationType::Error, - range: (205, 207), + range: (193, 195), }, SourceAnnotation { label: "while parsing this struct", From dd3a5ca28103d5f8ba91d7f0bf77811d485796f3 Mon Sep 17 00:00:00 2001 From: Zibi Braniecki <zibi@braniecki.net> Date: Sun, 28 Jun 2020 00:45:22 -0700 Subject: [PATCH 007/455] annotate-snippets 0.9.0 --- CHANGELOG.md | 4 ++++ Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0390a71..eeaa5b06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ - … +## annotate-snippets 0.9.0 (June 28, 2020) + + - Add strip code to the left and right of long lines. (#36) + ## annotate-snippets 0.8.0 (April 14, 2020) - Replace `ansi_term` with `yansi-term` for improved performance. (#30) diff --git a/Cargo.toml b/Cargo.toml index 55a45f88..df44b278 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "annotate-snippets" -version = "0.8.0" +version = "0.9.0" edition = "2018" authors = ["Zibi Braniecki <gandalf@mozilla.com>"] description = "Library for building code annotations" From 29c8e7f502ef48cf6985ef6e94aa28478efb520a Mon Sep 17 00:00:00 2001 From: Juan Aguilar Santillana <mhpoin@gmail.com> Date: Mon, 24 Aug 2020 10:39:15 +0000 Subject: [PATCH 008/455] Fix character split when strip code --- src/formatter/mod.rs | 10 ++++++-- tests/fixtures/no-color/strip_line_char.toml | 25 ++++++++++++++++++++ tests/fixtures/no-color/strip_line_char.txt | 6 +++++ 3 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 tests/fixtures/no-color/strip_line_char.toml create mode 100644 tests/fixtures/no-color/strip_line_char.txt diff --git a/src/formatter/mod.rs b/src/formatter/mod.rs index ed31b86b..d34c7bfe 100644 --- a/src/formatter/mod.rs +++ b/src/formatter/mod.rs @@ -1,6 +1,7 @@ use std::{ cmp, fmt::{self, Display, Write}, + iter::once, }; pub mod style; @@ -217,16 +218,21 @@ impl<'a> DisplayList<'a> { } else { false }; + let mut ended = false; let range = text .char_indices() + .chain(once((text.len(), '\0'))) .skip(left) .take_while(|(_, ch)| { + if ended { + return false; + } // Make sure that the trimming on the right will fall within the terminal width. // FIXME: `unicode_width` sometimes disagrees with terminals on how wide a `char` is. // For now, just accept that sometimes the code line will be longer than desired. taken += unicode_width::UnicodeWidthChar::width(*ch).unwrap_or(1); if taken > right - left { - return false; + ended = true; } true }) @@ -238,7 +244,7 @@ impl<'a> DisplayList<'a> { } }); - text[range.0.expect("One character at line")..=range.1].fmt(f)?; + text[range.0.expect("One character at line")..range.1].fmt(f)?; if cut_right { // We have stripped some code after the right-most span end, make it clear we did so. diff --git a/tests/fixtures/no-color/strip_line_char.toml b/tests/fixtures/no-color/strip_line_char.toml new file mode 100644 index 00000000..5b432beb --- /dev/null +++ b/tests/fixtures/no-color/strip_line_char.toml @@ -0,0 +1,25 @@ +[title] +id = "E0308" +label = "mismatched types" +annotation_type = "Error" + +[[slices]] +source = " let _: () = 42ñ" +line_start = 4 +origin = "$DIR/whitespace-trimming.rs" + +[[slices.annotations]] +label = "expected (), found integer" +annotation_type = "Error" +range = [192, 194] + +[opt] +color = false +anonymized_line_numbers = true +[opt.margin] +whitespace_left = 180 +span_left = 192 +span_right = 194 +label_right = 221 +column_width = 140 +max_line_len = 195 diff --git a/tests/fixtures/no-color/strip_line_char.txt b/tests/fixtures/no-color/strip_line_char.txt new file mode 100644 index 00000000..3d4b700c --- /dev/null +++ b/tests/fixtures/no-color/strip_line_char.txt @@ -0,0 +1,6 @@ +error[E0308]: mismatched types + --> $DIR/whitespace-trimming.rs:4:193 + | +LL | ... let _: () = 42ñ + | ^^ expected (), found integer + | From 65fced101d90837ef007e4515ef9aae59a86a532 Mon Sep 17 00:00:00 2001 From: Juan Aguilar Santillana <mhpoin@gmail.com> Date: Mon, 24 Aug 2020 12:30:48 +0000 Subject: [PATCH 009/455] Add documentation --- src/formatter/mod.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/formatter/mod.rs b/src/formatter/mod.rs index d34c7bfe..c93048d9 100644 --- a/src/formatter/mod.rs +++ b/src/formatter/mod.rs @@ -218,12 +218,17 @@ impl<'a> DisplayList<'a> { } else { false }; + // Specifies that it will end on the next character, so it will return + // until the next one to the final condition. let mut ended = false; let range = text .char_indices() - .chain(once((text.len(), '\0'))) .skip(left) + // Complete char iterator with final character + .chain(once((text.len(), '\0'))) + // Take until the next one to the final condition .take_while(|(_, ch)| { + // Fast return to iterate over final byte position if ended { return false; } @@ -236,6 +241,7 @@ impl<'a> DisplayList<'a> { } true }) + // Reduce to start and end byte position .fold((None, 0), |acc, (i, _)| { if acc.0.is_some() { (acc.0, i) @@ -244,6 +250,7 @@ impl<'a> DisplayList<'a> { } }); + // Format text with margins text[range.0.expect("One character at line")..range.1].fmt(f)?; if cut_right { From 713fe0f1e2d0f530d902206f212a6fd972f21c82 Mon Sep 17 00:00:00 2001 From: Mario Carneiro <di.gama@gmail.com> Date: Mon, 1 Mar 2021 00:51:16 -0800 Subject: [PATCH 010/455] fix off by one error in multiline highlighting --- src/display_list/from_snippet.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/display_list/from_snippet.rs b/src/display_list/from_snippet.rs index d32d1e01..272c063c 100644 --- a/src/display_list/from_snippet.rs +++ b/src/display_list/from_snippet.rs @@ -427,9 +427,10 @@ fn format_body( }); } + let end_mark = (end - line_start).saturating_sub(1); let range = ( - (end - line_start) - margin_left, - (end - line_start + 1) - margin_left, + end_mark - margin_left, + (end_mark + 1) - margin_left, ); body.insert( body_idx + 1, From 34df93e4a8ab568625d177a7ccecf1e69f3209df Mon Sep 17 00:00:00 2001 From: Mario Carneiro <di.gama@gmail.com> Date: Mon, 1 Mar 2021 21:30:29 -0500 Subject: [PATCH 011/455] fix tests --- tests/fixtures/no-color/multiline_annotation.toml | 2 +- tests/fixtures/no-color/multiline_annotation2.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/fixtures/no-color/multiline_annotation.toml b/tests/fixtures/no-color/multiline_annotation.toml index bdb577f4..c3dc1e9e 100644 --- a/tests/fixtures/no-color/multiline_annotation.toml +++ b/tests/fixtures/no-color/multiline_annotation.toml @@ -33,7 +33,7 @@ range = [5, 19] [[slices.annotations]] label = "expected enum `std::option::Option`, found ()" annotation_type = "Error" -range = [22, 765] +range = [22, 766] [title] label = "mismatched types" id = "E0308" diff --git a/tests/fixtures/no-color/multiline_annotation2.toml b/tests/fixtures/no-color/multiline_annotation2.toml index 6ec0b1fe..845bf9f2 100644 --- a/tests/fixtures/no-color/multiline_annotation2.toml +++ b/tests/fixtures/no-color/multiline_annotation2.toml @@ -10,7 +10,7 @@ fold = false [[slices.annotations]] label = "missing fields `lineno`, `content`" annotation_type = "Error" -range = [31, 127] +range = [31, 128] [title] label = "pattern does not mention fields `lineno`, `content`" From 1142c99fb16e29cdf609f95a44f314c0a8e9eb36 Mon Sep 17 00:00:00 2001 From: Mario Carneiro <di.gama@gmail.com> Date: Sat, 6 Mar 2021 00:08:21 -0800 Subject: [PATCH 012/455] fix usage example fixes #43 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f80d6597..d7394fba 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ fn main() { SourceAnnotation { label: "", annotation_type: AnnotationType::Error, - range: (205, 207), + range: (187, 189), }, SourceAnnotation { label: "while parsing this struct", From 4a52fe11e18bf5583e743c4268fb8c3698bfae9c Mon Sep 17 00:00:00 2001 From: Alex Touchet <alextouchet@outlook.com> Date: Wed, 1 Sep 2021 09:45:19 -0700 Subject: [PATCH 013/455] Fix crates.io badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f80d6597..90c3bf3c 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ `annotate-snippets` is a Rust library for annotation of programming code slices. -[](https://crates.io/crates/annotate-snippets) +[](https://crates.io/crates/annotate-snippets) [](https://travis-ci.com/rust-lang/annotate-snippets-rs) [](https://coveralls.io/github/rust-lang/annotate-snippets-rs?branch=master) From ac31f8aa46ff0a155bc7a4be5a68e5605213902c Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka <yusuktan@maguro.dev> Date: Sat, 4 Sep 2021 23:09:25 +0900 Subject: [PATCH 014/455] Fix display of annotation for double width characters --- src/display_list/from_snippet.rs | 78 +++++++++++++++++++++++--------- tests/formatter.rs | 28 ++++++++++++ 2 files changed, 85 insertions(+), 21 deletions(-) diff --git a/src/display_list/from_snippet.rs b/src/display_list/from_snippet.rs index 272c063c..189781e8 100644 --- a/src/display_list/from_snippet.rs +++ b/src/display_list/from_snippet.rs @@ -293,11 +293,23 @@ fn format_body( let mut body = vec![]; let mut current_line = slice.line_start; let mut current_index = 0; - let mut line_index_ranges = vec![]; + let mut line_info = vec![]; + + struct LineInfo { + line_start_index: usize, + line_end_index: usize, + // How many spaces each character in the line take up when displayed + char_widths: Vec<usize>, + } for (line, end_line) in CursorLines::new(slice.source) { let line_length = line.chars().count(); let line_range = (current_index, current_index + line_length); + let char_widths = line + .chars() + .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) + .chain(std::iter::once(1)) // treat the end of line as signle-width + .collect::<Vec<_>>(); body.push(DisplayLine::Source { lineno: Some(current_line), inline_marks: vec![], @@ -306,16 +318,28 @@ fn format_body( range: line_range, }, }); - line_index_ranges.push(line_range); + line_info.push(LineInfo { + line_start_index: line_range.0, + line_end_index: line_range.1, + char_widths, + }); current_line += 1; current_index += line_length + end_line as usize; } let mut annotation_line_count = 0; let mut annotations = slice.annotations; - for (idx, (line_start, line_end)) in line_index_ranges.into_iter().enumerate() { + for ( + idx, + LineInfo { + line_start_index, + line_end_index, + char_widths, + }, + ) in line_info.into_iter().enumerate() + { let margin_left = margin - .map(|m| m.left(line_end - line_start)) + .map(|m| m.left(line_end_index - line_start_index)) .unwrap_or_default(); // It would be nice to use filter_drain here once it's stable. annotations = annotations @@ -328,15 +352,22 @@ fn format_body( _ => DisplayAnnotationType::from(annotation.annotation_type), }; match annotation.range { - (start, _) if start > line_end => true, + (start, _) if start > line_end_index => true, (start, end) - if start >= line_start && end <= line_end - || start == line_end && end - start <= 1 => + if start >= line_start_index && end <= line_end_index + || start == line_end_index && end - start <= 1 => { - let range = ( - (start - line_start) - margin_left, - (end - line_start) - margin_left, - ); + let annotation_start_col = char_widths + .iter() + .take(start - line_start_index) + .sum::<usize>() + - margin_left; + let annotation_end_col = char_widths + .iter() + .take(end - line_start_index) + .sum::<usize>() + - margin_left; + let range = (annotation_start_col, annotation_end_col); body.insert( body_idx + 1, DisplayLine::Source { @@ -359,8 +390,12 @@ fn format_body( annotation_line_count += 1; false } - (start, end) if start >= line_start && start <= line_end && end > line_end => { - if start - line_start == 0 { + (start, end) + if start >= line_start_index + && start <= line_end_index + && end > line_end_index => + { + if start - line_start_index == 0 { if let DisplayLine::Source { ref mut inline_marks, .. @@ -374,7 +409,7 @@ fn format_body( }); } } else { - let range = (start - line_start, start - line_start + 1); + let range = (start - line_start_index, start - line_start_index + 1); body.insert( body_idx + 1, DisplayLine::Source { @@ -398,7 +433,7 @@ fn format_body( } true } - (start, end) if start < line_start && end > line_end => { + (start, end) if start < line_start_index && end > line_end_index => { if let DisplayLine::Source { ref mut inline_marks, .. @@ -413,7 +448,11 @@ fn format_body( } true } - (start, end) if start < line_start && end >= line_start && end <= line_end => { + (start, end) + if start < line_start_index + && end >= line_start_index + && end <= line_end_index => + { if let DisplayLine::Source { ref mut inline_marks, .. @@ -427,11 +466,8 @@ fn format_body( }); } - let end_mark = (end - line_start).saturating_sub(1); - let range = ( - end_mark - margin_left, - (end_mark + 1) - margin_left, - ); + let end_mark = (end - line_start_index).saturating_sub(1); + let range = (end_mark - margin_left, (end_mark + 1) - margin_left); body.insert( body_idx + 1, DisplayLine::Source { diff --git a/tests/formatter.rs b/tests/formatter.rs index 5c7211d1..68ce75d1 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -550,3 +550,31 @@ fn test_i_29() { assert_eq!(DisplayList::from(snippets).to_string(), expected); } + +#[test] +fn test_point_to_double_width_characters() { + let snippets = Snippet { + slices: vec![snippet::Slice { + source: "こんにちは、世界", + line_start: 1, + origin: Some("<current file>"), + annotations: vec![snippet::SourceAnnotation { + range: (6, 8), + label: "world", + annotation_type: snippet::AnnotationType::Error, + }], + fold: false, + }], + title: None, + footer: vec![], + opt: Default::default(), + }; + + let expected = r#" --> <current file>:1:7 + | +1 | こんにちは、世界 + | ^^^^ world + |"#; + + assert_eq!(DisplayList::from(snippets).to_string(), expected); +} From 29cfa73370918b64937edaa63d9a1c346b81fd35 Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka <yusuktan@maguro.dev> Date: Sat, 4 Sep 2021 23:27:05 +0900 Subject: [PATCH 015/455] Display annotation correctly for double width characters across lines --- src/display_list/from_snippet.rs | 12 ++++++++++-- tests/formatter.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/display_list/from_snippet.rs b/src/display_list/from_snippet.rs index 189781e8..f81d38ae 100644 --- a/src/display_list/from_snippet.rs +++ b/src/display_list/from_snippet.rs @@ -409,7 +409,11 @@ fn format_body( }); } } else { - let range = (start - line_start_index, start - line_start_index + 1); + let annotation_start_col = char_widths + .iter() + .take(start - line_start_index) + .sum::<usize>(); + let range = (annotation_start_col, annotation_start_col + 1); body.insert( body_idx + 1, DisplayLine::Source { @@ -466,7 +470,11 @@ fn format_body( }); } - let end_mark = (end - line_start_index).saturating_sub(1); + let end_mark = char_widths + .iter() + .take(end - line_start_index) + .sum::<usize>() + .saturating_sub(1); let range = (end_mark - margin_left, (end_mark + 1) - margin_left); body.insert( body_idx + 1, diff --git a/tests/formatter.rs b/tests/formatter.rs index 68ce75d1..744ac1e9 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -578,3 +578,33 @@ fn test_point_to_double_width_characters() { assert_eq!(DisplayList::from(snippets).to_string(), expected); } + +#[test] +fn test_point_to_double_width_characters_across_lines() { + let snippets = Snippet { + slices: vec![snippet::Slice { + source: "おはよう\nございます", + line_start: 1, + origin: Some("<current file>"), + annotations: vec![snippet::SourceAnnotation { + range: (2, 8), + label: "Good morning", + annotation_type: snippet::AnnotationType::Error, + }], + fold: false, + }], + title: None, + footer: vec![], + opt: Default::default(), + }; + + let expected = r#" --> <current file>:1:3 + | +1 | おはよう + | _____^ +2 | | ございます + | |______^ Good morning + |"#; + + assert_eq!(DisplayList::from(snippets).to_string(), expected); +} From bb47ac0e0eff5d2b9b2694829c477549c36d16ae Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka <yusuktan@maguro.dev> Date: Sat, 4 Sep 2021 23:35:22 +0900 Subject: [PATCH 016/455] Add test containing multiple snippets for double width characters --- tests/formatter.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/formatter.rs b/tests/formatter.rs index 744ac1e9..b90defc3 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -608,3 +608,40 @@ fn test_point_to_double_width_characters_across_lines() { assert_eq!(DisplayList::from(snippets).to_string(), expected); } + +#[test] +fn test_point_to_double_width_characters_multiple() { + let snippets = Snippet { + slices: vec![snippet::Slice { + source: "お寿司\n食べたい🍣", + line_start: 1, + origin: Some("<current file>"), + annotations: vec![ + snippet::SourceAnnotation { + range: (0, 3), + label: "Sushi1", + annotation_type: snippet::AnnotationType::Error, + }, + snippet::SourceAnnotation { + range: (6, 8), + label: "Sushi2", + annotation_type: snippet::AnnotationType::Note, + }, + ], + fold: false, + }], + title: None, + footer: vec![], + opt: Default::default(), + }; + + let expected = r#" --> <current file>:1:1 + | +1 | お寿司 + | ^^^^^^ Sushi1 +2 | 食べたい🍣 + | ---- note: Sushi2 + |"#; + + assert_eq!(DisplayList::from(snippets).to_string(), expected); +} From fbdab5c79adbcd860512466b79c8e05042d251f2 Mon Sep 17 00:00:00 2001 From: Yusuke Tanaka <yusuktan@maguro.dev> Date: Sun, 5 Sep 2021 01:46:29 +0900 Subject: [PATCH 017/455] Add test to ensure it works when double-width and signle-width are mixed --- tests/formatter.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/formatter.rs b/tests/formatter.rs index b90defc3..b1392a1d 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -645,3 +645,31 @@ fn test_point_to_double_width_characters_multiple() { assert_eq!(DisplayList::from(snippets).to_string(), expected); } + +#[test] +fn test_point_to_double_width_characters_mixed() { + let snippets = Snippet { + slices: vec![snippet::Slice { + source: "こんにちは、新しいWorld!", + line_start: 1, + origin: Some("<current file>"), + annotations: vec![snippet::SourceAnnotation { + range: (6, 14), + label: "New world", + annotation_type: snippet::AnnotationType::Error, + }], + fold: false, + }], + title: None, + footer: vec![], + opt: Default::default(), + }; + + let expected = r#" --> <current file>:1:7 + | +1 | こんにちは、新しいWorld! + | ^^^^^^^^^^^ New world + |"#; + + assert_eq!(DisplayList::from(snippets).to_string(), expected); +} From 542e41e9a767c1c294564d549c46b2c3974b6481 Mon Sep 17 00:00:00 2001 From: Zibi Braniecki <zibi@braniecki.net> Date: Sat, 4 Sep 2021 12:54:42 -0700 Subject: [PATCH 018/455] Apply clippy lints --- src/display_list/from_snippet.rs | 15 +++++++-------- src/formatter/mod.rs | 9 +++------ tests/snippet/mod.rs | 2 +- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/display_list/from_snippet.rs b/src/display_list/from_snippet.rs index f81d38ae..faf48f2a 100644 --- a/src/display_list/from_snippet.rs +++ b/src/display_list/from_snippet.rs @@ -39,7 +39,7 @@ impl<'a> Iterator for CursorLines<'a> { ret }) .or_else(|| { - let ret = Some((&self.0[..], EndLine::EOF)); + let ret = Some((self.0, EndLine::EOF)); self.0 = ""; ret }) @@ -79,7 +79,7 @@ fn format_title(annotation: snippet::Annotation<'_>) -> DisplayLine<'_> { annotation: Annotation { annotation_type: DisplayAnnotationType::from(annotation.annotation_type), id: annotation.id, - label: format_label(Some(&label), Some(DisplayTextStyle::Emphasis)), + label: format_label(Some(label), Some(DisplayTextStyle::Emphasis)), }, source_aligned: false, continuation: false, @@ -182,7 +182,7 @@ fn fold_body(mut body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> { enum Line { Fold(usize), Source(usize), - }; + } let mut lines = vec![]; let mut no_annotation_lines_counter = 0; @@ -193,8 +193,8 @@ fn fold_body(mut body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> { line: DisplaySourceLine::Annotation { .. }, .. } => { + let fold_start = idx - no_annotation_lines_counter; if no_annotation_lines_counter > 2 { - let fold_start = idx - no_annotation_lines_counter; let fold_end = idx; let pre_len = if no_annotation_lines_counter > 8 { 4 @@ -224,8 +224,7 @@ fn fold_body(mut body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> { lines.push(Line::Source(i)); } } else { - let start = idx - no_annotation_lines_counter; - for (i, _) in body.iter().enumerate().take(idx).skip(start) { + for (i, _) in body.iter().enumerate().take(idx).skip(fold_start) { lines.push(Line::Source(i)); } } @@ -377,7 +376,7 @@ fn format_body( annotation: Annotation { annotation_type, id: None, - label: format_label(Some(&annotation.label), None), + label: format_label(Some(annotation.label), None), }, range, annotation_type: DisplayAnnotationType::from( @@ -490,7 +489,7 @@ fn format_body( annotation: Annotation { annotation_type, id: None, - label: format_label(Some(&annotation.label), None), + label: format_label(Some(annotation.label), None), }, range, annotation_type: DisplayAnnotationType::from( diff --git a/src/formatter/mod.rs b/src/formatter/mod.rs index c93048d9..16889baa 100644 --- a/src/formatter/mod.rs +++ b/src/formatter/mod.rs @@ -132,7 +132,7 @@ impl<'a> DisplayList<'a> { for fragment in label { match fragment.style { DisplayTextStyle::Regular => fragment.content.fmt(f)?, - DisplayTextStyle::Emphasis => emphasis_style.paint(&fragment.content, f)?, + DisplayTextStyle::Emphasis => emphasis_style.paint(fragment.content, f)?, } } Ok(()) @@ -298,7 +298,7 @@ impl<'a> DisplayList<'a> { f, )?; - if !is_annotation_empty(&annotation) { + if !is_annotation_empty(annotation) { f.write_char(' ')?; color.paint_fn( Box::new(|f| { @@ -361,18 +361,15 @@ impl<'a> DisplayList<'a> { if *source_aligned { if *continuation { format_repeat_char(' ', lineno_width + 3, f)?; - self.format_annotation(annotation, *continuation, false, f) } else { let lineno_color = self.stylesheet.get_style(StyleClass::LineNo); format_repeat_char(' ', lineno_width, f)?; f.write_char(' ')?; lineno_color.paint("=", f)?; f.write_char(' ')?; - self.format_annotation(annotation, *continuation, false, f) } - } else { - self.format_annotation(annotation, *continuation, false, f) } + self.format_annotation(annotation, *continuation, false, f) } } } diff --git a/tests/snippet/mod.rs b/tests/snippet/mod.rs index 247c26d7..40249f40 100644 --- a/tests/snippet/mod.rs +++ b/tests/snippet/mod.rs @@ -74,7 +74,7 @@ where label_right: usize, column_width: usize, max_line_len: usize, - }; + } Option::<Wrapper>::deserialize(deserializer).map(|opt_wrapped: Option<Wrapper>| { opt_wrapped.map(|wrapped: Wrapper| { From 77bd1c8e03466ff290c15dc025eac13088c244ee Mon Sep 17 00:00:00 2001 From: Zibi Braniecki <zibi@braniecki.net> Date: Sat, 4 Sep 2021 12:59:22 -0700 Subject: [PATCH 019/455] annotate-snippets 0.9.1 --- CHANGELOG.md | 6 ++++++ Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eeaa5b06..21e5f2fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ - … +## annotate-snippets 0.9.1 (September 4, 2021) + + - Fix character split when strip code. (#37) + - Fix off by one error in multiline highlighting. (#42) + - Fix display of annotation for double width characters. (#46) + ## annotate-snippets 0.9.0 (June 28, 2020) - Add strip code to the left and right of long lines. (#36) diff --git a/Cargo.toml b/Cargo.toml index df44b278..b7a97fd0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "annotate-snippets" -version = "0.9.0" +version = "0.9.1" edition = "2018" authors = ["Zibi Braniecki <gandalf@mozilla.com>"] description = "Library for building code annotations" From caf72a18a76f299cf35cb26b1a715f53fc95d8cb Mon Sep 17 00:00:00 2001 From: Alex Touchet <alextouchet@outlook.com> Date: Sat, 4 Sep 2021 13:04:05 -0700 Subject: [PATCH 020/455] Switch Travis CI URL back to .org --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a3e1683f..6482ff1a 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ `annotate-snippets` is a Rust library for annotation of programming code slices. [](https://crates.io/crates/annotate-snippets) -[](https://travis-ci.com/rust-lang/annotate-snippets-rs) +[](https://travis-ci.org/rust-lang/annotate-snippets-rs) [](https://coveralls.io/github/rust-lang/annotate-snippets-rs?branch=master) The library helps visualize meta information annotating source code slices. From baf725b3344eece9bc4f10614acdd48bec8469de Mon Sep 17 00:00:00 2001 From: Jimmy <30603522+jim4067@users.noreply.github.com> Date: Sun, 5 Sep 2021 18:44:50 +0300 Subject: [PATCH 021/455] set up github actions --- .github/workflows/rust.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/rust.yml diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 00000000..3c13d1be --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,22 @@ +name: Rust + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Build + run: cargo build --verbose + - name: Run tests + run: cargo test --verbose From 3e46f97b101ffc8e03669675f519675a444e88df Mon Sep 17 00:00:00 2001 From: jim4067 <jimmyimpulse2@gmail.com> Date: Sun, 12 Sep 2021 18:27:09 +0300 Subject: [PATCH 022/455] add rustfmt to workflow --- .github/workflows/rust.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 3c13d1be..442d3919 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -16,6 +16,8 @@ jobs: steps: - uses: actions/checkout@v2 + - name: run rustfmt + - run: cargo fmt -- --check - name: Build run: cargo build --verbose - name: Run tests From c3fdb1c933e81f46419b4c3841c1242fab7f2744 Mon Sep 17 00:00:00 2001 From: jim4067 <jimmyimpulse2@gmail.com> Date: Sun, 12 Sep 2021 18:28:15 +0300 Subject: [PATCH 023/455] rename workflow to ci --- .github/workflows/{rust.yml => ci.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{rust.yml => ci.yml} (97%) diff --git a/.github/workflows/rust.yml b/.github/workflows/ci.yml similarity index 97% rename from .github/workflows/rust.yml rename to .github/workflows/ci.yml index 442d3919..a182a9b6 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: Rust +name: CI on: push: From e2bf6e83745d25513e9954b978de5d5bf2523121 Mon Sep 17 00:00:00 2001 From: jim4067 <jimmyimpulse2@gmail.com> Date: Sun, 12 Sep 2021 18:29:18 +0300 Subject: [PATCH 024/455] remove travis ci badge and add github workflows badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6482ff1a..f60c96e1 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ `annotate-snippets` is a Rust library for annotation of programming code slices. [](https://crates.io/crates/annotate-snippets) -[](https://travis-ci.org/rust-lang/annotate-snippets-rs) + [](https://coveralls.io/github/rust-lang/annotate-snippets-rs?branch=master) The library helps visualize meta information annotating source code slices. From 21b23ee4c333da7f899edcae47bd63db88246038 Mon Sep 17 00:00:00 2001 From: Jimmy <30603522+jim4067@users.noreply.github.com> Date: Sun, 12 Sep 2021 18:48:36 +0300 Subject: [PATCH 025/455] update ci.yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a182a9b6..1d21ba79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@v2 - - name: run rustfmt + - name: Run rustfmt - run: cargo fmt -- --check - name: Build run: cargo build --verbose From ee58f7765489a403aedf7286dcd0f0c1d9aab42a Mon Sep 17 00:00:00 2001 From: Jimmy <30603522+jim4067@users.noreply.github.com> Date: Sun, 12 Sep 2021 18:50:22 +0300 Subject: [PATCH 026/455] update ci.yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1d21ba79..f40ab584 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Run rustfmt - - run: cargo fmt -- --check + run: cargo fmt -- --check - name: Build run: cargo build --verbose - name: Run tests From ee4687bf8d668f8cb6b76e5a518de7016a5ef7bd Mon Sep 17 00:00:00 2001 From: Neil Mitchell <ndmitchell@gmail.com> Date: Mon, 3 Jan 2022 15:57:04 +0000 Subject: [PATCH 027/455] Remove parsing of __ in title strings, fixes #53 --- src/display_list/from_snippet.rs | 21 +++++---------------- tests/dl_from_snippet.rs | 18 ++++-------------- 2 files changed, 9 insertions(+), 30 deletions(-) diff --git a/src/display_list/from_snippet.rs b/src/display_list/from_snippet.rs index faf48f2a..98683fb1 100644 --- a/src/display_list/from_snippet.rs +++ b/src/display_list/from_snippet.rs @@ -53,22 +53,11 @@ fn format_label( ) -> Vec<DisplayTextFragment<'_>> { let mut result = vec![]; if let Some(label) = label { - for (idx, element) in label.split("__").enumerate() { - let element_style = match style { - Some(s) => s, - None => { - if idx % 2 == 0 { - DisplayTextStyle::Regular - } else { - DisplayTextStyle::Emphasis - } - } - }; - result.push(DisplayTextFragment { - content: element, - style: element_style, - }); - } + let element_style = style.unwrap_or(DisplayTextStyle::Regular); + result.push(DisplayTextFragment { + content: label, + style: element_style, + }); } result } diff --git a/tests/dl_from_snippet.rs b/tests/dl_from_snippet.rs index 0dcfcfa3..d6b79ff2 100644 --- a/tests/dl_from_snippet.rs +++ b/tests/dl_from_snippet.rs @@ -262,20 +262,10 @@ fn test_format_label() { annotation: dl::Annotation { annotation_type: dl::DisplayAnnotationType::Error, id: None, - label: vec![ - dl::DisplayTextFragment { - content: "This ", - style: dl::DisplayTextStyle::Regular, - }, - dl::DisplayTextFragment { - content: "is", - style: dl::DisplayTextStyle::Emphasis, - }, - dl::DisplayTextFragment { - content: " a title", - style: dl::DisplayTextStyle::Regular, - }, - ], + label: vec![dl::DisplayTextFragment { + content: "This __is__ a title", + style: dl::DisplayTextStyle::Regular, + }], }, source_aligned: true, continuation: false, From 08141133f0d84f81525f2b596ed89f23d8592a8e Mon Sep 17 00:00:00 2001 From: Inky-developer <developerinky@gmail.com> Date: Mon, 3 Jan 2022 20:42:12 +0100 Subject: [PATCH 028/455] Fix #52 This fixes wrong line numbers shown when the `fold` is set to `true` --- src/display_list/from_snippet.rs | 12 ++++++------ tests/fixtures/no-color/issue_52.toml | 16 ++++++++++++++++ tests/fixtures/no-color/issue_52.txt | 7 +++++++ 3 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 tests/fixtures/no-color/issue_52.toml create mode 100644 tests/fixtures/no-color/issue_52.txt diff --git a/src/display_list/from_snippet.rs b/src/display_list/from_snippet.rs index faf48f2a..83e51f5d 100644 --- a/src/display_list/from_snippet.rs +++ b/src/display_list/from_snippet.rs @@ -111,10 +111,9 @@ fn format_slice( ) -> Vec<DisplayLine<'_>> { let main_range = slice.annotations.get(0).map(|x| x.range.0); let origin = slice.origin; - let line_start = slice.line_start; let need_empty_header = origin.is_some() || is_first; let mut body = format_body(slice, need_empty_header, has_footer, margin); - let header = format_header(origin, main_range, line_start, &body, is_first); + let header = format_header(origin, main_range, &body, is_first); let mut result = vec![]; if let Some(header) = header { @@ -133,7 +132,6 @@ fn zip_opt<A, B>(a: Option<A>, b: Option<B>) -> Option<(A, B)> { fn format_header<'a>( origin: Option<&'a str>, main_range: Option<usize>, - mut row: usize, body: &[DisplayLine<'_>], is_first: bool, ) -> Option<DisplayLine<'a>> { @@ -145,24 +143,26 @@ fn format_header<'a>( if let Some((main_range, path)) = zip_opt(main_range, origin) { let mut col = 1; + let mut line_offset = 1; for item in body { if let DisplayLine::Source { line: DisplaySourceLine::Content { range, .. }, + lineno, .. } = item { if main_range >= range.0 && main_range <= range.1 { col = main_range - range.0 + 1; + line_offset = lineno.unwrap_or(1); break; } - row += 1; } } return Some(DisplayLine::Raw(DisplayRawLine::Origin { path, - pos: Some((row, col)), + pos: Some((line_offset, col)), header_type: display_header, })); } @@ -307,7 +307,7 @@ fn format_body( let char_widths = line .chars() .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) - .chain(std::iter::once(1)) // treat the end of line as signle-width + .chain(std::iter::once(1)) // treat the end of line as single-width .collect::<Vec<_>>(); body.push(DisplayLine::Source { lineno: Some(current_line), diff --git a/tests/fixtures/no-color/issue_52.toml b/tests/fixtures/no-color/issue_52.toml new file mode 100644 index 00000000..8d54613f --- /dev/null +++ b/tests/fixtures/no-color/issue_52.toml @@ -0,0 +1,16 @@ +[title] +annotation_type = "Error" + +[[slices]] +source = """ + + +invalid syntax +""" +line_start = 1 +origin = "path/to/error.rs" +fold = true +[[slices.annotations]] +label = "error here" +annotation_type = "Warning" +range = [2,16] diff --git a/tests/fixtures/no-color/issue_52.txt b/tests/fixtures/no-color/issue_52.txt new file mode 100644 index 00000000..b1c6bf21 --- /dev/null +++ b/tests/fixtures/no-color/issue_52.txt @@ -0,0 +1,7 @@ +error + --> path/to/error.rs:3:1 + | +... +3 | invalid syntax + | -------------- error here + | \ No newline at end of file From e7b7555d1516d0b274e7269961fce9ec9b30bc98 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 29 Mar 2023 14:33:22 -0500 Subject: [PATCH 029/455] chore: First step --- .cargo/config | 5 + .clippy.toml | 12 ++ .github/renovate.json5 | 71 +++++++++++ .github/settings.yml | 52 ++++++++ .github/workflows/audit.yml | 49 ++++++++ .github/workflows/ci.yml | 128 ++++++++++++++++++++ .github/workflows/committed.yml | 24 ++++ .github/workflows/pre-commit.yml | 23 ++++ .github/workflows/rust-next.yml | 88 ++++++++++++++ .github/workflows/spelling.yml | 21 ++++ .gitignore | 1 + .pre-commit-config.yaml | 26 ++++ CHANGELOG.md | 11 ++ CONTRIBUTING.md | 70 +++++++++++ Cargo.lock | 7 ++ Cargo.toml | 38 ++++++ LICENSE-APACHE | 202 +++++++++++++++++++++++++++++++ LICENSE-MIT | 19 +++ README.md | 26 ++++ committed.toml | 3 + deny.toml | 135 +++++++++++++++++++++ release.toml | 6 + src/lib.rs | 2 + 23 files changed, 1019 insertions(+) create mode 100644 .cargo/config create mode 100644 .clippy.toml create mode 100644 .github/renovate.json5 create mode 100644 .github/settings.yml create mode 100644 .github/workflows/audit.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/committed.yml create mode 100644 .github/workflows/pre-commit.yml create mode 100644 .github/workflows/rust-next.yml create mode 100644 .github/workflows/spelling.yml create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE-APACHE create mode 100644 LICENSE-MIT create mode 100644 README.md create mode 100644 committed.toml create mode 100644 deny.toml create mode 100644 release.toml create mode 100644 src/lib.rs diff --git a/.cargo/config b/.cargo/config new file mode 100644 index 00000000..ba321231 --- /dev/null +++ b/.cargo/config @@ -0,0 +1,5 @@ +[target.x86_64-pc-windows-msvc] +rustflags = ["-Ctarget-feature=+crt-static"] + +[target.i686-pc-windows-msvc] +rustflags = ["-Ctarget-feature=+crt-static"] diff --git a/.clippy.toml b/.clippy.toml new file mode 100644 index 00000000..16749abd --- /dev/null +++ b/.clippy.toml @@ -0,0 +1,12 @@ +msrv = "1.64.0" # MSRV +warn-on-all-wildcard-imports = true +allow-expect-in-tests = true +allow-unwrap-in-tests = true +allow-dbg-in-tests = true +allow-print-in-tests = true +disallowed-methods = [ + { path = "std::option::Option::map_or", reason = "use `map(..).unwrap_or(..)`" }, + { path = "std::option::Option::map_or_else", reason = "use `map(..).unwrap_or_else(..)`" }, + { path = "std::result::Result::map_or", reason = "use `map(..).unwrap_or(..)`" }, + { path = "std::result::Result::map_or_else", reason = "use `map(..).unwrap_or_else(..)`" }, +] diff --git a/.github/renovate.json5 b/.github/renovate.json5 new file mode 100644 index 00000000..51faa753 --- /dev/null +++ b/.github/renovate.json5 @@ -0,0 +1,71 @@ +{ + "schedule": [ + "before 3am on the first day of the month" + ], + "semanticCommits": "enabled", + "configMigration": true, + "dependencyDashboard": true, + "regexManagers": [ + { + "fileMatch": [ + "^rust-toolchain\\.toml$", + "Cargo.toml$", + "clippy.toml$", + "\.clippy.toml$", + "^\.github/workflows/ci.yml$", + "^\.github/workflows/rust-next.yml$", + ], + "matchStrings": [ + "MSRV.*?(?<currentValue>\\d+\\.\\d+(\\.\\d+)?)", + "(?<currentValue>\\d+\\.\\d+(\\.\\d+)?).*?MSRV", + ], + "depNameTemplate": "rust", + "packageNameTemplate": "rust-lang/rust", + "datasourceTemplate": "github-releases", + } + ], + "packageRules": [ + { + "commitMessageTopic": "MSRV", + "matchManagers": ["regex"], + "matchPackageNames": ["rust"], + "stabilityDays": 126, // 3 releases * 6 weeks per release * 7 days per week + }, + // Goals: + // - Keep version reqs low, ignoring compatible normal/build dependencies + // - Take advantage of latest dev-dependencies + // - Rollup safe upgrades to reduce CI runner load + // - Help keep number of versions down by always using latest breaking change + // - Have lockfile and manifest in-sync + { + "matchManagers": ["cargo"], + "matchDepTypes": ["build-dependencies", "dependencies"], + "matchCurrentVersion": ">=0.1.0", + "matchUpdateTypes": ["patch"], + "enabled": false, + }, + { + "matchManagers": ["cargo"], + "matchDepTypes": ["build-dependencies", "dependencies"], + "matchCurrentVersion": ">=1.0.0", + "matchUpdateTypes": ["minor"], + "enabled": false, + }, + { + "matchManagers": ["cargo"], + "matchDepTypes": ["dev-dependencies"], + "matchCurrentVersion": ">=0.1.0", + "matchUpdateTypes": ["patch"], + "automerge": true, + "groupName": "compatible (dev)", + }, + { + "matchManagers": ["cargo"], + "matchDepTypes": ["dev-dependencies"], + "matchCurrentVersion": ">=1.0.0", + "matchUpdateTypes": ["minor"], + "automerge": true, + "groupName": "compatible (dev)", + }, + ], +} diff --git a/.github/settings.yml b/.github/settings.yml new file mode 100644 index 00000000..0469378d --- /dev/null +++ b/.github/settings.yml @@ -0,0 +1,52 @@ +# These settings are synced to GitHub by https://probot.github.io/apps/settings/ + +repository: + description: "DESCRIPTION" + homepage: "https://docs.rs/PROJECT" + topics: "" + has_issues: true + has_projects: false + has_wiki: false + has_downloads: true + default_branch: main + + allow_squash_merge: true + allow_merge_commit: true + allow_rebase_merge: true + + allow_auto_merge: true + delete_branch_on_merge: true + + squash_merge_commit_title: "PR_TITLE" + squash_merge_commit_message: "PR_BODY" + merge_commit_message: "PR_BODY" + +labels: + # Type + - name: bug + color: '#b60205' + description: Not as expected + - name: enhancement + color: '#1d76db' + description: Improve the expected + # Flavor + - name: question + color: "#cc317c" + description: Uncertainty is involved + - name: breaking-change + color: "#e99695" + - name: good first issue + color: '#c2e0c6' + description: Help wanted! + +branches: + - name: main + protection: + required_pull_request_reviews: null + required_conversation_resolution: true + required_status_checks: + # Required. Require branches to be up to date before merging. + strict: false + contexts: ["CI", "Lint Commits", "Spell Check with Typos"] + enforce_admins: false + restrictions: null diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml new file mode 100644 index 00000000..5b7e83ac --- /dev/null +++ b/.github/workflows/audit.yml @@ -0,0 +1,49 @@ +name: Security audit + +permissions: + contents: read + +on: + pull_request: + paths: + - '**/Cargo.toml' + - '**/Cargo.lock' + push: + branches: + - main + +env: + RUST_BACKTRACE: 1 + CARGO_TERM_COLOR: always + CLICOLOR: 1 + +jobs: + security_audit: + permissions: + issues: write # to create issues (actions-rs/audit-check) + checks: write # to create check (actions-rs/audit-check) + runs-on: ubuntu-latest + # Prevent sudden announcement of a new advisory from failing ci: + continue-on-error: true + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - uses: actions-rs/audit-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + cargo_deny: + permissions: + issues: write # to create issues (actions-rs/audit-check) + checks: write # to create check (actions-rs/audit-check) + runs-on: ubuntu-latest + strategy: + matrix: + checks: + - bans licenses sources + steps: + - uses: actions/checkout@v3 + - uses: EmbarkStudios/cargo-deny-action@v1 + with: + command: check ${{ matrix.checks }} + rust-version: stable diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..783247cb --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,128 @@ +name: CI + +permissions: + contents: read + +on: + pull_request: + push: + branches: + - main + +env: + RUST_BACKTRACE: 1 + CARGO_TERM_COLOR: always + CLICOLOR: 1 + +jobs: + ci: + permissions: + contents: none + name: CI + needs: [test, msrv, docs, rustfmt, clippy] + runs-on: ubuntu-latest + steps: + - name: Done + run: exit 0 + test: + name: Test + strategy: + matrix: + os: ["ubuntu-latest", "windows-latest", "macos-latest"] + rust: ["stable"] + continue-on-error: ${{ matrix.rust != 'stable' }} + runs-on: ${{ matrix.os }} + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ matrix.rust }} + - uses: Swatinem/rust-cache@v2 + - name: Build + run: cargo test --no-run --workspace --all-features + - name: Default features + run: cargo test --workspace + - name: All features + run: cargo test --workspace --all-features + - name: No-default features + run: cargo test --workspace --no-default-features + msrv: + name: "Check MSRV: 1.64.0" + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: 1.64.0 # MSRV + - uses: Swatinem/rust-cache@v2 + - name: Default features + run: cargo check --workspace --all-targets + - name: All features + run: cargo check --workspace --all-targets --all-features + - name: No-default features + run: cargo check --workspace --all-targets --no-default-features + docs: + name: Docs + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + - uses: Swatinem/rust-cache@v2 + - name: Check documentation + env: + RUSTDOCFLAGS: -D warnings + run: cargo doc --workspace --all-features --no-deps --document-private-items + rustfmt: + name: rustfmt + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + # Not MSRV because its harder to jump between versions and people are + # more likely to have stable + toolchain: stable + components: rustfmt + - uses: Swatinem/rust-cache@v2 + - name: Check formatting + run: cargo fmt --all -- --check + clippy: + name: clippy + runs-on: ubuntu-latest + permissions: + security-events: write # to upload sarif results + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: 1.64.0 # MSRV + components: clippy + - uses: Swatinem/rust-cache@v2 + - name: Install SARIF tools + run: cargo install clippy-sarif --version 0.3.4 --locked # Held back due to msrv + - name: Install SARIF tools + run: cargo install sarif-fmt --version 0.3.4 --locked # Held back due to msrv + - name: Check + run: > + cargo clippy --workspace --all-features --all-targets --message-format=json -- -D warnings --allow deprecated + | clippy-sarif + | tee clippy-results.sarif + | sarif-fmt + continue-on-error: true + - name: Upload + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: clippy-results.sarif + wait-for-processing: true diff --git a/.github/workflows/committed.yml b/.github/workflows/committed.yml new file mode 100644 index 00000000..509be080 --- /dev/null +++ b/.github/workflows/committed.yml @@ -0,0 +1,24 @@ +# Not run as part of pre-commit checks because they don't handle sending the correct commit +# range to `committed` +name: Lint Commits +on: [pull_request] + +permissions: + contents: read + +env: + RUST_BACKTRACE: 1 + CARGO_TERM_COLOR: always + CLICOLOR: 1 + +jobs: + committed: + name: Lint Commits + runs-on: ubuntu-latest + steps: + - name: Checkout Actions Repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Lint Commits + uses: crate-ci/committed@master diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 00000000..d4b0f84a --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,23 @@ +name: pre-commit + +permissions: {} # none + +on: + pull_request: + push: + branches: [main] + +env: + RUST_BACKTRACE: 1 + CARGO_TERM_COLOR: always + CLICOLOR: 1 + +jobs: + pre-commit: + permissions: + contents: read + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + - uses: pre-commit/action@v3.0.0 diff --git a/.github/workflows/rust-next.yml b/.github/workflows/rust-next.yml new file mode 100644 index 00000000..8faba304 --- /dev/null +++ b/.github/workflows/rust-next.yml @@ -0,0 +1,88 @@ +name: rust-next + +permissions: + contents: read + +on: + schedule: + - cron: '1 1 1 * *' + +env: + RUST_BACKTRACE: 1 + CARGO_TERM_COLOR: always + CLICOLOR: 1 + +jobs: + test: + name: Test + strategy: + matrix: + os: ["ubuntu-latest", "windows-latest", "macos-latest"] + rust: ["stable", "beta"] + include: + - os: ubuntu-latest + rust: "nightly" + continue-on-error: ${{ matrix.rust != 'stable' }} + runs-on: ${{ matrix.os }} + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ matrix.rust }} + - uses: Swatinem/rust-cache@v2 + - name: Default features + run: cargo test --workspace + - name: All features + run: cargo test --workspace --all-features + - name: No-default features + run: cargo test --workspace --no-default-features + rustfmt: + name: rustfmt + strategy: + matrix: + rust: + - stable + - beta + continue-on-error: ${{ matrix.rust != 'stable' }} + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ matrix.rust }} + components: rustfmt + - uses: Swatinem/rust-cache@v2 + - name: Check formatting + run: cargo fmt --all -- --check + clippy: + name: clippy + runs-on: ubuntu-latest + permissions: + security-events: write # to upload sarif results + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + components: clippy + - uses: Swatinem/rust-cache@v2 + - name: Install SARIF tools + run: cargo install clippy-sarif sarif-fmt + - name: Check + run: > + cargo clippy --workspace --all-features --all-targets --message-format=json -- -D warnings --allow deprecated + | clippy-sarif + | tee clippy-results.sarif + | sarif-fmt + continue-on-error: true + - name: Upload + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: clippy-results.sarif + wait-for-processing: true diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml new file mode 100644 index 00000000..f31c7ed8 --- /dev/null +++ b/.github/workflows/spelling.yml @@ -0,0 +1,21 @@ +name: Spelling + +permissions: + contents: read + +on: [pull_request] + +env: + RUST_BACKTRACE: 1 + CARGO_TERM_COLOR: always + CLICOLOR: 1 + +jobs: + spelling: + name: Spell Check with Typos + runs-on: ubuntu-latest + steps: + - name: Checkout Actions Repository + uses: actions/checkout@v3 + - name: Spell Check Repo + uses: crate-ci/typos@master diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..eb5a316c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..f751dec5 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,26 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.3.0 + hooks: + - id: check-yaml + stages: [commit] + - id: check-json + stages: [commit] + - id: check-toml + stages: [commit] + - id: check-merge-conflict + stages: [commit] + - id: check-case-conflict + stages: [commit] + - id: detect-private-key + stages: [commit] + - repo: https://github.com/crate-ci/typos + rev: v1.11.1 + hooks: + - id: typos + stages: [commit] + - repo: https://github.com/crate-ci/committed + rev: v1.0.4 + hooks: + - id: committed + stages: [commit-msg] diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..23a247b5 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +# Change Log +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + +<!-- next-header --> +## [Unreleased] - ReleaseDate + +<!-- next-url --> +[Unreleased]: https://github.com/rust-cli/argfile/compare/v0.1.5...HEAD diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..ce840a94 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,70 @@ +# Contributing to PROJECT + +Thanks for wanting to contribute! There are many ways to contribute and we +appreciate any level you're willing to do. + +## Feature Requests + +Need some new functionality to help? You can let us know by opening an +[issue][new issue]. It's helpful to look through [all issues][all issues] in +case its already being talked about. + +## Bug Reports + +Please let us know about what problems you run into, whether in behavior or +ergonomics of API. You can do this by opening an [issue][new issue]. It's +helpful to look through [all issues][all issues] in case its already being +talked about. + +## Pull Requests + +Looking for an idea? Check our [issues][issues]. If it's look more open ended, +it is probably best to post on the issue how you are thinking of resolving the +issue so you can get feedback early in the process. We want you to be +successful and it can be discouraging to find out a lot of re-work is needed. + +Already have an idea? It might be good to first [create an issue][new issue] +to propose it so we can make sure we are aligned and lower the risk of having +to re-work some of it and the discouragement that goes along with that. + +### Process + +Before posting a PR, we request that the commit history get cleaned up. +However, we recommend avoiding this during the review to make it easier to +check how feedback was handled. Once the PR is ready, we'll ask you to clean up +the commit history from the review. Once you let us know this is done, we can +move forward with merging! If you are uncomfortable with these parts of git, +let us know and we can help. + +For commit messages, we use [Conventional](https://www.conventionalcommits.org) +style. If you already wrote your commits and don't feel comfortable changing +them, don't worry and go ahead and create your PR. We'll work with you on the +best route forward. You can check your branch locally with +[`committed`](https://github.com/crate-ci/committed). + +As a heads up, we'll be running your PR through the following gauntlet: +- warnings turned to compile errors +- `cargo test` +- `rustfmt` +- `clippy` +- `rustdoc` +- [`committed`](https://github.com/crate-ci/committed) +- [`typos`](https://github.com/crate-ci/typos) + +## Releasing + +Pre-requisites +- Running `cargo login` +- A member of `ORG:Maintainers` +- Push permission to the repo +- [`cargo-release`](https://github.com/crate-ci/cargo-release/) + +When we're ready to release, a project owner should do the following +1. Update the changelog (see `cargo release changes` for ideas) +2. Determine what the next version is, according to semver +3. Run [`cargo release -x <level>`](https://github.com/crate-ci/cargo-release) + +[issues]: https://github.com/ORG/PROJECT/issues +[new issue]: https://github.com/ORG/PROJECT/issues/new +[all issues]: https://github.com/ORG/PROJECT/issues?utf8=%E2%9C%93&q=is%3Aissue +[travis]: https://github.com/ORG/PROJECT/blob/master/.travis.yml diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..49c1f2dc --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "PROJECT" +version = "0.0.1" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..55dc8553 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "PROJECT" +version = "0.0.1" +description = "DESCRIPTION" +license = "MIT OR Apache-2.0" +categories = [] +keywords = [] +edition = "2021" +rust-version = "1.64.0" # MSRV +include = [ + "build.rs", + "src/**/*", + "Cargo.toml", + "LICENSE*", + "README.md", + "benches/**/*", + "examples/**/*" +] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[package.metadata.release] +pre-release-replacements = [ + {file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1}, + {file="CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}", exactly=1}, + {file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1}, + {file="CHANGELOG.md", search="<!-- next-header -->", replace="<!-- next-header -->\n## [Unreleased] - ReleaseDate\n", exactly=1}, + {file="CHANGELOG.md", search="<!-- next-url -->", replace="<!-- next-url -->\n[Unreleased]: https://github.com/ORG/PROJECT/compare/{{tag_name}}...HEAD", exactly=1}, +] + +[features] +default = [] + +[dependencies] + +[dev-dependencies] diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 00000000..8f71f43f --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 00000000..a2d01088 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) Individual contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..41d5a974 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# PROJECT + +> DESCRIPTION + +[][Documentation] + +[](https://crates.io/crates/PROJECT) + +## License + +Licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. + +[Crates.io]: https://crates.io/crates/PROJECT +[Documentation]: https://docs.rs/PROJECT diff --git a/committed.toml b/committed.toml new file mode 100644 index 00000000..4211ae38 --- /dev/null +++ b/committed.toml @@ -0,0 +1,3 @@ +style="conventional" +ignore_author_re="(dependabot|renovate)" +merge_commit = false diff --git a/deny.toml b/deny.toml new file mode 100644 index 00000000..ad23fbb3 --- /dev/null +++ b/deny.toml @@ -0,0 +1,135 @@ +# Note that all fields that take a lint level have these possible values: +# * deny - An error will be produced and the check will fail +# * warn - A warning will be produced, but the check will not fail +# * allow - No warning or error will be produced, though in some cases a note +# will be + +# This section is considered when running `cargo deny check advisories` +# More documentation for the advisories section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html +[advisories] +# The lint level for security vulnerabilities +vulnerability = "deny" +# The lint level for unmaintained crates +unmaintained = "warn" +# The lint level for crates that have been yanked from their source registry +yanked = "warn" +# The lint level for crates with security notices. Note that as of +# 2019-12-17 there are no security notice advisories in +# https://github.com/rustsec/advisory-db +notice = "warn" +# A list of advisory IDs to ignore. Note that ignored advisories will still +# output a note when they are encountered. +# +# e.g. "RUSTSEC-0000-0000", +ignore = [ +] + +# This section is considered when running `cargo deny check licenses` +# More documentation for the licenses section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[licenses] +unlicensed = "deny" +# List of explicitly allowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +allow = [ + "MIT", + "Apache-2.0", + #"Apache-2.0 WITH LLVM-exception", +] +# List of explicitly disallowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +deny = [ +] +# Lint level for licenses considered copyleft +copyleft = "deny" +# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses +# * both - The license will be approved if it is both OSI-approved *AND* FSF +# * either - The license will be approved if it is either OSI-approved *OR* FSF +# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF +# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved +# * neither - This predicate is ignored and the default lint level is used +allow-osi-fsf-free = "neither" +# Lint level used when no other predicates are matched +# 1. License isn't in the allow or deny lists +# 2. License isn't copyleft +# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither" +default = "deny" +# The confidence threshold for detecting a license from license text. +# The higher the value, the more closely the license text must be to the +# canonical license text of a valid SPDX license file. +# [possible values: any between 0.0 and 1.0]. +confidence-threshold = 0.8 +# Allow 1 or more licenses on a per-crate basis, so that particular licenses +# aren't accepted for every possible crate as with the normal allow list +exceptions = [ + # Each entry is the crate and version constraint, and its specific allow + # list + #{ allow = ["Zlib"], name = "adler32", version = "*" }, +] + +[licenses.private] +# If true, ignores workspace crates that aren't published, or are only +# published to private registries. +# To see how to mark a crate as unpublished (to the official registry), +# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. +ignore = true + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +# Lint level for when multiple versions of the same crate are detected +multiple-versions = "warn" +# Lint level for when a crate version requirement is `*` +wildcards = "deny" +# The graph highlighting used when creating dotgraphs for crates +# with multiple versions +# * lowest-version - The path to the lowest versioned duplicate is highlighted +# * simplest-path - The path to the version with the fewest edges is highlighted +# * all - Both lowest-version and simplest-path are used +highlight = "all" +# The default lint level for `default` features for crates that are members of +# the workspace that is being checked. This can be overridden by allowing/denying +# `default` on a crate-by-crate basis if desired. +workspace-default-features = "allow" +# The default lint level for `default` features for external crates that are not +# members of the workspace. This can be overridden by allowing/denying `default` +# on a crate-by-crate basis if desired. +external-default-features = "allow" +# List of crates that are allowed. Use with care! +allow = [ + #{ name = "ansi_term", version = "=0.11.0" }, +] +# List of crates to deny +deny = [ + # Each entry the name of a crate and a version range. If version is + # not specified, all versions will be matched. + #{ name = "ansi_term", version = "=0.11.0" }, + # + # Wrapper crates can optionally be specified to allow the crate when it + # is a direct dependency of the otherwise banned crate + #{ name = "ansi_term", version = "=0.11.0", wrappers = [] }, +] + +# This section is considered when running `cargo deny check sources`. +# More documentation about the 'sources' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html +[sources] +# Lint level for what to happen when a crate from a crate registry that is not +# in the allow list is encountered +unknown-registry = "deny" +# Lint level for what to happen when a crate from a git repository that is not +# in the allow list is encountered +unknown-git = "deny" +# List of URLs for allowed crate registries. Defaults to the crates.io index +# if not specified. If it is specified but empty, no registries are allowed. +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +# List of URLs for allowed Git repositories +allow-git = [] + +[sources.allow-org] +# 1 or more github.com organizations to allow git sources for +github = [] diff --git a/release.toml b/release.toml new file mode 100644 index 00000000..16df989c --- /dev/null +++ b/release.toml @@ -0,0 +1,6 @@ +pre-release-commit-message = "chore: Release" +tag-message = "{{tag_name}}" +tag-name = "{{prefix}}v{{version}}" +consolidate-commits = true +consolidate-pushes = true +allow-branch = ["main"] diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..45bf577c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,2 @@ +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![allow(non_snake_case)] // TODO: Delete me From d6b4446cd761d82313a0e69cf0da82ebfc4084cb Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 29 Mar 2023 14:33:42 -0500 Subject: [PATCH 030/455] docs: Set changelog base --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23a247b5..e378dd7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,4 +8,4 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] - ReleaseDate <!-- next-url --> -[Unreleased]: https://github.com/rust-cli/argfile/compare/v0.1.5...HEAD +[Unreleased]: https://github.com/rust-cli/argfile/compare/e7b7555...HEAD From fbaab420b9e4e01e60522f87e89e2e0a28250c73 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 1 Apr 2023 00:32:08 +0000 Subject: [PATCH 031/455] chore(deps): update msrv to v1.65.0 --- .clippy.toml | 2 +- .github/workflows/ci.yml | 6 +++--- Cargo.toml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.clippy.toml b/.clippy.toml index 16749abd..5c6f9841 100644 --- a/.clippy.toml +++ b/.clippy.toml @@ -1,4 +1,4 @@ -msrv = "1.64.0" # MSRV +msrv = "1.65.0" # MSRV warn-on-all-wildcard-imports = true allow-expect-in-tests = true allow-unwrap-in-tests = true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 783247cb..017d45ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,7 +49,7 @@ jobs: - name: No-default features run: cargo test --workspace --no-default-features msrv: - name: "Check MSRV: 1.64.0" + name: "Check MSRV: 1.65.0" runs-on: ubuntu-latest steps: - name: Checkout repository @@ -57,7 +57,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: 1.64.0 # MSRV + toolchain: 1.65.0 # MSRV - uses: Swatinem/rust-cache@v2 - name: Default features run: cargo check --workspace --all-targets @@ -107,7 +107,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: 1.64.0 # MSRV + toolchain: 1.65.0 # MSRV components: clippy - uses: Swatinem/rust-cache@v2 - name: Install SARIF tools diff --git a/Cargo.toml b/Cargo.toml index 55dc8553..1c84a5ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT OR Apache-2.0" categories = [] keywords = [] edition = "2021" -rust-version = "1.64.0" # MSRV +rust-version = "1.65.0" # MSRV include = [ "build.rs", "src/**/*", From 614b0a2376b9ae6d95a1b768b93d06057f4b82d6 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 29 Mar 2023 14:40:57 -0500 Subject: [PATCH 032/455] docs(contrib): Remove reference to travis --- CONTRIBUTING.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ce840a94..e9d70793 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -67,4 +67,3 @@ When we're ready to release, a project owner should do the following [issues]: https://github.com/ORG/PROJECT/issues [new issue]: https://github.com/ORG/PROJECT/issues/new [all issues]: https://github.com/ORG/PROJECT/issues?utf8=%E2%9C%93&q=is%3Aissue -[travis]: https://github.com/ORG/PROJECT/blob/master/.travis.yml From afeff23549a05cd0e5997f129e5d7a564ec41866 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 29 Mar 2023 14:41:29 -0500 Subject: [PATCH 033/455] chore(ci): Quote strings in yaml --- .github/settings.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/settings.yml b/.github/settings.yml index 0469378d..8ead1bac 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -25,19 +25,19 @@ labels: # Type - name: bug color: '#b60205' - description: Not as expected + description: "Not as expected" - name: enhancement color: '#1d76db' - description: Improve the expected + description: "Improve the expected" # Flavor - name: question color: "#cc317c" - description: Uncertainty is involved + description: "Uncertainty is involved" - name: breaking-change color: "#e99695" - name: good first issue color: '#c2e0c6' - description: Help wanted! + description: "Help wanted!" branches: - name: main From 2768727452315929d88dda7d0686440d8e668736 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 29 Mar 2023 14:46:23 -0500 Subject: [PATCH 034/455] chore: Don't set rustflags by default Doing so can cause unnecessary recompilation --- .cargo/config | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .cargo/config diff --git a/.cargo/config b/.cargo/config deleted file mode 100644 index ba321231..00000000 --- a/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[target.x86_64-pc-windows-msvc] -rustflags = ["-Ctarget-feature=+crt-static"] - -[target.i686-pc-windows-msvc] -rustflags = ["-Ctarget-feature=+crt-static"] From 083884043cc08394c6f91df81e6407721b2dc19e Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 29 Mar 2023 14:51:13 -0500 Subject: [PATCH 035/455] chore: Update release process --- release.toml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/release.toml b/release.toml index 16df989c..160b061b 100644 --- a/release.toml +++ b/release.toml @@ -1,6 +1,2 @@ -pre-release-commit-message = "chore: Release" -tag-message = "{{tag_name}}" -tag-name = "{{prefix}}v{{version}}" -consolidate-commits = true -consolidate-pushes = true +dependent-version = "fix" allow-branch = ["main"] From afd6a45ef73201bf5d5f3d4f0317f432b17c60d0 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 29 Mar 2023 14:53:08 -0500 Subject: [PATCH 036/455] chore: Use workspace inheritance --- Cargo.toml | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1c84a5ee..6e698fb3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,8 @@ -[package] -name = "PROJECT" -version = "0.0.1" -description = "DESCRIPTION" +[workspace] +resolver = "2" + +[workspace.package] license = "MIT OR Apache-2.0" -categories = [] -keywords = [] edition = "2021" rust-version = "1.65.0" # MSRV include = [ @@ -17,6 +15,17 @@ include = [ "examples/**/*" ] +[package] +name = "PROJECT" +version = "0.0.1" +description = "DESCRIPTION" +categories = [] +keywords = [] +license.workspace = true +edition.workspace = true +rust-version.workspace = true +include.workspace = true + [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] From 037f37906dad6d39f9fad371bc9a8ab76e8bd5c4 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 29 Mar 2023 15:07:09 -0500 Subject: [PATCH 037/455] chore(ci): Remove rustfmt/clippy next jobs --- .github/workflows/rust-next.yml | 48 --------------------------------- 1 file changed, 48 deletions(-) diff --git a/.github/workflows/rust-next.yml b/.github/workflows/rust-next.yml index 8faba304..e90121bc 100644 --- a/.github/workflows/rust-next.yml +++ b/.github/workflows/rust-next.yml @@ -38,51 +38,3 @@ jobs: run: cargo test --workspace --all-features - name: No-default features run: cargo test --workspace --no-default-features - rustfmt: - name: rustfmt - strategy: - matrix: - rust: - - stable - - beta - continue-on-error: ${{ matrix.rust != 'stable' }} - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - name: Install Rust - uses: dtolnay/rust-toolchain@stable - with: - toolchain: ${{ matrix.rust }} - components: rustfmt - - uses: Swatinem/rust-cache@v2 - - name: Check formatting - run: cargo fmt --all -- --check - clippy: - name: clippy - runs-on: ubuntu-latest - permissions: - security-events: write # to upload sarif results - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - name: Install Rust - uses: dtolnay/rust-toolchain@stable - with: - toolchain: stable - components: clippy - - uses: Swatinem/rust-cache@v2 - - name: Install SARIF tools - run: cargo install clippy-sarif sarif-fmt - - name: Check - run: > - cargo clippy --workspace --all-features --all-targets --message-format=json -- -D warnings --allow deprecated - | clippy-sarif - | tee clippy-results.sarif - | sarif-fmt - continue-on-error: true - - name: Upload - uses: github/codeql-action/upload-sarif@v2 - with: - sarif_file: clippy-results.sarif - wait-for-processing: true From d1dd4ae94067be2f3158fa46b0e78504705dfb26 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 29 Mar 2023 15:28:54 -0500 Subject: [PATCH 038/455] chore(ci): Expand approved licenses --- deny.toml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/deny.toml b/deny.toml index ad23fbb3..942e08db 100644 --- a/deny.toml +++ b/deny.toml @@ -35,8 +35,12 @@ unlicensed = "deny" # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. allow = [ "MIT", + "MIT-0", "Apache-2.0", - #"Apache-2.0 WITH LLVM-exception", + "BSD-3-Clause", + "MPL-2.0", + "Unicode-DFS-2016", + "CC0-1.0", ] # List of explicitly disallowed licenses # See https://spdx.org/licenses/ for list of possible licenses From 6c8df60dc4015279cef303cab8f4760efb5ebea8 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 29 Mar 2023 22:38:45 -0500 Subject: [PATCH 039/455] chore: Include Cargo.lock --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 6e698fb3..b8ecde11 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ include = [ "build.rs", "src/**/*", "Cargo.toml", + "Cargo.lock", "LICENSE*", "README.md", "benches/**/*", From f7b990b803a4aa448e81a323df3a54e66d2d8df4 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 17 Apr 2023 08:50:19 -0500 Subject: [PATCH 040/455] fix(ci): Fix Renovate regexes --- .github/renovate.json5 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 51faa753..5e8e7e24 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -11,9 +11,9 @@ "^rust-toolchain\\.toml$", "Cargo.toml$", "clippy.toml$", - "\.clippy.toml$", - "^\.github/workflows/ci.yml$", - "^\.github/workflows/rust-next.yml$", + "\\.clippy.toml$", + "^\\.github/workflows/ci.yml$", + "^\\.github/workflows/rust-next.yml$", ], "matchStrings": [ "MSRV.*?(?<currentValue>\\d+\\.\\d+(\\.\\d+)?)", From 4163ad78c72df3a993bea6084fc05c6a2a44b9c2 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 17 Apr 2023 08:51:48 -0500 Subject: [PATCH 041/455] style(ci): Match auto-generated style This will make reviewing auto-update PRs easier --- .github/renovate.json5 | 126 +++++++++++++++++++++++++---------------- 1 file changed, 78 insertions(+), 48 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 5e8e7e24..0393074e 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -1,35 +1,39 @@ { - "schedule": [ - "before 3am on the first day of the month" + schedule: [ + 'before 3am on the first day of the month' ], - "semanticCommits": "enabled", - "configMigration": true, - "dependencyDashboard": true, - "regexManagers": [ + semanticCommits: 'enabled', + configMigration: true, + dependencyDashboard: true, + regexManagers: [ { - "fileMatch": [ - "^rust-toolchain\\.toml$", - "Cargo.toml$", - "clippy.toml$", - "\\.clippy.toml$", - "^\\.github/workflows/ci.yml$", - "^\\.github/workflows/rust-next.yml$", - ], - "matchStrings": [ - "MSRV.*?(?<currentValue>\\d+\\.\\d+(\\.\\d+)?)", - "(?<currentValue>\\d+\\.\\d+(\\.\\d+)?).*?MSRV", - ], - "depNameTemplate": "rust", - "packageNameTemplate": "rust-lang/rust", - "datasourceTemplate": "github-releases", + fileMatch: [ + '^rust-toolchain\\.toml$', + 'Cargo.toml$', + 'clippy.toml$', + '\\.clippy.toml$', + '^\\.github/workflows/ci.yml$', + '^\\.github/workflows/rust-next.yml$', + ], + matchStrings: [ + 'MSRV.*?(?<currentValue>\\d+\\.\\d+(\\.\\d+)?)', + '(?<currentValue>\\d+\\.\\d+(\\.\\d+)?).*?MSRV', + ], + depNameTemplate: 'rust', + packageNameTemplate: 'rust-lang/rust', + datasourceTemplate: 'github-releases', } ], - "packageRules": [ + packageRules: [ { - "commitMessageTopic": "MSRV", - "matchManagers": ["regex"], - "matchPackageNames": ["rust"], - "stabilityDays": 126, // 3 releases * 6 weeks per release * 7 days per week + commitMessageTopic: 'MSRV', + matchManagers: [ + 'regex', + ], + matchPackageNames: [ + 'rust', + ], + stabilityDays: 126, // 3 releases * 6 weeks per release * 7 days per week }, // Goals: // - Keep version reqs low, ignoring compatible normal/build dependencies @@ -38,34 +42,60 @@ // - Help keep number of versions down by always using latest breaking change // - Have lockfile and manifest in-sync { - "matchManagers": ["cargo"], - "matchDepTypes": ["build-dependencies", "dependencies"], - "matchCurrentVersion": ">=0.1.0", - "matchUpdateTypes": ["patch"], - "enabled": false, + matchManagers: [ + 'cargo', + ], + matchDepTypes: [ + 'build-dependencies', + 'dependencies', + ], + matchCurrentVersion: '>=0.1.0', + matchUpdateTypes: [ + 'patch', + ], + enabled: false, }, { - "matchManagers": ["cargo"], - "matchDepTypes": ["build-dependencies", "dependencies"], - "matchCurrentVersion": ">=1.0.0", - "matchUpdateTypes": ["minor"], - "enabled": false, + matchManagers: [ + 'cargo', + ], + matchDepTypes: [ + 'build-dependencies', + 'dependencies', + ], + matchCurrentVersion: '>=1.0.0', + matchUpdateTypes: [ + 'minor', + ], + enabled: false, }, { - "matchManagers": ["cargo"], - "matchDepTypes": ["dev-dependencies"], - "matchCurrentVersion": ">=0.1.0", - "matchUpdateTypes": ["patch"], - "automerge": true, - "groupName": "compatible (dev)", + matchManagers: [ + 'cargo', + ], + matchDepTypes: [ + 'dev-dependencies', + ], + matchCurrentVersion: '>=0.1.0', + matchUpdateTypes: [ + 'patch', + ], + automerge: true, + groupName: 'compatible (dev)', }, { - "matchManagers": ["cargo"], - "matchDepTypes": ["dev-dependencies"], - "matchCurrentVersion": ">=1.0.0", - "matchUpdateTypes": ["minor"], - "automerge": true, - "groupName": "compatible (dev)", + matchManagers: [ + 'cargo', + ], + matchDepTypes: [ + 'dev-dependencies', + ], + matchCurrentVersion: '>=1.0.0', + matchUpdateTypes: [ + 'minor', + ], + automerge: true, + groupName: 'compatible (dev)', }, ], } From 563de12d25e777e7244a73308090adcfb8b90014 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 17 Apr 2023 09:01:54 -0500 Subject: [PATCH 042/455] chore(ci): Update stabilidyDays to new syntax --- .github/renovate.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 0393074e..d5485d2c 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -33,7 +33,7 @@ matchPackageNames: [ 'rust', ], - stabilityDays: 126, // 3 releases * 6 weeks per release * 7 days per week + stabilityDays: "126 days", // 3 releases * 6 weeks per release * 7 days per week }, // Goals: // - Keep version reqs low, ignoring compatible normal/build dependencies From 2c4a7f574f6fed6655e8b2f25916c22d7bf08ad1 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 17 Apr 2023 09:02:40 -0500 Subject: [PATCH 043/455] chore(ci): Delay Renovate PRs until ready --- .github/renovate.json5 | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index d5485d2c..0e8f1d62 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -34,6 +34,7 @@ 'rust', ], stabilityDays: "126 days", // 3 releases * 6 weeks per release * 7 days per week + internalChecksFilter: "strict", }, // Goals: // - Keep version reqs low, ignoring compatible normal/build dependencies From 62401b8eafb71d8a928137f6f8dfc25340e39bbf Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 17 Apr 2023 09:05:31 -0500 Subject: [PATCH 044/455] chore(ci): Lower the MSRV churn for template --- .github/renovate.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 0e8f1d62..900feaf9 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -33,7 +33,7 @@ matchPackageNames: [ 'rust', ], - stabilityDays: "126 days", // 3 releases * 6 weeks per release * 7 days per week + stabilityDays: "336 days", // 8 releases * 6 weeks per release * 7 days per week internalChecksFilter: "strict", }, // Goals: From d99db2e632b25a8b020491c3e1d40bf2efd3472a Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 17 Apr 2023 09:54:05 -0500 Subject: [PATCH 045/455] style(ci): Match auto-generated style --- .github/renovate.json5 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 900feaf9..54bc5935 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -1,6 +1,6 @@ { schedule: [ - 'before 3am on the first day of the month' + 'before 3am on the first day of the month', ], semanticCommits: 'enabled', configMigration: true, @@ -22,7 +22,7 @@ depNameTemplate: 'rust', packageNameTemplate: 'rust-lang/rust', datasourceTemplate: 'github-releases', - } + }, ], packageRules: [ { From afaba35d39c75d13138e2928cddeb0b93601cee3 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 17 Apr 2023 09:54:21 -0500 Subject: [PATCH 046/455] chore(ci): Use new minimumReleaseAge field --- .github/renovate.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 54bc5935..79e5152c 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -33,7 +33,7 @@ matchPackageNames: [ 'rust', ], - stabilityDays: "336 days", // 8 releases * 6 weeks per release * 7 days per week + minimumReleaseAge: "336 days", // 8 releases * 6 weeks per release * 7 days per week internalChecksFilter: "strict", }, // Goals: From 60a8ec89e3f97baad0dbe097e03dc0cd30899e02 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 17 Apr 2023 20:03:56 -0500 Subject: [PATCH 047/455] chore(ci): Ban for_each --- .clippy.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.clippy.toml b/.clippy.toml index 5c6f9841..56d269a8 100644 --- a/.clippy.toml +++ b/.clippy.toml @@ -9,4 +9,6 @@ disallowed-methods = [ { path = "std::option::Option::map_or_else", reason = "use `map(..).unwrap_or_else(..)`" }, { path = "std::result::Result::map_or", reason = "use `map(..).unwrap_or(..)`" }, { path = "std::result::Result::map_or_else", reason = "use `map(..).unwrap_or_else(..)`" }, + { path = "std::iter::Iterator::for_each", reason = "prefer `for` for side-effects" }, + { path = "std::iter::Iterator::try_for_each", reason = "prefer `for` for side-effects" }, ] From 96297f038d8d931bb9d5ba4dfcdced18d7c81061 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 17 Apr 2023 20:04:56 -0500 Subject: [PATCH 048/455] chore(ci): Clarify why map_or is banned --- .clippy.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.clippy.toml b/.clippy.toml index 56d269a8..22fe10b7 100644 --- a/.clippy.toml +++ b/.clippy.toml @@ -5,10 +5,10 @@ allow-unwrap-in-tests = true allow-dbg-in-tests = true allow-print-in-tests = true disallowed-methods = [ - { path = "std::option::Option::map_or", reason = "use `map(..).unwrap_or(..)`" }, - { path = "std::option::Option::map_or_else", reason = "use `map(..).unwrap_or_else(..)`" }, - { path = "std::result::Result::map_or", reason = "use `map(..).unwrap_or(..)`" }, - { path = "std::result::Result::map_or_else", reason = "use `map(..).unwrap_or_else(..)`" }, + { path = "std::option::Option::map_or", reason = "prefer `map(..).unwrap_or(..)` for legibility" }, + { path = "std::option::Option::map_or_else", reason = "prefer `map(..).unwrap_or_else(..)` for legibility" }, + { path = "std::result::Result::map_or", reason = "prefer `map(..).unwrap_or(..)` for legibility" }, + { path = "std::result::Result::map_or_else", reason = "prefer `map(..).unwrap_or_else(..)` for legibility" }, { path = "std::iter::Iterator::for_each", reason = "prefer `for` for side-effects" }, { path = "std::iter::Iterator::try_for_each", reason = "prefer `for` for side-effects" }, ] From 716170eaa853ddf3032baa9b107eb3e44d6a4124 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 17 Apr 2023 20:13:36 -0500 Subject: [PATCH 049/455] chore(gh): Ban rebase merges --- .github/settings.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/settings.yml b/.github/settings.yml index 8ead1bac..7d5e4fce 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -10,9 +10,12 @@ repository: has_downloads: true default_branch: main - allow_squash_merge: true + # Preference: people do clean commits allow_merge_commit: true - allow_rebase_merge: true + # Backup in case we need to clean up commits + allow_squash_merge: true + # Not really needed + allow_rebase_merge: false allow_auto_merge: true delete_branch_on_merge: true From 80d4cdd688e88b897f384b770f9c13268ecb3793 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 18 May 2023 14:57:02 -0500 Subject: [PATCH 050/455] chore: Remove clippy lint past MSRV (needs 1.67) --- .clippy.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/.clippy.toml b/.clippy.toml index 22fe10b7..090e2bec 100644 --- a/.clippy.toml +++ b/.clippy.toml @@ -3,7 +3,6 @@ warn-on-all-wildcard-imports = true allow-expect-in-tests = true allow-unwrap-in-tests = true allow-dbg-in-tests = true -allow-print-in-tests = true disallowed-methods = [ { path = "std::option::Option::map_or", reason = "prefer `map(..).unwrap_or(..)` for legibility" }, { path = "std::option::Option::map_or_else", reason = "prefer `map(..).unwrap_or_else(..)` for legibility" }, From 2b6bb28cd18916a6244a2632a6abcba9362b9fd0 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 18 May 2023 14:58:59 -0500 Subject: [PATCH 051/455] chore(ci): Catch clippy config failures --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 017d45ec..a7bb3256 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -126,3 +126,5 @@ jobs: with: sarif_file: clippy-results.sarif wait-for-processing: true + - name: Report status + run: cargo clippy --workspace --all-features --all-targets -- -D warnings --allow deprecated From 4d44cd7ca51f05fb06185677642d73c0ff0da079 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Fri, 19 May 2023 13:12:26 -0500 Subject: [PATCH 052/455] chore: Update precommit hooks --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f751dec5..fd77abba 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.4.0 hooks: - id: check-yaml stages: [commit] @@ -15,12 +15,12 @@ repos: - id: detect-private-key stages: [commit] - repo: https://github.com/crate-ci/typos - rev: v1.11.1 + rev: v1.14.10 hooks: - id: typos stages: [commit] - repo: https://github.com/crate-ci/committed - rev: v1.0.4 + rev: v1.0.17 hooks: - id: committed stages: [commit-msg] From d6075a44bff9073c811510e86d73216baa844a69 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 2 Aug 2023 11:11:52 -0500 Subject: [PATCH 053/455] chore: Expand update window so more likely to be hit --- .github/renovate.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 79e5152c..e5733ed2 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -1,6 +1,6 @@ { schedule: [ - 'before 3am on the first day of the month', + 'before 5am on the first day of the month', ], semanticCommits: 'enabled', configMigration: true, From 67eb1d9e3d396cc7f786d767e287d7e946ed3118 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 7 Aug 2023 16:16:17 -0500 Subject: [PATCH 054/455] chore(ci): Ensure lockfile isn't stale --- .github/workflows/ci.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a7bb3256..26c9b0f4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,6 +65,18 @@ jobs: run: cargo check --workspace --all-targets --all-features - name: No-default features run: cargo check --workspace --all-targets --no-default-features + lockfile: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + - uses: Swatinem/rust-cache@v2 + - name: "Is lockfile updated?" + run: cargo fetch --locked docs: name: Docs runs-on: ubuntu-latest From ba76b8bd911b98ab78fec3cf6c8e7ee679721a6f Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Fri, 11 Aug 2023 13:29:06 -0500 Subject: [PATCH 055/455] chore(ci): Ensure latest deps are good --- .github/workflows/rust-next.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/rust-next.yml b/.github/workflows/rust-next.yml index e90121bc..a540ba58 100644 --- a/.github/workflows/rust-next.yml +++ b/.github/workflows/rust-next.yml @@ -38,3 +38,22 @@ jobs: run: cargo test --workspace --all-features - name: No-default features run: cargo test --workspace --no-default-features + latest: + name: "Check latest dependencies" + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + - uses: Swatinem/rust-cache@v2 + - name: Update dependencues + run: cargo update + - name: Default features + run: cargo test --workspace --all-targets + - name: All features + run: cargo test --workspace --all-targets --all-features + - name: No-default features + run: cargo test --workspace --all-targets --no-default-features From 528638729492300730aebee283d2a837325b4a62 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Fri, 11 Aug 2023 16:04:07 -0500 Subject: [PATCH 056/455] chore: Update pre-commit hooks --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fd77abba..3d9e40fd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,12 +15,12 @@ repos: - id: detect-private-key stages: [commit] - repo: https://github.com/crate-ci/typos - rev: v1.14.10 + rev: v1.16.3 hooks: - id: typos stages: [commit] - repo: https://github.com/crate-ci/committed - rev: v1.0.17 + rev: v1.0.20 hooks: - id: committed stages: [commit-msg] From efe14d60899ec75c901c88b46174ccd3fc5e14d8 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Tue, 22 Aug 2023 11:06:55 -0500 Subject: [PATCH 057/455] chore(renovate): Make style consistent --- .github/renovate.json5 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index e5733ed2..8e31ad0f 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -33,8 +33,8 @@ matchPackageNames: [ 'rust', ], - minimumReleaseAge: "336 days", // 8 releases * 6 weeks per release * 7 days per week - internalChecksFilter: "strict", + minimumReleaseAge: '336 days', // 8 releases * 6 weeks per release * 7 days per week + internalChecksFilter: 'strict', }, // Goals: // - Keep version reqs low, ignoring compatible normal/build dependencies From a6ecf92327e4c75e6545cdd238cc40171337c403 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Tue, 22 Aug 2023 11:07:34 -0500 Subject: [PATCH 058/455] chore(renovate): Update config --- .github/renovate.json5 | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 8e31ad0f..7b75c589 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -7,6 +7,7 @@ dependencyDashboard: true, regexManagers: [ { + customType: 'regex', fileMatch: [ '^rust-toolchain\\.toml$', 'Cargo.toml$', From c8624f0538c30bb5498db489f456af6012988bdb Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 23 Aug 2023 09:24:15 -0500 Subject: [PATCH 059/455] chore(renovate): Update MSRV on release --- .github/renovate.json5 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 7b75c589..a367a473 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -36,6 +36,9 @@ ], minimumReleaseAge: '336 days', // 8 releases * 6 weeks per release * 7 days per week internalChecksFilter: 'strict', + schedule: [ + 'every day', + ], }, // Goals: // - Keep version reqs low, ignoring compatible normal/build dependencies From 44604fc1d369e255c7edd0954979310131e24fa4 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 23 Aug 2023 09:35:47 -0500 Subject: [PATCH 060/455] chore(renovate): Try to fix schedule --- .github/renovate.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index a367a473..28e23ee1 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -37,7 +37,7 @@ minimumReleaseAge: '336 days', // 8 releases * 6 weeks per release * 7 days per week internalChecksFilter: 'strict', schedule: [ - 'every day', + '* * * * *', ], }, // Goals: From ff82d6960a9903e3c62414cdacc9b2fa0a7ce2cb Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 23 Aug 2023 10:43:57 -0500 Subject: [PATCH 061/455] chore(ci): Don't fail on wildcards See EmbarkStudios/cargo-deny#241 --- deny.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deny.toml b/deny.toml index 942e08db..58cc98d5 100644 --- a/deny.toml +++ b/deny.toml @@ -88,7 +88,7 @@ ignore = true # Lint level for when multiple versions of the same crate are detected multiple-versions = "warn" # Lint level for when a crate version requirement is `*` -wildcards = "deny" +wildcards = "warn" # The graph highlighting used when creating dotgraphs for crates # with multiple versions # * lowest-version - The path to the lowest versioned duplicate is highlighted From 5749aa0932d42cd0ee484b6cb9fcf6f6dd026749 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 7 Sep 2023 09:33:44 -0500 Subject: [PATCH 062/455] chore: Approve ISC --- deny.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/deny.toml b/deny.toml index 58cc98d5..21fa937f 100644 --- a/deny.toml +++ b/deny.toml @@ -41,6 +41,7 @@ allow = [ "MPL-2.0", "Unicode-DFS-2016", "CC0-1.0", + "ISC", ] # List of explicitly disallowed licenses # See https://spdx.org/licenses/ for list of possible licenses From 4173c8f4767296f76a6eb96d70b7ca6c13bb38bd Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 20 Sep 2023 09:05:41 -0500 Subject: [PATCH 063/455] chore(ci): Don't set patch for MSRV --- .github/renovate.json5 | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 28e23ee1..56cf1e33 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -36,6 +36,7 @@ ], minimumReleaseAge: '336 days', // 8 releases * 6 weeks per release * 7 days per week internalChecksFilter: 'strict', + "extractVersion": "^(?<version>\\d+\\.\\d+)", // Drop the patch version schedule: [ '* * * * *', ], From 86c29dea384c7392a2b682fa0150f52c0f4c7f00 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Tue, 26 Sep 2023 08:16:33 -0500 Subject: [PATCH 064/455] chore(ci): Updaet Renovate schema --- .github/renovate.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 56cf1e33..4eaf67fb 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -5,7 +5,7 @@ semanticCommits: 'enabled', configMigration: true, dependencyDashboard: true, - regexManagers: [ + customManagers: [ { customType: 'regex', fileMatch: [ From ac51f0925003597dec21529538597dbd7872d1ac Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Tue, 26 Sep 2023 08:16:47 -0500 Subject: [PATCH 065/455] chore(ci): Normalize json5 syntax --- .github/renovate.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 4eaf67fb..72d05795 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -36,7 +36,7 @@ ], minimumReleaseAge: '336 days', // 8 releases * 6 weeks per release * 7 days per week internalChecksFilter: 'strict', - "extractVersion": "^(?<version>\\d+\\.\\d+)", // Drop the patch version + extractVersion: '^(?<version>\\d+\\.\\d+)', // Drop the patch version schedule: [ '* * * * *', ], From 305798083f34bb57951fb6351aa6b897790907eb Mon Sep 17 00:00:00 2001 From: Peter Kehl <peter.kehl@gmail.com> Date: Fri, 29 Sep 2023 22:59:44 -0700 Subject: [PATCH 066/455] README.md 'Crates Status' icon link now uses the Markdown placeholder/substitution name 'Crates.io' --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 41d5a974..6958ee06 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [][Documentation]  -[](https://crates.io/crates/PROJECT) +[][Crates.io] ## License From cad9b4717162cc4dbd4253227fc9c5705a302758 Mon Sep 17 00:00:00 2001 From: Peter Kehl <peter.kehl@gmail.com> Date: Fri, 29 Sep 2023 23:04:45 -0700 Subject: [PATCH 067/455] README.md list indentation and no bare URLs, as per Markdown Lint VS Code extension. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 41d5a974..6f307686 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,8 @@ Licensed under either of - * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) - * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) +* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or <http://www.apache.org/licenses/LICENSE-2.0>) +* MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>) at your option. From 6d3f888975aedf79e10336c8090d6aab20751b10 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 1 Oct 2023 01:37:07 +0000 Subject: [PATCH 068/455] chore(deps): update actions/checkout action to v4 --- .github/workflows/audit.yml | 4 ++-- .github/workflows/ci.yml | 12 ++++++------ .github/workflows/committed.yml | 2 +- .github/workflows/pre-commit.yml | 2 +- .github/workflows/rust-next.yml | 4 ++-- .github/workflows/spelling.yml | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 5b7e83ac..ccee1fef 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -27,7 +27,7 @@ jobs: continue-on-error: true steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - uses: actions-rs/audit-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} @@ -42,7 +42,7 @@ jobs: checks: - bans licenses sources steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: EmbarkStudios/cargo-deny-action@v1 with: command: check ${{ matrix.checks }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 26c9b0f4..1a703726 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-toolchain@stable with: @@ -53,7 +53,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-toolchain@stable with: @@ -69,7 +69,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-toolchain@stable with: @@ -82,7 +82,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-toolchain@stable with: @@ -97,7 +97,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-toolchain@stable with: @@ -115,7 +115,7 @@ jobs: security-events: write # to upload sarif results steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-toolchain@stable with: diff --git a/.github/workflows/committed.yml b/.github/workflows/committed.yml index 509be080..04625584 100644 --- a/.github/workflows/committed.yml +++ b/.github/workflows/committed.yml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Actions Repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Lint Commits diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index d4b0f84a..80447507 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -18,6 +18,6 @@ jobs: contents: read runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-python@v4 - uses: pre-commit/action@v3.0.0 diff --git a/.github/workflows/rust-next.yml b/.github/workflows/rust-next.yml index a540ba58..d8c2d257 100644 --- a/.github/workflows/rust-next.yml +++ b/.github/workflows/rust-next.yml @@ -26,7 +26,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-toolchain@stable with: @@ -43,7 +43,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-toolchain@stable with: diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml index f31c7ed8..12f75859 100644 --- a/.github/workflows/spelling.yml +++ b/.github/workflows/spelling.yml @@ -16,6 +16,6 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Actions Repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Spell Check Repo uses: crate-ci/typos@master From 5e3b324b5e6488667be2f00a424781030e37a277 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 5 Oct 2023 14:41:40 -0500 Subject: [PATCH 069/455] chore(ci): Ensure MSRV is quoted Switching from specifying patch to not, with a minor version with a trailing zero, is causing YAML to convert `1.70` to `1.7`. --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1a703726..19efcf9f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,7 +57,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: 1.65.0 # MSRV + toolchain: "1.65.0" # MSRV - uses: Swatinem/rust-cache@v2 - name: Default features run: cargo check --workspace --all-targets @@ -119,7 +119,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: 1.65.0 # MSRV + toolchain: "1.65.0" # MSRV components: clippy - uses: Swatinem/rust-cache@v2 - name: Install SARIF tools From 5ebe30b9722ac700d414043ff099bad7f3978582 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 26 Oct 2023 09:14:51 -0500 Subject: [PATCH 070/455] chore(ci): Update pre-commit hooks --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3d9e40fd..68db968e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-yaml stages: [commit] @@ -15,7 +15,7 @@ repos: - id: detect-private-key stages: [commit] - repo: https://github.com/crate-ci/typos - rev: v1.16.3 + rev: v1.16.20 hooks: - id: typos stages: [commit] From bc504bc3ef6c856152d63d6304845014d046a2bd Mon Sep 17 00:00:00 2001 From: Zibi Braniecki <zibi@amazon.com> Date: Mon, 30 Oct 2023 12:58:50 +0100 Subject: [PATCH 071/455] annotate-snippets 0.9.2 --- CHANGELOG.md | 5 +++++ Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21e5f2fe..c66ad41e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ - … +## annotate-snippets 0.9.2 (October 30, 2023) + + - Remove parsing of __ in title strings, fixes (#53) + - Origin line number is not correct when using a slice with fold: true (#52) + ## annotate-snippets 0.9.1 (September 4, 2021) - Fix character split when strip code. (#37) diff --git a/Cargo.toml b/Cargo.toml index b7a97fd0..e4a23517 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "annotate-snippets" -version = "0.9.1" +version = "0.9.2" edition = "2018" authors = ["Zibi Braniecki <gandalf@mozilla.com>"] description = "Library for building code annotations" From c777fb52573071308399b7fa781b0daf110c0814 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 8 Nov 2023 09:51:18 -0600 Subject: [PATCH 072/455] style: Make clippy happy --- src/display_list/from_snippet.rs | 273 +++++++++++++++---------------- tests/dl_from_snippet.rs | 4 +- tests/fixtures_test.rs | 4 +- tests/snippet/mod.rs | 6 +- 4 files changed, 142 insertions(+), 145 deletions(-) diff --git a/src/display_list/from_snippet.rs b/src/display_list/from_snippet.rs index d7c1ac0a..4734147a 100644 --- a/src/display_list/from_snippet.rs +++ b/src/display_list/from_snippet.rs @@ -11,9 +11,9 @@ impl<'a> CursorLines<'a> { } enum EndLine { - EOF = 0, - CRLF = 1, - LF = 2, + Eof = 0, + Crlf = 1, + Lf = 2, } impl<'a> Iterator for CursorLines<'a> { @@ -28,18 +28,18 @@ impl<'a> Iterator for CursorLines<'a> { .map(|x| { let ret = if 0 < x { if self.0.as_bytes()[x - 1] == b'\r' { - (&self.0[..x - 1], EndLine::LF) + (&self.0[..x - 1], EndLine::Lf) } else { - (&self.0[..x], EndLine::CRLF) + (&self.0[..x], EndLine::Crlf) } } else { - ("", EndLine::CRLF) + ("", EndLine::Crlf) }; self.0 = &self.0[x + 1..]; ret }) .or_else(|| { - let ret = Some((self.0, EndLine::EOF)); + let ret = Some((self.0, EndLine::Eof)); self.0 = ""; ret }) @@ -330,171 +330,168 @@ fn format_body( .map(|m| m.left(line_end_index - line_start_index)) .unwrap_or_default(); // It would be nice to use filter_drain here once it's stable. - annotations = annotations - .into_iter() - .filter(|annotation| { - let body_idx = idx + annotation_line_count; - let annotation_type = match annotation.annotation_type { - snippet::AnnotationType::Error => DisplayAnnotationType::None, - snippet::AnnotationType::Warning => DisplayAnnotationType::None, - _ => DisplayAnnotationType::from(annotation.annotation_type), - }; - match annotation.range { - (start, _) if start > line_end_index => true, - (start, end) - if start >= line_start_index && end <= line_end_index - || start == line_end_index && end - start <= 1 => - { - let annotation_start_col = char_widths - .iter() - .take(start - line_start_index) - .sum::<usize>() - - margin_left; - let annotation_end_col = char_widths - .iter() - .take(end - line_start_index) - .sum::<usize>() - - margin_left; - let range = (annotation_start_col, annotation_end_col); - body.insert( - body_idx + 1, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - annotation: Annotation { - annotation_type, - id: None, - label: format_label(Some(annotation.label), None), - }, - range, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), - annotation_part: DisplayAnnotationPart::Standalone, - }, - }, - ); - annotation_line_count += 1; - false - } - (start, end) - if start >= line_start_index - && start <= line_end_index - && end > line_end_index => - { - if start - line_start_index == 0 { - if let DisplayLine::Source { - ref mut inline_marks, - .. - } = body[body_idx] - { - inline_marks.push(DisplayMark { - mark_type: DisplayMarkType::AnnotationStart, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), - }); - } - } else { - let annotation_start_col = char_widths - .iter() - .take(start - line_start_index) - .sum::<usize>(); - let range = (annotation_start_col, annotation_start_col + 1); - body.insert( - body_idx + 1, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::None, - id: None, - label: vec![], - }, - range, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), - annotation_part: DisplayAnnotationPart::MultilineStart, - }, + annotations.retain(|annotation| { + let body_idx = idx + annotation_line_count; + let annotation_type = match annotation.annotation_type { + snippet::AnnotationType::Error => DisplayAnnotationType::None, + snippet::AnnotationType::Warning => DisplayAnnotationType::None, + _ => DisplayAnnotationType::from(annotation.annotation_type), + }; + match annotation.range { + (start, _) if start > line_end_index => true, + (start, end) + if start >= line_start_index && end <= line_end_index + || start == line_end_index && end - start <= 1 => + { + let annotation_start_col = char_widths + .iter() + .take(start - line_start_index) + .sum::<usize>() + - margin_left; + let annotation_end_col = char_widths + .iter() + .take(end - line_start_index) + .sum::<usize>() + - margin_left; + let range = (annotation_start_col, annotation_end_col); + body.insert( + body_idx + 1, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Annotation { + annotation: Annotation { + annotation_type, + id: None, + label: format_label(Some(annotation.label), None), }, - ); - annotation_line_count += 1; - } - true - } - (start, end) if start < line_start_index && end > line_end_index => { - if let DisplayLine::Source { - ref mut inline_marks, - .. - } = body[body_idx] - { - inline_marks.push(DisplayMark { - mark_type: DisplayMarkType::AnnotationThrough, + range, annotation_type: DisplayAnnotationType::from( annotation.annotation_type, ), - }); - } - true - } - (start, end) - if start < line_start_index - && end >= line_start_index - && end <= line_end_index => - { + annotation_part: DisplayAnnotationPart::Standalone, + }, + }, + ); + annotation_line_count += 1; + false + } + (start, end) + if start >= line_start_index + && start <= line_end_index + && end > line_end_index => + { + if start - line_start_index == 0 { if let DisplayLine::Source { ref mut inline_marks, .. } = body[body_idx] { inline_marks.push(DisplayMark { - mark_type: DisplayMarkType::AnnotationThrough, + mark_type: DisplayMarkType::AnnotationStart, annotation_type: DisplayAnnotationType::from( annotation.annotation_type, ), }); } - - let end_mark = char_widths + } else { + let annotation_start_col = char_widths .iter() - .take(end - line_start_index) - .sum::<usize>() - .saturating_sub(1); - let range = (end_mark - margin_left, (end_mark + 1) - margin_left); + .take(start - line_start_index) + .sum::<usize>(); + let range = (annotation_start_col, annotation_start_col + 1); body.insert( body_idx + 1, DisplayLine::Source { lineno: None, - inline_marks: vec![DisplayMark { - mark_type: DisplayMarkType::AnnotationThrough, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), - }], + inline_marks: vec![], line: DisplaySourceLine::Annotation { annotation: Annotation { - annotation_type, + annotation_type: DisplayAnnotationType::None, id: None, - label: format_label(Some(annotation.label), None), + label: vec![], }, range, annotation_type: DisplayAnnotationType::from( annotation.annotation_type, ), - annotation_part: DisplayAnnotationPart::MultilineEnd, + annotation_part: DisplayAnnotationPart::MultilineStart, }, }, ); annotation_line_count += 1; - false } - _ => true, + true + } + (start, end) if start < line_start_index && end > line_end_index => { + if let DisplayLine::Source { + ref mut inline_marks, + .. + } = body[body_idx] + { + inline_marks.push(DisplayMark { + mark_type: DisplayMarkType::AnnotationThrough, + annotation_type: DisplayAnnotationType::from( + annotation.annotation_type, + ), + }); + } + true } - }) - .collect(); + (start, end) + if start < line_start_index + && end >= line_start_index + && end <= line_end_index => + { + if let DisplayLine::Source { + ref mut inline_marks, + .. + } = body[body_idx] + { + inline_marks.push(DisplayMark { + mark_type: DisplayMarkType::AnnotationThrough, + annotation_type: DisplayAnnotationType::from( + annotation.annotation_type, + ), + }); + } + + let end_mark = char_widths + .iter() + .take(end - line_start_index) + .sum::<usize>() + .saturating_sub(1); + let range = (end_mark - margin_left, (end_mark + 1) - margin_left); + body.insert( + body_idx + 1, + DisplayLine::Source { + lineno: None, + inline_marks: vec![DisplayMark { + mark_type: DisplayMarkType::AnnotationThrough, + annotation_type: DisplayAnnotationType::from( + annotation.annotation_type, + ), + }], + line: DisplaySourceLine::Annotation { + annotation: Annotation { + annotation_type, + id: None, + label: format_label(Some(annotation.label), None), + }, + range, + annotation_type: DisplayAnnotationType::from( + annotation.annotation_type, + ), + annotation_part: DisplayAnnotationPart::MultilineEnd, + }, + }, + ); + annotation_line_count += 1; + false + } + _ => true, + } + }); } if slice.fold { diff --git a/tests/dl_from_snippet.rs b/tests/dl_from_snippet.rs index d6b79ff2..06f2ff76 100644 --- a/tests/dl_from_snippet.rs +++ b/tests/dl_from_snippet.rs @@ -37,7 +37,7 @@ fn test_format_title() { fn test_format_slice() { let line_1 = "This is line 1"; let line_2 = "This is line 2"; - let source = vec![line_1, line_2].join("\n"); + let source = [line_1, line_2].join("\n"); let input = snippet::Snippet { title: None, footer: vec![], @@ -173,7 +173,7 @@ fn test_format_slices_continuation() { fn test_format_slice_annotation_standalone() { let line_1 = "This is line 1"; let line_2 = "This is line 2"; - let source = vec![line_1, line_2].join("\n"); + let source = [line_1, line_2].join("\n"); // In line 2 let range = (22, 24); let input = snippet::Snippet { diff --git a/tests/fixtures_test.rs b/tests/fixtures_test.rs index e471521f..ffc38f92 100644 --- a/tests/fixtures_test.rs +++ b/tests/fixtures_test.rs @@ -13,7 +13,7 @@ fn read_file(path: &str) -> Result<String, io::Error> { Ok(s.trim_end().to_string()) } -fn read_fixture<'de>(src: &'de str) -> Result<Snippet<'de>, Box<dyn Error>> { +fn read_fixture(src: &str) -> Result<Snippet<'_>, Box<dyn Error>> { Ok(toml::from_str(src).map(|a: SnippetDef| a.into())?) } @@ -25,7 +25,7 @@ fn test_fixtures() { let path_in = p.to_str().expect("Can't print path"); let path_out = path_in.replace(".toml", ".txt"); - let src = read_file(&path_in).expect("Failed to read file"); + let src = read_file(path_in).expect("Failed to read file"); let snippet = read_fixture(&src).expect("Failed to read file"); let expected_out = read_file(&path_out).expect("Failed to read file"); diff --git a/tests/snippet/mod.rs b/tests/snippet/mod.rs index 40249f40..92d272de 100644 --- a/tests/snippet/mod.rs +++ b/tests/snippet/mod.rs @@ -23,14 +23,14 @@ pub struct SnippetDef<'a> { pub slices: Vec<Slice<'a>>, } -impl<'a> Into<Snippet<'a>> for SnippetDef<'a> { - fn into(self) -> Snippet<'a> { +impl<'a> From<SnippetDef<'a>> for Snippet<'a> { + fn from(val: SnippetDef<'a>) -> Self { let SnippetDef { title, footer, opt, slices, - } = self; + } = val; Snippet { title, footer, From 807ac679b0d8cd425f664de5516968c00114508f Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 8 Nov 2023 09:47:45 -0600 Subject: [PATCH 073/455] chore(ci): Start tracking golden set of deps --- .gitignore | 4 +- Cargo.lock | 600 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 601 insertions(+), 3 deletions(-) create mode 100644 Cargo.lock diff --git a/.gitignore b/.gitignore index 69369904..eb5a316c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1 @@ -/target -**/*.rs.bk -Cargo.lock +target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..bd6a75dc --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,600 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "annotate-snippets" +version = "0.9.2" +dependencies = [ + "criterion", + "difference", + "glob", + "serde", + "toml", + "unicode-width", + "yansi-term", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "bitflags", + "textwrap", + "unicode-width", +] + +[[package]] +name = "criterion" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" +dependencies = [ + "atty", + "cast", + "clap", + "criterion-plot", + "csv", + "itertools", + "lazy_static", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_cbor", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "csv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + +[[package]] +name = "difference" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "js-sys" +version = "0.3.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + +[[package]] +name = "plotters" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" + +[[package]] +name = "plotters-svg" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rayon" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.192" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.192" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "2.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" + +[[package]] +name = "web-sys" +version = "0.3.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "yansi-term" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe5c30ade05e61656247b2e334a031dfd0cc466fadef865bdcdea8d537951bf1" +dependencies = [ + "winapi", +] From 5061ce2366932dcf26e1f6a4fba8290bf9c1c266 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 8 Nov 2023 09:49:42 -0600 Subject: [PATCH 074/455] chore(ci): Transition to epage's CI pipeline --- .github/workflows/ci.yml | 142 +++++++++++++++++++++++++++++--- .github/workflows/rust-next.yml | 59 +++++++++++++ .travis.yml | 30 ------- tests/fixtures_test.rs | 1 + 4 files changed, 190 insertions(+), 42 deletions(-) create mode 100644 .github/workflows/rust-next.yml delete mode 100644 .travis.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f40ab584..50c2ddfe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,24 +1,142 @@ name: CI +permissions: + contents: read + on: - push: - branches: [ master ] pull_request: - branches: [ master ] + push: + branches: + - master env: + RUST_BACKTRACE: 1 CARGO_TERM_COLOR: always + CLICOLOR: 1 jobs: - build: - + ci: + permissions: + contents: none + name: CI + needs: [test, msrv, docs, rustfmt, clippy] runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Run rustfmt - run: cargo fmt -- --check + - name: Done + run: exit 0 + test: + name: Test + strategy: + matrix: + os: ["ubuntu-latest", "windows-latest", "macos-latest"] + rust: ["stable"] + continue-on-error: ${{ matrix.rust != 'stable' }} + runs-on: ${{ matrix.os }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ matrix.rust }} + - uses: Swatinem/rust-cache@v2 - name: Build - run: cargo build --verbose - - name: Run tests - run: cargo test --verbose + run: cargo test --no-run --workspace --all-features + - name: Default features + run: cargo test --workspace + - name: All features + run: cargo test --workspace --all-features + - name: No-default features + run: cargo test --workspace --no-default-features + msrv: + name: "Check MSRV: 1.70" + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: "1.70" # MSRV + - uses: Swatinem/rust-cache@v2 + - name: Default features + run: cargo check --workspace --all-targets + - name: All features + run: cargo check --workspace --all-targets --all-features + - name: No-default features + run: cargo check --workspace --all-targets --no-default-features + lockfile: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + - uses: Swatinem/rust-cache@v2 + - name: "Is lockfile updated?" + run: cargo fetch --locked + docs: + name: Docs + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + - uses: Swatinem/rust-cache@v2 + - name: Check documentation + env: + RUSTDOCFLAGS: -D warnings + run: cargo doc --workspace --all-features --no-deps --document-private-items + rustfmt: + name: rustfmt + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + # Not MSRV because its harder to jump between versions and people are + # more likely to have stable + toolchain: stable + components: rustfmt + - uses: Swatinem/rust-cache@v2 + - name: Check formatting + run: cargo fmt --all -- --check + clippy: + name: clippy + runs-on: ubuntu-latest + permissions: + security-events: write # to upload sarif results + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: "1.70" # MSRV + components: clippy + - uses: Swatinem/rust-cache@v2 + - name: Install SARIF tools + run: cargo install clippy-sarif --version 0.3.4 --locked # Held back due to msrv + - name: Install SARIF tools + run: cargo install sarif-fmt --version 0.3.4 --locked # Held back due to msrv + - name: Check + run: > + cargo clippy --workspace --all-features --all-targets --message-format=json -- -D warnings --allow deprecated + | clippy-sarif + | tee clippy-results.sarif + | sarif-fmt + continue-on-error: true + - name: Upload + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: clippy-results.sarif + wait-for-processing: true + - name: Report status + run: cargo clippy --workspace --all-features --all-targets -- -D warnings --allow deprecated diff --git a/.github/workflows/rust-next.yml b/.github/workflows/rust-next.yml new file mode 100644 index 00000000..d8c2d257 --- /dev/null +++ b/.github/workflows/rust-next.yml @@ -0,0 +1,59 @@ +name: rust-next + +permissions: + contents: read + +on: + schedule: + - cron: '1 1 1 * *' + +env: + RUST_BACKTRACE: 1 + CARGO_TERM_COLOR: always + CLICOLOR: 1 + +jobs: + test: + name: Test + strategy: + matrix: + os: ["ubuntu-latest", "windows-latest", "macos-latest"] + rust: ["stable", "beta"] + include: + - os: ubuntu-latest + rust: "nightly" + continue-on-error: ${{ matrix.rust != 'stable' }} + runs-on: ${{ matrix.os }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: ${{ matrix.rust }} + - uses: Swatinem/rust-cache@v2 + - name: Default features + run: cargo test --workspace + - name: All features + run: cargo test --workspace --all-features + - name: No-default features + run: cargo test --workspace --no-default-features + latest: + name: "Check latest dependencies" + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + - uses: Swatinem/rust-cache@v2 + - name: Update dependencues + run: cargo update + - name: Default features + run: cargo test --workspace --all-targets + - name: All features + run: cargo test --workspace --all-targets --all-features + - name: No-default features + run: cargo test --workspace --all-targets --no-default-features diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e186cc24..00000000 --- a/.travis.yml +++ /dev/null @@ -1,30 +0,0 @@ -language: rust -sudo: required -dist: trusty -addons: - apt: - packages: - - libssl-dev -cache: cargo -rust: - - stable - - beta - - nightly -matrix: - allow_failures: - - rust: nightly - -before_cache: | - if [[ "$TRAVIS_RUST_VERSION" == nightly ]]; then - RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin -f - fi - -script: -- cargo clean -- cargo build -- cargo test - -after_success: | - if [[ "$TRAVIS_RUST_VERSION" == nightly ]]; then - cargo tarpaulin --ciserver travis-ci --coveralls $TRAVIS_JOB_ID - fi diff --git a/tests/fixtures_test.rs b/tests/fixtures_test.rs index ffc38f92..13f85431 100644 --- a/tests/fixtures_test.rs +++ b/tests/fixtures_test.rs @@ -18,6 +18,7 @@ fn read_fixture(src: &str) -> Result<Snippet<'_>, Box<dyn Error>> { } #[test] +#[cfg(not(windows))] // HACK: Not working on windows due to a serde error fn test_fixtures() { for entry in glob("./tests/fixtures/no-color/**/*.toml").expect("Failed to read glob pattern") { let p = entry.expect("Error while getting an entry"); From 23e561537d795364e0fff729d2cdf6d97b214fe2 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 8 Nov 2023 09:56:18 -0600 Subject: [PATCH 075/455] chore(ci): Use cargo-deny --- .github/workflows/audit.yml | 49 +++++++++++++ deny.toml | 140 ++++++++++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 .github/workflows/audit.yml create mode 100644 deny.toml diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml new file mode 100644 index 00000000..442e6379 --- /dev/null +++ b/.github/workflows/audit.yml @@ -0,0 +1,49 @@ +name: Security audit + +permissions: + contents: read + +on: + pull_request: + paths: + - '**/Cargo.toml' + - '**/Cargo.lock' + push: + branches: + - master + +env: + RUST_BACKTRACE: 1 + CARGO_TERM_COLOR: always + CLICOLOR: 1 + +jobs: + security_audit: + permissions: + issues: write # to create issues (actions-rs/audit-check) + checks: write # to create check (actions-rs/audit-check) + runs-on: ubuntu-latest + # Prevent sudden announcement of a new advisory from failing ci: + continue-on-error: true + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - uses: actions-rs/audit-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + cargo_deny: + permissions: + issues: write # to create issues (actions-rs/audit-check) + checks: write # to create check (actions-rs/audit-check) + runs-on: ubuntu-latest + strategy: + matrix: + checks: + - bans licenses sources + steps: + - uses: actions/checkout@v4 + - uses: EmbarkStudios/cargo-deny-action@v1 + with: + command: check ${{ matrix.checks }} + rust-version: stable diff --git a/deny.toml b/deny.toml new file mode 100644 index 00000000..21fa937f --- /dev/null +++ b/deny.toml @@ -0,0 +1,140 @@ +# Note that all fields that take a lint level have these possible values: +# * deny - An error will be produced and the check will fail +# * warn - A warning will be produced, but the check will not fail +# * allow - No warning or error will be produced, though in some cases a note +# will be + +# This section is considered when running `cargo deny check advisories` +# More documentation for the advisories section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html +[advisories] +# The lint level for security vulnerabilities +vulnerability = "deny" +# The lint level for unmaintained crates +unmaintained = "warn" +# The lint level for crates that have been yanked from their source registry +yanked = "warn" +# The lint level for crates with security notices. Note that as of +# 2019-12-17 there are no security notice advisories in +# https://github.com/rustsec/advisory-db +notice = "warn" +# A list of advisory IDs to ignore. Note that ignored advisories will still +# output a note when they are encountered. +# +# e.g. "RUSTSEC-0000-0000", +ignore = [ +] + +# This section is considered when running `cargo deny check licenses` +# More documentation for the licenses section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html +[licenses] +unlicensed = "deny" +# List of explicitly allowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +allow = [ + "MIT", + "MIT-0", + "Apache-2.0", + "BSD-3-Clause", + "MPL-2.0", + "Unicode-DFS-2016", + "CC0-1.0", + "ISC", +] +# List of explicitly disallowed licenses +# See https://spdx.org/licenses/ for list of possible licenses +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. +deny = [ +] +# Lint level for licenses considered copyleft +copyleft = "deny" +# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses +# * both - The license will be approved if it is both OSI-approved *AND* FSF +# * either - The license will be approved if it is either OSI-approved *OR* FSF +# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF +# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved +# * neither - This predicate is ignored and the default lint level is used +allow-osi-fsf-free = "neither" +# Lint level used when no other predicates are matched +# 1. License isn't in the allow or deny lists +# 2. License isn't copyleft +# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither" +default = "deny" +# The confidence threshold for detecting a license from license text. +# The higher the value, the more closely the license text must be to the +# canonical license text of a valid SPDX license file. +# [possible values: any between 0.0 and 1.0]. +confidence-threshold = 0.8 +# Allow 1 or more licenses on a per-crate basis, so that particular licenses +# aren't accepted for every possible crate as with the normal allow list +exceptions = [ + # Each entry is the crate and version constraint, and its specific allow + # list + #{ allow = ["Zlib"], name = "adler32", version = "*" }, +] + +[licenses.private] +# If true, ignores workspace crates that aren't published, or are only +# published to private registries. +# To see how to mark a crate as unpublished (to the official registry), +# visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. +ignore = true + +# This section is considered when running `cargo deny check bans`. +# More documentation about the 'bans' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html +[bans] +# Lint level for when multiple versions of the same crate are detected +multiple-versions = "warn" +# Lint level for when a crate version requirement is `*` +wildcards = "warn" +# The graph highlighting used when creating dotgraphs for crates +# with multiple versions +# * lowest-version - The path to the lowest versioned duplicate is highlighted +# * simplest-path - The path to the version with the fewest edges is highlighted +# * all - Both lowest-version and simplest-path are used +highlight = "all" +# The default lint level for `default` features for crates that are members of +# the workspace that is being checked. This can be overridden by allowing/denying +# `default` on a crate-by-crate basis if desired. +workspace-default-features = "allow" +# The default lint level for `default` features for external crates that are not +# members of the workspace. This can be overridden by allowing/denying `default` +# on a crate-by-crate basis if desired. +external-default-features = "allow" +# List of crates that are allowed. Use with care! +allow = [ + #{ name = "ansi_term", version = "=0.11.0" }, +] +# List of crates to deny +deny = [ + # Each entry the name of a crate and a version range. If version is + # not specified, all versions will be matched. + #{ name = "ansi_term", version = "=0.11.0" }, + # + # Wrapper crates can optionally be specified to allow the crate when it + # is a direct dependency of the otherwise banned crate + #{ name = "ansi_term", version = "=0.11.0", wrappers = [] }, +] + +# This section is considered when running `cargo deny check sources`. +# More documentation about the 'sources' section can be found here: +# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html +[sources] +# Lint level for what to happen when a crate from a crate registry that is not +# in the allow list is encountered +unknown-registry = "deny" +# Lint level for what to happen when a crate from a git repository that is not +# in the allow list is encountered +unknown-git = "deny" +# List of URLs for allowed crate registries. Defaults to the crates.io index +# if not specified. If it is specified but empty, no registries are allowed. +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +# List of URLs for allowed Git repositories +allow-git = [] + +[sources.allow-org] +# 1 or more github.com organizations to allow git sources for +github = [] From 64f3433f97efc37a08f1e8deecafb4aa03fde806 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 8 Nov 2023 13:31:06 -0700 Subject: [PATCH 076/455] chore: Remove unneeded badges --- Cargo.toml | 3 --- README.md | 1 - 2 files changed, 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e4a23517..c051c284 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,9 +10,6 @@ readme = "README.md" keywords = ["code", "analysis", "ascii", "errors", "debug"] [badges] -travis-ci = { repository = "rust-lang/annotate-snippets-rs", branch = "master" } -coveralls = { repository = "rust-lang/annotate-snippets-rs", branch = "master", service = "github" } - maintenance = { status = "actively-developed" } [dependencies] diff --git a/README.md b/README.md index f60c96e1..2e5a20fd 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,6 @@ [](https://crates.io/crates/annotate-snippets)  -[](https://coveralls.io/github/rust-lang/annotate-snippets-rs?branch=master) The library helps visualize meta information annotating source code slices. It takes a data structure called `Snippet` on the input and produces a `String` From fba7bfef0f0ea5c47fdc6d640b4b615b001f85e4 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 8 Nov 2023 13:35:03 -0700 Subject: [PATCH 077/455] chore: Update to edition 2021 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c051c284..e9bb6891 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "annotate-snippets" version = "0.9.2" -edition = "2018" +edition = "2021" authors = ["Zibi Braniecki <gandalf@mozilla.com>"] description = "Library for building code annotations" license = "Apache-2.0/MIT" From bac76bfc809f1d3b975e34c3438c2950ffebaa2e Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 8 Nov 2023 15:29:57 -0700 Subject: [PATCH 078/455] chore: Set MSRV to `1.70.0` --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index e9bb6891..904c4429 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ name = "annotate-snippets" version = "0.9.2" edition = "2021" +rust-version = "1.70.0" authors = ["Zibi Braniecki <gandalf@mozilla.com>"] description = "Library for building code annotations" license = "Apache-2.0/MIT" From 9770283a6d3bfc5957f76eb565f6cce43338fb3d Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 8 Nov 2023 15:30:48 -0700 Subject: [PATCH 079/455] chore: Sort dependencies alphabetically --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 904c4429..79348d64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,12 +18,12 @@ unicode-width = "0.1" yansi-term = { version = "0.1", optional = true } [dev-dependencies] +criterion = "0.3" +difference = "2.0" glob = "0.3" -toml = "0.5" serde = { version = "1.0", features = ["derive"] } -difference = "2.0" +toml = "0.5" yansi-term = "0.1" -criterion = "0.3" [[bench]] name = "simple" From 9bb9cadbb01bef952ed7c615bdd1ffd4de8b6dc5 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 8 Nov 2023 15:32:17 -0700 Subject: [PATCH 080/455] chore: Fully specify dependency versions --- Cargo.toml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 79348d64..44c7f2d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,16 +14,16 @@ keywords = ["code", "analysis", "ascii", "errors", "debug"] maintenance = { status = "actively-developed" } [dependencies] -unicode-width = "0.1" -yansi-term = { version = "0.1", optional = true } +unicode-width = "0.1.11" +yansi-term = { version = "0.1.2", optional = true } [dev-dependencies] -criterion = "0.3" -difference = "2.0" -glob = "0.3" -serde = { version = "1.0", features = ["derive"] } -toml = "0.5" -yansi-term = "0.1" +criterion = "0.3.6" +difference = "2.0.0" +glob = "0.3.1" +serde = { version = "1.0.192", features = ["derive"] } +toml = "0.5.11" +yansi-term = "0.1.2" [[bench]] name = "simple" From 7f8322ca8f1e359d558968382d8c37c6a9f62839 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 8 Nov 2023 15:34:39 -0700 Subject: [PATCH 081/455] chore: Update criterion version --- Cargo.lock | 243 +++++++++++++++++++++++++++++++++++++---------------- Cargo.toml | 2 +- 2 files changed, 172 insertions(+), 73 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bd6a75dc..c15507c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "annotate-snippets" version = "0.9.2" @@ -25,15 +31,10 @@ dependencies = [ ] [[package]] -name = "atty" -version = "0.2.14" +name = "anstyle" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" [[package]] name = "autocfg" @@ -43,9 +44,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bitflags" -version = "1.3.2" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "bumpalo" @@ -65,37 +66,78 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "ciborium" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" + +[[package]] +name = "ciborium-ll" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "clap" -version = "2.34.0" +version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b" dependencies = [ - "bitflags", - "textwrap", - "unicode-width", + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663" +dependencies = [ + "anstyle", + "clap_lex", ] +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + [[package]] name = "criterion" -version = "0.3.6" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" dependencies = [ - "atty", + "anes", "cast", + "ciborium", "clap", "criterion-plot", - "csv", + "is-terminal", "itertools", - "lazy_static", "num-traits", + "once_cell", "oorandom", "plotters", "rayon", "regex", "serde", - "serde_cbor", "serde_derive", "serde_json", "tinytemplate", @@ -104,9 +146,9 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.4.5" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", "itertools", @@ -145,27 +187,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "csv" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" -dependencies = [ - "csv-core", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "csv-core" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" -dependencies = [ - "memchr", -] - [[package]] name = "difference" version = "2.0.0" @@ -178,6 +199,16 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +[[package]] +name = "errno" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "glob" version = "0.3.1" @@ -192,11 +223,19 @@ checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "hermit-abi" -version = "0.1.19" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" + +[[package]] +name = "is-terminal" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "libc", + "hermit-abi", + "rustix", + "windows-sys", ] [[package]] @@ -223,18 +262,18 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - [[package]] name = "libc" version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +[[package]] +name = "linux-raw-sys" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" + [[package]] name = "log" version = "0.4.20" @@ -372,6 +411,19 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "rustix" +version = "0.38.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "ryu" version = "1.0.15" @@ -402,16 +454,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde_cbor" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" -dependencies = [ - "half", - "serde", -] - [[package]] name = "serde_derive" version = "1.0.192" @@ -445,15 +487,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] - [[package]] name = "tinytemplate" version = "1.2.1" @@ -590,6 +623,72 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "yansi-term" version = "0.1.2" diff --git a/Cargo.toml b/Cargo.toml index 44c7f2d2..497c1cdc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ unicode-width = "0.1.11" yansi-term = { version = "0.1.2", optional = true } [dev-dependencies] -criterion = "0.3.6" +criterion = "0.5.1" difference = "2.0.0" glob = "0.3.1" serde = { version = "1.0.192", features = ["derive"] } From e0083cd1debe1a70590e5ba2aef78f9ce7d5a3c9 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 9 Nov 2023 15:59:17 -0600 Subject: [PATCH 082/455] chore(ci): Validate basics in CI / pre-commit --- .github/workflows/pre-commit.yml | 23 +++++++++++++++++++++++ .pre-commit-config.yaml | 16 ++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 .github/workflows/pre-commit.yml create mode 100644 .pre-commit-config.yaml diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 00000000..80447507 --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,23 @@ +name: pre-commit + +permissions: {} # none + +on: + pull_request: + push: + branches: [main] + +env: + RUST_BACKTRACE: 1 + CARGO_TERM_COLOR: always + CLICOLOR: 1 + +jobs: + pre-commit: + permissions: + contents: read + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v4 + - uses: pre-commit/action@v3.0.0 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..7d2fdd9d --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,16 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: check-yaml + stages: [commit] + - id: check-json + stages: [commit] + - id: check-toml + stages: [commit] + - id: check-merge-conflict + stages: [commit] + - id: check-case-conflict + stages: [commit] + - id: detect-private-key + stages: [commit] From 0da8be6b27b0837c512125c923d45648679a9479 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 9 Nov 2023 16:01:54 -0600 Subject: [PATCH 083/455] docs: Fix typos --- src/display_list/structs.rs | 4 ++-- tests/formatter.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/display_list/structs.rs b/src/display_list/structs.rs index 7941d5fc..5a31d1e1 100644 --- a/src/display_list/structs.rs +++ b/src/display_list/structs.rs @@ -225,7 +225,7 @@ pub enum DisplayRawLine<'a> { source_aligned: bool, /// If set to `true`, only the label of the `Annotation` will be /// displayed. It allows for a multiline annotation to be aligned - /// without displaing the meta information (`type` and `id`) to be + /// without displaying the meta information (`type` and `id`) to be /// displayed on each line. continuation: bool, }, @@ -283,7 +283,7 @@ pub enum DisplayMarkType { /// There are several ways to uses this information when formatting the `DisplayList`: /// /// * An annotation may display the name of the type like `error` or `info`. -/// * An underline for `Error` may be `^^^` while for `Warning` it coule be `---`. +/// * An underline for `Error` may be `^^^` while for `Warning` it could be `---`. /// * `ColorStylesheet` may use different colors for different annotations. #[derive(Debug, Clone, PartialEq)] pub enum DisplayAnnotationType { diff --git a/tests/formatter.rs b/tests/formatter.rs index b1392a1d..f95b0026 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -513,7 +513,7 @@ fn test_raw_origin_initial_pos_anon_lines() { header_type: DisplayHeaderType::Initial, })]); - // Using anonymized_line_numbers should not affect the inital position + // Using anonymized_line_numbers should not affect the initial position dl.anonymized_line_numbers = true; assert_eq!(dl.to_string(), "--> src/test.rs:23:15"); } From cf579ff37061f25d6d95d75ae4d4e059e26a990b Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 9 Nov 2023 16:01:25 -0600 Subject: [PATCH 084/455] chore(ci): Check typos --- .github/workflows/spelling.yml | 21 +++++++++++++++++++++ .pre-commit-config.yaml | 5 +++++ 2 files changed, 26 insertions(+) create mode 100644 .github/workflows/spelling.yml diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml new file mode 100644 index 00000000..12f75859 --- /dev/null +++ b/.github/workflows/spelling.yml @@ -0,0 +1,21 @@ +name: Spelling + +permissions: + contents: read + +on: [pull_request] + +env: + RUST_BACKTRACE: 1 + CARGO_TERM_COLOR: always + CLICOLOR: 1 + +jobs: + spelling: + name: Spell Check with Typos + runs-on: ubuntu-latest + steps: + - name: Checkout Actions Repository + uses: actions/checkout@v4 + - name: Spell Check Repo + uses: crate-ci/typos@master diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7d2fdd9d..5340e097 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,3 +14,8 @@ repos: stages: [commit] - id: detect-private-key stages: [commit] + - repo: https://github.com/crate-ci/typos + rev: v1.16.20 + hooks: + - id: typos + stages: [commit] From e4e731c25c24dc124cd086d32c8d9ed2871bff5e Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 9 Nov 2023 16:05:16 -0600 Subject: [PATCH 085/455] chore(ci): Encourage conventional commits This would help with writing changelogs --- .github/workflows/committed.yml | 24 ++++++++++++++++++++++++ .pre-commit-config.yaml | 5 +++++ committed.toml | 3 +++ 3 files changed, 32 insertions(+) create mode 100644 .github/workflows/committed.yml create mode 100644 committed.toml diff --git a/.github/workflows/committed.yml b/.github/workflows/committed.yml new file mode 100644 index 00000000..04625584 --- /dev/null +++ b/.github/workflows/committed.yml @@ -0,0 +1,24 @@ +# Not run as part of pre-commit checks because they don't handle sending the correct commit +# range to `committed` +name: Lint Commits +on: [pull_request] + +permissions: + contents: read + +env: + RUST_BACKTRACE: 1 + CARGO_TERM_COLOR: always + CLICOLOR: 1 + +jobs: + committed: + name: Lint Commits + runs-on: ubuntu-latest + steps: + - name: Checkout Actions Repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Lint Commits + uses: crate-ci/committed@master diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5340e097..68db968e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,3 +19,8 @@ repos: hooks: - id: typos stages: [commit] + - repo: https://github.com/crate-ci/committed + rev: v1.0.20 + hooks: + - id: committed + stages: [commit-msg] diff --git a/committed.toml b/committed.toml new file mode 100644 index 00000000..4211ae38 --- /dev/null +++ b/committed.toml @@ -0,0 +1,3 @@ +style="conventional" +ignore_author_re="(dependabot|renovate)" +merge_commit = false From d384abe77dbf19e1c5206944ef68f29f1d63272d Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 9 Nov 2023 16:15:18 -0600 Subject: [PATCH 086/455] chore(ci): Auto-update versions Once infra enables RenovateBot, this will - Create individual PRs for breaking changes - Combined PRs for compatible changes This also auto-updates MSRV, like cargo and clap do, to avoid stagnation and people getting the wrong impression of the MSRV policy from that stagnation. This also avoids contributors being hesitant about what justifies updating MSRV. --- .github/renovate.json5 | 107 +++++++++++++++++++++++++++++++++++++++ .github/workflows/ci.yml | 2 +- Cargo.toml | 2 +- 3 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 .github/renovate.json5 diff --git a/.github/renovate.json5 b/.github/renovate.json5 new file mode 100644 index 00000000..21d082f3 --- /dev/null +++ b/.github/renovate.json5 @@ -0,0 +1,107 @@ +{ + schedule: [ + 'before 5am on the first day of the month', + ], + semanticCommits: 'enabled', + configMigration: true, + dependencyDashboard: true, + customManagers: [ + { + customType: 'regex', + fileMatch: [ + '^rust-toolchain\\.toml$', + 'Cargo.toml$', + 'clippy.toml$', + '\\.clippy.toml$', + '^\\.github/workflows/ci.yml$', + '^\\.github/workflows/rust-next.yml$', + ], + matchStrings: [ + 'MSRV.*?(?<currentValue>\\d+\\.\\d+(\\.\\d+)?)', + '(?<currentValue>\\d+\\.\\d+(\\.\\d+)?).*?MSRV', + ], + depNameTemplate: 'rust', + packageNameTemplate: 'rust-lang/rust', + datasourceTemplate: 'github-releases', + }, + ], + packageRules: [ + { + commitMessageTopic: 'MSRV', + matchManagers: [ + 'regex', + ], + matchPackageNames: [ + 'rust', + ], + minimumReleaseAge: '84 days', // 2 releases back * 6 weeks per release * 7 days per week + internalChecksFilter: 'strict', + extractVersion: '^(?<version>\\d+\\.\\d+)', // Drop the patch version + schedule: [ + '* * * * *', + ], + }, + // Goals: + // - Keep version reqs low, ignoring compatible normal/build dependencies + // - Take advantage of latest dev-dependencies + // - Rollup safe upgrades to reduce CI runner load + // - Help keep number of versions down by always using latest breaking change + // - Have lockfile and manifest in-sync + { + matchManagers: [ + 'cargo', + ], + matchDepTypes: [ + 'build-dependencies', + 'dependencies', + ], + matchCurrentVersion: '>=0.1.0', + matchUpdateTypes: [ + 'patch', + ], + enabled: false, + }, + { + matchManagers: [ + 'cargo', + ], + matchDepTypes: [ + 'build-dependencies', + 'dependencies', + ], + matchCurrentVersion: '>=1.0.0', + matchUpdateTypes: [ + 'minor', + ], + enabled: false, + }, + { + matchManagers: [ + 'cargo', + ], + matchDepTypes: [ + 'dev-dependencies', + ], + matchCurrentVersion: '>=0.1.0', + matchUpdateTypes: [ + 'patch', + ], + automerge: true, + groupName: 'compatible (dev)', + }, + { + matchManagers: [ + 'cargo', + ], + matchDepTypes: [ + 'dev-dependencies', + ], + matchCurrentVersion: '>=1.0.0', + matchUpdateTypes: [ + 'minor', + ], + automerge: true, + groupName: 'compatible (dev)', + }, + ], +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 50c2ddfe..3a3d8128 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,7 +49,7 @@ jobs: - name: No-default features run: cargo test --workspace --no-default-features msrv: - name: "Check MSRV: 1.70" + name: "Check MSRV: 1.70" # MSRV runs-on: ubuntu-latest steps: - name: Checkout repository diff --git a/Cargo.toml b/Cargo.toml index 497c1cdc..86666519 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "annotate-snippets" version = "0.9.2" edition = "2021" -rust-version = "1.70.0" +rust-version = "1.70" # MSRV authors = ["Zibi Braniecki <gandalf@mozilla.com>"] description = "Library for building code annotations" license = "Apache-2.0/MIT" From 7f0986fae45864d4836ed6b21783841d234c46d1 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 9 Nov 2023 16:27:00 -0600 Subject: [PATCH 087/455] docs: Match clap/cargo for changelog style --- CHANGELOG.md | 74 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 44 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c66ad41e..c1ab9621 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,44 +1,58 @@ -# Changelog - -## Unreleased - - - … +# Change Log +All notable changes to this project will be documented in this file. -## annotate-snippets 0.9.2 (October 30, 2023) +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + +## [Unreleased] - ReleaseDate + +## [0.9.2] - October 30, 2023 - - Remove parsing of __ in title strings, fixes (#53) - - Origin line number is not correct when using a slice with fold: true (#52) +- Remove parsing of __ in title strings, fixes (#53) +- Origin line number is not correct when using a slice with fold: true (#52) -## annotate-snippets 0.9.1 (September 4, 2021) +## [0.9.1] - September 4, 2021 - - Fix character split when strip code. (#37) - - Fix off by one error in multiline highlighting. (#42) - - Fix display of annotation for double width characters. (#46) +- Fix character split when strip code. (#37) +- Fix off by one error in multiline highlighting. (#42) +- Fix display of annotation for double width characters. (#46) -## annotate-snippets 0.9.0 (June 28, 2020) +## [0.9.0] - June 28, 2020 - - Add strip code to the left and right of long lines. (#36) +- Add strip code to the left and right of long lines. (#36) -## annotate-snippets 0.8.0 (April 14, 2020) +## [0.8.0] - April 14, 2020 - - Replace `ansi_term` with `yansi-term` for improved performance. (#30) - - Turn `Snippet` and `Slice` to work on borrowed slices, rather than Strings. (#32) - - Fix `\r\n` end of lines. (#29) +- Replace `ansi_term` with `yansi-term` for improved performance. (#30) +- Turn `Snippet` and `Slice` to work on borrowed slices, rather than Strings. (#32) +- Fix `\r\n` end of lines. (#29) -## annotate-snippets 0.7.0 (March 30, 2020) +## [0.7.0] - March 30, 2020 - - Refactor API to use `fmt::Display` (#27) - - Fix SourceAnnotation range (#27) - - Fix column numbers (#22) - - Derive `PartialEq` for `AnnotationType` (#19) - - Update `ansi_term` to 0.12. +- Refactor API to use `fmt::Display` (#27) +- Fix SourceAnnotation range (#27) +- Fix column numbers (#22) +- Derive `PartialEq` for `AnnotationType` (#19) +- Update `ansi_term` to 0.12. -## annotate-snippets 0.6.1 (July 23, 2019) +## [0.6.1] - July 23, 2019 - - Fix too many anonymized line numbers (#5) +- Fix too many anonymized line numbers (#5) -## annotate-snippets 0.6.0 (June 26, 2019) +## [0.6.0] - June 26, 2019 - - Add an option to anonymize line numbers (#3) - - Transition the crate to rust-lang org. - - Update the syntax to Rust 2018 idioms. (#4) +- Add an option to anonymize line numbers (#3) +- Transition the crate to rust-lang org. +- Update the syntax to Rust 2018 idioms. (#4) + +<!-- next-url --> +[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.9.2...HEAD +[0.9.2]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.9.1...0.9.2 +[0.9.1]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.9.0...0.9.1 +[0.9.0]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.8.0...0.9.0 +[0.8.0]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.7.0...0.8.0 +[0.7.0]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.6.1...0.7.0 +[0.6.1]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.6.0...0.6.1 +[0.6.0]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.5.0...0.6.0 +[0.5.0]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.1.0...0.5.0 +[0.1.0]: https://github.com/rust-lang/annotate-snippets-rs/compare/6015d08d7d10151c126c6a70c14f234c0c01b50e...0.1.0 From bc59c126ea08bb01c0425bb6e82377b8a3bc9e6e Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Sun, 12 Nov 2023 12:30:48 -0700 Subject: [PATCH 088/455] refactor: Move impls near definitions --- src/display_list/from_snippet.rs | 580 ------------- src/display_list/mod.rs | 1317 +++++++++++++++++++++++++++++- src/display_list/structs.rs | 308 ------- src/formatter/mod.rs | 437 +--------- 4 files changed, 1316 insertions(+), 1326 deletions(-) delete mode 100644 src/display_list/from_snippet.rs delete mode 100644 src/display_list/structs.rs diff --git a/src/display_list/from_snippet.rs b/src/display_list/from_snippet.rs deleted file mode 100644 index 4734147a..00000000 --- a/src/display_list/from_snippet.rs +++ /dev/null @@ -1,580 +0,0 @@ -//! Trait for converting `Snippet` to `DisplayList`. -use super::*; -use crate::{formatter::get_term_style, snippet}; - -struct CursorLines<'a>(&'a str); - -impl<'a> CursorLines<'a> { - fn new(src: &str) -> CursorLines<'_> { - CursorLines(src) - } -} - -enum EndLine { - Eof = 0, - Crlf = 1, - Lf = 2, -} - -impl<'a> Iterator for CursorLines<'a> { - type Item = (&'a str, EndLine); - - fn next(&mut self) -> Option<Self::Item> { - if self.0.is_empty() { - None - } else { - self.0 - .find('\n') - .map(|x| { - let ret = if 0 < x { - if self.0.as_bytes()[x - 1] == b'\r' { - (&self.0[..x - 1], EndLine::Lf) - } else { - (&self.0[..x], EndLine::Crlf) - } - } else { - ("", EndLine::Crlf) - }; - self.0 = &self.0[x + 1..]; - ret - }) - .or_else(|| { - let ret = Some((self.0, EndLine::Eof)); - self.0 = ""; - ret - }) - } - } -} - -fn format_label( - label: Option<&str>, - style: Option<DisplayTextStyle>, -) -> Vec<DisplayTextFragment<'_>> { - let mut result = vec![]; - if let Some(label) = label { - let element_style = style.unwrap_or(DisplayTextStyle::Regular); - result.push(DisplayTextFragment { - content: label, - style: element_style, - }); - } - result -} - -fn format_title(annotation: snippet::Annotation<'_>) -> DisplayLine<'_> { - let label = annotation.label.unwrap_or_default(); - DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::from(annotation.annotation_type), - id: annotation.id, - label: format_label(Some(label), Some(DisplayTextStyle::Emphasis)), - }, - source_aligned: false, - continuation: false, - }) -} - -fn format_annotation(annotation: snippet::Annotation<'_>) -> Vec<DisplayLine<'_>> { - let mut result = vec![]; - let label = annotation.label.unwrap_or_default(); - for (i, line) in label.lines().enumerate() { - result.push(DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::from(annotation.annotation_type), - id: None, - label: format_label(Some(line), None), - }, - source_aligned: true, - continuation: i != 0, - })); - } - result -} - -fn format_slice( - slice: snippet::Slice<'_>, - is_first: bool, - has_footer: bool, - margin: Option<Margin>, -) -> Vec<DisplayLine<'_>> { - let main_range = slice.annotations.get(0).map(|x| x.range.0); - let origin = slice.origin; - let need_empty_header = origin.is_some() || is_first; - let mut body = format_body(slice, need_empty_header, has_footer, margin); - let header = format_header(origin, main_range, &body, is_first); - let mut result = vec![]; - - if let Some(header) = header { - result.push(header); - } - result.append(&mut body); - result -} - -#[inline] -// TODO: option_zip -fn zip_opt<A, B>(a: Option<A>, b: Option<B>) -> Option<(A, B)> { - a.and_then(|a| b.map(|b| (a, b))) -} - -fn format_header<'a>( - origin: Option<&'a str>, - main_range: Option<usize>, - body: &[DisplayLine<'_>], - is_first: bool, -) -> Option<DisplayLine<'a>> { - let display_header = if is_first { - DisplayHeaderType::Initial - } else { - DisplayHeaderType::Continuation - }; - - if let Some((main_range, path)) = zip_opt(main_range, origin) { - let mut col = 1; - let mut line_offset = 1; - - for item in body { - if let DisplayLine::Source { - line: DisplaySourceLine::Content { range, .. }, - lineno, - .. - } = item - { - if main_range >= range.0 && main_range <= range.1 { - col = main_range - range.0 + 1; - line_offset = lineno.unwrap_or(1); - break; - } - } - } - - return Some(DisplayLine::Raw(DisplayRawLine::Origin { - path, - pos: Some((line_offset, col)), - header_type: display_header, - })); - } - - if let Some(path) = origin { - return Some(DisplayLine::Raw(DisplayRawLine::Origin { - path, - pos: None, - header_type: display_header, - })); - } - - None -} - -fn fold_body(mut body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> { - enum Line { - Fold(usize), - Source(usize), - } - - let mut lines = vec![]; - let mut no_annotation_lines_counter = 0; - - for (idx, line) in body.iter().enumerate() { - match line { - DisplayLine::Source { - line: DisplaySourceLine::Annotation { .. }, - .. - } => { - let fold_start = idx - no_annotation_lines_counter; - if no_annotation_lines_counter > 2 { - let fold_end = idx; - let pre_len = if no_annotation_lines_counter > 8 { - 4 - } else { - 0 - }; - let post_len = if no_annotation_lines_counter > 8 { - 2 - } else { - 1 - }; - for (i, _) in body - .iter() - .enumerate() - .take(fold_start + pre_len) - .skip(fold_start) - { - lines.push(Line::Source(i)); - } - lines.push(Line::Fold(idx)); - for (i, _) in body - .iter() - .enumerate() - .take(fold_end) - .skip(fold_end - post_len) - { - lines.push(Line::Source(i)); - } - } else { - for (i, _) in body.iter().enumerate().take(idx).skip(fold_start) { - lines.push(Line::Source(i)); - } - } - no_annotation_lines_counter = 0; - } - DisplayLine::Source { .. } => { - no_annotation_lines_counter += 1; - continue; - } - _ => { - no_annotation_lines_counter += 1; - } - } - lines.push(Line::Source(idx)); - } - - let mut new_body = vec![]; - let mut removed = 0; - for line in lines { - match line { - Line::Source(i) => { - new_body.push(body.remove(i - removed)); - removed += 1; - } - Line::Fold(i) => { - if let DisplayLine::Source { - line: DisplaySourceLine::Annotation { .. }, - ref inline_marks, - .. - } = body.get(i - removed).unwrap() - { - new_body.push(DisplayLine::Fold { - inline_marks: inline_marks.clone(), - }) - } else { - unreachable!() - } - } - } - } - - new_body -} - -fn format_body( - slice: snippet::Slice<'_>, - need_empty_header: bool, - has_footer: bool, - margin: Option<Margin>, -) -> Vec<DisplayLine<'_>> { - let source_len = slice.source.chars().count(); - if let Some(bigger) = slice.annotations.iter().find_map(|x| { - if source_len < x.range.1 { - Some(x.range) - } else { - None - } - }) { - panic!( - "SourceAnnotation range `{:?}` is bigger than source length `{}`", - bigger, source_len - ) - } - - let mut body = vec![]; - let mut current_line = slice.line_start; - let mut current_index = 0; - let mut line_info = vec![]; - - struct LineInfo { - line_start_index: usize, - line_end_index: usize, - // How many spaces each character in the line take up when displayed - char_widths: Vec<usize>, - } - - for (line, end_line) in CursorLines::new(slice.source) { - let line_length = line.chars().count(); - let line_range = (current_index, current_index + line_length); - let char_widths = line - .chars() - .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) - .chain(std::iter::once(1)) // treat the end of line as single-width - .collect::<Vec<_>>(); - body.push(DisplayLine::Source { - lineno: Some(current_line), - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: line, - range: line_range, - }, - }); - line_info.push(LineInfo { - line_start_index: line_range.0, - line_end_index: line_range.1, - char_widths, - }); - current_line += 1; - current_index += line_length + end_line as usize; - } - - let mut annotation_line_count = 0; - let mut annotations = slice.annotations; - for ( - idx, - LineInfo { - line_start_index, - line_end_index, - char_widths, - }, - ) in line_info.into_iter().enumerate() - { - let margin_left = margin - .map(|m| m.left(line_end_index - line_start_index)) - .unwrap_or_default(); - // It would be nice to use filter_drain here once it's stable. - annotations.retain(|annotation| { - let body_idx = idx + annotation_line_count; - let annotation_type = match annotation.annotation_type { - snippet::AnnotationType::Error => DisplayAnnotationType::None, - snippet::AnnotationType::Warning => DisplayAnnotationType::None, - _ => DisplayAnnotationType::from(annotation.annotation_type), - }; - match annotation.range { - (start, _) if start > line_end_index => true, - (start, end) - if start >= line_start_index && end <= line_end_index - || start == line_end_index && end - start <= 1 => - { - let annotation_start_col = char_widths - .iter() - .take(start - line_start_index) - .sum::<usize>() - - margin_left; - let annotation_end_col = char_widths - .iter() - .take(end - line_start_index) - .sum::<usize>() - - margin_left; - let range = (annotation_start_col, annotation_end_col); - body.insert( - body_idx + 1, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - annotation: Annotation { - annotation_type, - id: None, - label: format_label(Some(annotation.label), None), - }, - range, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), - annotation_part: DisplayAnnotationPart::Standalone, - }, - }, - ); - annotation_line_count += 1; - false - } - (start, end) - if start >= line_start_index - && start <= line_end_index - && end > line_end_index => - { - if start - line_start_index == 0 { - if let DisplayLine::Source { - ref mut inline_marks, - .. - } = body[body_idx] - { - inline_marks.push(DisplayMark { - mark_type: DisplayMarkType::AnnotationStart, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), - }); - } - } else { - let annotation_start_col = char_widths - .iter() - .take(start - line_start_index) - .sum::<usize>(); - let range = (annotation_start_col, annotation_start_col + 1); - body.insert( - body_idx + 1, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::None, - id: None, - label: vec![], - }, - range, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), - annotation_part: DisplayAnnotationPart::MultilineStart, - }, - }, - ); - annotation_line_count += 1; - } - true - } - (start, end) if start < line_start_index && end > line_end_index => { - if let DisplayLine::Source { - ref mut inline_marks, - .. - } = body[body_idx] - { - inline_marks.push(DisplayMark { - mark_type: DisplayMarkType::AnnotationThrough, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), - }); - } - true - } - (start, end) - if start < line_start_index - && end >= line_start_index - && end <= line_end_index => - { - if let DisplayLine::Source { - ref mut inline_marks, - .. - } = body[body_idx] - { - inline_marks.push(DisplayMark { - mark_type: DisplayMarkType::AnnotationThrough, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), - }); - } - - let end_mark = char_widths - .iter() - .take(end - line_start_index) - .sum::<usize>() - .saturating_sub(1); - let range = (end_mark - margin_left, (end_mark + 1) - margin_left); - body.insert( - body_idx + 1, - DisplayLine::Source { - lineno: None, - inline_marks: vec![DisplayMark { - mark_type: DisplayMarkType::AnnotationThrough, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), - }], - line: DisplaySourceLine::Annotation { - annotation: Annotation { - annotation_type, - id: None, - label: format_label(Some(annotation.label), None), - }, - range, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), - annotation_part: DisplayAnnotationPart::MultilineEnd, - }, - }, - ); - annotation_line_count += 1; - false - } - _ => true, - } - }); - } - - if slice.fold { - body = fold_body(body); - } - - if need_empty_header { - body.insert( - 0, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }, - ); - } - - if has_footer { - body.push(DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }); - } else if let Some(DisplayLine::Source { .. }) = body.last() { - body.push(DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }); - } - body -} - -impl<'a> From<snippet::Snippet<'a>> for DisplayList<'a> { - fn from( - snippet::Snippet { - title, - footer, - slices, - opt, - }: snippet::Snippet<'a>, - ) -> DisplayList<'a> { - let mut body = vec![]; - if let Some(annotation) = title { - body.push(format_title(annotation)); - } - - for (idx, slice) in slices.into_iter().enumerate() { - body.append(&mut format_slice( - slice, - idx == 0, - !footer.is_empty(), - opt.margin, - )); - } - - for annotation in footer { - body.append(&mut format_annotation(annotation)); - } - - let FormatOptions { - color, - anonymized_line_numbers, - margin, - } = opt; - - Self { - body, - stylesheet: get_term_style(color), - anonymized_line_numbers, - margin, - } - } -} - -impl From<snippet::AnnotationType> for DisplayAnnotationType { - fn from(at: snippet::AnnotationType) -> Self { - match at { - snippet::AnnotationType::Error => DisplayAnnotationType::Error, - snippet::AnnotationType::Warning => DisplayAnnotationType::Warning, - snippet::AnnotationType::Info => DisplayAnnotationType::Info, - snippet::AnnotationType::Note => DisplayAnnotationType::Note, - snippet::AnnotationType::Help => DisplayAnnotationType::Help, - } - } -} diff --git a/src/display_list/mod.rs b/src/display_list/mod.rs index 224a9f58..1ad328aa 100644 --- a/src/display_list/mod.rs +++ b/src/display_list/mod.rs @@ -31,7 +31,1318 @@ //! styling. //! //! The above snippet has been built out of the following structure: -mod from_snippet; -mod structs; +use std::cmp::{max, min}; +use std::fmt::{Display, Write}; +use std::{cmp, fmt}; -pub use self::structs::*; +use crate::formatter::style::{Style, StyleClass}; +use crate::formatter::{get_term_style, style::Stylesheet}; +use crate::snippet; + +/// List of lines to be displayed. +pub struct DisplayList<'a> { + pub body: Vec<DisplayLine<'a>>, + pub stylesheet: Box<dyn Stylesheet>, + pub anonymized_line_numbers: bool, + pub margin: Option<Margin>, +} + +impl<'a> From<Vec<DisplayLine<'a>>> for DisplayList<'a> { + fn from(body: Vec<DisplayLine<'a>>) -> DisplayList<'a> { + Self { + body, + anonymized_line_numbers: false, + stylesheet: get_term_style(false), + margin: None, + } + } +} + +impl<'a> PartialEq for DisplayList<'a> { + fn eq(&self, other: &Self) -> bool { + self.body == other.body && self.anonymized_line_numbers == other.anonymized_line_numbers + } +} + +impl<'a> fmt::Debug for DisplayList<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DisplayList") + .field("body", &self.body) + .field("anonymized_line_numbers", &self.anonymized_line_numbers) + .finish() + } +} + +impl<'a> Display for DisplayList<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let lineno_width = self.body.iter().fold(0, |max, line| match line { + DisplayLine::Source { + lineno: Some(lineno), + .. + } => { + // The largest line is the largest width. + cmp::max(*lineno, max) + } + _ => max, + }); + let lineno_width = if lineno_width == 0 { + lineno_width + } else if self.anonymized_line_numbers { + Self::ANONYMIZED_LINE_NUM.len() + } else { + ((lineno_width as f64).log10().floor() as usize) + 1 + }; + let inline_marks_width = self.body.iter().fold(0, |max, line| match line { + DisplayLine::Source { inline_marks, .. } => cmp::max(inline_marks.len(), max), + _ => max, + }); + + for (i, line) in self.body.iter().enumerate() { + self.format_line(line, lineno_width, inline_marks_width, f)?; + if i + 1 < self.body.len() { + f.write_char('\n')?; + } + } + Ok(()) + } +} + +impl<'a> From<snippet::Snippet<'a>> for DisplayList<'a> { + fn from( + snippet::Snippet { + title, + footer, + slices, + opt, + }: snippet::Snippet<'a>, + ) -> DisplayList<'a> { + let mut body = vec![]; + if let Some(annotation) = title { + body.push(format_title(annotation)); + } + + for (idx, slice) in slices.into_iter().enumerate() { + body.append(&mut format_slice( + slice, + idx == 0, + !footer.is_empty(), + opt.margin, + )); + } + + for annotation in footer { + body.append(&mut format_annotation(annotation)); + } + + let FormatOptions { + color, + anonymized_line_numbers, + margin, + } = opt; + + Self { + body, + stylesheet: get_term_style(color), + anonymized_line_numbers, + margin, + } + } +} + +impl<'a> DisplayList<'a> { + const ANONYMIZED_LINE_NUM: &'static str = "LL"; + const ERROR_TXT: &'static str = "error"; + const HELP_TXT: &'static str = "help"; + const INFO_TXT: &'static str = "info"; + const NOTE_TXT: &'static str = "note"; + const WARNING_TXT: &'static str = "warning"; + + #[inline] + fn format_annotation_type( + annotation_type: &DisplayAnnotationType, + f: &mut fmt::Formatter<'_>, + ) -> fmt::Result { + match annotation_type { + DisplayAnnotationType::Error => f.write_str(Self::ERROR_TXT), + DisplayAnnotationType::Help => f.write_str(Self::HELP_TXT), + DisplayAnnotationType::Info => f.write_str(Self::INFO_TXT), + DisplayAnnotationType::Note => f.write_str(Self::NOTE_TXT), + DisplayAnnotationType::Warning => f.write_str(Self::WARNING_TXT), + DisplayAnnotationType::None => Ok(()), + } + } + + fn annotation_type_len(annotation_type: &DisplayAnnotationType) -> usize { + match annotation_type { + DisplayAnnotationType::Error => Self::ERROR_TXT.len(), + DisplayAnnotationType::Help => Self::HELP_TXT.len(), + DisplayAnnotationType::Info => Self::INFO_TXT.len(), + DisplayAnnotationType::Note => Self::NOTE_TXT.len(), + DisplayAnnotationType::Warning => Self::WARNING_TXT.len(), + DisplayAnnotationType::None => 0, + } + } + + fn get_annotation_style(&self, annotation_type: &DisplayAnnotationType) -> Box<dyn Style> { + self.stylesheet.get_style(match annotation_type { + DisplayAnnotationType::Error => StyleClass::Error, + DisplayAnnotationType::Warning => StyleClass::Warning, + DisplayAnnotationType::Info => StyleClass::Info, + DisplayAnnotationType::Note => StyleClass::Note, + DisplayAnnotationType::Help => StyleClass::Help, + DisplayAnnotationType::None => StyleClass::None, + }) + } + + fn format_label( + &self, + label: &[DisplayTextFragment<'_>], + f: &mut fmt::Formatter<'_>, + ) -> fmt::Result { + let emphasis_style = self.stylesheet.get_style(StyleClass::Emphasis); + + for fragment in label { + match fragment.style { + DisplayTextStyle::Regular => fragment.content.fmt(f)?, + DisplayTextStyle::Emphasis => emphasis_style.paint(fragment.content, f)?, + } + } + Ok(()) + } + + fn format_annotation( + &self, + annotation: &Annotation<'_>, + continuation: bool, + in_source: bool, + f: &mut fmt::Formatter<'_>, + ) -> fmt::Result { + let color = self.get_annotation_style(&annotation.annotation_type); + let formatted_len = if let Some(id) = &annotation.id { + 2 + id.len() + Self::annotation_type_len(&annotation.annotation_type) + } else { + Self::annotation_type_len(&annotation.annotation_type) + }; + + if continuation { + format_repeat_char(' ', formatted_len + 2, f)?; + return self.format_label(&annotation.label, f); + } + if formatted_len == 0 { + self.format_label(&annotation.label, f) + } else { + color.paint_fn( + Box::new(|f| { + Self::format_annotation_type(&annotation.annotation_type, f)?; + if let Some(id) = &annotation.id { + f.write_char('[')?; + f.write_str(id)?; + f.write_char(']')?; + } + Ok(()) + }), + f, + )?; + if !is_annotation_empty(annotation) { + if in_source { + color.paint_fn( + Box::new(|f| { + f.write_str(": ")?; + self.format_label(&annotation.label, f) + }), + f, + )?; + } else { + f.write_str(": ")?; + self.format_label(&annotation.label, f)?; + } + } + Ok(()) + } + } + + #[inline] + fn format_source_line( + &self, + line: &DisplaySourceLine<'_>, + f: &mut fmt::Formatter<'_>, + ) -> fmt::Result { + match line { + DisplaySourceLine::Empty => Ok(()), + DisplaySourceLine::Content { text, .. } => { + f.write_char(' ')?; + if let Some(margin) = self.margin { + let line_len = text.chars().count(); + let mut left = margin.left(line_len); + let right = margin.right(line_len); + + if margin.was_cut_left() { + // We have stripped some code/whitespace from the beginning, make it clear. + "...".fmt(f)?; + left += 3; + } + + // On long lines, we strip the source line, accounting for unicode. + let mut taken = 0; + let cut_right = if margin.was_cut_right(line_len) { + taken += 3; + true + } else { + false + }; + // Specifies that it will end on the next character, so it will return + // until the next one to the final condition. + let mut ended = false; + let range = text + .char_indices() + .skip(left) + // Complete char iterator with final character + .chain(std::iter::once((text.len(), '\0'))) + // Take until the next one to the final condition + .take_while(|(_, ch)| { + // Fast return to iterate over final byte position + if ended { + return false; + } + // Make sure that the trimming on the right will fall within the terminal width. + // FIXME: `unicode_width` sometimes disagrees with terminals on how wide a `char` is. + // For now, just accept that sometimes the code line will be longer than desired. + taken += unicode_width::UnicodeWidthChar::width(*ch).unwrap_or(1); + if taken > right - left { + ended = true; + } + true + }) + // Reduce to start and end byte position + .fold((None, 0), |acc, (i, _)| { + if acc.0.is_some() { + (acc.0, i) + } else { + (Some(i), i) + } + }); + + // Format text with margins + text[range.0.expect("One character at line")..range.1].fmt(f)?; + + if cut_right { + // We have stripped some code after the right-most span end, make it clear we did so. + "...".fmt(f)?; + } + Ok(()) + } else { + text.fmt(f) + } + } + DisplaySourceLine::Annotation { + range, + annotation, + annotation_type, + annotation_part, + } => { + let indent_char = match annotation_part { + DisplayAnnotationPart::Standalone => ' ', + DisplayAnnotationPart::LabelContinuation => ' ', + DisplayAnnotationPart::Consequitive => ' ', + DisplayAnnotationPart::MultilineStart => '_', + DisplayAnnotationPart::MultilineEnd => '_', + }; + let mark = match annotation_type { + DisplayAnnotationType::Error => '^', + DisplayAnnotationType::Warning => '-', + DisplayAnnotationType::Info => '-', + DisplayAnnotationType::Note => '-', + DisplayAnnotationType::Help => '-', + DisplayAnnotationType::None => ' ', + }; + let color = self.get_annotation_style(annotation_type); + let indent_length = match annotation_part { + DisplayAnnotationPart::LabelContinuation => range.1, + DisplayAnnotationPart::Consequitive => range.1, + _ => range.0, + }; + + color.paint_fn( + Box::new(|f| { + format_repeat_char(indent_char, indent_length + 1, f)?; + format_repeat_char(mark, range.1 - indent_length, f) + }), + f, + )?; + + if !is_annotation_empty(annotation) { + f.write_char(' ')?; + color.paint_fn( + Box::new(|f| { + self.format_annotation( + annotation, + annotation_part == &DisplayAnnotationPart::LabelContinuation, + true, + f, + ) + }), + f, + )?; + } + + Ok(()) + } + } + } + + #[inline] + fn format_raw_line( + &self, + line: &DisplayRawLine<'_>, + lineno_width: usize, + f: &mut fmt::Formatter<'_>, + ) -> fmt::Result { + match line { + DisplayRawLine::Origin { + path, + pos, + header_type, + } => { + let header_sigil = match header_type { + DisplayHeaderType::Initial => "-->", + DisplayHeaderType::Continuation => ":::", + }; + let lineno_color = self.stylesheet.get_style(StyleClass::LineNo); + + if let Some((col, row)) = pos { + format_repeat_char(' ', lineno_width, f)?; + lineno_color.paint(header_sigil, f)?; + f.write_char(' ')?; + path.fmt(f)?; + f.write_char(':')?; + col.fmt(f)?; + f.write_char(':')?; + row.fmt(f) + } else { + format_repeat_char(' ', lineno_width, f)?; + lineno_color.paint(header_sigil, f)?; + f.write_char(' ')?; + path.fmt(f) + } + } + DisplayRawLine::Annotation { + annotation, + source_aligned, + continuation, + } => { + if *source_aligned { + if *continuation { + format_repeat_char(' ', lineno_width + 3, f)?; + } else { + let lineno_color = self.stylesheet.get_style(StyleClass::LineNo); + format_repeat_char(' ', lineno_width, f)?; + f.write_char(' ')?; + lineno_color.paint("=", f)?; + f.write_char(' ')?; + } + } + self.format_annotation(annotation, *continuation, false, f) + } + } + } + + #[inline] + fn format_line( + &self, + dl: &DisplayLine<'_>, + lineno_width: usize, + inline_marks_width: usize, + f: &mut fmt::Formatter<'_>, + ) -> fmt::Result { + match dl { + DisplayLine::Source { + lineno, + inline_marks, + line, + } => { + let lineno_color = self.stylesheet.get_style(StyleClass::LineNo); + if self.anonymized_line_numbers && lineno.is_some() { + lineno_color.paint_fn( + Box::new(|f| { + f.write_str(Self::ANONYMIZED_LINE_NUM)?; + f.write_str(" |") + }), + f, + )?; + } else { + lineno_color.paint_fn( + Box::new(|f| { + match lineno { + Some(n) => write!(f, "{:>width$}", n, width = lineno_width), + None => format_repeat_char(' ', lineno_width, f), + }?; + f.write_str(" |") + }), + f, + )?; + } + if *line != DisplaySourceLine::Empty { + if !inline_marks.is_empty() || 0 < inline_marks_width { + f.write_char(' ')?; + self.format_inline_marks(inline_marks, inline_marks_width, f)?; + } + self.format_source_line(line, f)?; + } else if !inline_marks.is_empty() { + f.write_char(' ')?; + self.format_inline_marks(inline_marks, inline_marks_width, f)?; + } + Ok(()) + } + DisplayLine::Fold { inline_marks } => { + f.write_str("...")?; + if !inline_marks.is_empty() || 0 < inline_marks_width { + format_repeat_char(' ', lineno_width, f)?; + self.format_inline_marks(inline_marks, inline_marks_width, f)?; + } + Ok(()) + } + DisplayLine::Raw(line) => self.format_raw_line(line, lineno_width, f), + } + } + + fn format_inline_marks( + &self, + inline_marks: &[DisplayMark], + inline_marks_width: usize, + f: &mut fmt::Formatter<'_>, + ) -> fmt::Result { + format_repeat_char(' ', inline_marks_width - inline_marks.len(), f)?; + for mark in inline_marks { + self.get_annotation_style(&mark.annotation_type).paint_fn( + Box::new(|f| { + f.write_char(match mark.mark_type { + DisplayMarkType::AnnotationThrough => '|', + DisplayMarkType::AnnotationStart => '/', + }) + }), + f, + )?; + } + Ok(()) + } +} + +#[derive(Debug, Default, Copy, Clone)] +pub struct FormatOptions { + pub color: bool, + pub anonymized_line_numbers: bool, + pub margin: Option<Margin>, +} + +#[derive(Clone, Copy, Debug)] +pub struct Margin { + /// The available whitespace in the left that can be consumed when centering. + whitespace_left: usize, + /// The column of the beginning of left-most span. + span_left: usize, + /// The column of the end of right-most span. + span_right: usize, + /// The beginning of the line to be displayed. + computed_left: usize, + /// The end of the line to be displayed. + computed_right: usize, + /// The current width of the terminal. 140 by default and in tests. + column_width: usize, + /// The end column of a span label, including the span. Doesn't account for labels not in the + /// same line as the span. + label_right: usize, +} + +impl Margin { + pub fn new( + whitespace_left: usize, + span_left: usize, + span_right: usize, + label_right: usize, + column_width: usize, + max_line_len: usize, + ) -> Self { + // The 6 is padding to give a bit of room for `...` when displaying: + // ``` + // error: message + // --> file.rs:16:58 + // | + // 16 | ... fn foo(self) -> Self::Bar { + // | ^^^^^^^^^ + // ``` + + let mut m = Margin { + whitespace_left: whitespace_left.saturating_sub(6), + span_left: span_left.saturating_sub(6), + span_right: span_right + 6, + computed_left: 0, + computed_right: 0, + column_width, + label_right: label_right + 6, + }; + m.compute(max_line_len); + m + } + + pub(crate) fn was_cut_left(&self) -> bool { + self.computed_left > 0 + } + + pub(crate) fn was_cut_right(&self, line_len: usize) -> bool { + let right = + if self.computed_right == self.span_right || self.computed_right == self.label_right { + // Account for the "..." padding given above. Otherwise we end up with code lines that + // do fit but end in "..." as if they were trimmed. + self.computed_right - 6 + } else { + self.computed_right + }; + right < line_len && self.computed_left + self.column_width < line_len + } + + fn compute(&mut self, max_line_len: usize) { + // When there's a lot of whitespace (>20), we want to trim it as it is useless. + self.computed_left = if self.whitespace_left > 20 { + self.whitespace_left - 16 // We want some padding. + } else { + 0 + }; + // We want to show as much as possible, max_line_len is the right-most boundary for the + // relevant code. + self.computed_right = max(max_line_len, self.computed_left); + + if self.computed_right - self.computed_left > self.column_width { + // Trimming only whitespace isn't enough, let's get craftier. + if self.label_right - self.whitespace_left <= self.column_width { + // Attempt to fit the code window only trimming whitespace. + self.computed_left = self.whitespace_left; + self.computed_right = self.computed_left + self.column_width; + } else if self.label_right - self.span_left <= self.column_width { + // Attempt to fit the code window considering only the spans and labels. + let padding_left = (self.column_width - (self.label_right - self.span_left)) / 2; + self.computed_left = self.span_left.saturating_sub(padding_left); + self.computed_right = self.computed_left + self.column_width; + } else if self.span_right - self.span_left <= self.column_width { + // Attempt to fit the code window considering the spans and labels plus padding. + let padding_left = (self.column_width - (self.span_right - self.span_left)) / 5 * 2; + self.computed_left = self.span_left.saturating_sub(padding_left); + self.computed_right = self.computed_left + self.column_width; + } else { + // Mostly give up but still don't show the full line. + self.computed_left = self.span_left; + self.computed_right = self.span_right; + } + } + } + + pub(crate) fn left(&self, line_len: usize) -> usize { + min(self.computed_left, line_len) + } + + pub(crate) fn right(&self, line_len: usize) -> usize { + if line_len.saturating_sub(self.computed_left) <= self.column_width { + line_len + } else { + min(line_len, self.computed_right) + } + } +} + +/// Inline annotation which can be used in either Raw or Source line. +#[derive(Debug, PartialEq)] +pub struct Annotation<'a> { + pub annotation_type: DisplayAnnotationType, + pub id: Option<&'a str>, + pub label: Vec<DisplayTextFragment<'a>>, +} + +/// A single line used in `DisplayList`. +#[derive(Debug, PartialEq)] +pub enum DisplayLine<'a> { + /// A line with `lineno` portion of the slice. + Source { + lineno: Option<usize>, + inline_marks: Vec<DisplayMark>, + line: DisplaySourceLine<'a>, + }, + + /// A line indicating a folded part of the slice. + Fold { inline_marks: Vec<DisplayMark> }, + + /// A line which is displayed outside of slices. + Raw(DisplayRawLine<'a>), +} + +/// A source line. +#[derive(Debug, PartialEq)] +pub enum DisplaySourceLine<'a> { + /// A line with the content of the Slice. + Content { + text: &'a str, + range: (usize, usize), // meta information for annotation placement. + }, + + /// An annotation line which is displayed in context of the slice. + Annotation { + annotation: Annotation<'a>, + range: (usize, usize), + annotation_type: DisplayAnnotationType, + annotation_part: DisplayAnnotationPart, + }, + + /// An empty source line. + Empty, +} + +/// Raw line - a line which does not have the `lineno` part and is not considered +/// a part of the snippet. +#[derive(Debug, PartialEq)] +pub enum DisplayRawLine<'a> { + /// A line which provides information about the location of the given + /// slice in the project structure. + Origin { + path: &'a str, + pos: Option<(usize, usize)>, + header_type: DisplayHeaderType, + }, + + /// An annotation line which is not part of any snippet. + Annotation { + annotation: Annotation<'a>, + + /// If set to `true`, the annotation will be aligned to the + /// lineno delimiter of the snippet. + source_aligned: bool, + /// If set to `true`, only the label of the `Annotation` will be + /// displayed. It allows for a multiline annotation to be aligned + /// without displaying the meta information (`type` and `id`) to be + /// displayed on each line. + continuation: bool, + }, +} + +/// An inline text fragment which any label is composed of. +#[derive(Debug, PartialEq)] +pub struct DisplayTextFragment<'a> { + pub content: &'a str, + pub style: DisplayTextStyle, +} + +/// A style for the `DisplayTextFragment` which can be visually formatted. +/// +/// This information may be used to emphasis parts of the label. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum DisplayTextStyle { + Regular, + Emphasis, +} + +/// An indicator of what part of the annotation a given `Annotation` is. +#[derive(Debug, Clone, PartialEq)] +pub enum DisplayAnnotationPart { + /// A standalone, single-line annotation. + Standalone, + /// A continuation of a multi-line label of an annotation. + LabelContinuation, + /// A consequitive annotation in case multiple annotations annotate a single line. + Consequitive, + /// A line starting a multiline annotation. + MultilineStart, + /// A line ending a multiline annotation. + MultilineEnd, +} + +/// A visual mark used in `inline_marks` field of the `DisplaySourceLine`. +#[derive(Debug, Clone, PartialEq)] +pub struct DisplayMark { + pub mark_type: DisplayMarkType, + pub annotation_type: DisplayAnnotationType, +} + +/// A type of the `DisplayMark`. +#[derive(Debug, Clone, PartialEq)] +pub enum DisplayMarkType { + /// A mark indicating a multiline annotation going through the current line. + AnnotationThrough, + /// A mark indicating a multiline annotation starting on the given line. + AnnotationStart, +} + +/// A type of the `Annotation` which may impact the sigils, style or text displayed. +/// +/// There are several ways to uses this information when formatting the `DisplayList`: +/// +/// * An annotation may display the name of the type like `error` or `info`. +/// * An underline for `Error` may be `^^^` while for `Warning` it could be `---`. +/// * `ColorStylesheet` may use different colors for different annotations. +#[derive(Debug, Clone, PartialEq)] +pub enum DisplayAnnotationType { + None, + Error, + Warning, + Info, + Note, + Help, +} + +impl From<snippet::AnnotationType> for DisplayAnnotationType { + fn from(at: snippet::AnnotationType) -> Self { + match at { + snippet::AnnotationType::Error => DisplayAnnotationType::Error, + snippet::AnnotationType::Warning => DisplayAnnotationType::Warning, + snippet::AnnotationType::Info => DisplayAnnotationType::Info, + snippet::AnnotationType::Note => DisplayAnnotationType::Note, + snippet::AnnotationType::Help => DisplayAnnotationType::Help, + } + } +} + +/// Information whether the header is the initial one or a consequitive one +/// for multi-slice cases. +// TODO: private +#[derive(Debug, Clone, PartialEq)] +pub enum DisplayHeaderType { + /// Initial header is the first header in the snippet. + Initial, + + /// Continuation marks all headers of following slices in the snippet. + Continuation, +} + +struct CursorLines<'a>(&'a str); + +impl<'a> CursorLines<'a> { + fn new(src: &str) -> CursorLines<'_> { + CursorLines(src) + } +} + +enum EndLine { + Eof = 0, + Crlf = 1, + Lf = 2, +} + +impl<'a> Iterator for CursorLines<'a> { + type Item = (&'a str, EndLine); + + fn next(&mut self) -> Option<Self::Item> { + if self.0.is_empty() { + None + } else { + self.0 + .find('\n') + .map(|x| { + let ret = if 0 < x { + if self.0.as_bytes()[x - 1] == b'\r' { + (&self.0[..x - 1], EndLine::Lf) + } else { + (&self.0[..x], EndLine::Crlf) + } + } else { + ("", EndLine::Crlf) + }; + self.0 = &self.0[x + 1..]; + ret + }) + .or_else(|| { + let ret = Some((self.0, EndLine::Eof)); + self.0 = ""; + ret + }) + } + } +} + +fn format_label( + label: Option<&str>, + style: Option<DisplayTextStyle>, +) -> Vec<DisplayTextFragment<'_>> { + let mut result = vec![]; + if let Some(label) = label { + let element_style = style.unwrap_or(DisplayTextStyle::Regular); + result.push(DisplayTextFragment { + content: label, + style: element_style, + }); + } + result +} + +fn format_title(annotation: snippet::Annotation<'_>) -> DisplayLine<'_> { + let label = annotation.label.unwrap_or_default(); + DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::from(annotation.annotation_type), + id: annotation.id, + label: format_label(Some(label), Some(DisplayTextStyle::Emphasis)), + }, + source_aligned: false, + continuation: false, + }) +} + +fn format_annotation(annotation: snippet::Annotation<'_>) -> Vec<DisplayLine<'_>> { + let mut result = vec![]; + let label = annotation.label.unwrap_or_default(); + for (i, line) in label.lines().enumerate() { + result.push(DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::from(annotation.annotation_type), + id: None, + label: format_label(Some(line), None), + }, + source_aligned: true, + continuation: i != 0, + })); + } + result +} + +fn format_slice( + slice: snippet::Slice<'_>, + is_first: bool, + has_footer: bool, + margin: Option<Margin>, +) -> Vec<DisplayLine<'_>> { + let main_range = slice.annotations.get(0).map(|x| x.range.0); + let origin = slice.origin; + let need_empty_header = origin.is_some() || is_first; + let mut body = format_body(slice, need_empty_header, has_footer, margin); + let header = format_header(origin, main_range, &body, is_first); + let mut result = vec![]; + + if let Some(header) = header { + result.push(header); + } + result.append(&mut body); + result +} + +#[inline] +// TODO: option_zip +fn zip_opt<A, B>(a: Option<A>, b: Option<B>) -> Option<(A, B)> { + a.and_then(|a| b.map(|b| (a, b))) +} + +fn format_header<'a>( + origin: Option<&'a str>, + main_range: Option<usize>, + body: &[DisplayLine<'_>], + is_first: bool, +) -> Option<DisplayLine<'a>> { + let display_header = if is_first { + DisplayHeaderType::Initial + } else { + DisplayHeaderType::Continuation + }; + + if let Some((main_range, path)) = zip_opt(main_range, origin) { + let mut col = 1; + let mut line_offset = 1; + + for item in body { + if let DisplayLine::Source { + line: DisplaySourceLine::Content { range, .. }, + lineno, + .. + } = item + { + if main_range >= range.0 && main_range <= range.1 { + col = main_range - range.0 + 1; + line_offset = lineno.unwrap_or(1); + break; + } + } + } + + return Some(DisplayLine::Raw(DisplayRawLine::Origin { + path, + pos: Some((line_offset, col)), + header_type: display_header, + })); + } + + if let Some(path) = origin { + return Some(DisplayLine::Raw(DisplayRawLine::Origin { + path, + pos: None, + header_type: display_header, + })); + } + + None +} + +fn fold_body(mut body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> { + enum Line { + Fold(usize), + Source(usize), + } + + let mut lines = vec![]; + let mut no_annotation_lines_counter = 0; + + for (idx, line) in body.iter().enumerate() { + match line { + DisplayLine::Source { + line: DisplaySourceLine::Annotation { .. }, + .. + } => { + let fold_start = idx - no_annotation_lines_counter; + if no_annotation_lines_counter > 2 { + let fold_end = idx; + let pre_len = if no_annotation_lines_counter > 8 { + 4 + } else { + 0 + }; + let post_len = if no_annotation_lines_counter > 8 { + 2 + } else { + 1 + }; + for (i, _) in body + .iter() + .enumerate() + .take(fold_start + pre_len) + .skip(fold_start) + { + lines.push(Line::Source(i)); + } + lines.push(Line::Fold(idx)); + for (i, _) in body + .iter() + .enumerate() + .take(fold_end) + .skip(fold_end - post_len) + { + lines.push(Line::Source(i)); + } + } else { + for (i, _) in body.iter().enumerate().take(idx).skip(fold_start) { + lines.push(Line::Source(i)); + } + } + no_annotation_lines_counter = 0; + } + DisplayLine::Source { .. } => { + no_annotation_lines_counter += 1; + continue; + } + _ => { + no_annotation_lines_counter += 1; + } + } + lines.push(Line::Source(idx)); + } + + let mut new_body = vec![]; + let mut removed = 0; + for line in lines { + match line { + Line::Source(i) => { + new_body.push(body.remove(i - removed)); + removed += 1; + } + Line::Fold(i) => { + if let DisplayLine::Source { + line: DisplaySourceLine::Annotation { .. }, + ref inline_marks, + .. + } = body.get(i - removed).unwrap() + { + new_body.push(DisplayLine::Fold { + inline_marks: inline_marks.clone(), + }) + } else { + unreachable!() + } + } + } + } + + new_body +} + +fn format_body( + slice: snippet::Slice<'_>, + need_empty_header: bool, + has_footer: bool, + margin: Option<Margin>, +) -> Vec<DisplayLine<'_>> { + let source_len = slice.source.chars().count(); + if let Some(bigger) = slice.annotations.iter().find_map(|x| { + if source_len < x.range.1 { + Some(x.range) + } else { + None + } + }) { + panic!( + "SourceAnnotation range `{:?}` is bigger than source length `{}`", + bigger, source_len + ) + } + + let mut body = vec![]; + let mut current_line = slice.line_start; + let mut current_index = 0; + let mut line_info = vec![]; + + struct LineInfo { + line_start_index: usize, + line_end_index: usize, + // How many spaces each character in the line take up when displayed + char_widths: Vec<usize>, + } + + for (line, end_line) in CursorLines::new(slice.source) { + let line_length = line.chars().count(); + let line_range = (current_index, current_index + line_length); + let char_widths = line + .chars() + .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) + .chain(std::iter::once(1)) // treat the end of line as single-width + .collect::<Vec<_>>(); + body.push(DisplayLine::Source { + lineno: Some(current_line), + inline_marks: vec![], + line: DisplaySourceLine::Content { + text: line, + range: line_range, + }, + }); + line_info.push(LineInfo { + line_start_index: line_range.0, + line_end_index: line_range.1, + char_widths, + }); + current_line += 1; + current_index += line_length + end_line as usize; + } + + let mut annotation_line_count = 0; + let mut annotations = slice.annotations; + for ( + idx, + LineInfo { + line_start_index, + line_end_index, + char_widths, + }, + ) in line_info.into_iter().enumerate() + { + let margin_left = margin + .map(|m| m.left(line_end_index - line_start_index)) + .unwrap_or_default(); + // It would be nice to use filter_drain here once it's stable. + annotations.retain(|annotation| { + let body_idx = idx + annotation_line_count; + let annotation_type = match annotation.annotation_type { + snippet::AnnotationType::Error => DisplayAnnotationType::None, + snippet::AnnotationType::Warning => DisplayAnnotationType::None, + _ => DisplayAnnotationType::from(annotation.annotation_type), + }; + match annotation.range { + (start, _) if start > line_end_index => true, + (start, end) + if start >= line_start_index && end <= line_end_index + || start == line_end_index && end - start <= 1 => + { + let annotation_start_col = char_widths + .iter() + .take(start - line_start_index) + .sum::<usize>() + - margin_left; + let annotation_end_col = char_widths + .iter() + .take(end - line_start_index) + .sum::<usize>() + - margin_left; + let range = (annotation_start_col, annotation_end_col); + body.insert( + body_idx + 1, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Annotation { + annotation: Annotation { + annotation_type, + id: None, + label: format_label(Some(annotation.label), None), + }, + range, + annotation_type: DisplayAnnotationType::from( + annotation.annotation_type, + ), + annotation_part: DisplayAnnotationPart::Standalone, + }, + }, + ); + annotation_line_count += 1; + false + } + (start, end) + if start >= line_start_index + && start <= line_end_index + && end > line_end_index => + { + if start - line_start_index == 0 { + if let DisplayLine::Source { + ref mut inline_marks, + .. + } = body[body_idx] + { + inline_marks.push(DisplayMark { + mark_type: DisplayMarkType::AnnotationStart, + annotation_type: DisplayAnnotationType::from( + annotation.annotation_type, + ), + }); + } + } else { + let annotation_start_col = char_widths + .iter() + .take(start - line_start_index) + .sum::<usize>(); + let range = (annotation_start_col, annotation_start_col + 1); + body.insert( + body_idx + 1, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::None, + id: None, + label: vec![], + }, + range, + annotation_type: DisplayAnnotationType::from( + annotation.annotation_type, + ), + annotation_part: DisplayAnnotationPart::MultilineStart, + }, + }, + ); + annotation_line_count += 1; + } + true + } + (start, end) if start < line_start_index && end > line_end_index => { + if let DisplayLine::Source { + ref mut inline_marks, + .. + } = body[body_idx] + { + inline_marks.push(DisplayMark { + mark_type: DisplayMarkType::AnnotationThrough, + annotation_type: DisplayAnnotationType::from( + annotation.annotation_type, + ), + }); + } + true + } + (start, end) + if start < line_start_index + && end >= line_start_index + && end <= line_end_index => + { + if let DisplayLine::Source { + ref mut inline_marks, + .. + } = body[body_idx] + { + inline_marks.push(DisplayMark { + mark_type: DisplayMarkType::AnnotationThrough, + annotation_type: DisplayAnnotationType::from( + annotation.annotation_type, + ), + }); + } + + let end_mark = char_widths + .iter() + .take(end - line_start_index) + .sum::<usize>() + .saturating_sub(1); + let range = (end_mark - margin_left, (end_mark + 1) - margin_left); + body.insert( + body_idx + 1, + DisplayLine::Source { + lineno: None, + inline_marks: vec![DisplayMark { + mark_type: DisplayMarkType::AnnotationThrough, + annotation_type: DisplayAnnotationType::from( + annotation.annotation_type, + ), + }], + line: DisplaySourceLine::Annotation { + annotation: Annotation { + annotation_type, + id: None, + label: format_label(Some(annotation.label), None), + }, + range, + annotation_type: DisplayAnnotationType::from( + annotation.annotation_type, + ), + annotation_part: DisplayAnnotationPart::MultilineEnd, + }, + }, + ); + annotation_line_count += 1; + false + } + _ => true, + } + }); + } + + if slice.fold { + body = fold_body(body); + } + + if need_empty_header { + body.insert( + 0, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + ); + } + + if has_footer { + body.push(DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }); + } else if let Some(DisplayLine::Source { .. }) = body.last() { + body.push(DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }); + } + body +} + +fn format_repeat_char(c: char, n: usize, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for _ in 0..n { + f.write_char(c)?; + } + Ok(()) +} + +#[inline] +fn is_annotation_empty(annotation: &Annotation<'_>) -> bool { + annotation + .label + .iter() + .all(|fragment| fragment.content.is_empty()) +} diff --git a/src/display_list/structs.rs b/src/display_list/structs.rs deleted file mode 100644 index 5a31d1e1..00000000 --- a/src/display_list/structs.rs +++ /dev/null @@ -1,308 +0,0 @@ -use std::cmp::{max, min}; -use std::fmt; - -use crate::formatter::{get_term_style, style::Stylesheet}; - -/// List of lines to be displayed. -pub struct DisplayList<'a> { - pub body: Vec<DisplayLine<'a>>, - pub stylesheet: Box<dyn Stylesheet>, - pub anonymized_line_numbers: bool, - pub margin: Option<Margin>, -} - -impl<'a> From<Vec<DisplayLine<'a>>> for DisplayList<'a> { - fn from(body: Vec<DisplayLine<'a>>) -> DisplayList<'a> { - Self { - body, - anonymized_line_numbers: false, - stylesheet: get_term_style(false), - margin: None, - } - } -} - -impl<'a> PartialEq for DisplayList<'a> { - fn eq(&self, other: &Self) -> bool { - self.body == other.body && self.anonymized_line_numbers == other.anonymized_line_numbers - } -} - -impl<'a> fmt::Debug for DisplayList<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("DisplayList") - .field("body", &self.body) - .field("anonymized_line_numbers", &self.anonymized_line_numbers) - .finish() - } -} - -#[derive(Debug, Default, Copy, Clone)] -pub struct FormatOptions { - pub color: bool, - pub anonymized_line_numbers: bool, - pub margin: Option<Margin>, -} - -#[derive(Clone, Copy, Debug)] -pub struct Margin { - /// The available whitespace in the left that can be consumed when centering. - whitespace_left: usize, - /// The column of the beginning of left-most span. - span_left: usize, - /// The column of the end of right-most span. - span_right: usize, - /// The beginning of the line to be displayed. - computed_left: usize, - /// The end of the line to be displayed. - computed_right: usize, - /// The current width of the terminal. 140 by default and in tests. - column_width: usize, - /// The end column of a span label, including the span. Doesn't account for labels not in the - /// same line as the span. - label_right: usize, -} - -impl Margin { - pub fn new( - whitespace_left: usize, - span_left: usize, - span_right: usize, - label_right: usize, - column_width: usize, - max_line_len: usize, - ) -> Self { - // The 6 is padding to give a bit of room for `...` when displaying: - // ``` - // error: message - // --> file.rs:16:58 - // | - // 16 | ... fn foo(self) -> Self::Bar { - // | ^^^^^^^^^ - // ``` - - let mut m = Margin { - whitespace_left: whitespace_left.saturating_sub(6), - span_left: span_left.saturating_sub(6), - span_right: span_right + 6, - computed_left: 0, - computed_right: 0, - column_width, - label_right: label_right + 6, - }; - m.compute(max_line_len); - m - } - - pub(crate) fn was_cut_left(&self) -> bool { - self.computed_left > 0 - } - - pub(crate) fn was_cut_right(&self, line_len: usize) -> bool { - let right = - if self.computed_right == self.span_right || self.computed_right == self.label_right { - // Account for the "..." padding given above. Otherwise we end up with code lines that - // do fit but end in "..." as if they were trimmed. - self.computed_right - 6 - } else { - self.computed_right - }; - right < line_len && self.computed_left + self.column_width < line_len - } - - fn compute(&mut self, max_line_len: usize) { - // When there's a lot of whitespace (>20), we want to trim it as it is useless. - self.computed_left = if self.whitespace_left > 20 { - self.whitespace_left - 16 // We want some padding. - } else { - 0 - }; - // We want to show as much as possible, max_line_len is the right-most boundary for the - // relevant code. - self.computed_right = max(max_line_len, self.computed_left); - - if self.computed_right - self.computed_left > self.column_width { - // Trimming only whitespace isn't enough, let's get craftier. - if self.label_right - self.whitespace_left <= self.column_width { - // Attempt to fit the code window only trimming whitespace. - self.computed_left = self.whitespace_left; - self.computed_right = self.computed_left + self.column_width; - } else if self.label_right - self.span_left <= self.column_width { - // Attempt to fit the code window considering only the spans and labels. - let padding_left = (self.column_width - (self.label_right - self.span_left)) / 2; - self.computed_left = self.span_left.saturating_sub(padding_left); - self.computed_right = self.computed_left + self.column_width; - } else if self.span_right - self.span_left <= self.column_width { - // Attempt to fit the code window considering the spans and labels plus padding. - let padding_left = (self.column_width - (self.span_right - self.span_left)) / 5 * 2; - self.computed_left = self.span_left.saturating_sub(padding_left); - self.computed_right = self.computed_left + self.column_width; - } else { - // Mostly give up but still don't show the full line. - self.computed_left = self.span_left; - self.computed_right = self.span_right; - } - } - } - - pub(crate) fn left(&self, line_len: usize) -> usize { - min(self.computed_left, line_len) - } - - pub(crate) fn right(&self, line_len: usize) -> usize { - if line_len.saturating_sub(self.computed_left) <= self.column_width { - line_len - } else { - min(line_len, self.computed_right) - } - } -} - -/// Inline annotation which can be used in either Raw or Source line. -#[derive(Debug, PartialEq)] -pub struct Annotation<'a> { - pub annotation_type: DisplayAnnotationType, - pub id: Option<&'a str>, - pub label: Vec<DisplayTextFragment<'a>>, -} - -/// A single line used in `DisplayList`. -#[derive(Debug, PartialEq)] -pub enum DisplayLine<'a> { - /// A line with `lineno` portion of the slice. - Source { - lineno: Option<usize>, - inline_marks: Vec<DisplayMark>, - line: DisplaySourceLine<'a>, - }, - - /// A line indicating a folded part of the slice. - Fold { inline_marks: Vec<DisplayMark> }, - - /// A line which is displayed outside of slices. - Raw(DisplayRawLine<'a>), -} - -/// A source line. -#[derive(Debug, PartialEq)] -pub enum DisplaySourceLine<'a> { - /// A line with the content of the Slice. - Content { - text: &'a str, - range: (usize, usize), // meta information for annotation placement. - }, - - /// An annotation line which is displayed in context of the slice. - Annotation { - annotation: Annotation<'a>, - range: (usize, usize), - annotation_type: DisplayAnnotationType, - annotation_part: DisplayAnnotationPart, - }, - - /// An empty source line. - Empty, -} - -/// Raw line - a line which does not have the `lineno` part and is not considered -/// a part of the snippet. -#[derive(Debug, PartialEq)] -pub enum DisplayRawLine<'a> { - /// A line which provides information about the location of the given - /// slice in the project structure. - Origin { - path: &'a str, - pos: Option<(usize, usize)>, - header_type: DisplayHeaderType, - }, - - /// An annotation line which is not part of any snippet. - Annotation { - annotation: Annotation<'a>, - - /// If set to `true`, the annotation will be aligned to the - /// lineno delimiter of the snippet. - source_aligned: bool, - /// If set to `true`, only the label of the `Annotation` will be - /// displayed. It allows for a multiline annotation to be aligned - /// without displaying the meta information (`type` and `id`) to be - /// displayed on each line. - continuation: bool, - }, -} - -/// An inline text fragment which any label is composed of. -#[derive(Debug, PartialEq)] -pub struct DisplayTextFragment<'a> { - pub content: &'a str, - pub style: DisplayTextStyle, -} - -/// A style for the `DisplayTextFragment` which can be visually formatted. -/// -/// This information may be used to emphasis parts of the label. -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum DisplayTextStyle { - Regular, - Emphasis, -} - -/// An indicator of what part of the annotation a given `Annotation` is. -#[derive(Debug, Clone, PartialEq)] -pub enum DisplayAnnotationPart { - /// A standalone, single-line annotation. - Standalone, - /// A continuation of a multi-line label of an annotation. - LabelContinuation, - /// A consequitive annotation in case multiple annotations annotate a single line. - Consequitive, - /// A line starting a multiline annotation. - MultilineStart, - /// A line ending a multiline annotation. - MultilineEnd, -} - -/// A visual mark used in `inline_marks` field of the `DisplaySourceLine`. -#[derive(Debug, Clone, PartialEq)] -pub struct DisplayMark { - pub mark_type: DisplayMarkType, - pub annotation_type: DisplayAnnotationType, -} - -/// A type of the `DisplayMark`. -#[derive(Debug, Clone, PartialEq)] -pub enum DisplayMarkType { - /// A mark indicating a multiline annotation going through the current line. - AnnotationThrough, - /// A mark indicating a multiline annotation starting on the given line. - AnnotationStart, -} - -/// A type of the `Annotation` which may impact the sigils, style or text displayed. -/// -/// There are several ways to uses this information when formatting the `DisplayList`: -/// -/// * An annotation may display the name of the type like `error` or `info`. -/// * An underline for `Error` may be `^^^` while for `Warning` it could be `---`. -/// * `ColorStylesheet` may use different colors for different annotations. -#[derive(Debug, Clone, PartialEq)] -pub enum DisplayAnnotationType { - None, - Error, - Warning, - Info, - Note, - Help, -} - -/// Information whether the header is the initial one or a consequitive one -/// for multi-slice cases. -// TODO: private -#[derive(Debug, Clone, PartialEq)] -pub enum DisplayHeaderType { - /// Initial header is the first header in the snippet. - Initial, - - /// Continuation marks all headers of following slices in the snippet. - Continuation, -} diff --git a/src/formatter/mod.rs b/src/formatter/mod.rs index 16889baa..72bf9c77 100644 --- a/src/formatter/mod.rs +++ b/src/formatter/mod.rs @@ -1,31 +1,10 @@ -use std::{ - cmp, - fmt::{self, Display, Write}, - iter::once, -}; - pub mod style; -use self::style::{Style, StyleClass, Stylesheet}; +use self::style::Stylesheet; #[cfg(feature = "color")] use crate::stylesheets::color::AnsiTermStylesheet; -use crate::{display_list::*, stylesheets::no_color::NoColorStylesheet}; - -fn format_repeat_char(c: char, n: usize, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for _ in 0..n { - f.write_char(c)?; - } - Ok(()) -} - -#[inline] -fn is_annotation_empty(annotation: &Annotation<'_>) -> bool { - annotation - .label - .iter() - .all(|fragment| fragment.content.is_empty()) -} +use crate::stylesheets::no_color::NoColorStylesheet; #[cfg(feature = "color")] #[inline] @@ -42,415 +21,3 @@ pub fn get_term_style(color: bool) -> Box<dyn Stylesheet> { pub fn get_term_style(_color: bool) -> Box<dyn Stylesheet> { Box::new(NoColorStylesheet) } - -impl<'a> fmt::Display for DisplayList<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let lineno_width = self.body.iter().fold(0, |max, line| match line { - DisplayLine::Source { - lineno: Some(lineno), - .. - } => { - // The largest line is the largest width. - cmp::max(*lineno, max) - } - _ => max, - }); - let lineno_width = if lineno_width == 0 { - lineno_width - } else if self.anonymized_line_numbers { - Self::ANONYMIZED_LINE_NUM.len() - } else { - ((lineno_width as f64).log10().floor() as usize) + 1 - }; - let inline_marks_width = self.body.iter().fold(0, |max, line| match line { - DisplayLine::Source { inline_marks, .. } => cmp::max(inline_marks.len(), max), - _ => max, - }); - - for (i, line) in self.body.iter().enumerate() { - self.format_line(line, lineno_width, inline_marks_width, f)?; - if i + 1 < self.body.len() { - f.write_char('\n')?; - } - } - Ok(()) - } -} - -impl<'a> DisplayList<'a> { - const ANONYMIZED_LINE_NUM: &'static str = "LL"; - const ERROR_TXT: &'static str = "error"; - const HELP_TXT: &'static str = "help"; - const INFO_TXT: &'static str = "info"; - const NOTE_TXT: &'static str = "note"; - const WARNING_TXT: &'static str = "warning"; - - #[inline] - fn format_annotation_type( - annotation_type: &DisplayAnnotationType, - f: &mut fmt::Formatter<'_>, - ) -> fmt::Result { - match annotation_type { - DisplayAnnotationType::Error => f.write_str(Self::ERROR_TXT), - DisplayAnnotationType::Help => f.write_str(Self::HELP_TXT), - DisplayAnnotationType::Info => f.write_str(Self::INFO_TXT), - DisplayAnnotationType::Note => f.write_str(Self::NOTE_TXT), - DisplayAnnotationType::Warning => f.write_str(Self::WARNING_TXT), - DisplayAnnotationType::None => Ok(()), - } - } - - fn annotation_type_len(annotation_type: &DisplayAnnotationType) -> usize { - match annotation_type { - DisplayAnnotationType::Error => Self::ERROR_TXT.len(), - DisplayAnnotationType::Help => Self::HELP_TXT.len(), - DisplayAnnotationType::Info => Self::INFO_TXT.len(), - DisplayAnnotationType::Note => Self::NOTE_TXT.len(), - DisplayAnnotationType::Warning => Self::WARNING_TXT.len(), - DisplayAnnotationType::None => 0, - } - } - - fn get_annotation_style(&self, annotation_type: &DisplayAnnotationType) -> Box<dyn Style> { - self.stylesheet.get_style(match annotation_type { - DisplayAnnotationType::Error => StyleClass::Error, - DisplayAnnotationType::Warning => StyleClass::Warning, - DisplayAnnotationType::Info => StyleClass::Info, - DisplayAnnotationType::Note => StyleClass::Note, - DisplayAnnotationType::Help => StyleClass::Help, - DisplayAnnotationType::None => StyleClass::None, - }) - } - - fn format_label( - &self, - label: &[DisplayTextFragment<'_>], - f: &mut fmt::Formatter<'_>, - ) -> fmt::Result { - let emphasis_style = self.stylesheet.get_style(StyleClass::Emphasis); - - for fragment in label { - match fragment.style { - DisplayTextStyle::Regular => fragment.content.fmt(f)?, - DisplayTextStyle::Emphasis => emphasis_style.paint(fragment.content, f)?, - } - } - Ok(()) - } - - fn format_annotation( - &self, - annotation: &Annotation<'_>, - continuation: bool, - in_source: bool, - f: &mut fmt::Formatter<'_>, - ) -> fmt::Result { - let color = self.get_annotation_style(&annotation.annotation_type); - let formatted_len = if let Some(id) = &annotation.id { - 2 + id.len() + Self::annotation_type_len(&annotation.annotation_type) - } else { - Self::annotation_type_len(&annotation.annotation_type) - }; - - if continuation { - format_repeat_char(' ', formatted_len + 2, f)?; - return self.format_label(&annotation.label, f); - } - if formatted_len == 0 { - self.format_label(&annotation.label, f) - } else { - color.paint_fn( - Box::new(|f| { - Self::format_annotation_type(&annotation.annotation_type, f)?; - if let Some(id) = &annotation.id { - f.write_char('[')?; - f.write_str(id)?; - f.write_char(']')?; - } - Ok(()) - }), - f, - )?; - if !is_annotation_empty(annotation) { - if in_source { - color.paint_fn( - Box::new(|f| { - f.write_str(": ")?; - self.format_label(&annotation.label, f) - }), - f, - )?; - } else { - f.write_str(": ")?; - self.format_label(&annotation.label, f)?; - } - } - Ok(()) - } - } - - #[inline] - fn format_source_line( - &self, - line: &DisplaySourceLine<'_>, - f: &mut fmt::Formatter<'_>, - ) -> fmt::Result { - match line { - DisplaySourceLine::Empty => Ok(()), - DisplaySourceLine::Content { text, .. } => { - f.write_char(' ')?; - if let Some(margin) = self.margin { - let line_len = text.chars().count(); - let mut left = margin.left(line_len); - let right = margin.right(line_len); - - if margin.was_cut_left() { - // We have stripped some code/whitespace from the beginning, make it clear. - "...".fmt(f)?; - left += 3; - } - - // On long lines, we strip the source line, accounting for unicode. - let mut taken = 0; - let cut_right = if margin.was_cut_right(line_len) { - taken += 3; - true - } else { - false - }; - // Specifies that it will end on the next character, so it will return - // until the next one to the final condition. - let mut ended = false; - let range = text - .char_indices() - .skip(left) - // Complete char iterator with final character - .chain(once((text.len(), '\0'))) - // Take until the next one to the final condition - .take_while(|(_, ch)| { - // Fast return to iterate over final byte position - if ended { - return false; - } - // Make sure that the trimming on the right will fall within the terminal width. - // FIXME: `unicode_width` sometimes disagrees with terminals on how wide a `char` is. - // For now, just accept that sometimes the code line will be longer than desired. - taken += unicode_width::UnicodeWidthChar::width(*ch).unwrap_or(1); - if taken > right - left { - ended = true; - } - true - }) - // Reduce to start and end byte position - .fold((None, 0), |acc, (i, _)| { - if acc.0.is_some() { - (acc.0, i) - } else { - (Some(i), i) - } - }); - - // Format text with margins - text[range.0.expect("One character at line")..range.1].fmt(f)?; - - if cut_right { - // We have stripped some code after the right-most span end, make it clear we did so. - "...".fmt(f)?; - } - Ok(()) - } else { - text.fmt(f) - } - } - DisplaySourceLine::Annotation { - range, - annotation, - annotation_type, - annotation_part, - } => { - let indent_char = match annotation_part { - DisplayAnnotationPart::Standalone => ' ', - DisplayAnnotationPart::LabelContinuation => ' ', - DisplayAnnotationPart::Consequitive => ' ', - DisplayAnnotationPart::MultilineStart => '_', - DisplayAnnotationPart::MultilineEnd => '_', - }; - let mark = match annotation_type { - DisplayAnnotationType::Error => '^', - DisplayAnnotationType::Warning => '-', - DisplayAnnotationType::Info => '-', - DisplayAnnotationType::Note => '-', - DisplayAnnotationType::Help => '-', - DisplayAnnotationType::None => ' ', - }; - let color = self.get_annotation_style(annotation_type); - let indent_length = match annotation_part { - DisplayAnnotationPart::LabelContinuation => range.1, - DisplayAnnotationPart::Consequitive => range.1, - _ => range.0, - }; - - color.paint_fn( - Box::new(|f| { - format_repeat_char(indent_char, indent_length + 1, f)?; - format_repeat_char(mark, range.1 - indent_length, f) - }), - f, - )?; - - if !is_annotation_empty(annotation) { - f.write_char(' ')?; - color.paint_fn( - Box::new(|f| { - self.format_annotation( - annotation, - annotation_part == &DisplayAnnotationPart::LabelContinuation, - true, - f, - ) - }), - f, - )?; - } - - Ok(()) - } - } - } - - #[inline] - fn format_raw_line( - &self, - line: &DisplayRawLine<'_>, - lineno_width: usize, - f: &mut fmt::Formatter<'_>, - ) -> fmt::Result { - match line { - DisplayRawLine::Origin { - path, - pos, - header_type, - } => { - let header_sigil = match header_type { - DisplayHeaderType::Initial => "-->", - DisplayHeaderType::Continuation => ":::", - }; - let lineno_color = self.stylesheet.get_style(StyleClass::LineNo); - - if let Some((col, row)) = pos { - format_repeat_char(' ', lineno_width, f)?; - lineno_color.paint(header_sigil, f)?; - f.write_char(' ')?; - path.fmt(f)?; - f.write_char(':')?; - col.fmt(f)?; - f.write_char(':')?; - row.fmt(f) - } else { - format_repeat_char(' ', lineno_width, f)?; - lineno_color.paint(header_sigil, f)?; - f.write_char(' ')?; - path.fmt(f) - } - } - DisplayRawLine::Annotation { - annotation, - source_aligned, - continuation, - } => { - if *source_aligned { - if *continuation { - format_repeat_char(' ', lineno_width + 3, f)?; - } else { - let lineno_color = self.stylesheet.get_style(StyleClass::LineNo); - format_repeat_char(' ', lineno_width, f)?; - f.write_char(' ')?; - lineno_color.paint("=", f)?; - f.write_char(' ')?; - } - } - self.format_annotation(annotation, *continuation, false, f) - } - } - } - - #[inline] - fn format_line( - &self, - dl: &DisplayLine<'_>, - lineno_width: usize, - inline_marks_width: usize, - f: &mut fmt::Formatter<'_>, - ) -> fmt::Result { - match dl { - DisplayLine::Source { - lineno, - inline_marks, - line, - } => { - let lineno_color = self.stylesheet.get_style(StyleClass::LineNo); - if self.anonymized_line_numbers && lineno.is_some() { - lineno_color.paint_fn( - Box::new(|f| { - f.write_str(Self::ANONYMIZED_LINE_NUM)?; - f.write_str(" |") - }), - f, - )?; - } else { - lineno_color.paint_fn( - Box::new(|f| { - match lineno { - Some(n) => write!(f, "{:>width$}", n, width = lineno_width), - None => format_repeat_char(' ', lineno_width, f), - }?; - f.write_str(" |") - }), - f, - )?; - } - if *line != DisplaySourceLine::Empty { - if !inline_marks.is_empty() || 0 < inline_marks_width { - f.write_char(' ')?; - self.format_inline_marks(inline_marks, inline_marks_width, f)?; - } - self.format_source_line(line, f)?; - } else if !inline_marks.is_empty() { - f.write_char(' ')?; - self.format_inline_marks(inline_marks, inline_marks_width, f)?; - } - Ok(()) - } - DisplayLine::Fold { inline_marks } => { - f.write_str("...")?; - if !inline_marks.is_empty() || 0 < inline_marks_width { - format_repeat_char(' ', lineno_width, f)?; - self.format_inline_marks(inline_marks, inline_marks_width, f)?; - } - Ok(()) - } - DisplayLine::Raw(line) => self.format_raw_line(line, lineno_width, f), - } - } - - fn format_inline_marks( - &self, - inline_marks: &[DisplayMark], - inline_marks_width: usize, - f: &mut fmt::Formatter<'_>, - ) -> fmt::Result { - format_repeat_char(' ', inline_marks_width - inline_marks.len(), f)?; - for mark in inline_marks { - self.get_annotation_style(&mark.annotation_type).paint_fn( - Box::new(|f| { - f.write_char(match mark.mark_type { - DisplayMarkType::AnnotationThrough => '|', - DisplayMarkType::AnnotationStart => '/', - }) - }), - f, - )?; - } - Ok(()) - } -} From 9076cbf66336e5137b47dc7a52df2999b6c82598 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Sun, 12 Nov 2023 12:44:16 -0700 Subject: [PATCH 089/455] feat: Add `Renderer` --- benches/simple.rs | 7 ++++--- examples/expected_type.rs | 7 ++++--- examples/footer.rs | 7 ++++--- examples/format.rs | 7 ++++--- examples/multislice.rs | 7 ++++--- src/lib.rs | 1 + src/renderer/mod.rs | 11 +++++++++++ tests/fixtures_test.rs | 7 ++++--- tests/formatter.rs | 16 +++++++++++----- 9 files changed, 47 insertions(+), 23 deletions(-) create mode 100644 src/renderer/mod.rs diff --git a/benches/simple.rs b/benches/simple.rs index 4c13a8f0..4427afde 100644 --- a/benches/simple.rs +++ b/benches/simple.rs @@ -4,8 +4,9 @@ extern crate criterion; use criterion::{black_box, Criterion}; +use annotate_snippets::renderer::Renderer; use annotate_snippets::{ - display_list::{DisplayList, FormatOptions}, + display_list::FormatOptions, snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, }; @@ -62,8 +63,8 @@ fn create_snippet() { }, }; - let dl = DisplayList::from(snippet); - let _result = dl.to_string(); + let renderer = Renderer; + let _result = renderer.render(snippet).to_string(); } pub fn criterion_benchmark(c: &mut Criterion) { diff --git a/examples/expected_type.rs b/examples/expected_type.rs index 6f2a0d9a..cd23bfcf 100644 --- a/examples/expected_type.rs +++ b/examples/expected_type.rs @@ -1,5 +1,6 @@ +use annotate_snippets::renderer::Renderer; use annotate_snippets::{ - display_list::{DisplayList, FormatOptions}, + display_list::FormatOptions, snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, }; @@ -38,6 +39,6 @@ fn main() { }, }; - let dl = DisplayList::from(snippet); - println!("{}", dl); + let renderer = Renderer; + println!("{}", renderer.render(snippet)); } diff --git a/examples/footer.rs b/examples/footer.rs index f3c15c41..6bf6f5ed 100644 --- a/examples/footer.rs +++ b/examples/footer.rs @@ -1,5 +1,6 @@ +use annotate_snippets::renderer::Renderer; use annotate_snippets::{ - display_list::{DisplayList, FormatOptions}, + display_list::FormatOptions, snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, }; @@ -34,6 +35,6 @@ fn main() { }, }; - let dl = DisplayList::from(snippet); - println!("{}", dl); + let renderer = Renderer; + println!("{}", renderer.render(snippet)); } diff --git a/examples/format.rs b/examples/format.rs index 98b77a14..a053ccc2 100644 --- a/examples/format.rs +++ b/examples/format.rs @@ -1,5 +1,6 @@ +use annotate_snippets::renderer::Renderer; use annotate_snippets::{ - display_list::{DisplayList, FormatOptions}, + display_list::FormatOptions, snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, }; @@ -56,6 +57,6 @@ fn main() { }, }; - let dl = DisplayList::from(snippet); - println!("{}", dl); + let renderer = Renderer; + println!("{}", renderer.render(snippet)); } diff --git a/examples/multislice.rs b/examples/multislice.rs index 5675a07d..a875ae26 100644 --- a/examples/multislice.rs +++ b/examples/multislice.rs @@ -1,5 +1,6 @@ +use annotate_snippets::renderer::Renderer; use annotate_snippets::{ - display_list::{DisplayList, FormatOptions}, + display_list::FormatOptions, snippet::{Annotation, AnnotationType, Slice, Snippet}, }; @@ -33,6 +34,6 @@ fn main() { }, }; - let dl = DisplayList::from(snippet); - println!("{}", dl); + let renderer = Renderer; + println!("{}", renderer.render(snippet)); } diff --git a/src/lib.rs b/src/lib.rs index d5813672..e11e9d58 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,5 +50,6 @@ pub mod display_list; pub mod formatter; +pub mod renderer; pub mod snippet; pub mod stylesheets; diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs new file mode 100644 index 00000000..9a66c1c2 --- /dev/null +++ b/src/renderer/mod.rs @@ -0,0 +1,11 @@ +use crate::display_list::DisplayList; +use crate::snippet::Snippet; +use std::fmt::Display; + +pub struct Renderer; + +impl Renderer { + pub fn render<'a>(&'a self, snippet: Snippet<'a>) -> impl Display + 'a { + DisplayList::from(snippet) + } +} diff --git a/tests/fixtures_test.rs b/tests/fixtures_test.rs index 13f85431..8dfd229e 100644 --- a/tests/fixtures_test.rs +++ b/tests/fixtures_test.rs @@ -2,7 +2,8 @@ mod diff; mod snippet; use crate::snippet::SnippetDef; -use annotate_snippets::{display_list::DisplayList, snippet::Snippet}; +use annotate_snippets::renderer::Renderer; +use annotate_snippets::snippet::Snippet; use glob::glob; use std::{error::Error, fs::File, io, io::prelude::*}; @@ -30,8 +31,8 @@ fn test_fixtures() { let snippet = read_fixture(&src).expect("Failed to read file"); let expected_out = read_file(&path_out).expect("Failed to read file"); - let dl = DisplayList::from(snippet); - let actual_out = dl.to_string(); + let renderer = Renderer; + let actual_out = renderer.render(snippet).to_string(); println!("{}", expected_out); println!("{}", actual_out.trim_end()); diff --git a/tests/formatter.rs b/tests/formatter.rs index f95b0026..1226ab49 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -1,4 +1,5 @@ use annotate_snippets::display_list::*; +use annotate_snippets::renderer::Renderer; use annotate_snippets::snippet::{self, Snippet}; #[test] @@ -548,7 +549,8 @@ fn test_i_29() { | ^^^^ oops |"#; - assert_eq!(DisplayList::from(snippets).to_string(), expected); + let renderer = Renderer; + assert_eq!(renderer.render(snippets).to_string(), expected); } #[test] @@ -576,7 +578,8 @@ fn test_point_to_double_width_characters() { | ^^^^ world |"#; - assert_eq!(DisplayList::from(snippets).to_string(), expected); + let renderer = Renderer; + assert_eq!(renderer.render(snippets).to_string(), expected); } #[test] @@ -606,7 +609,8 @@ fn test_point_to_double_width_characters_across_lines() { | |______^ Good morning |"#; - assert_eq!(DisplayList::from(snippets).to_string(), expected); + let renderer = Renderer; + assert_eq!(renderer.render(snippets).to_string(), expected); } #[test] @@ -643,7 +647,8 @@ fn test_point_to_double_width_characters_multiple() { | ---- note: Sushi2 |"#; - assert_eq!(DisplayList::from(snippets).to_string(), expected); + let renderer = Renderer; + assert_eq!(renderer.render(snippets).to_string(), expected); } #[test] @@ -671,5 +676,6 @@ fn test_point_to_double_width_characters_mixed() { | ^^^^^^^^^^^ New world |"#; - assert_eq!(DisplayList::from(snippets).to_string(), expected); + let renderer = Renderer; + assert_eq!(renderer.render(snippets).to_string(), expected); } From 598c6244983fb392457f3fbec9badf25fab6d051 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 3 Dec 2023 09:19:35 +0000 Subject: [PATCH 090/455] chore(config): migrate config .github/renovate.json5 --- .github/renovate.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 72d05795..3119c425 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -29,7 +29,7 @@ { commitMessageTopic: 'MSRV', matchManagers: [ - 'regex', + 'custom.regex', ], matchPackageNames: [ 'rust', From c84cb0f1ae2815f98dbe1b12dee8015c591ac4f1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 5 Dec 2023 05:17:36 +0000 Subject: [PATCH 091/455] chore(config): migrate config .github/renovate.json5 --- .github/renovate.json5 | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 21d082f3..87676d4d 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -29,24 +29,18 @@ { commitMessageTopic: 'MSRV', matchManagers: [ - 'regex', + 'custom.regex', ], matchPackageNames: [ 'rust', ], - minimumReleaseAge: '84 days', // 2 releases back * 6 weeks per release * 7 days per week + minimumReleaseAge: '84 days', internalChecksFilter: 'strict', - extractVersion: '^(?<version>\\d+\\.\\d+)', // Drop the patch version + extractVersion: '^(?<version>\\d+\\.\\d+)', schedule: [ '* * * * *', ], }, - // Goals: - // - Keep version reqs low, ignoring compatible normal/build dependencies - // - Take advantage of latest dev-dependencies - // - Rollup safe upgrades to reduce CI runner load - // - Help keep number of versions down by always using latest breaking change - // - Have lockfile and manifest in-sync { matchManagers: [ 'cargo', From d0c65b26493d60f86a82c5919ef736b35808c23a Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Sat, 2 Dec 2023 13:01:27 -0700 Subject: [PATCH 092/455] fix!: Move format options to `Renderer` BREAKING CHANGE: This removes `opt` from `Snippet` --- benches/simple.rs | 16 +-- examples/expected_type.rs | 11 +- examples/footer.rs | 11 +- examples/format.rs | 11 +- examples/multislice.rs | 11 +- src/display_list/mod.rs | 43 +++--- src/renderer/mod.rs | 49 ++++++- src/snippet.rs | 3 - tests/{snippet => deserialize}/mod.rs | 131 +++++++++--------- tests/dl_from_snippet.rs | 27 ++-- tests/fixtures/no-color/issue_52.toml | 6 +- tests/fixtures/no-color/issue_9.toml | 14 +- .../no-color/multiline_annotation.toml | 8 +- .../no-color/multiline_annotation2.toml | 6 +- .../no-color/multiline_annotation3.toml | 6 +- .../no-color/multiple_annotations.toml | 8 +- tests/fixtures/no-color/simple.toml | 10 +- tests/fixtures/no-color/strip_line.toml | 10 +- tests/fixtures/no-color/strip_line_char.toml | 10 +- .../fixtures/no-color/strip_line_non_ws.toml | 11 +- tests/fixtures_test.rs | 11 +- tests/formatter.rs | 15 +- 22 files changed, 208 insertions(+), 220 deletions(-) rename tests/{snippet => deserialize}/mod.rs (88%) diff --git a/benches/simple.rs b/benches/simple.rs index 4427afde..8ccd18f7 100644 --- a/benches/simple.rs +++ b/benches/simple.rs @@ -5,12 +5,9 @@ extern crate criterion; use criterion::{black_box, Criterion}; use annotate_snippets::renderer::Renderer; -use annotate_snippets::{ - display_list::FormatOptions, - snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, -}; +use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}; -fn create_snippet() { +fn create_snippet(renderer: Renderer) { let snippet = Snippet { slices: vec![Slice { source: r#") -> Option<String> { @@ -57,18 +54,15 @@ fn create_snippet() { annotation_type: AnnotationType::Error, }), footer: vec![], - opt: FormatOptions { - color: true, - ..Default::default() - }, }; - let renderer = Renderer; let _result = renderer.render(snippet).to_string(); } pub fn criterion_benchmark(c: &mut Criterion) { - c.bench_function("format", |b| b.iter(|| black_box(create_snippet()))); + c.bench_function("format", |b| { + b.iter(|| black_box(create_snippet(Renderer::plain()))) + }); } criterion_group!(benches, criterion_benchmark); diff --git a/examples/expected_type.rs b/examples/expected_type.rs index cd23bfcf..959419c2 100644 --- a/examples/expected_type.rs +++ b/examples/expected_type.rs @@ -1,8 +1,5 @@ use annotate_snippets::renderer::Renderer; -use annotate_snippets::{ - display_list::FormatOptions, - snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, -}; +use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}; fn main() { let snippet = Snippet { @@ -33,12 +30,8 @@ fn main() { }, ], }], - opt: FormatOptions { - color: true, - ..Default::default() - }, }; - let renderer = Renderer; + let renderer = Renderer::plain(); println!("{}", renderer.render(snippet)); } diff --git a/examples/footer.rs b/examples/footer.rs index 6bf6f5ed..0191c1d6 100644 --- a/examples/footer.rs +++ b/examples/footer.rs @@ -1,8 +1,5 @@ use annotate_snippets::renderer::Renderer; -use annotate_snippets::{ - display_list::FormatOptions, - snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, -}; +use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}; fn main() { let snippet = Snippet { @@ -29,12 +26,8 @@ fn main() { annotation_type: AnnotationType::Error, }], }], - opt: FormatOptions { - color: true, - ..Default::default() - }, }; - let renderer = Renderer; + let renderer = Renderer::plain(); println!("{}", renderer.render(snippet)); } diff --git a/examples/format.rs b/examples/format.rs index a053ccc2..7302eefe 100644 --- a/examples/format.rs +++ b/examples/format.rs @@ -1,8 +1,5 @@ use annotate_snippets::renderer::Renderer; -use annotate_snippets::{ - display_list::FormatOptions, - snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, -}; +use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}; fn main() { let snippet = Snippet { @@ -51,12 +48,8 @@ fn main() { annotation_type: AnnotationType::Error, }), footer: vec![], - opt: FormatOptions { - color: true, - ..Default::default() - }, }; - let renderer = Renderer; + let renderer = Renderer::plain(); println!("{}", renderer.render(snippet)); } diff --git a/examples/multislice.rs b/examples/multislice.rs index a875ae26..dc51d4f5 100644 --- a/examples/multislice.rs +++ b/examples/multislice.rs @@ -1,8 +1,5 @@ use annotate_snippets::renderer::Renderer; -use annotate_snippets::{ - display_list::FormatOptions, - snippet::{Annotation, AnnotationType, Slice, Snippet}, -}; +use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet}; fn main() { let snippet = Snippet { @@ -28,12 +25,8 @@ fn main() { annotations: vec![], }, ], - opt: FormatOptions { - color: true, - ..Default::default() - }, }; - let renderer = Renderer; + let renderer = Renderer::plain(); println!("{}", renderer.render(snippet)); } diff --git a/src/display_list/mod.rs b/src/display_list/mod.rs index 1ad328aa..1498dc5e 100644 --- a/src/display_list/mod.rs +++ b/src/display_list/mod.rs @@ -108,13 +108,28 @@ impl<'a> Display for DisplayList<'a> { } impl<'a> From<snippet::Snippet<'a>> for DisplayList<'a> { - fn from( + fn from(snippet: snippet::Snippet<'a>) -> DisplayList<'a> { + Self::new(snippet, false, false, None) + } +} + +impl<'a> DisplayList<'a> { + const ANONYMIZED_LINE_NUM: &'static str = "LL"; + const ERROR_TXT: &'static str = "error"; + const HELP_TXT: &'static str = "help"; + const INFO_TXT: &'static str = "info"; + const NOTE_TXT: &'static str = "note"; + const WARNING_TXT: &'static str = "warning"; + + pub(crate) fn new( snippet::Snippet { title, footer, slices, - opt, }: snippet::Snippet<'a>, + color: bool, + anonymized_line_numbers: bool, + margin: Option<Margin>, ) -> DisplayList<'a> { let mut body = vec![]; if let Some(annotation) = title { @@ -126,7 +141,7 @@ impl<'a> From<snippet::Snippet<'a>> for DisplayList<'a> { slice, idx == 0, !footer.is_empty(), - opt.margin, + margin, )); } @@ -134,12 +149,6 @@ impl<'a> From<snippet::Snippet<'a>> for DisplayList<'a> { body.append(&mut format_annotation(annotation)); } - let FormatOptions { - color, - anonymized_line_numbers, - margin, - } = opt; - Self { body, stylesheet: get_term_style(color), @@ -147,15 +156,6 @@ impl<'a> From<snippet::Snippet<'a>> for DisplayList<'a> { margin, } } -} - -impl<'a> DisplayList<'a> { - const ANONYMIZED_LINE_NUM: &'static str = "LL"; - const ERROR_TXT: &'static str = "error"; - const HELP_TXT: &'static str = "help"; - const INFO_TXT: &'static str = "info"; - const NOTE_TXT: &'static str = "note"; - const WARNING_TXT: &'static str = "warning"; #[inline] fn format_annotation_type( @@ -527,13 +527,6 @@ impl<'a> DisplayList<'a> { } } -#[derive(Debug, Default, Copy, Clone)] -pub struct FormatOptions { - pub color: bool, - pub anonymized_line_numbers: bool, - pub margin: Option<Margin>, -} - #[derive(Clone, Copy, Debug)] pub struct Margin { /// The available whitespace in the left that can be consumed when centering. diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 9a66c1c2..b1f5f055 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -1,11 +1,54 @@ -use crate::display_list::DisplayList; +use crate::display_list::{DisplayList, Margin}; use crate::snippet::Snippet; use std::fmt::Display; -pub struct Renderer; +#[derive(Clone)] +pub struct Renderer { + color: bool, + anonymized_line_numbers: bool, + margin: Option<Margin>, +} impl Renderer { + /// No terminal styling + pub fn plain() -> Self { + Self { + color: false, + anonymized_line_numbers: false, + margin: None, + } + } + + /// Default terminal styling + pub fn styled() -> Self { + Self { + color: true, + anonymized_line_numbers: false, + margin: None, + } + } + + pub fn anonymized_line_numbers(mut self, anonymized_line_numbers: bool) -> Self { + self.anonymized_line_numbers = anonymized_line_numbers; + self + } + + pub fn color(mut self, color: bool) -> Self { + self.color = color; + self + } + + pub fn margin(mut self, margin: Option<Margin>) -> Self { + self.margin = margin; + self + } + pub fn render<'a>(&'a self, snippet: Snippet<'a>) -> impl Display + 'a { - DisplayList::from(snippet) + DisplayList::new( + snippet, + self.color, + self.anonymized_line_numbers, + self.margin, + ) } } diff --git a/src/snippet.rs b/src/snippet.rs index bc7ba009..c1914c96 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -28,10 +28,8 @@ //! annotations: vec![], //! }, //! ], -//! opt: Default::default(), //! }; //! ``` -use crate::display_list::FormatOptions; /// Primary structure provided for formatting #[derive(Debug, Default)] @@ -39,7 +37,6 @@ pub struct Snippet<'a> { pub title: Option<Annotation<'a>>, pub footer: Vec<Annotation<'a>>, pub slices: Vec<Slice<'a>>, - pub opt: FormatOptions, } /// Structure containing the slice of text to be annotated and diff --git a/tests/snippet/mod.rs b/tests/deserialize/mod.rs similarity index 88% rename from tests/snippet/mod.rs rename to tests/deserialize/mod.rs index 92d272de..58d14ef0 100644 --- a/tests/snippet/mod.rs +++ b/tests/deserialize/mod.rs @@ -1,10 +1,19 @@ use serde::{Deserialize, Deserializer, Serialize}; +use annotate_snippets::renderer::Renderer; use annotate_snippets::{ - display_list::{FormatOptions, Margin}, + display_list::Margin, snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, }; +#[derive(Deserialize)] +pub struct Fixture<'a> { + #[serde(default)] + pub renderer: RendererDef, + #[serde(borrow)] + pub snippet: SnippetDef<'a>, +} + #[derive(Deserialize)] pub struct SnippetDef<'a> { #[serde(deserialize_with = "deserialize_annotation")] @@ -15,9 +24,6 @@ pub struct SnippetDef<'a> { #[serde(default)] #[serde(borrow)] pub footer: Vec<Annotation<'a>>, - #[serde(deserialize_with = "deserialize_opt")] - #[serde(default)] - pub opt: FormatOptions, #[serde(deserialize_with = "deserialize_slices")] #[serde(borrow)] pub slices: Vec<Slice<'a>>, @@ -28,76 +34,16 @@ impl<'a> From<SnippetDef<'a>> for Snippet<'a> { let SnippetDef { title, footer, - opt, slices, } = val; Snippet { title, footer, slices, - opt, } } } -fn deserialize_opt<'de, D>(deserializer: D) -> Result<FormatOptions, D::Error> -where - D: Deserializer<'de>, -{ - #[derive(Deserialize)] - struct Wrapper(#[serde(with = "FormatOptionsDef")] FormatOptions); - - Wrapper::deserialize(deserializer).map(|w| w.0) -} - -#[derive(Deserialize)] -#[serde(remote = "FormatOptions")] -pub struct FormatOptionsDef { - #[serde(default)] - pub color: bool, - #[serde(default)] - pub anonymized_line_numbers: bool, - #[serde(deserialize_with = "deserialize_margin")] - #[serde(default)] - pub margin: Option<Margin>, -} - -fn deserialize_margin<'de, D>(deserializer: D) -> Result<Option<Margin>, D::Error> -where - D: Deserializer<'de>, -{ - #[derive(Deserialize)] - struct Wrapper { - whitespace_left: usize, - span_left: usize, - span_right: usize, - label_right: usize, - column_width: usize, - max_line_len: usize, - } - - Option::<Wrapper>::deserialize(deserializer).map(|opt_wrapped: Option<Wrapper>| { - opt_wrapped.map(|wrapped: Wrapper| { - let Wrapper { - whitespace_left, - span_left, - span_right, - label_right, - column_width, - max_line_len, - } = wrapped; - Margin::new( - whitespace_left, - span_left, - span_right, - label_right, - column_width, - max_line_len, - ) - }) - }) -} - fn deserialize_slices<'de, D>(deserializer: D) -> Result<Vec<Slice<'de>>, D::Error> where D: Deserializer<'de>, @@ -206,3 +152,60 @@ enum AnnotationTypeDef { Note, Help, } + +#[derive(Default, Deserialize)] +pub struct RendererDef { + #[serde(default)] + anonymized_line_numbers: bool, + #[serde(deserialize_with = "deserialize_margin")] + #[serde(default)] + margin: Option<Margin>, +} + +impl From<RendererDef> for Renderer { + fn from(val: RendererDef) -> Self { + let RendererDef { + anonymized_line_numbers, + margin, + } = val; + Renderer::plain() + .anonymized_line_numbers(anonymized_line_numbers) + .margin(margin) + } +} + +fn deserialize_margin<'de, D>(deserializer: D) -> Result<Option<Margin>, D::Error> +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + struct Wrapper { + whitespace_left: usize, + span_left: usize, + span_right: usize, + label_right: usize, + column_width: usize, + max_line_len: usize, + } + + Option::<Wrapper>::deserialize(deserializer).map(|opt_wrapped: Option<Wrapper>| { + opt_wrapped.map(|wrapped: Wrapper| { + let Wrapper { + whitespace_left, + span_left, + span_right, + label_right, + column_width, + max_line_len, + } = wrapped; + Margin::new( + whitespace_left, + span_left, + span_right, + label_right, + column_width, + max_line_len, + ) + }) + }) +} diff --git a/tests/dl_from_snippet.rs b/tests/dl_from_snippet.rs index 06f2ff76..9971c7eb 100644 --- a/tests/dl_from_snippet.rs +++ b/tests/dl_from_snippet.rs @@ -11,7 +11,6 @@ fn test_format_title() { }), footer: vec![], slices: vec![], - opt: Default::default(), }; let output = dl::DisplayList { body: vec![dl::DisplayLine::Raw(dl::DisplayRawLine::Annotation { @@ -26,8 +25,8 @@ fn test_format_title() { source_aligned: false, continuation: false, })], - stylesheet: get_term_style(input.opt.color), - anonymized_line_numbers: input.opt.anonymized_line_numbers, + stylesheet: get_term_style(false), + anonymized_line_numbers: false, margin: None, }; assert_eq!(dl::DisplayList::from(input), output); @@ -48,7 +47,6 @@ fn test_format_slice() { annotations: vec![], fold: false, }], - opt: Default::default(), }; let output = dl::DisplayList { body: vec![ @@ -79,8 +77,8 @@ fn test_format_slice() { line: dl::DisplaySourceLine::Empty, }, ], - stylesheet: get_term_style(input.opt.color), - anonymized_line_numbers: input.opt.anonymized_line_numbers, + stylesheet: get_term_style(false), + anonymized_line_numbers: false, margin: None, }; assert_eq!(dl::DisplayList::from(input), output); @@ -111,7 +109,6 @@ fn test_format_slices_continuation() { fold: false, }, ], - opt: Default::default(), }; let output = dl::DisplayList { body: vec![ @@ -162,8 +159,8 @@ fn test_format_slices_continuation() { line: dl::DisplaySourceLine::Empty, }, ], - stylesheet: get_term_style(input.opt.color), - anonymized_line_numbers: input.opt.anonymized_line_numbers, + stylesheet: get_term_style(false), + anonymized_line_numbers: false, margin: None, }; assert_eq!(dl::DisplayList::from(input), output); @@ -190,7 +187,6 @@ fn test_format_slice_annotation_standalone() { }], fold: false, }], - opt: Default::default(), }; let output = dl::DisplayList { body: vec![ @@ -238,8 +234,8 @@ fn test_format_slice_annotation_standalone() { line: dl::DisplaySourceLine::Empty, }, ], - stylesheet: get_term_style(input.opt.color), - anonymized_line_numbers: input.opt.anonymized_line_numbers, + stylesheet: get_term_style(false), + anonymized_line_numbers: false, margin: None, }; assert_eq!(dl::DisplayList::from(input), output); @@ -255,7 +251,6 @@ fn test_format_label() { annotation_type: snippet::AnnotationType::Error, }], slices: vec![], - opt: Default::default(), }; let output = dl::DisplayList { body: vec![dl::DisplayLine::Raw(dl::DisplayRawLine::Annotation { @@ -270,8 +265,8 @@ fn test_format_label() { source_aligned: true, continuation: false, })], - stylesheet: get_term_style(input.opt.color), - anonymized_line_numbers: input.opt.anonymized_line_numbers, + stylesheet: get_term_style(false), + anonymized_line_numbers: false, margin: None, }; assert_eq!(dl::DisplayList::from(input), output); @@ -296,7 +291,6 @@ fn test_i26() { origin: None, fold: false, }], - opt: Default::default(), }; let _ = dl::DisplayList::from(input); @@ -322,7 +316,6 @@ fn test_i_29() { }], fold: true, }], - opt: Default::default(), }; let expected = DisplayList { diff --git a/tests/fixtures/no-color/issue_52.toml b/tests/fixtures/no-color/issue_52.toml index 8d54613f..d31e0cb2 100644 --- a/tests/fixtures/no-color/issue_52.toml +++ b/tests/fixtures/no-color/issue_52.toml @@ -1,7 +1,7 @@ -[title] +[snippet.title] annotation_type = "Error" -[[slices]] +[[snippet.slices]] source = """ @@ -10,7 +10,7 @@ invalid syntax line_start = 1 origin = "path/to/error.rs" fold = true -[[slices.annotations]] +[[snippet.slices.annotations]] label = "error here" annotation_type = "Warning" range = [2,16] diff --git a/tests/fixtures/no-color/issue_9.toml b/tests/fixtures/no-color/issue_9.toml index a30563b2..ee1fe27b 100644 --- a/tests/fixtures/no-color/issue_9.toml +++ b/tests/fixtures/no-color/issue_9.toml @@ -1,28 +1,28 @@ -[title] +[snippet.title] label = "expected one of `.`, `;`, `?`, or an operator, found `for`" annotation_type = "Error" -[[slices]] +[[snippet.slices]] source = "let x = vec![1];" line_start = 4 origin = "/code/rust/src/test/ui/annotate-snippet/suggestion.rs" -[[slices.annotations]] +[[snippet.slices.annotations]] label = "move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait" annotation_type = "Warning" range = [4, 5] -[[slices]] +[[snippet.slices]] source = "let y = x;" line_start = 7 -[[slices.annotations]] +[[snippet.slices.annotations]] label = "value moved here" annotation_type = "Warning" range = [8, 9] -[[slices]] +[[snippet.slices]] source = "x;" line_start = 9 -[[slices.annotations]] +[[snippet.slices.annotations]] label = "value used here after move" annotation_type = "Error" range = [0, 1] diff --git a/tests/fixtures/no-color/multiline_annotation.toml b/tests/fixtures/no-color/multiline_annotation.toml index c3dc1e9e..604e04b0 100644 --- a/tests/fixtures/no-color/multiline_annotation.toml +++ b/tests/fixtures/no-color/multiline_annotation.toml @@ -1,4 +1,4 @@ -[[slices]] +[[snippet.slices]] source = """ ) -> Option<String> { for ann in annotations { @@ -26,15 +26,15 @@ source = """ line_start = 51 origin = "src/format.rs" fold = true -[[slices.annotations]] +[[snippet.slices.annotations]] label = "expected `std::option::Option<std::string::String>` because of return type" annotation_type = "Warning" range = [5, 19] -[[slices.annotations]] +[[snippet.slices.annotations]] label = "expected enum `std::option::Option`, found ()" annotation_type = "Error" range = [22, 766] -[title] +[snippet.title] label = "mismatched types" id = "E0308" annotation_type = "Error" diff --git a/tests/fixtures/no-color/multiline_annotation2.toml b/tests/fixtures/no-color/multiline_annotation2.toml index 845bf9f2..3287fdce 100644 --- a/tests/fixtures/no-color/multiline_annotation2.toml +++ b/tests/fixtures/no-color/multiline_annotation2.toml @@ -1,4 +1,4 @@ -[[slices]] +[[snippet.slices]] source = """ if let DisplayLine::Source { ref mut inline_marks, @@ -7,12 +7,12 @@ source = """ line_start = 139 origin = "src/display_list.rs" fold = false -[[slices.annotations]] +[[snippet.slices.annotations]] label = "missing fields `lineno`, `content`" annotation_type = "Error" range = [31, 128] -[title] +[snippet.title] label = "pattern does not mention fields `lineno`, `content`" id = "E0027" annotation_type = "Error" diff --git a/tests/fixtures/no-color/multiline_annotation3.toml b/tests/fixtures/no-color/multiline_annotation3.toml index 21bbcd85..9fe85fb4 100644 --- a/tests/fixtures/no-color/multiline_annotation3.toml +++ b/tests/fixtures/no-color/multiline_annotation3.toml @@ -1,4 +1,4 @@ -[[slices]] +[[snippet.slices]] source = """ This is an exampl e of an edge case of an annotation overflowing @@ -7,12 +7,12 @@ to exactly one character on next line. line_start = 26 origin = "foo.txt" fold = false -[[slices.annotations]] +[[snippet.slices.annotations]] label = "this should not be on separate lines" annotation_type = "Error" range = [11, 18] -[title] +[snippet.title] label = "spacing error found" id = "E####" annotation_type = "Error" diff --git a/tests/fixtures/no-color/multiple_annotations.toml b/tests/fixtures/no-color/multiple_annotations.toml index 84efc5f1..f037c9a1 100644 --- a/tests/fixtures/no-color/multiple_annotations.toml +++ b/tests/fixtures/no-color/multiple_annotations.toml @@ -1,4 +1,4 @@ -[[slices]] +[[snippet.slices]] source = """ fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>) { if let Some(annotation) = main_annotation { @@ -11,15 +11,15 @@ fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation> } """ line_start = 96 -[[slices.annotations]] +[[snippet.slices.annotations]] label = "Variable defined here" annotation_type = "Error" range = [100, 110] -[[slices.annotations]] +[[snippet.slices.annotations]] label = "Referenced here" annotation_type = "Error" range = [184, 194] -[[slices.annotations]] +[[snippet.slices.annotations]] label = "Referenced again here" annotation_type = "Error" range = [243, 253] diff --git a/tests/fixtures/no-color/simple.toml b/tests/fixtures/no-color/simple.toml index 6c38674a..2e9b6d89 100644 --- a/tests/fixtures/no-color/simple.toml +++ b/tests/fixtures/no-color/simple.toml @@ -1,18 +1,18 @@ -[[slices]] +[[snippet.slices]] source = """ }) for line in &self.body {""" line_start = 169 origin = "src/format_color.rs" -[[slices.annotations]] +[[snippet.slices.annotations]] label = "unexpected token" annotation_type = "Error" range = [20, 23] -[[slices.annotations]] +[[snippet.slices.annotations]] label = "expected one of `.`, `;`, `?`, or an operator here" annotation_type = "Warning" range = [10, 11] -[title] +[snippet.title] label = "expected one of `.`, `;`, `?`, or an operator, found `for`" -annotation_type = "Error" +annotation_type = "Error" \ No newline at end of file diff --git a/tests/fixtures/no-color/strip_line.toml b/tests/fixtures/no-color/strip_line.toml index 76d9519b..4c609c45 100644 --- a/tests/fixtures/no-color/strip_line.toml +++ b/tests/fixtures/no-color/strip_line.toml @@ -1,22 +1,22 @@ -[title] +[snippet.title] id = "E0308" label = "mismatched types" annotation_type = "Error" -[[slices]] +[[snippet.slices]] source = " let _: () = 42;" line_start = 4 origin = "$DIR/whitespace-trimming.rs" -[[slices.annotations]] +[[snippet.slices.annotations]] label = "expected (), found integer" annotation_type = "Error" range = [192, 194] -[opt] +[renderer] color = false anonymized_line_numbers = true -[opt.margin] +[renderer.margin] whitespace_left = 180 span_left = 192 span_right = 194 diff --git a/tests/fixtures/no-color/strip_line_char.toml b/tests/fixtures/no-color/strip_line_char.toml index 5b432beb..76ea7499 100644 --- a/tests/fixtures/no-color/strip_line_char.toml +++ b/tests/fixtures/no-color/strip_line_char.toml @@ -1,22 +1,22 @@ -[title] +[snippet.title] id = "E0308" label = "mismatched types" annotation_type = "Error" -[[slices]] +[[snippet.slices]] source = " let _: () = 42ñ" line_start = 4 origin = "$DIR/whitespace-trimming.rs" -[[slices.annotations]] +[[snippet.slices.annotations]] label = "expected (), found integer" annotation_type = "Error" range = [192, 194] -[opt] +[renderer] color = false anonymized_line_numbers = true -[opt.margin] +[renderer.margin] whitespace_left = 180 span_left = 192 span_right = 194 diff --git a/tests/fixtures/no-color/strip_line_non_ws.toml b/tests/fixtures/no-color/strip_line_non_ws.toml index 5129f5ce..c46d6e27 100644 --- a/tests/fixtures/no-color/strip_line_non_ws.toml +++ b/tests/fixtures/no-color/strip_line_non_ws.toml @@ -1,22 +1,21 @@ -[title] +[snippet.title] id = "E0308" label = "mismatched types" annotation_type = "Error" -[[slices]] +[[snippet.slices]] source = " let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = ();" line_start = 4 origin = "$DIR/non-whitespace-trimming.rs" -[[slices.annotations]] +[[snippet.slices.annotations]] label = "expected (), found integer" annotation_type = "Error" range = [240, 242] -[opt] -color = false +[renderer] anonymized_line_numbers = true -[opt.margin] +[renderer.margin] whitespace_left = 4 span_left = 240 span_right = 242 diff --git a/tests/fixtures_test.rs b/tests/fixtures_test.rs index 8dfd229e..854719ff 100644 --- a/tests/fixtures_test.rs +++ b/tests/fixtures_test.rs @@ -1,7 +1,7 @@ +mod deserialize; mod diff; -mod snippet; -use crate::snippet::SnippetDef; +use crate::deserialize::Fixture; use annotate_snippets::renderer::Renderer; use annotate_snippets::snippet::Snippet; use glob::glob; @@ -14,8 +14,8 @@ fn read_file(path: &str) -> Result<String, io::Error> { Ok(s.trim_end().to_string()) } -fn read_fixture(src: &str) -> Result<Snippet<'_>, Box<dyn Error>> { - Ok(toml::from_str(src).map(|a: SnippetDef| a.into())?) +fn read_fixture(src: &str) -> Result<(Renderer, Snippet<'_>), Box<dyn Error>> { + Ok(toml::from_str(src).map(|a: Fixture| (a.renderer.into(), a.snippet.into()))?) } #[test] @@ -28,10 +28,9 @@ fn test_fixtures() { let path_out = path_in.replace(".toml", ".txt"); let src = read_file(path_in).expect("Failed to read file"); - let snippet = read_fixture(&src).expect("Failed to read file"); + let (renderer, snippet) = read_fixture(&src).expect("Failed to read file"); let expected_out = read_file(&path_out).expect("Failed to read file"); - let renderer = Renderer; let actual_out = renderer.render(snippet).to_string(); println!("{}", expected_out); println!("{}", actual_out.trim_end()); diff --git a/tests/formatter.rs b/tests/formatter.rs index 1226ab49..2117fec2 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -539,7 +539,6 @@ fn test_i_29() { }], fold: true, }], - opt: Default::default(), }; let expected = r#"error: oops --> <current file>:2:8 @@ -549,7 +548,7 @@ fn test_i_29() { | ^^^^ oops |"#; - let renderer = Renderer; + let renderer = Renderer::plain(); assert_eq!(renderer.render(snippets).to_string(), expected); } @@ -569,7 +568,6 @@ fn test_point_to_double_width_characters() { }], title: None, footer: vec![], - opt: Default::default(), }; let expected = r#" --> <current file>:1:7 @@ -578,7 +576,7 @@ fn test_point_to_double_width_characters() { | ^^^^ world |"#; - let renderer = Renderer; + let renderer = Renderer::plain(); assert_eq!(renderer.render(snippets).to_string(), expected); } @@ -598,7 +596,6 @@ fn test_point_to_double_width_characters_across_lines() { }], title: None, footer: vec![], - opt: Default::default(), }; let expected = r#" --> <current file>:1:3 @@ -609,7 +606,7 @@ fn test_point_to_double_width_characters_across_lines() { | |______^ Good morning |"#; - let renderer = Renderer; + let renderer = Renderer::plain(); assert_eq!(renderer.render(snippets).to_string(), expected); } @@ -636,7 +633,6 @@ fn test_point_to_double_width_characters_multiple() { }], title: None, footer: vec![], - opt: Default::default(), }; let expected = r#" --> <current file>:1:1 @@ -647,7 +643,7 @@ fn test_point_to_double_width_characters_multiple() { | ---- note: Sushi2 |"#; - let renderer = Renderer; + let renderer = Renderer::plain(); assert_eq!(renderer.render(snippets).to_string(), expected); } @@ -667,7 +663,6 @@ fn test_point_to_double_width_characters_mixed() { }], title: None, footer: vec![], - opt: Default::default(), }; let expected = r#" --> <current file>:1:7 @@ -676,6 +671,6 @@ fn test_point_to_double_width_characters_mixed() { | ^^^^^^^^^^^ New world |"#; - let renderer = Renderer; + let renderer = Renderer::plain(); assert_eq!(renderer.render(snippets).to_string(), expected); } From 4affdfb50ea0670d85e52737c082c03f89ae8ada Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Sat, 2 Dec 2023 13:36:57 -0700 Subject: [PATCH 093/455] fix!: Move to a new `StyleSheet` BREAKING CHANGE: This removes the `formatter` and `stylesheets` modules --- src/display_list/mod.rs | 114 +++++++++++++++++------------------- src/formatter/mod.rs | 23 -------- src/formatter/style.rs | 51 ---------------- src/lib.rs | 2 - src/renderer/mod.rs | 68 +++++++++++++++++---- src/renderer/stylesheet.rs | 62 ++++++++++++++++++++ src/stylesheets/color.rs | 50 ---------------- src/stylesheets/mod.rs | 11 ---- src/stylesheets/no_color.rs | 31 ---------- tests/dl_from_snippet.rs | 15 ++--- 10 files changed, 182 insertions(+), 245 deletions(-) delete mode 100644 src/formatter/mod.rs delete mode 100644 src/formatter/style.rs create mode 100644 src/renderer/stylesheet.rs delete mode 100644 src/stylesheets/color.rs delete mode 100644 src/stylesheets/mod.rs delete mode 100644 src/stylesheets/no_color.rs diff --git a/src/display_list/mod.rs b/src/display_list/mod.rs index 1498dc5e..6b1720f9 100644 --- a/src/display_list/mod.rs +++ b/src/display_list/mod.rs @@ -31,18 +31,18 @@ //! styling. //! //! The above snippet has been built out of the following structure: +use crate::snippet; use std::cmp::{max, min}; use std::fmt::{Display, Write}; use std::{cmp, fmt}; +use yansi_term::Style; -use crate::formatter::style::{Style, StyleClass}; -use crate::formatter::{get_term_style, style::Stylesheet}; -use crate::snippet; +use crate::renderer::stylesheet::Stylesheet; /// List of lines to be displayed. pub struct DisplayList<'a> { pub body: Vec<DisplayLine<'a>>, - pub stylesheet: Box<dyn Stylesheet>, + pub stylesheet: Stylesheet, pub anonymized_line_numbers: bool, pub margin: Option<Margin>, } @@ -52,7 +52,7 @@ impl<'a> From<Vec<DisplayLine<'a>>> for DisplayList<'a> { Self { body, anonymized_line_numbers: false, - stylesheet: get_term_style(false), + stylesheet: Stylesheet::default(), margin: None, } } @@ -109,7 +109,7 @@ impl<'a> Display for DisplayList<'a> { impl<'a> From<snippet::Snippet<'a>> for DisplayList<'a> { fn from(snippet: snippet::Snippet<'a>) -> DisplayList<'a> { - Self::new(snippet, false, false, None) + Self::new(snippet, Stylesheet::default(), false, None) } } @@ -127,7 +127,7 @@ impl<'a> DisplayList<'a> { footer, slices, }: snippet::Snippet<'a>, - color: bool, + stylesheet: Stylesheet, anonymized_line_numbers: bool, margin: Option<Margin>, ) -> DisplayList<'a> { @@ -151,7 +151,7 @@ impl<'a> DisplayList<'a> { Self { body, - stylesheet: get_term_style(color), + stylesheet, anonymized_line_numbers, margin, } @@ -183,15 +183,15 @@ impl<'a> DisplayList<'a> { } } - fn get_annotation_style(&self, annotation_type: &DisplayAnnotationType) -> Box<dyn Style> { - self.stylesheet.get_style(match annotation_type { - DisplayAnnotationType::Error => StyleClass::Error, - DisplayAnnotationType::Warning => StyleClass::Warning, - DisplayAnnotationType::Info => StyleClass::Info, - DisplayAnnotationType::Note => StyleClass::Note, - DisplayAnnotationType::Help => StyleClass::Help, - DisplayAnnotationType::None => StyleClass::None, - }) + fn get_annotation_style(&self, annotation_type: &DisplayAnnotationType) -> &Style { + match annotation_type { + DisplayAnnotationType::Error => self.stylesheet.error(), + DisplayAnnotationType::Warning => self.stylesheet.warning(), + DisplayAnnotationType::Info => self.stylesheet.info(), + DisplayAnnotationType::Note => self.stylesheet.note(), + DisplayAnnotationType::Help => self.stylesheet.help(), + DisplayAnnotationType::None => self.stylesheet.none(), + } } fn format_label( @@ -199,12 +199,12 @@ impl<'a> DisplayList<'a> { label: &[DisplayTextFragment<'_>], f: &mut fmt::Formatter<'_>, ) -> fmt::Result { - let emphasis_style = self.stylesheet.get_style(StyleClass::Emphasis); + let emphasis_style = self.stylesheet.emphasis(); for fragment in label { match fragment.style { DisplayTextStyle::Regular => fragment.content.fmt(f)?, - DisplayTextStyle::Emphasis => emphasis_style.paint(fragment.content, f)?, + DisplayTextStyle::Emphasis => emphasis_style.paint(fragment.content).fmt(f)?, } } Ok(()) @@ -231,8 +231,8 @@ impl<'a> DisplayList<'a> { if formatted_len == 0 { self.format_label(&annotation.label, f) } else { - color.paint_fn( - Box::new(|f| { + color + .paint_fn(Box::new(|f: &mut fmt::Formatter<'_>| { Self::format_annotation_type(&annotation.annotation_type, f)?; if let Some(id) = &annotation.id { f.write_char('[')?; @@ -240,18 +240,17 @@ impl<'a> DisplayList<'a> { f.write_char(']')?; } Ok(()) - }), - f, - )?; + })) + .fmt(f)?; + if !is_annotation_empty(annotation) { if in_source { - color.paint_fn( - Box::new(|f| { + color + .paint_fn(Box::new(|f: &mut fmt::Formatter<'_>| { f.write_str(": ")?; self.format_label(&annotation.label, f) - }), - f, - )?; + })) + .fmt(f)?; } else { f.write_str(": ")?; self.format_label(&annotation.label, f)?; @@ -362,27 +361,25 @@ impl<'a> DisplayList<'a> { _ => range.0, }; - color.paint_fn( - Box::new(|f| { + color + .paint_fn(|f| { format_repeat_char(indent_char, indent_length + 1, f)?; format_repeat_char(mark, range.1 - indent_length, f) - }), - f, - )?; + }) + .fmt(f)?; if !is_annotation_empty(annotation) { f.write_char(' ')?; - color.paint_fn( - Box::new(|f| { + color + .paint_fn(|f| { self.format_annotation( annotation, annotation_part == &DisplayAnnotationPart::LabelContinuation, true, f, ) - }), - f, - )?; + }) + .fmt(f)?; } Ok(()) @@ -407,11 +404,11 @@ impl<'a> DisplayList<'a> { DisplayHeaderType::Initial => "-->", DisplayHeaderType::Continuation => ":::", }; - let lineno_color = self.stylesheet.get_style(StyleClass::LineNo); + let lineno_color = self.stylesheet.line_no(); if let Some((col, row)) = pos { format_repeat_char(' ', lineno_width, f)?; - lineno_color.paint(header_sigil, f)?; + lineno_color.paint(header_sigil).fmt(f)?; f.write_char(' ')?; path.fmt(f)?; f.write_char(':')?; @@ -420,7 +417,7 @@ impl<'a> DisplayList<'a> { row.fmt(f) } else { format_repeat_char(' ', lineno_width, f)?; - lineno_color.paint(header_sigil, f)?; + lineno_color.paint(header_sigil).fmt(f)?; f.write_char(' ')?; path.fmt(f) } @@ -434,10 +431,10 @@ impl<'a> DisplayList<'a> { if *continuation { format_repeat_char(' ', lineno_width + 3, f)?; } else { - let lineno_color = self.stylesheet.get_style(StyleClass::LineNo); + let lineno_color = self.stylesheet.line_no(); format_repeat_char(' ', lineno_width, f)?; f.write_char(' ')?; - lineno_color.paint("=", f)?; + lineno_color.paint("=").fmt(f)?; f.write_char(' ')?; } } @@ -460,26 +457,24 @@ impl<'a> DisplayList<'a> { inline_marks, line, } => { - let lineno_color = self.stylesheet.get_style(StyleClass::LineNo); + let lineno_color = self.stylesheet.line_no(); if self.anonymized_line_numbers && lineno.is_some() { - lineno_color.paint_fn( - Box::new(|f| { + lineno_color + .paint_fn(Box::new(|f: &mut fmt::Formatter<'_>| { f.write_str(Self::ANONYMIZED_LINE_NUM)?; f.write_str(" |") - }), - f, - )?; + })) + .fmt(f)?; } else { - lineno_color.paint_fn( - Box::new(|f| { + lineno_color + .paint_fn(Box::new(|f: &mut fmt::Formatter<'_>| { match lineno { Some(n) => write!(f, "{:>width$}", n, width = lineno_width), None => format_repeat_char(' ', lineno_width, f), }?; f.write_str(" |") - }), - f, - )?; + })) + .fmt(f)?; } if *line != DisplaySourceLine::Empty { if !inline_marks.is_empty() || 0 < inline_marks_width { @@ -513,15 +508,14 @@ impl<'a> DisplayList<'a> { ) -> fmt::Result { format_repeat_char(' ', inline_marks_width - inline_marks.len(), f)?; for mark in inline_marks { - self.get_annotation_style(&mark.annotation_type).paint_fn( - Box::new(|f| { + self.get_annotation_style(&mark.annotation_type) + .paint_fn(Box::new(|f: &mut fmt::Formatter<'_>| { f.write_char(match mark.mark_type { DisplayMarkType::AnnotationThrough => '|', DisplayMarkType::AnnotationStart => '/', }) - }), - f, - )?; + })) + .fmt(f)?; } Ok(()) } diff --git a/src/formatter/mod.rs b/src/formatter/mod.rs deleted file mode 100644 index 72bf9c77..00000000 --- a/src/formatter/mod.rs +++ /dev/null @@ -1,23 +0,0 @@ -pub mod style; - -use self::style::Stylesheet; - -#[cfg(feature = "color")] -use crate::stylesheets::color::AnsiTermStylesheet; -use crate::stylesheets::no_color::NoColorStylesheet; - -#[cfg(feature = "color")] -#[inline] -pub fn get_term_style(color: bool) -> Box<dyn Stylesheet> { - if color { - Box::new(AnsiTermStylesheet) - } else { - Box::new(NoColorStylesheet) - } -} - -#[cfg(not(feature = "color"))] -#[inline] -pub fn get_term_style(_color: bool) -> Box<dyn Stylesheet> { - Box::new(NoColorStylesheet) -} diff --git a/src/formatter/style.rs b/src/formatter/style.rs deleted file mode 100644 index 3fc01c19..00000000 --- a/src/formatter/style.rs +++ /dev/null @@ -1,51 +0,0 @@ -//! Set of structures required to implement a stylesheet -//! -//! In order to provide additional styling information for the -//! formatter, a structs can implement `Stylesheet` and `Style` -//! traits. -//! -use std::fmt; - -/// StyleClass is a collection of named variants of style classes -pub enum StyleClass { - /// Message indicating an error. - Error, - /// Message indicating a warning. - Warning, - /// Message indicating an information. - Info, - /// Message indicating a note. - Note, - /// Message indicating a help. - Help, - - /// Style for line numbers. - LineNo, - - /// Parts of the text that are to be emphasised. - Emphasis, - - /// Parts of the text that are regular. Usually a no-op. - None, -} - -/// This trait implements a return value for the `Stylesheet::get_style`. -pub trait Style { - /// The method used to write text with formatter - fn paint(&self, text: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result; - /// The method used to write display function with formatter - fn paint_fn<'a>( - &self, - c: Box<dyn FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result + 'a>, - f: &mut fmt::Formatter<'_>, - ) -> fmt::Result; - /// The method used by the `Formatter` to display the message in bold font. - fn bold(&self) -> Box<dyn Style>; -} - -/// Trait to annotate structs that can provide `Style` implementations for -/// every `StyleClass` variant. -pub trait Stylesheet { - /// Returns a `Style` implementer based on the requested `StyleClass` variant. - fn get_style(&self, class: StyleClass) -> Box<dyn Style>; -} diff --git a/src/lib.rs b/src/lib.rs index e11e9d58..bf0dc058 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -49,7 +49,5 @@ // TODO: check documentation pub mod display_list; -pub mod formatter; pub mod renderer; pub mod snippet; -pub mod stylesheets; diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index b1f5f055..a79d8d2b 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -1,30 +1,43 @@ +pub mod stylesheet; + use crate::display_list::{DisplayList, Margin}; use crate::snippet::Snippet; use std::fmt::Display; +use stylesheet::Stylesheet; +use yansi_term::Color::Fixed; +use yansi_term::Style; #[derive(Clone)] pub struct Renderer { - color: bool, anonymized_line_numbers: bool, margin: Option<Margin>, + stylesheet: Stylesheet, } impl Renderer { /// No terminal styling pub fn plain() -> Self { Self { - color: false, anonymized_line_numbers: false, margin: None, + stylesheet: Stylesheet::default(), } } /// Default terminal styling pub fn styled() -> Self { Self { - color: true, - anonymized_line_numbers: false, - margin: None, + stylesheet: Stylesheet { + error: Fixed(9).bold(), + warning: Fixed(11).bold(), + info: Fixed(12).bold(), + note: Style::new().bold(), + help: Fixed(14).bold(), + line_no: Fixed(12).bold(), + emphasis: Style::new().bold(), + none: Style::new(), + }, + ..Self::plain() } } @@ -33,20 +46,55 @@ impl Renderer { self } - pub fn color(mut self, color: bool) -> Self { - self.color = color; + pub fn margin(mut self, margin: Option<Margin>) -> Self { + self.margin = margin; self } - pub fn margin(mut self, margin: Option<Margin>) -> Self { - self.margin = margin; + pub fn error(mut self, style: Style) -> Self { + self.stylesheet.error = style; + self + } + + pub fn warning(mut self, style: Style) -> Self { + self.stylesheet.warning = style; + self + } + + pub fn info(mut self, style: Style) -> Self { + self.stylesheet.info = style; + self + } + + pub fn note(mut self, style: Style) -> Self { + self.stylesheet.note = style; + self + } + + pub fn help(mut self, style: Style) -> Self { + self.stylesheet.help = style; + self + } + + pub fn line_no(mut self, style: Style) -> Self { + self.stylesheet.line_no = style; + self + } + + pub fn emphasis(mut self, style: Style) -> Self { + self.stylesheet.emphasis = style; + self + } + + pub fn none(mut self, style: Style) -> Self { + self.stylesheet.none = style; self } pub fn render<'a>(&'a self, snippet: Snippet<'a>) -> impl Display + 'a { DisplayList::new( snippet, - self.color, + self.stylesheet, self.anonymized_line_numbers, self.margin, ) diff --git a/src/renderer/stylesheet.rs b/src/renderer/stylesheet.rs new file mode 100644 index 00000000..f52c1396 --- /dev/null +++ b/src/renderer/stylesheet.rs @@ -0,0 +1,62 @@ +use yansi_term::Style; + +#[derive(Clone, Copy, Debug)] +pub struct Stylesheet { + pub(crate) error: Style, + pub(crate) warning: Style, + pub(crate) info: Style, + pub(crate) note: Style, + pub(crate) help: Style, + pub(crate) line_no: Style, + pub(crate) emphasis: Style, + pub(crate) none: Style, +} + +impl Default for Stylesheet { + fn default() -> Self { + Self { + error: Style::new(), + warning: Style::new(), + info: Style::new(), + note: Style::new(), + help: Style::new(), + line_no: Style::new(), + emphasis: Style::new(), + none: Style::new(), + } + } +} + +impl Stylesheet { + pub(crate) fn error(&self) -> &Style { + &self.error + } + + pub(crate) fn warning(&self) -> &Style { + &self.warning + } + + pub(crate) fn info(&self) -> &Style { + &self.info + } + + pub(crate) fn note(&self) -> &Style { + &self.note + } + + pub(crate) fn help(&self) -> &Style { + &self.help + } + + pub(crate) fn line_no(&self) -> &Style { + &self.line_no + } + + pub(crate) fn emphasis(&self) -> &Style { + &self.emphasis + } + + pub(crate) fn none(&self) -> &Style { + &self.none + } +} diff --git a/src/stylesheets/color.rs b/src/stylesheets/color.rs deleted file mode 100644 index 024dd06f..00000000 --- a/src/stylesheets/color.rs +++ /dev/null @@ -1,50 +0,0 @@ -use std::fmt::{self, Display}; - -use yansi_term::{Color::Fixed, Style as AnsiTermStyle}; - -use crate::formatter::style::{Style, StyleClass, Stylesheet}; - -struct AnsiTermStyleWrapper { - style: AnsiTermStyle, -} - -impl Style for AnsiTermStyleWrapper { - fn paint(&self, text: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.style.paint(text).fmt(f) - } - - fn paint_fn<'a>( - &self, - c: Box<dyn FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result + 'a>, - f: &mut fmt::Formatter<'_>, - ) -> fmt::Result { - self.style.paint_fn(c).fmt(f) - } - - fn bold(&self) -> Box<dyn Style> { - Box::new(AnsiTermStyleWrapper { style: self.style }) - } -} - -pub struct AnsiTermStylesheet; - -impl Stylesheet for AnsiTermStylesheet { - fn get_style(&self, class: StyleClass) -> Box<dyn Style> { - let ansi_term_style = match class { - StyleClass::Error => Fixed(9).bold(), - StyleClass::Warning => Fixed(11).bold(), - StyleClass::Info => Fixed(12).bold(), - StyleClass::Note => AnsiTermStyle::new().bold(), - StyleClass::Help => Fixed(14).bold(), - - StyleClass::LineNo => Fixed(12).bold(), - - StyleClass::Emphasis => AnsiTermStyle::new().bold(), - - StyleClass::None => AnsiTermStyle::new(), - }; - Box::new(AnsiTermStyleWrapper { - style: ansi_term_style, - }) - } -} diff --git a/src/stylesheets/mod.rs b/src/stylesheets/mod.rs deleted file mode 100644 index 4648852a..00000000 --- a/src/stylesheets/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! List of stylesheets -//! -//! The list depends on what optional dependencies the crate has been -//! compiled with. -//! -//! By default the `no_color` is available. If the crate gets compiled -//! with `ansi_term`, the `color` stylesheet is added. - -#[cfg(feature = "color")] -pub mod color; -pub mod no_color; diff --git a/src/stylesheets/no_color.rs b/src/stylesheets/no_color.rs deleted file mode 100644 index 21cb2695..00000000 --- a/src/stylesheets/no_color.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::fmt; - -use crate::formatter::style::{Style, StyleClass, Stylesheet}; - -pub struct NoOpStyle {} - -impl Style for NoOpStyle { - fn paint(&self, text: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(text) - } - - fn paint_fn<'a>( - &self, - c: Box<dyn FnOnce(&mut fmt::Formatter<'_>) -> fmt::Result + 'a>, - f: &mut fmt::Formatter<'_>, - ) -> fmt::Result { - c(f) - } - - fn bold(&self) -> Box<dyn Style> { - Box::new(NoOpStyle {}) - } -} - -pub struct NoColorStylesheet; - -impl Stylesheet for NoColorStylesheet { - fn get_style(&self, _class: StyleClass) -> Box<dyn Style> { - Box::new(NoOpStyle {}) - } -} diff --git a/tests/dl_from_snippet.rs b/tests/dl_from_snippet.rs index 9971c7eb..5fb0762d 100644 --- a/tests/dl_from_snippet.rs +++ b/tests/dl_from_snippet.rs @@ -1,5 +1,6 @@ use annotate_snippets::display_list::DisplayList; -use annotate_snippets::{display_list as dl, formatter::get_term_style, snippet}; +use annotate_snippets::renderer::stylesheet::Stylesheet; +use annotate_snippets::{display_list as dl, snippet}; #[test] fn test_format_title() { @@ -25,7 +26,7 @@ fn test_format_title() { source_aligned: false, continuation: false, })], - stylesheet: get_term_style(false), + stylesheet: Stylesheet::default(), anonymized_line_numbers: false, margin: None, }; @@ -77,7 +78,7 @@ fn test_format_slice() { line: dl::DisplaySourceLine::Empty, }, ], - stylesheet: get_term_style(false), + stylesheet: Stylesheet::default(), anonymized_line_numbers: false, margin: None, }; @@ -159,7 +160,7 @@ fn test_format_slices_continuation() { line: dl::DisplaySourceLine::Empty, }, ], - stylesheet: get_term_style(false), + stylesheet: Stylesheet::default(), anonymized_line_numbers: false, margin: None, }; @@ -234,7 +235,7 @@ fn test_format_slice_annotation_standalone() { line: dl::DisplaySourceLine::Empty, }, ], - stylesheet: get_term_style(false), + stylesheet: Stylesheet::default(), anonymized_line_numbers: false, margin: None, }; @@ -265,7 +266,7 @@ fn test_format_label() { source_aligned: true, continuation: false, })], - stylesheet: get_term_style(false), + stylesheet: Stylesheet::default(), anonymized_line_numbers: false, margin: None, }; @@ -381,7 +382,7 @@ fn test_i_29() { line: dl::DisplaySourceLine::Empty, }, ], - stylesheet: get_term_style(false), + stylesheet: Stylesheet::default(), anonymized_line_numbers: false, margin: None, }; From dfd4e87d6f31ec50d29af26d7310cff5e66ca978 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Sat, 2 Dec 2023 13:53:43 -0700 Subject: [PATCH 094/455] fix!: Switch to `anstyle` for color BREAKING CHANGE: This removes `color` feature --- Cargo.lock | 11 +-- Cargo.toml | 4 +- src/display_list/mod.rs | 134 ++++++++++++++++++++----------------- src/renderer/mod.rs | 17 +++-- src/renderer/stylesheet.rs | 2 +- tests/diff/mod.rs | 32 +++++++-- 6 files changed, 109 insertions(+), 91 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c15507c8..0d9659ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,13 +21,13 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" name = "annotate-snippets" version = "0.9.2" dependencies = [ + "anstyle", "criterion", "difference", "glob", "serde", "toml", "unicode-width", - "yansi-term", ] [[package]] @@ -688,12 +688,3 @@ name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "yansi-term" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe5c30ade05e61656247b2e334a031dfd0cc466fadef865bdcdea8d537951bf1" -dependencies = [ - "winapi", -] diff --git a/Cargo.toml b/Cargo.toml index 86666519..ec39e99d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,8 +14,8 @@ keywords = ["code", "analysis", "ascii", "errors", "debug"] maintenance = { status = "actively-developed" } [dependencies] +anstyle = "1.0.4" unicode-width = "0.1.11" -yansi-term = { version = "0.1.2", optional = true } [dev-dependencies] criterion = "0.5.1" @@ -23,7 +23,6 @@ difference = "2.0.0" glob = "0.3.1" serde = { version = "1.0.192", features = ["derive"] } toml = "0.5.11" -yansi-term = "0.1.2" [[bench]] name = "simple" @@ -31,4 +30,3 @@ harness = false [features] default = [] -color = ["yansi-term"] diff --git a/src/display_list/mod.rs b/src/display_list/mod.rs index 6b1720f9..b07d2506 100644 --- a/src/display_list/mod.rs +++ b/src/display_list/mod.rs @@ -35,9 +35,8 @@ use crate::snippet; use std::cmp::{max, min}; use std::fmt::{Display, Write}; use std::{cmp, fmt}; -use yansi_term::Style; -use crate::renderer::stylesheet::Stylesheet; +use crate::renderer::{stylesheet::Stylesheet, Style}; /// List of lines to be displayed. pub struct DisplayList<'a> { @@ -204,7 +203,15 @@ impl<'a> DisplayList<'a> { for fragment in label { match fragment.style { DisplayTextStyle::Regular => fragment.content.fmt(f)?, - DisplayTextStyle::Emphasis => emphasis_style.paint(fragment.content).fmt(f)?, + DisplayTextStyle::Emphasis => { + write!( + f, + "{}{}{}", + emphasis_style.render(), + fragment.content, + emphasis_style.render_reset() + )?; + } } } Ok(()) @@ -231,26 +238,21 @@ impl<'a> DisplayList<'a> { if formatted_len == 0 { self.format_label(&annotation.label, f) } else { - color - .paint_fn(Box::new(|f: &mut fmt::Formatter<'_>| { - Self::format_annotation_type(&annotation.annotation_type, f)?; - if let Some(id) = &annotation.id { - f.write_char('[')?; - f.write_str(id)?; - f.write_char(']')?; - } - Ok(()) - })) - .fmt(f)?; + write!(f, "{}", color.render())?; + Self::format_annotation_type(&annotation.annotation_type, f)?; + if let Some(id) = &annotation.id { + f.write_char('[')?; + f.write_str(id)?; + f.write_char(']')?; + } + write!(f, "{}", color.render_reset())?; if !is_annotation_empty(annotation) { if in_source { - color - .paint_fn(Box::new(|f: &mut fmt::Formatter<'_>| { - f.write_str(": ")?; - self.format_label(&annotation.label, f) - })) - .fmt(f)?; + write!(f, "{}", color.render())?; + f.write_str(": ")?; + self.format_label(&annotation.label, f)?; + write!(f, "{}", color.render_reset())?; } else { f.write_str(": ")?; self.format_label(&annotation.label, f)?; @@ -361,25 +363,21 @@ impl<'a> DisplayList<'a> { _ => range.0, }; - color - .paint_fn(|f| { - format_repeat_char(indent_char, indent_length + 1, f)?; - format_repeat_char(mark, range.1 - indent_length, f) - }) - .fmt(f)?; + write!(f, "{}", color.render())?; + format_repeat_char(indent_char, indent_length + 1, f)?; + format_repeat_char(mark, range.1 - indent_length, f)?; + write!(f, "{}", color.render_reset())?; if !is_annotation_empty(annotation) { f.write_char(' ')?; - color - .paint_fn(|f| { - self.format_annotation( - annotation, - annotation_part == &DisplayAnnotationPart::LabelContinuation, - true, - f, - ) - }) - .fmt(f)?; + write!(f, "{}", color.render())?; + self.format_annotation( + annotation, + annotation_part == &DisplayAnnotationPart::LabelContinuation, + true, + f, + )?; + write!(f, "{}", color.render_reset())?; } Ok(()) @@ -408,7 +406,13 @@ impl<'a> DisplayList<'a> { if let Some((col, row)) = pos { format_repeat_char(' ', lineno_width, f)?; - lineno_color.paint(header_sigil).fmt(f)?; + write!( + f, + "{}{}{}", + lineno_color.render(), + header_sigil, + lineno_color.render_reset() + )?; f.write_char(' ')?; path.fmt(f)?; f.write_char(':')?; @@ -417,7 +421,13 @@ impl<'a> DisplayList<'a> { row.fmt(f) } else { format_repeat_char(' ', lineno_width, f)?; - lineno_color.paint(header_sigil).fmt(f)?; + write!( + f, + "{}{}{}", + lineno_color.render(), + header_sigil, + lineno_color.render_reset() + )?; f.write_char(' ')?; path.fmt(f) } @@ -434,7 +444,12 @@ impl<'a> DisplayList<'a> { let lineno_color = self.stylesheet.line_no(); format_repeat_char(' ', lineno_width, f)?; f.write_char(' ')?; - lineno_color.paint("=").fmt(f)?; + write!( + f, + "{}={}", + lineno_color.render(), + lineno_color.render_reset() + )?; f.write_char(' ')?; } } @@ -459,22 +474,18 @@ impl<'a> DisplayList<'a> { } => { let lineno_color = self.stylesheet.line_no(); if self.anonymized_line_numbers && lineno.is_some() { - lineno_color - .paint_fn(Box::new(|f: &mut fmt::Formatter<'_>| { - f.write_str(Self::ANONYMIZED_LINE_NUM)?; - f.write_str(" |") - })) - .fmt(f)?; + write!(f, "{}", lineno_color.render())?; + f.write_str(Self::ANONYMIZED_LINE_NUM)?; + f.write_str(" |")?; + write!(f, "{}", lineno_color.render_reset())?; } else { - lineno_color - .paint_fn(Box::new(|f: &mut fmt::Formatter<'_>| { - match lineno { - Some(n) => write!(f, "{:>width$}", n, width = lineno_width), - None => format_repeat_char(' ', lineno_width, f), - }?; - f.write_str(" |") - })) - .fmt(f)?; + write!(f, "{}", lineno_color.render())?; + match lineno { + Some(n) => write!(f, "{:>width$}", n, width = lineno_width), + None => format_repeat_char(' ', lineno_width, f), + }?; + f.write_str(" |")?; + write!(f, "{}", lineno_color.render_reset())?; } if *line != DisplaySourceLine::Empty { if !inline_marks.is_empty() || 0 < inline_marks_width { @@ -508,14 +519,13 @@ impl<'a> DisplayList<'a> { ) -> fmt::Result { format_repeat_char(' ', inline_marks_width - inline_marks.len(), f)?; for mark in inline_marks { - self.get_annotation_style(&mark.annotation_type) - .paint_fn(Box::new(|f: &mut fmt::Formatter<'_>| { - f.write_char(match mark.mark_type { - DisplayMarkType::AnnotationThrough => '|', - DisplayMarkType::AnnotationStart => '/', - }) - })) - .fmt(f)?; + let annotation_style = self.get_annotation_style(&mark.annotation_type); + write!(f, "{}", annotation_style.render())?; + f.write_char(match mark.mark_type { + DisplayMarkType::AnnotationThrough => '|', + DisplayMarkType::AnnotationStart => '/', + })?; + write!(f, "{}", annotation_style.render_reset())?; } Ok(()) } diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index a79d8d2b..5b6d55be 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -2,10 +2,9 @@ pub mod stylesheet; use crate::display_list::{DisplayList, Margin}; use crate::snippet::Snippet; +pub use anstyle::*; use std::fmt::Display; use stylesheet::Stylesheet; -use yansi_term::Color::Fixed; -use yansi_term::Style; #[derive(Clone)] pub struct Renderer { @@ -28,13 +27,13 @@ impl Renderer { pub fn styled() -> Self { Self { stylesheet: Stylesheet { - error: Fixed(9).bold(), - warning: Fixed(11).bold(), - info: Fixed(12).bold(), - note: Style::new().bold(), - help: Fixed(14).bold(), - line_no: Fixed(12).bold(), - emphasis: Style::new().bold(), + error: AnsiColor::BrightRed.on_default().effects(Effects::BOLD), + warning: AnsiColor::BrightYellow.on_default().effects(Effects::BOLD), + info: AnsiColor::BrightBlue.on_default().effects(Effects::BOLD), + note: Style::new().effects(Effects::BOLD), + help: AnsiColor::BrightCyan.on_default().effects(Effects::BOLD), + line_no: AnsiColor::BrightBlue.on_default().effects(Effects::BOLD), + emphasis: Style::new().effects(Effects::BOLD), none: Style::new(), }, ..Self::plain() diff --git a/src/renderer/stylesheet.rs b/src/renderer/stylesheet.rs index f52c1396..899d9a76 100644 --- a/src/renderer/stylesheet.rs +++ b/src/renderer/stylesheet.rs @@ -1,4 +1,4 @@ -use yansi_term::Style; +use anstyle::Style; #[derive(Clone, Copy, Debug)] pub struct Stylesheet { diff --git a/tests/diff/mod.rs b/tests/diff/mod.rs index 576c6c4d..60ccae19 100644 --- a/tests/diff/mod.rs +++ b/tests/diff/mod.rs @@ -1,6 +1,7 @@ +use annotate_snippets::renderer::{AnsiColor, Color, Style}; use difference::{Changeset, Difference}; -use yansi_term::Color::{Black, Green, Red}; +const GREEN: Style = AnsiColor::Green.on_default(); pub fn get_diff(left: &str, right: &str) -> String { let mut output = String::new(); @@ -14,15 +15,28 @@ pub fn get_diff(left: &str, right: &str) -> String { Difference::Add(ref x) => { match diffs[i - 1] { Difference::Rem(ref y) => { - output += &format!("{}", Green.paint("+")); + output += &format!("{}+{}", GREEN.render(), GREEN.render_reset()); let Changeset { diffs, .. } = Changeset::new(y, x, " "); for c in diffs { match c { Difference::Same(ref z) => { - output += &format!("{} ", Green.paint(z.as_str())); + output += &format!( + "{}{}{} ", + GREEN.render(), + z.as_str(), + GREEN.render_reset() + ); } Difference::Add(ref z) => { - output += &format!("{} ", Black.on(Green).paint(z.as_str())); + let black_on_green = Style::new() + .bg_color(Some(Color::Ansi(AnsiColor::Green))) + .fg_color(Some(Color::Ansi(AnsiColor::Black))); + output += &format!( + "{}{}{} ", + black_on_green.render(), + z.as_str(), + black_on_green.render_reset() + ); } _ => (), } @@ -30,12 +44,18 @@ pub fn get_diff(left: &str, right: &str) -> String { output += "\n"; } _ => { - output += &format!("+{}\n", Green.paint(x.as_str())); + output += &format!( + "+{}{}{}\n", + GREEN.render(), + x.as_str(), + GREEN.render_reset() + ); } }; } Difference::Rem(ref x) => { - output += &format!("-{}\n", Red.paint(x.as_str())); + let red = AnsiColor::Red.on_default(); + output += &format!("-{}{}{}\n", red.render(), x.as_str(), red.render_reset()); } } } From 79f657ea252c3c0ce55fa69894ee520f8820b4bf Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Sat, 2 Dec 2023 13:57:04 -0700 Subject: [PATCH 095/455] fix!: Move `Margin` to `renderer` --- src/display_list/mod.rs | 117 +------------------------------------- src/renderer/margin.rs | 119 +++++++++++++++++++++++++++++++++++++++ src/renderer/mod.rs | 4 +- tests/deserialize/mod.rs | 2 +- 4 files changed, 124 insertions(+), 118 deletions(-) create mode 100644 src/renderer/margin.rs diff --git a/src/display_list/mod.rs b/src/display_list/mod.rs index b07d2506..d9b7f34e 100644 --- a/src/display_list/mod.rs +++ b/src/display_list/mod.rs @@ -32,11 +32,10 @@ //! //! The above snippet has been built out of the following structure: use crate::snippet; -use std::cmp::{max, min}; use std::fmt::{Display, Write}; use std::{cmp, fmt}; -use crate::renderer::{stylesheet::Stylesheet, Style}; +use crate::renderer::{stylesheet::Stylesheet, Margin, Style}; /// List of lines to be displayed. pub struct DisplayList<'a> { @@ -531,120 +530,6 @@ impl<'a> DisplayList<'a> { } } -#[derive(Clone, Copy, Debug)] -pub struct Margin { - /// The available whitespace in the left that can be consumed when centering. - whitespace_left: usize, - /// The column of the beginning of left-most span. - span_left: usize, - /// The column of the end of right-most span. - span_right: usize, - /// The beginning of the line to be displayed. - computed_left: usize, - /// The end of the line to be displayed. - computed_right: usize, - /// The current width of the terminal. 140 by default and in tests. - column_width: usize, - /// The end column of a span label, including the span. Doesn't account for labels not in the - /// same line as the span. - label_right: usize, -} - -impl Margin { - pub fn new( - whitespace_left: usize, - span_left: usize, - span_right: usize, - label_right: usize, - column_width: usize, - max_line_len: usize, - ) -> Self { - // The 6 is padding to give a bit of room for `...` when displaying: - // ``` - // error: message - // --> file.rs:16:58 - // | - // 16 | ... fn foo(self) -> Self::Bar { - // | ^^^^^^^^^ - // ``` - - let mut m = Margin { - whitespace_left: whitespace_left.saturating_sub(6), - span_left: span_left.saturating_sub(6), - span_right: span_right + 6, - computed_left: 0, - computed_right: 0, - column_width, - label_right: label_right + 6, - }; - m.compute(max_line_len); - m - } - - pub(crate) fn was_cut_left(&self) -> bool { - self.computed_left > 0 - } - - pub(crate) fn was_cut_right(&self, line_len: usize) -> bool { - let right = - if self.computed_right == self.span_right || self.computed_right == self.label_right { - // Account for the "..." padding given above. Otherwise we end up with code lines that - // do fit but end in "..." as if they were trimmed. - self.computed_right - 6 - } else { - self.computed_right - }; - right < line_len && self.computed_left + self.column_width < line_len - } - - fn compute(&mut self, max_line_len: usize) { - // When there's a lot of whitespace (>20), we want to trim it as it is useless. - self.computed_left = if self.whitespace_left > 20 { - self.whitespace_left - 16 // We want some padding. - } else { - 0 - }; - // We want to show as much as possible, max_line_len is the right-most boundary for the - // relevant code. - self.computed_right = max(max_line_len, self.computed_left); - - if self.computed_right - self.computed_left > self.column_width { - // Trimming only whitespace isn't enough, let's get craftier. - if self.label_right - self.whitespace_left <= self.column_width { - // Attempt to fit the code window only trimming whitespace. - self.computed_left = self.whitespace_left; - self.computed_right = self.computed_left + self.column_width; - } else if self.label_right - self.span_left <= self.column_width { - // Attempt to fit the code window considering only the spans and labels. - let padding_left = (self.column_width - (self.label_right - self.span_left)) / 2; - self.computed_left = self.span_left.saturating_sub(padding_left); - self.computed_right = self.computed_left + self.column_width; - } else if self.span_right - self.span_left <= self.column_width { - // Attempt to fit the code window considering the spans and labels plus padding. - let padding_left = (self.column_width - (self.span_right - self.span_left)) / 5 * 2; - self.computed_left = self.span_left.saturating_sub(padding_left); - self.computed_right = self.computed_left + self.column_width; - } else { - // Mostly give up but still don't show the full line. - self.computed_left = self.span_left; - self.computed_right = self.span_right; - } - } - } - - pub(crate) fn left(&self, line_len: usize) -> usize { - min(self.computed_left, line_len) - } - - pub(crate) fn right(&self, line_len: usize) -> usize { - if line_len.saturating_sub(self.computed_left) <= self.column_width { - line_len - } else { - min(line_len, self.computed_right) - } - } -} - /// Inline annotation which can be used in either Raw or Source line. #[derive(Debug, PartialEq)] pub struct Annotation<'a> { diff --git a/src/renderer/margin.rs b/src/renderer/margin.rs new file mode 100644 index 00000000..361f5f36 --- /dev/null +++ b/src/renderer/margin.rs @@ -0,0 +1,119 @@ +use std::cmp::{max, min}; + +const ELLIPSIS_PASSING: usize = 6; +const LONG_WHITESPACE: usize = 20; +const LONG_WHITESPACE_PADDING: usize = 4; + +#[derive(Clone, Copy, Debug)] +pub struct Margin { + /// The available whitespace in the left that can be consumed when centering. + whitespace_left: usize, + /// The column of the beginning of left-most span. + span_left: usize, + /// The column of the end of right-most span. + span_right: usize, + /// The beginning of the line to be displayed. + computed_left: usize, + /// The end of the line to be displayed. + computed_right: usize, + /// The current width of the terminal. 140 by default and in tests. + column_width: usize, + /// The end column of a span label, including the span. Doesn't account for labels not in the + /// same line as the span. + label_right: usize, +} + +impl Margin { + pub fn new( + whitespace_left: usize, + span_left: usize, + span_right: usize, + label_right: usize, + column_width: usize, + max_line_len: usize, + ) -> Self { + // The 6 is padding to give a bit of room for `...` when displaying: + // ``` + // error: message + // --> file.rs:16:58 + // | + // 16 | ... fn foo(self) -> Self::Bar { + // | ^^^^^^^^^ + // ``` + + let mut m = Margin { + whitespace_left: whitespace_left.saturating_sub(ELLIPSIS_PASSING), + span_left: span_left.saturating_sub(ELLIPSIS_PASSING), + span_right: span_right + ELLIPSIS_PASSING, + computed_left: 0, + computed_right: 0, + column_width, + label_right: label_right + ELLIPSIS_PASSING, + }; + m.compute(max_line_len); + m + } + + pub(crate) fn was_cut_left(&self) -> bool { + self.computed_left > 0 + } + + pub(crate) fn was_cut_right(&self, line_len: usize) -> bool { + let right = + if self.computed_right == self.span_right || self.computed_right == self.label_right { + // Account for the "..." padding given above. Otherwise we end up with code lines that + // do fit but end in "..." as if they were trimmed. + self.computed_right - ELLIPSIS_PASSING + } else { + self.computed_right + }; + right < line_len && self.computed_left + self.column_width < line_len + } + + fn compute(&mut self, max_line_len: usize) { + // When there's a lot of whitespace (>20), we want to trim it as it is useless. + self.computed_left = if self.whitespace_left > LONG_WHITESPACE { + self.whitespace_left - (LONG_WHITESPACE - LONG_WHITESPACE_PADDING) // We want some padding. + } else { + 0 + }; + // We want to show as much as possible, max_line_len is the right-most boundary for the + // relevant code. + self.computed_right = max(max_line_len, self.computed_left); + + if self.computed_right - self.computed_left > self.column_width { + // Trimming only whitespace isn't enough, let's get craftier. + if self.label_right - self.whitespace_left <= self.column_width { + // Attempt to fit the code window only trimming whitespace. + self.computed_left = self.whitespace_left; + self.computed_right = self.computed_left + self.column_width; + } else if self.label_right - self.span_left <= self.column_width { + // Attempt to fit the code window considering only the spans and labels. + let padding_left = (self.column_width - (self.label_right - self.span_left)) / 2; + self.computed_left = self.span_left.saturating_sub(padding_left); + self.computed_right = self.computed_left + self.column_width; + } else if self.span_right - self.span_left <= self.column_width { + // Attempt to fit the code window considering the spans and labels plus padding. + let padding_left = (self.column_width - (self.span_right - self.span_left)) / 5 * 2; + self.computed_left = self.span_left.saturating_sub(padding_left); + self.computed_right = self.computed_left + self.column_width; + } else { + // Mostly give up but still don't show the full line. + self.computed_left = self.span_left; + self.computed_right = self.span_right; + } + } + } + + pub(crate) fn left(&self, line_len: usize) -> usize { + min(self.computed_left, line_len) + } + + pub(crate) fn right(&self, line_len: usize) -> usize { + if line_len.saturating_sub(self.computed_left) <= self.column_width { + line_len + } else { + min(line_len, self.computed_right) + } + } +} diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 5b6d55be..712d2039 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -1,8 +1,10 @@ +mod margin; pub mod stylesheet; -use crate::display_list::{DisplayList, Margin}; +use crate::display_list::DisplayList; use crate::snippet::Snippet; pub use anstyle::*; +pub use margin::Margin; use std::fmt::Display; use stylesheet::Stylesheet; diff --git a/tests/deserialize/mod.rs b/tests/deserialize/mod.rs index 58d14ef0..af959cbf 100644 --- a/tests/deserialize/mod.rs +++ b/tests/deserialize/mod.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Deserializer, Serialize}; use annotate_snippets::renderer::Renderer; use annotate_snippets::{ - display_list::Margin, + renderer::Margin, snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, }; From da45f4858af3ec4c0d792ecc40225e27fdd2bac8 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Sat, 2 Dec 2023 14:04:43 -0700 Subject: [PATCH 096/455] fix!: Make `display_list` private --- src/display_list/mod.rs | 899 ++++++++++++++++++++++++++++++++++++- src/lib.rs | 21 +- src/renderer/mod.rs | 2 +- src/renderer/stylesheet.rs | 2 +- tests/dl_from_snippet.rs | 391 ---------------- tests/formatter.rs | 518 --------------------- 6 files changed, 903 insertions(+), 930 deletions(-) delete mode 100644 tests/dl_from_snippet.rs diff --git a/src/display_list/mod.rs b/src/display_list/mod.rs index d9b7f34e..da1a05b2 100644 --- a/src/display_list/mod.rs +++ b/src/display_list/mod.rs @@ -38,7 +38,7 @@ use std::{cmp, fmt}; use crate::renderer::{stylesheet::Stylesheet, Margin, Style}; /// List of lines to be displayed. -pub struct DisplayList<'a> { +pub(crate) struct DisplayList<'a> { pub body: Vec<DisplayLine<'a>>, pub stylesheet: Stylesheet, pub anonymized_line_numbers: bool, @@ -343,7 +343,6 @@ impl<'a> DisplayList<'a> { let indent_char = match annotation_part { DisplayAnnotationPart::Standalone => ' ', DisplayAnnotationPart::LabelContinuation => ' ', - DisplayAnnotationPart::Consequitive => ' ', DisplayAnnotationPart::MultilineStart => '_', DisplayAnnotationPart::MultilineEnd => '_', }; @@ -358,7 +357,6 @@ impl<'a> DisplayList<'a> { let color = self.get_annotation_style(annotation_type); let indent_length = match annotation_part { DisplayAnnotationPart::LabelContinuation => range.1, - DisplayAnnotationPart::Consequitive => range.1, _ => range.0, }; @@ -626,8 +624,6 @@ pub enum DisplayAnnotationPart { Standalone, /// A continuation of a multi-line label of an annotation. LabelContinuation, - /// A consequitive annotation in case multiple annotations annotate a single line. - Consequitive, /// A line starting a multiline annotation. MultilineStart, /// A line ending a multiline annotation. @@ -1228,3 +1224,896 @@ fn is_annotation_empty(annotation: &Annotation<'_>) -> bool { .iter() .all(|fragment| fragment.content.is_empty()) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_format_title() { + let input = snippet::Snippet { + title: Some(snippet::Annotation { + id: Some("E0001"), + label: Some("This is a title"), + annotation_type: snippet::AnnotationType::Error, + }), + footer: vec![], + slices: vec![], + }; + let output = DisplayList { + body: vec![DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::Error, + id: Some("E0001"), + label: vec![DisplayTextFragment { + content: "This is a title", + style: DisplayTextStyle::Emphasis, + }], + }, + source_aligned: false, + continuation: false, + })], + stylesheet: Stylesheet::default(), + anonymized_line_numbers: false, + margin: None, + }; + assert_eq!(DisplayList::from(input), output); + } + + #[test] + fn test_format_slice() { + let line_1 = "This is line 1"; + let line_2 = "This is line 2"; + let source = [line_1, line_2].join("\n"); + let input = snippet::Snippet { + title: None, + footer: vec![], + slices: vec![snippet::Slice { + source: &source, + line_start: 5402, + origin: None, + annotations: vec![], + fold: false, + }], + }; + let output = DisplayList { + body: vec![ + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + DisplayLine::Source { + lineno: Some(5402), + inline_marks: vec![], + line: DisplaySourceLine::Content { + text: line_1, + range: (0, line_1.len()), + }, + }, + DisplayLine::Source { + lineno: Some(5403), + inline_marks: vec![], + line: DisplaySourceLine::Content { + range: (line_1.len() + 1, source.len()), + text: line_2, + }, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + ], + stylesheet: Stylesheet::default(), + anonymized_line_numbers: false, + margin: None, + }; + assert_eq!(DisplayList::from(input), output); + } + + #[test] + fn test_format_slices_continuation() { + let src_0 = "This is slice 1"; + let src_0_len = src_0.len(); + let src_1 = "This is slice 2"; + let src_1_len = src_1.len(); + let input = snippet::Snippet { + title: None, + footer: vec![], + slices: vec![ + snippet::Slice { + source: src_0, + line_start: 5402, + origin: Some("file1.rs"), + annotations: vec![], + fold: false, + }, + snippet::Slice { + source: src_1, + line_start: 2, + origin: Some("file2.rs"), + annotations: vec![], + fold: false, + }, + ], + }; + let output = DisplayList { + body: vec![ + DisplayLine::Raw(DisplayRawLine::Origin { + path: "file1.rs", + pos: None, + header_type: DisplayHeaderType::Initial, + }), + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + DisplayLine::Source { + lineno: Some(5402), + inline_marks: vec![], + line: DisplaySourceLine::Content { + text: src_0, + range: (0, src_0_len), + }, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + DisplayLine::Raw(DisplayRawLine::Origin { + path: "file2.rs", + pos: None, + header_type: DisplayHeaderType::Continuation, + }), + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + DisplayLine::Source { + lineno: Some(2), + inline_marks: vec![], + line: DisplaySourceLine::Content { + text: src_1, + range: (0, src_1_len), + }, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + ], + stylesheet: Stylesheet::default(), + anonymized_line_numbers: false, + margin: None, + }; + assert_eq!(DisplayList::from(input), output); + } + + #[test] + fn test_format_slice_annotation_standalone() { + let line_1 = "This is line 1"; + let line_2 = "This is line 2"; + let source = [line_1, line_2].join("\n"); + // In line 2 + let range = (22, 24); + let input = snippet::Snippet { + title: None, + footer: vec![], + slices: vec![snippet::Slice { + source: &source, + line_start: 5402, + origin: None, + annotations: vec![snippet::SourceAnnotation { + range, + label: "Test annotation", + annotation_type: snippet::AnnotationType::Info, + }], + fold: false, + }], + }; + let output = DisplayList { + body: vec![ + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + DisplayLine::Source { + lineno: Some(5402), + inline_marks: vec![], + line: DisplaySourceLine::Content { + range: (0, line_1.len()), + text: line_1, + }, + }, + DisplayLine::Source { + lineno: Some(5403), + inline_marks: vec![], + line: DisplaySourceLine::Content { + range: (line_1.len() + 1, source.len()), + text: line_2, + }, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::Info, + id: None, + label: vec![DisplayTextFragment { + content: "Test annotation", + style: DisplayTextStyle::Regular, + }], + }, + range: (range.0 - (line_1.len() + 1), range.1 - (line_1.len() + 1)), + annotation_type: DisplayAnnotationType::Info, + annotation_part: DisplayAnnotationPart::Standalone, + }, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + ], + stylesheet: Stylesheet::default(), + anonymized_line_numbers: false, + margin: None, + }; + assert_eq!(DisplayList::from(input), output); + } + + #[test] + fn test_format_label() { + let input = snippet::Snippet { + title: None, + footer: vec![snippet::Annotation { + id: None, + label: Some("This __is__ a title"), + annotation_type: snippet::AnnotationType::Error, + }], + slices: vec![], + }; + let output = DisplayList { + body: vec![DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::Error, + id: None, + label: vec![DisplayTextFragment { + content: "This __is__ a title", + style: DisplayTextStyle::Regular, + }], + }, + source_aligned: true, + continuation: false, + })], + stylesheet: Stylesheet::default(), + anonymized_line_numbers: false, + margin: None, + }; + assert_eq!(DisplayList::from(input), output); + } + + #[test] + #[should_panic] + fn test_i26() { + let source = "short"; + let label = "label"; + let input = snippet::Snippet { + title: None, + footer: vec![], + slices: vec![snippet::Slice { + annotations: vec![snippet::SourceAnnotation { + range: (0, source.len() + 1), + label, + annotation_type: snippet::AnnotationType::Error, + }], + source, + line_start: 0, + origin: None, + fold: false, + }], + }; + + let _ = DisplayList::from(input); + } + + #[test] + fn test_i_29() { + let snippets = snippet::Snippet { + title: Some(snippet::Annotation { + id: None, + label: Some("oops"), + annotation_type: snippet::AnnotationType::Error, + }), + footer: vec![], + slices: vec![snippet::Slice { + source: "First line\r\nSecond oops line", + line_start: 1, + origin: Some("<current file>"), + annotations: vec![snippet::SourceAnnotation { + range: (19, 23), + label: "oops", + annotation_type: snippet::AnnotationType::Error, + }], + fold: true, + }], + }; + + let expected = DisplayList { + body: vec![ + DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::Error, + id: None, + label: vec![DisplayTextFragment { + content: "oops", + style: DisplayTextStyle::Emphasis, + }], + }, + source_aligned: false, + continuation: false, + }), + DisplayLine::Raw(DisplayRawLine::Origin { + path: "<current file>", + pos: Some((2, 8)), + header_type: DisplayHeaderType::Initial, + }), + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + DisplayLine::Source { + lineno: Some(1), + inline_marks: vec![], + line: DisplaySourceLine::Content { + text: "First line", + range: (0, 10), + }, + }, + DisplayLine::Source { + lineno: Some(2), + inline_marks: vec![], + line: DisplaySourceLine::Content { + text: "Second oops line", + range: (12, 28), + }, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::None, + id: None, + label: vec![DisplayTextFragment { + content: "oops", + style: DisplayTextStyle::Regular, + }], + }, + range: (7, 11), + annotation_type: DisplayAnnotationType::Error, + annotation_part: DisplayAnnotationPart::Standalone, + }, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + ], + stylesheet: Stylesheet::default(), + anonymized_line_numbers: false, + margin: None, + }; + + assert_eq!(DisplayList::from(snippets), expected); + } + + #[test] + fn test_source_empty() { + let dl = DisplayList::from(vec![DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }]); + + assert_eq!(dl.to_string(), " |"); + } + + #[test] + fn test_source_content() { + let dl = DisplayList::from(vec![ + DisplayLine::Source { + lineno: Some(56), + inline_marks: vec![], + line: DisplaySourceLine::Content { + text: "This is an example", + range: (0, 19), + }, + }, + DisplayLine::Source { + lineno: Some(57), + inline_marks: vec![], + line: DisplaySourceLine::Content { + text: "of content lines", + range: (0, 19), + }, + }, + ]); + + assert_eq!( + dl.to_string(), + "56 | This is an example\n57 | of content lines" + ); + } + + #[test] + fn test_source_annotation_standalone_singleline() { + let dl = DisplayList::from(vec![DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Annotation { + range: (0, 5), + annotation: Annotation { + annotation_type: DisplayAnnotationType::None, + id: None, + label: vec![DisplayTextFragment { + content: "Example string", + style: DisplayTextStyle::Regular, + }], + }, + annotation_type: DisplayAnnotationType::Error, + annotation_part: DisplayAnnotationPart::Standalone, + }, + }]); + + assert_eq!(dl.to_string(), " | ^^^^^ Example string"); + } + + #[test] + fn test_source_annotation_standalone_multiline() { + let dl = DisplayList::from(vec![ + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Annotation { + range: (0, 5), + annotation: Annotation { + annotation_type: DisplayAnnotationType::Help, + id: None, + label: vec![DisplayTextFragment { + content: "Example string", + style: DisplayTextStyle::Regular, + }], + }, + annotation_type: DisplayAnnotationType::Warning, + annotation_part: DisplayAnnotationPart::Standalone, + }, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Annotation { + range: (0, 5), + annotation: Annotation { + annotation_type: DisplayAnnotationType::Help, + id: None, + label: vec![DisplayTextFragment { + content: "Second line", + style: DisplayTextStyle::Regular, + }], + }, + annotation_type: DisplayAnnotationType::Warning, + annotation_part: DisplayAnnotationPart::LabelContinuation, + }, + }, + ]); + + assert_eq!( + dl.to_string(), + " | ----- help: Example string\n | Second line" + ); + } + + #[test] + fn test_source_annotation_standalone_multi_annotation() { + let dl = DisplayList::from(vec![ + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Annotation { + range: (0, 5), + annotation: Annotation { + annotation_type: DisplayAnnotationType::Info, + id: None, + label: vec![DisplayTextFragment { + content: "Example string", + style: DisplayTextStyle::Regular, + }], + }, + annotation_type: DisplayAnnotationType::Note, + annotation_part: DisplayAnnotationPart::Standalone, + }, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Annotation { + range: (0, 5), + annotation: Annotation { + annotation_type: DisplayAnnotationType::Info, + id: None, + label: vec![DisplayTextFragment { + content: "Second line", + style: DisplayTextStyle::Regular, + }], + }, + annotation_type: DisplayAnnotationType::Note, + annotation_part: DisplayAnnotationPart::LabelContinuation, + }, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Annotation { + range: (0, 5), + annotation: Annotation { + annotation_type: DisplayAnnotationType::Warning, + id: None, + label: vec![DisplayTextFragment { + content: "Second line of the warning", + style: DisplayTextStyle::Regular, + }], + }, + annotation_type: DisplayAnnotationType::Note, + annotation_part: DisplayAnnotationPart::LabelContinuation, + }, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Annotation { + range: (0, 5), + annotation: Annotation { + annotation_type: DisplayAnnotationType::Info, + id: None, + label: vec![DisplayTextFragment { + content: "This is an info", + style: DisplayTextStyle::Regular, + }], + }, + annotation_type: DisplayAnnotationType::Info, + annotation_part: DisplayAnnotationPart::Standalone, + }, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Annotation { + range: (0, 5), + annotation: Annotation { + annotation_type: DisplayAnnotationType::Help, + id: None, + label: vec![DisplayTextFragment { + content: "This is help", + style: DisplayTextStyle::Regular, + }], + }, + annotation_type: DisplayAnnotationType::Help, + annotation_part: DisplayAnnotationPart::Standalone, + }, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Annotation { + range: (0, 0), + annotation: Annotation { + annotation_type: DisplayAnnotationType::None, + id: None, + label: vec![DisplayTextFragment { + content: "This is an annotation of type none", + style: DisplayTextStyle::Regular, + }], + }, + annotation_type: DisplayAnnotationType::None, + annotation_part: DisplayAnnotationPart::Standalone, + }, + }, + ]); + + assert_eq!(dl.to_string(), " | ----- info: Example string\n | Second line\n | Second line of the warning\n | ----- info: This is an info\n | ----- help: This is help\n | This is an annotation of type none"); + } + + #[test] + fn test_fold_line() { + let dl = DisplayList::from(vec![ + DisplayLine::Source { + lineno: Some(5), + inline_marks: vec![], + line: DisplaySourceLine::Content { + text: "This is line 5", + range: (0, 19), + }, + }, + DisplayLine::Fold { + inline_marks: vec![], + }, + DisplayLine::Source { + lineno: Some(10021), + inline_marks: vec![], + line: DisplaySourceLine::Content { + text: "... and now we're at line 10021", + range: (0, 19), + }, + }, + ]); + + assert_eq!( + dl.to_string(), + " 5 | This is line 5\n...\n10021 | ... and now we're at line 10021" + ); + } + + #[test] + fn test_raw_origin_initial_nopos() { + let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin { + path: "src/test.rs", + pos: None, + header_type: DisplayHeaderType::Initial, + })]); + + assert_eq!(dl.to_string(), "--> src/test.rs"); + } + + #[test] + fn test_raw_origin_initial_pos() { + let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin { + path: "src/test.rs", + pos: Some((23, 15)), + header_type: DisplayHeaderType::Initial, + })]); + + assert_eq!(dl.to_string(), "--> src/test.rs:23:15"); + } + + #[test] + fn test_raw_origin_continuation() { + let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin { + path: "src/test.rs", + pos: Some((23, 15)), + header_type: DisplayHeaderType::Continuation, + })]); + + assert_eq!(dl.to_string(), "::: src/test.rs:23:15"); + } + + #[test] + fn test_raw_annotation_unaligned() { + let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::Error, + id: Some("E0001"), + label: vec![DisplayTextFragment { + content: "This is an error", + style: DisplayTextStyle::Regular, + }], + }, + source_aligned: false, + continuation: false, + })]); + + assert_eq!(dl.to_string(), "error[E0001]: This is an error"); + } + + #[test] + fn test_raw_annotation_unaligned_multiline() { + let dl = DisplayList::from(vec![ + DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::Warning, + id: Some("E0001"), + label: vec![DisplayTextFragment { + content: "This is an error", + style: DisplayTextStyle::Regular, + }], + }, + source_aligned: false, + continuation: false, + }), + DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::Warning, + id: Some("E0001"), + label: vec![DisplayTextFragment { + content: "Second line of the error", + style: DisplayTextStyle::Regular, + }], + }, + source_aligned: false, + continuation: true, + }), + ]); + + assert_eq!( + dl.to_string(), + "warning[E0001]: This is an error\n Second line of the error" + ); + } + + #[test] + fn test_raw_annotation_aligned() { + let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::Error, + id: Some("E0001"), + label: vec![DisplayTextFragment { + content: "This is an error", + style: DisplayTextStyle::Regular, + }], + }, + source_aligned: true, + continuation: false, + })]); + + assert_eq!(dl.to_string(), " = error[E0001]: This is an error"); + } + + #[test] + fn test_raw_annotation_aligned_multiline() { + let dl = DisplayList::from(vec![ + DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::Warning, + id: Some("E0001"), + label: vec![DisplayTextFragment { + content: "This is an error", + style: DisplayTextStyle::Regular, + }], + }, + source_aligned: true, + continuation: false, + }), + DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::Warning, + id: Some("E0001"), + label: vec![DisplayTextFragment { + content: "Second line of the error", + style: DisplayTextStyle::Regular, + }], + }, + source_aligned: true, + continuation: true, + }), + ]); + + assert_eq!( + dl.to_string(), + " = warning[E0001]: This is an error\n Second line of the error" + ); + } + + #[test] + fn test_different_annotation_types() { + let dl = DisplayList::from(vec![ + DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::Note, + id: None, + label: vec![DisplayTextFragment { + content: "This is a note", + style: DisplayTextStyle::Regular, + }], + }, + source_aligned: false, + continuation: false, + }), + DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::None, + id: None, + label: vec![DisplayTextFragment { + content: "This is just a string", + style: DisplayTextStyle::Regular, + }], + }, + source_aligned: false, + continuation: false, + }), + DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::None, + id: None, + label: vec![DisplayTextFragment { + content: "Second line of none type annotation", + style: DisplayTextStyle::Regular, + }], + }, + source_aligned: false, + continuation: true, + }), + ]); + + assert_eq!( + dl.to_string(), + "note: This is a note\nThis is just a string\n Second line of none type annotation", + ); + } + + #[test] + fn test_inline_marks_empty_line() { + let dl = DisplayList::from(vec![DisplayLine::Source { + lineno: None, + inline_marks: vec![DisplayMark { + mark_type: DisplayMarkType::AnnotationThrough, + annotation_type: DisplayAnnotationType::Error, + }], + line: DisplaySourceLine::Empty, + }]); + + assert_eq!(dl.to_string(), " | |",); + } + + #[test] + fn test_anon_lines() { + let mut dl = DisplayList::from(vec![ + DisplayLine::Source { + lineno: Some(56), + inline_marks: vec![], + line: DisplaySourceLine::Content { + text: "This is an example", + range: (0, 19), + }, + }, + DisplayLine::Source { + lineno: Some(57), + inline_marks: vec![], + line: DisplaySourceLine::Content { + text: "of content lines", + range: (0, 19), + }, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Content { + text: "abc", + range: (0, 19), + }, + }, + ]); + + dl.anonymized_line_numbers = true; + assert_eq!( + dl.to_string(), + "LL | This is an example\nLL | of content lines\n |\n | abc" + ); + } + + #[test] + fn test_raw_origin_initial_pos_anon_lines() { + let mut dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin { + path: "src/test.rs", + pos: Some((23, 15)), + header_type: DisplayHeaderType::Initial, + })]); + + // Using anonymized_line_numbers should not affect the initial position + dl.anonymized_line_numbers = true; + assert_eq!(dl.to_string(), "--> src/test.rs:23:15"); + } +} diff --git a/src/lib.rs b/src/lib.rs index bf0dc058..342db23c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,28 +26,21 @@ //! The crate uses a three stage process with two conversions between states: //! //! ```text -//! Snippet --> DisplayList --> String +//! Snippet --> Renderer --> impl Display //! ``` //! //! The input type - [Snippet](self::snippet) is a structure designed //! to align with likely output from any parser whose code snippet is to be //! annotated. //! -//! The middle structure - [DisplayList](self::display_list) is a -//! structure designed to store the snippet data converted into a vector -//! of lines containing semantic information about each line. -//! This structure is the easiest to manipulate and organize. +//! The middle structure - [Renderer](self::renderer) is a structure designed +//! to convert a snippet into an internal structure that is designed to store +//! the snippet data in a way that is easy to format. +//! [Renderer](self::renderer) also handles the user-configurable formatting +//! options, such as color, or margins. //! //! Finally, `impl Display` into a final `String` output. -//! -//! A user of the crate may choose to provide their own equivalent of the input -//! structure with an `Into<DisplayList>` trait. -//! -//! A user of the crate may also choose to provide their own formatter logic, -//! to convert a `DisplayList` into a `String`, or just a `Stylesheet` to -//! use the crate's formatting logic, but with a custom stylesheet. -// TODO: check documentation -pub mod display_list; +mod display_list; pub mod renderer; pub mod snippet; diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 712d2039..2f033418 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -1,5 +1,5 @@ mod margin; -pub mod stylesheet; +pub(crate) mod stylesheet; use crate::display_list::DisplayList; use crate::snippet::Snippet; diff --git a/src/renderer/stylesheet.rs b/src/renderer/stylesheet.rs index 899d9a76..b3dbbc3d 100644 --- a/src/renderer/stylesheet.rs +++ b/src/renderer/stylesheet.rs @@ -1,7 +1,7 @@ use anstyle::Style; #[derive(Clone, Copy, Debug)] -pub struct Stylesheet { +pub(crate) struct Stylesheet { pub(crate) error: Style, pub(crate) warning: Style, pub(crate) info: Style, diff --git a/tests/dl_from_snippet.rs b/tests/dl_from_snippet.rs deleted file mode 100644 index 5fb0762d..00000000 --- a/tests/dl_from_snippet.rs +++ /dev/null @@ -1,391 +0,0 @@ -use annotate_snippets::display_list::DisplayList; -use annotate_snippets::renderer::stylesheet::Stylesheet; -use annotate_snippets::{display_list as dl, snippet}; - -#[test] -fn test_format_title() { - let input = snippet::Snippet { - title: Some(snippet::Annotation { - id: Some("E0001"), - label: Some("This is a title"), - annotation_type: snippet::AnnotationType::Error, - }), - footer: vec![], - slices: vec![], - }; - let output = dl::DisplayList { - body: vec![dl::DisplayLine::Raw(dl::DisplayRawLine::Annotation { - annotation: dl::Annotation { - annotation_type: dl::DisplayAnnotationType::Error, - id: Some("E0001"), - label: vec![dl::DisplayTextFragment { - content: "This is a title", - style: dl::DisplayTextStyle::Emphasis, - }], - }, - source_aligned: false, - continuation: false, - })], - stylesheet: Stylesheet::default(), - anonymized_line_numbers: false, - margin: None, - }; - assert_eq!(dl::DisplayList::from(input), output); -} - -#[test] -fn test_format_slice() { - let line_1 = "This is line 1"; - let line_2 = "This is line 2"; - let source = [line_1, line_2].join("\n"); - let input = snippet::Snippet { - title: None, - footer: vec![], - slices: vec![snippet::Slice { - source: &source, - line_start: 5402, - origin: None, - annotations: vec![], - fold: false, - }], - }; - let output = dl::DisplayList { - body: vec![ - dl::DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: dl::DisplaySourceLine::Empty, - }, - dl::DisplayLine::Source { - lineno: Some(5402), - inline_marks: vec![], - line: dl::DisplaySourceLine::Content { - text: line_1, - range: (0, line_1.len()), - }, - }, - dl::DisplayLine::Source { - lineno: Some(5403), - inline_marks: vec![], - line: dl::DisplaySourceLine::Content { - range: (line_1.len() + 1, source.len()), - text: line_2, - }, - }, - dl::DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: dl::DisplaySourceLine::Empty, - }, - ], - stylesheet: Stylesheet::default(), - anonymized_line_numbers: false, - margin: None, - }; - assert_eq!(dl::DisplayList::from(input), output); -} - -#[test] -fn test_format_slices_continuation() { - let src_0 = "This is slice 1"; - let src_0_len = src_0.len(); - let src_1 = "This is slice 2"; - let src_1_len = src_1.len(); - let input = snippet::Snippet { - title: None, - footer: vec![], - slices: vec![ - snippet::Slice { - source: src_0, - line_start: 5402, - origin: Some("file1.rs"), - annotations: vec![], - fold: false, - }, - snippet::Slice { - source: src_1, - line_start: 2, - origin: Some("file2.rs"), - annotations: vec![], - fold: false, - }, - ], - }; - let output = dl::DisplayList { - body: vec![ - dl::DisplayLine::Raw(dl::DisplayRawLine::Origin { - path: "file1.rs", - pos: None, - header_type: dl::DisplayHeaderType::Initial, - }), - dl::DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: dl::DisplaySourceLine::Empty, - }, - dl::DisplayLine::Source { - lineno: Some(5402), - inline_marks: vec![], - line: dl::DisplaySourceLine::Content { - text: src_0, - range: (0, src_0_len), - }, - }, - dl::DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: dl::DisplaySourceLine::Empty, - }, - dl::DisplayLine::Raw(dl::DisplayRawLine::Origin { - path: "file2.rs", - pos: None, - header_type: dl::DisplayHeaderType::Continuation, - }), - dl::DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: dl::DisplaySourceLine::Empty, - }, - dl::DisplayLine::Source { - lineno: Some(2), - inline_marks: vec![], - line: dl::DisplaySourceLine::Content { - text: src_1, - range: (0, src_1_len), - }, - }, - dl::DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: dl::DisplaySourceLine::Empty, - }, - ], - stylesheet: Stylesheet::default(), - anonymized_line_numbers: false, - margin: None, - }; - assert_eq!(dl::DisplayList::from(input), output); -} - -#[test] -fn test_format_slice_annotation_standalone() { - let line_1 = "This is line 1"; - let line_2 = "This is line 2"; - let source = [line_1, line_2].join("\n"); - // In line 2 - let range = (22, 24); - let input = snippet::Snippet { - title: None, - footer: vec![], - slices: vec![snippet::Slice { - source: &source, - line_start: 5402, - origin: None, - annotations: vec![snippet::SourceAnnotation { - range, - label: "Test annotation", - annotation_type: snippet::AnnotationType::Info, - }], - fold: false, - }], - }; - let output = dl::DisplayList { - body: vec![ - dl::DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: dl::DisplaySourceLine::Empty, - }, - dl::DisplayLine::Source { - lineno: Some(5402), - inline_marks: vec![], - line: dl::DisplaySourceLine::Content { - range: (0, line_1.len()), - text: line_1, - }, - }, - dl::DisplayLine::Source { - lineno: Some(5403), - inline_marks: vec![], - line: dl::DisplaySourceLine::Content { - range: (line_1.len() + 1, source.len()), - text: line_2, - }, - }, - dl::DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: dl::DisplaySourceLine::Annotation { - annotation: dl::Annotation { - annotation_type: dl::DisplayAnnotationType::Info, - id: None, - label: vec![dl::DisplayTextFragment { - content: "Test annotation", - style: dl::DisplayTextStyle::Regular, - }], - }, - range: (range.0 - (line_1.len() + 1), range.1 - (line_1.len() + 1)), - annotation_type: dl::DisplayAnnotationType::Info, - annotation_part: dl::DisplayAnnotationPart::Standalone, - }, - }, - dl::DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: dl::DisplaySourceLine::Empty, - }, - ], - stylesheet: Stylesheet::default(), - anonymized_line_numbers: false, - margin: None, - }; - assert_eq!(dl::DisplayList::from(input), output); -} - -#[test] -fn test_format_label() { - let input = snippet::Snippet { - title: None, - footer: vec![snippet::Annotation { - id: None, - label: Some("This __is__ a title"), - annotation_type: snippet::AnnotationType::Error, - }], - slices: vec![], - }; - let output = dl::DisplayList { - body: vec![dl::DisplayLine::Raw(dl::DisplayRawLine::Annotation { - annotation: dl::Annotation { - annotation_type: dl::DisplayAnnotationType::Error, - id: None, - label: vec![dl::DisplayTextFragment { - content: "This __is__ a title", - style: dl::DisplayTextStyle::Regular, - }], - }, - source_aligned: true, - continuation: false, - })], - stylesheet: Stylesheet::default(), - anonymized_line_numbers: false, - margin: None, - }; - assert_eq!(dl::DisplayList::from(input), output); -} - -#[test] -#[should_panic] -fn test_i26() { - let source = "short"; - let label = "label"; - let input = snippet::Snippet { - title: None, - footer: vec![], - slices: vec![snippet::Slice { - annotations: vec![snippet::SourceAnnotation { - range: (0, source.len() + 1), - label, - annotation_type: snippet::AnnotationType::Error, - }], - source, - line_start: 0, - origin: None, - fold: false, - }], - }; - - let _ = dl::DisplayList::from(input); -} - -#[test] -fn test_i_29() { - let snippets = snippet::Snippet { - title: Some(snippet::Annotation { - id: None, - label: Some("oops"), - annotation_type: snippet::AnnotationType::Error, - }), - footer: vec![], - slices: vec![snippet::Slice { - source: "First line\r\nSecond oops line", - line_start: 1, - origin: Some("<current file>"), - annotations: vec![snippet::SourceAnnotation { - range: (19, 23), - label: "oops", - annotation_type: snippet::AnnotationType::Error, - }], - fold: true, - }], - }; - - let expected = DisplayList { - body: vec![ - dl::DisplayLine::Raw(dl::DisplayRawLine::Annotation { - annotation: dl::Annotation { - annotation_type: dl::DisplayAnnotationType::Error, - id: None, - label: vec![dl::DisplayTextFragment { - content: "oops", - style: dl::DisplayTextStyle::Emphasis, - }], - }, - source_aligned: false, - continuation: false, - }), - dl::DisplayLine::Raw(dl::DisplayRawLine::Origin { - path: "<current file>", - pos: Some((2, 8)), - header_type: dl::DisplayHeaderType::Initial, - }), - dl::DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: dl::DisplaySourceLine::Empty, - }, - dl::DisplayLine::Source { - lineno: Some(1), - inline_marks: vec![], - line: dl::DisplaySourceLine::Content { - text: "First line", - range: (0, 10), - }, - }, - dl::DisplayLine::Source { - lineno: Some(2), - inline_marks: vec![], - line: dl::DisplaySourceLine::Content { - text: "Second oops line", - range: (12, 28), - }, - }, - dl::DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: dl::DisplaySourceLine::Annotation { - annotation: dl::Annotation { - annotation_type: dl::DisplayAnnotationType::None, - id: None, - label: vec![dl::DisplayTextFragment { - content: "oops", - style: dl::DisplayTextStyle::Regular, - }], - }, - range: (7, 11), - annotation_type: dl::DisplayAnnotationType::Error, - annotation_part: dl::DisplayAnnotationPart::Standalone, - }, - }, - dl::DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: dl::DisplaySourceLine::Empty, - }, - ], - stylesheet: Stylesheet::default(), - anonymized_line_numbers: false, - margin: None, - }; - - assert_eq!(DisplayList::from(snippets), expected); -} diff --git a/tests/formatter.rs b/tests/formatter.rs index 2117fec2..3f85b695 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -1,524 +1,6 @@ -use annotate_snippets::display_list::*; use annotate_snippets::renderer::Renderer; use annotate_snippets::snippet::{self, Snippet}; -#[test] -fn test_source_empty() { - let dl = DisplayList::from(vec![DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }]); - - assert_eq!(dl.to_string(), " |"); -} - -#[test] -fn test_source_content() { - let dl = DisplayList::from(vec![ - DisplayLine::Source { - lineno: Some(56), - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: "This is an example", - range: (0, 19), - }, - }, - DisplayLine::Source { - lineno: Some(57), - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: "of content lines", - range: (0, 19), - }, - }, - ]); - - assert_eq!( - dl.to_string(), - "56 | This is an example\n57 | of content lines" - ); -} - -#[test] -fn test_source_annotation_standalone_singleline() { - let dl = DisplayList::from(vec![DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - range: (0, 5), - annotation: Annotation { - annotation_type: DisplayAnnotationType::None, - id: None, - label: vec![DisplayTextFragment { - content: "Example string", - style: DisplayTextStyle::Regular, - }], - }, - annotation_type: DisplayAnnotationType::Error, - annotation_part: DisplayAnnotationPart::Standalone, - }, - }]); - - assert_eq!(dl.to_string(), " | ^^^^^ Example string"); -} - -#[test] -fn test_source_annotation_standalone_multiline() { - let dl = DisplayList::from(vec![ - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - range: (0, 5), - annotation: Annotation { - annotation_type: DisplayAnnotationType::Help, - id: None, - label: vec![DisplayTextFragment { - content: "Example string", - style: DisplayTextStyle::Regular, - }], - }, - annotation_type: DisplayAnnotationType::Warning, - annotation_part: DisplayAnnotationPart::Standalone, - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - range: (0, 5), - annotation: Annotation { - annotation_type: DisplayAnnotationType::Help, - id: None, - label: vec![DisplayTextFragment { - content: "Second line", - style: DisplayTextStyle::Regular, - }], - }, - annotation_type: DisplayAnnotationType::Warning, - annotation_part: DisplayAnnotationPart::LabelContinuation, - }, - }, - ]); - - assert_eq!( - dl.to_string(), - " | ----- help: Example string\n | Second line" - ); -} - -#[test] -fn test_source_annotation_standalone_multi_annotation() { - let dl = DisplayList::from(vec![ - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - range: (0, 5), - annotation: Annotation { - annotation_type: DisplayAnnotationType::Info, - id: None, - label: vec![DisplayTextFragment { - content: "Example string", - style: DisplayTextStyle::Regular, - }], - }, - annotation_type: DisplayAnnotationType::Note, - annotation_part: DisplayAnnotationPart::Standalone, - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - range: (0, 5), - annotation: Annotation { - annotation_type: DisplayAnnotationType::Info, - id: None, - label: vec![DisplayTextFragment { - content: "Second line", - style: DisplayTextStyle::Regular, - }], - }, - annotation_type: DisplayAnnotationType::Note, - annotation_part: DisplayAnnotationPart::LabelContinuation, - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - range: (0, 5), - annotation: Annotation { - annotation_type: DisplayAnnotationType::Warning, - id: None, - label: vec![DisplayTextFragment { - content: "This is a note", - style: DisplayTextStyle::Regular, - }], - }, - annotation_type: DisplayAnnotationType::Note, - annotation_part: DisplayAnnotationPart::Consequitive, - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - range: (0, 5), - annotation: Annotation { - annotation_type: DisplayAnnotationType::Warning, - id: None, - label: vec![DisplayTextFragment { - content: "Second line of the warning", - style: DisplayTextStyle::Regular, - }], - }, - annotation_type: DisplayAnnotationType::Note, - annotation_part: DisplayAnnotationPart::LabelContinuation, - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - range: (0, 5), - annotation: Annotation { - annotation_type: DisplayAnnotationType::Info, - id: None, - label: vec![DisplayTextFragment { - content: "This is an info", - style: DisplayTextStyle::Regular, - }], - }, - annotation_type: DisplayAnnotationType::Info, - annotation_part: DisplayAnnotationPart::Standalone, - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - range: (0, 5), - annotation: Annotation { - annotation_type: DisplayAnnotationType::Help, - id: None, - label: vec![DisplayTextFragment { - content: "This is help", - style: DisplayTextStyle::Regular, - }], - }, - annotation_type: DisplayAnnotationType::Help, - annotation_part: DisplayAnnotationPart::Standalone, - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - range: (0, 0), - annotation: Annotation { - annotation_type: DisplayAnnotationType::None, - id: None, - label: vec![DisplayTextFragment { - content: "This is an annotation of type none", - style: DisplayTextStyle::Regular, - }], - }, - annotation_type: DisplayAnnotationType::None, - annotation_part: DisplayAnnotationPart::Standalone, - }, - }, - ]); - - assert_eq!(dl.to_string(), " | ----- info: Example string\n | Second line\n | warning: This is a note\n | Second line of the warning\n | ----- info: This is an info\n | ----- help: This is help\n | This is an annotation of type none"); -} - -#[test] -fn test_fold_line() { - let dl = DisplayList::from(vec![ - DisplayLine::Source { - lineno: Some(5), - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: "This is line 5", - range: (0, 19), - }, - }, - DisplayLine::Fold { - inline_marks: vec![], - }, - DisplayLine::Source { - lineno: Some(10021), - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: "... and now we're at line 10021", - range: (0, 19), - }, - }, - ]); - - assert_eq!( - dl.to_string(), - " 5 | This is line 5\n...\n10021 | ... and now we're at line 10021" - ); -} - -#[test] -fn test_raw_origin_initial_nopos() { - let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin { - path: "src/test.rs", - pos: None, - header_type: DisplayHeaderType::Initial, - })]); - - assert_eq!(dl.to_string(), "--> src/test.rs"); -} - -#[test] -fn test_raw_origin_initial_pos() { - let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin { - path: "src/test.rs", - pos: Some((23, 15)), - header_type: DisplayHeaderType::Initial, - })]); - - assert_eq!(dl.to_string(), "--> src/test.rs:23:15"); -} - -#[test] -fn test_raw_origin_continuation() { - let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin { - path: "src/test.rs", - pos: Some((23, 15)), - header_type: DisplayHeaderType::Continuation, - })]); - - assert_eq!(dl.to_string(), "::: src/test.rs:23:15"); -} - -#[test] -fn test_raw_annotation_unaligned() { - let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Error, - id: Some("E0001"), - label: vec![DisplayTextFragment { - content: "This is an error", - style: DisplayTextStyle::Regular, - }], - }, - source_aligned: false, - continuation: false, - })]); - - assert_eq!(dl.to_string(), "error[E0001]: This is an error"); -} - -#[test] -fn test_raw_annotation_unaligned_multiline() { - let dl = DisplayList::from(vec![ - DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Warning, - id: Some("E0001"), - label: vec![DisplayTextFragment { - content: "This is an error", - style: DisplayTextStyle::Regular, - }], - }, - source_aligned: false, - continuation: false, - }), - DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Warning, - id: Some("E0001"), - label: vec![DisplayTextFragment { - content: "Second line of the error", - style: DisplayTextStyle::Regular, - }], - }, - source_aligned: false, - continuation: true, - }), - ]); - - assert_eq!( - dl.to_string(), - "warning[E0001]: This is an error\n Second line of the error" - ); -} - -#[test] -fn test_raw_annotation_aligned() { - let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Error, - id: Some("E0001"), - label: vec![DisplayTextFragment { - content: "This is an error", - style: DisplayTextStyle::Regular, - }], - }, - source_aligned: true, - continuation: false, - })]); - - assert_eq!(dl.to_string(), " = error[E0001]: This is an error"); -} - -#[test] -fn test_raw_annotation_aligned_multiline() { - let dl = DisplayList::from(vec![ - DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Warning, - id: Some("E0001"), - label: vec![DisplayTextFragment { - content: "This is an error", - style: DisplayTextStyle::Regular, - }], - }, - source_aligned: true, - continuation: false, - }), - DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Warning, - id: Some("E0001"), - label: vec![DisplayTextFragment { - content: "Second line of the error", - style: DisplayTextStyle::Regular, - }], - }, - source_aligned: true, - continuation: true, - }), - ]); - - assert_eq!( - dl.to_string(), - " = warning[E0001]: This is an error\n Second line of the error" - ); -} - -#[test] -fn test_different_annotation_types() { - let dl = DisplayList::from(vec![ - DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Note, - id: None, - label: vec![DisplayTextFragment { - content: "This is a note", - style: DisplayTextStyle::Regular, - }], - }, - source_aligned: false, - continuation: false, - }), - DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::None, - id: None, - label: vec![DisplayTextFragment { - content: "This is just a string", - style: DisplayTextStyle::Regular, - }], - }, - source_aligned: false, - continuation: false, - }), - DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::None, - id: None, - label: vec![DisplayTextFragment { - content: "Second line of none type annotation", - style: DisplayTextStyle::Regular, - }], - }, - source_aligned: false, - continuation: true, - }), - ]); - - assert_eq!( - dl.to_string(), - "note: This is a note\nThis is just a string\n Second line of none type annotation", - ); -} - -#[test] -fn test_inline_marks_empty_line() { - let dl = DisplayList::from(vec![DisplayLine::Source { - lineno: None, - inline_marks: vec![DisplayMark { - mark_type: DisplayMarkType::AnnotationThrough, - annotation_type: DisplayAnnotationType::Error, - }], - line: DisplaySourceLine::Empty, - }]); - - assert_eq!(dl.to_string(), " | |",); -} - -#[test] -fn test_anon_lines() { - let mut dl = DisplayList::from(vec![ - DisplayLine::Source { - lineno: Some(56), - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: "This is an example", - range: (0, 19), - }, - }, - DisplayLine::Source { - lineno: Some(57), - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: "of content lines", - range: (0, 19), - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: "abc", - range: (0, 19), - }, - }, - ]); - - dl.anonymized_line_numbers = true; - assert_eq!( - dl.to_string(), - "LL | This is an example\nLL | of content lines\n |\n | abc" - ); -} - -#[test] -fn test_raw_origin_initial_pos_anon_lines() { - let mut dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin { - path: "src/test.rs", - pos: Some((23, 15)), - header_type: DisplayHeaderType::Initial, - })]); - - // Using anonymized_line_numbers should not affect the initial position - dl.anonymized_line_numbers = true; - assert_eq!(dl.to_string(), "--> src/test.rs:23:15"); -} - #[test] fn test_i_29() { let snippets = Snippet { From d45fbd42ff4eef34f8088a8a4a8dbac1b9af277a Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Sat, 2 Dec 2023 14:38:59 -0700 Subject: [PATCH 097/455] refactor(display_list): Remove `From` impls --- src/display_list/mod.rs | 493 +++++++++++++++++-------------------- src/renderer/stylesheet.rs | 6 + 2 files changed, 235 insertions(+), 264 deletions(-) diff --git a/src/display_list/mod.rs b/src/display_list/mod.rs index da1a05b2..b88b2138 100644 --- a/src/display_list/mod.rs +++ b/src/display_list/mod.rs @@ -45,17 +45,6 @@ pub(crate) struct DisplayList<'a> { pub margin: Option<Margin>, } -impl<'a> From<Vec<DisplayLine<'a>>> for DisplayList<'a> { - fn from(body: Vec<DisplayLine<'a>>) -> DisplayList<'a> { - Self { - body, - anonymized_line_numbers: false, - stylesheet: Stylesheet::default(), - margin: None, - } - } -} - impl<'a> PartialEq for DisplayList<'a> { fn eq(&self, other: &Self) -> bool { self.body == other.body && self.anonymized_line_numbers == other.anonymized_line_numbers @@ -105,12 +94,6 @@ impl<'a> Display for DisplayList<'a> { } } -impl<'a> From<snippet::Snippet<'a>> for DisplayList<'a> { - fn from(snippet: snippet::Snippet<'a>) -> DisplayList<'a> { - Self::new(snippet, Stylesheet::default(), false, None) - } -} - impl<'a> DisplayList<'a> { const ANONYMIZED_LINE_NUM: &'static str = "LL"; const ERROR_TXT: &'static str = "error"; @@ -1229,6 +1212,17 @@ fn is_annotation_empty(annotation: &Annotation<'_>) -> bool { mod tests { use super::*; + const STYLESHEET: Stylesheet = Stylesheet::plain(); + + fn from_display_lines(lines: Vec<DisplayLine<'_>>) -> DisplayList<'_> { + DisplayList { + body: lines, + stylesheet: STYLESHEET, + anonymized_line_numbers: false, + margin: None, + } + } + #[test] fn test_format_title() { let input = snippet::Snippet { @@ -1240,24 +1234,19 @@ mod tests { footer: vec![], slices: vec![], }; - let output = DisplayList { - body: vec![DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Error, - id: Some("E0001"), - label: vec![DisplayTextFragment { - content: "This is a title", - style: DisplayTextStyle::Emphasis, - }], - }, - source_aligned: false, - continuation: false, - })], - stylesheet: Stylesheet::default(), - anonymized_line_numbers: false, - margin: None, - }; - assert_eq!(DisplayList::from(input), output); + let output = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::Error, + id: Some("E0001"), + label: vec![DisplayTextFragment { + content: "This is a title", + style: DisplayTextStyle::Emphasis, + }], + }, + source_aligned: false, + continuation: false, + })]); + assert_eq!(DisplayList::new(input, STYLESHEET, false, None), output); } #[test] @@ -1276,40 +1265,35 @@ mod tests { fold: false, }], }; - let output = DisplayList { - body: vec![ - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }, - DisplayLine::Source { - lineno: Some(5402), - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: line_1, - range: (0, line_1.len()), - }, - }, - DisplayLine::Source { - lineno: Some(5403), - inline_marks: vec![], - line: DisplaySourceLine::Content { - range: (line_1.len() + 1, source.len()), - text: line_2, - }, + let output = from_display_lines(vec![ + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + DisplayLine::Source { + lineno: Some(5402), + inline_marks: vec![], + line: DisplaySourceLine::Content { + text: line_1, + range: (0, line_1.len()), }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, + }, + DisplayLine::Source { + lineno: Some(5403), + inline_marks: vec![], + line: DisplaySourceLine::Content { + range: (line_1.len() + 1, source.len()), + text: line_2, }, - ], - stylesheet: Stylesheet::default(), - anonymized_line_numbers: false, - margin: None, - }; - assert_eq!(DisplayList::from(input), output); + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + ]); + assert_eq!(DisplayList::new(input, STYLESHEET, false, None), output); } #[test] @@ -1338,60 +1322,55 @@ mod tests { }, ], }; - let output = DisplayList { - body: vec![ - DisplayLine::Raw(DisplayRawLine::Origin { - path: "file1.rs", - pos: None, - header_type: DisplayHeaderType::Initial, - }), - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }, - DisplayLine::Source { - lineno: Some(5402), - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: src_0, - range: (0, src_0_len), - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }, - DisplayLine::Raw(DisplayRawLine::Origin { - path: "file2.rs", - pos: None, - header_type: DisplayHeaderType::Continuation, - }), - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }, - DisplayLine::Source { - lineno: Some(2), - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: src_1, - range: (0, src_1_len), - }, + let output = from_display_lines(vec![ + DisplayLine::Raw(DisplayRawLine::Origin { + path: "file1.rs", + pos: None, + header_type: DisplayHeaderType::Initial, + }), + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + DisplayLine::Source { + lineno: Some(5402), + inline_marks: vec![], + line: DisplaySourceLine::Content { + text: src_0, + range: (0, src_0_len), }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + DisplayLine::Raw(DisplayRawLine::Origin { + path: "file2.rs", + pos: None, + header_type: DisplayHeaderType::Continuation, + }), + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + DisplayLine::Source { + lineno: Some(2), + inline_marks: vec![], + line: DisplaySourceLine::Content { + text: src_1, + range: (0, src_1_len), }, - ], - stylesheet: Stylesheet::default(), - anonymized_line_numbers: false, - margin: None, - }; - assert_eq!(DisplayList::from(input), output); + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + ]); + assert_eq!(DisplayList::new(input, STYLESHEET, false, None), output); } #[test] @@ -1416,57 +1395,52 @@ mod tests { fold: false, }], }; - let output = DisplayList { - body: vec![ - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }, - DisplayLine::Source { - lineno: Some(5402), - inline_marks: vec![], - line: DisplaySourceLine::Content { - range: (0, line_1.len()), - text: line_1, - }, + let output = from_display_lines(vec![ + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + DisplayLine::Source { + lineno: Some(5402), + inline_marks: vec![], + line: DisplaySourceLine::Content { + range: (0, line_1.len()), + text: line_1, }, - DisplayLine::Source { - lineno: Some(5403), - inline_marks: vec![], - line: DisplaySourceLine::Content { - range: (line_1.len() + 1, source.len()), - text: line_2, - }, + }, + DisplayLine::Source { + lineno: Some(5403), + inline_marks: vec![], + line: DisplaySourceLine::Content { + range: (line_1.len() + 1, source.len()), + text: line_2, }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Info, - id: None, - label: vec![DisplayTextFragment { - content: "Test annotation", - style: DisplayTextStyle::Regular, - }], - }, - range: (range.0 - (line_1.len() + 1), range.1 - (line_1.len() + 1)), + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Annotation { + annotation: Annotation { annotation_type: DisplayAnnotationType::Info, - annotation_part: DisplayAnnotationPart::Standalone, + id: None, + label: vec![DisplayTextFragment { + content: "Test annotation", + style: DisplayTextStyle::Regular, + }], }, + range: (range.0 - (line_1.len() + 1), range.1 - (line_1.len() + 1)), + annotation_type: DisplayAnnotationType::Info, + annotation_part: DisplayAnnotationPart::Standalone, }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }, - ], - stylesheet: Stylesheet::default(), - anonymized_line_numbers: false, - margin: None, - }; - assert_eq!(DisplayList::from(input), output); + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + ]); + assert_eq!(DisplayList::new(input, STYLESHEET, false, None), output); } #[test] @@ -1480,24 +1454,19 @@ mod tests { }], slices: vec![], }; - let output = DisplayList { - body: vec![DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Error, - id: None, - label: vec![DisplayTextFragment { - content: "This __is__ a title", - style: DisplayTextStyle::Regular, - }], - }, - source_aligned: true, - continuation: false, - })], - stylesheet: Stylesheet::default(), - anonymized_line_numbers: false, - margin: None, - }; - assert_eq!(DisplayList::from(input), output); + let output = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::Error, + id: None, + label: vec![DisplayTextFragment { + content: "This __is__ a title", + style: DisplayTextStyle::Regular, + }], + }, + source_aligned: true, + continuation: false, + })]); + assert_eq!(DisplayList::new(input, STYLESHEET, false, None), output); } #[test] @@ -1520,8 +1489,7 @@ mod tests { fold: false, }], }; - - let _ = DisplayList::from(input); + let _ = DisplayList::new(input, STYLESHEET, false, None); } #[test] @@ -1546,80 +1514,77 @@ mod tests { }], }; - let expected = DisplayList { - body: vec![ - DisplayLine::Raw(DisplayRawLine::Annotation { + let expected = from_display_lines(vec![ + DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::Error, + id: None, + label: vec![DisplayTextFragment { + content: "oops", + style: DisplayTextStyle::Emphasis, + }], + }, + source_aligned: false, + continuation: false, + }), + DisplayLine::Raw(DisplayRawLine::Origin { + path: "<current file>", + pos: Some((2, 8)), + header_type: DisplayHeaderType::Initial, + }), + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + DisplayLine::Source { + lineno: Some(1), + inline_marks: vec![], + line: DisplaySourceLine::Content { + text: "First line", + range: (0, 10), + }, + }, + DisplayLine::Source { + lineno: Some(2), + inline_marks: vec![], + line: DisplaySourceLine::Content { + text: "Second oops line", + range: (12, 28), + }, + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Annotation { annotation: Annotation { - annotation_type: DisplayAnnotationType::Error, + annotation_type: DisplayAnnotationType::None, id: None, label: vec![DisplayTextFragment { content: "oops", - style: DisplayTextStyle::Emphasis, + style: DisplayTextStyle::Regular, }], }, - source_aligned: false, - continuation: false, - }), - DisplayLine::Raw(DisplayRawLine::Origin { - path: "<current file>", - pos: Some((2, 8)), - header_type: DisplayHeaderType::Initial, - }), - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }, - DisplayLine::Source { - lineno: Some(1), - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: "First line", - range: (0, 10), - }, - }, - DisplayLine::Source { - lineno: Some(2), - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: "Second oops line", - range: (12, 28), - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::None, - id: None, - label: vec![DisplayTextFragment { - content: "oops", - style: DisplayTextStyle::Regular, - }], - }, - range: (7, 11), - annotation_type: DisplayAnnotationType::Error, - annotation_part: DisplayAnnotationPart::Standalone, - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, + range: (7, 11), + annotation_type: DisplayAnnotationType::Error, + annotation_part: DisplayAnnotationPart::Standalone, }, - ], - stylesheet: Stylesheet::default(), - anonymized_line_numbers: false, - margin: None, - }; - - assert_eq!(DisplayList::from(snippets), expected); + }, + DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + }, + ]); + assert_eq!( + DisplayList::new(snippets, STYLESHEET, false, None), + expected + ); } #[test] fn test_source_empty() { - let dl = DisplayList::from(vec![DisplayLine::Source { + let dl = from_display_lines(vec![DisplayLine::Source { lineno: None, inline_marks: vec![], line: DisplaySourceLine::Empty, @@ -1630,7 +1595,7 @@ mod tests { #[test] fn test_source_content() { - let dl = DisplayList::from(vec![ + let dl = from_display_lines(vec![ DisplayLine::Source { lineno: Some(56), inline_marks: vec![], @@ -1657,7 +1622,7 @@ mod tests { #[test] fn test_source_annotation_standalone_singleline() { - let dl = DisplayList::from(vec![DisplayLine::Source { + let dl = from_display_lines(vec![DisplayLine::Source { lineno: None, inline_marks: vec![], line: DisplaySourceLine::Annotation { @@ -1680,7 +1645,7 @@ mod tests { #[test] fn test_source_annotation_standalone_multiline() { - let dl = DisplayList::from(vec![ + let dl = from_display_lines(vec![ DisplayLine::Source { lineno: None, inline_marks: vec![], @@ -1725,7 +1690,7 @@ mod tests { #[test] fn test_source_annotation_standalone_multi_annotation() { - let dl = DisplayList::from(vec![ + let dl = from_display_lines(vec![ DisplayLine::Source { lineno: None, inline_marks: vec![], @@ -1835,7 +1800,7 @@ mod tests { #[test] fn test_fold_line() { - let dl = DisplayList::from(vec![ + let dl = from_display_lines(vec![ DisplayLine::Source { lineno: Some(5), inline_marks: vec![], @@ -1865,7 +1830,7 @@ mod tests { #[test] fn test_raw_origin_initial_nopos() { - let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin { + let dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Origin { path: "src/test.rs", pos: None, header_type: DisplayHeaderType::Initial, @@ -1876,7 +1841,7 @@ mod tests { #[test] fn test_raw_origin_initial_pos() { - let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin { + let dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Origin { path: "src/test.rs", pos: Some((23, 15)), header_type: DisplayHeaderType::Initial, @@ -1887,7 +1852,7 @@ mod tests { #[test] fn test_raw_origin_continuation() { - let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin { + let dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Origin { path: "src/test.rs", pos: Some((23, 15)), header_type: DisplayHeaderType::Continuation, @@ -1898,7 +1863,7 @@ mod tests { #[test] fn test_raw_annotation_unaligned() { - let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Annotation { + let dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { annotation_type: DisplayAnnotationType::Error, id: Some("E0001"), @@ -1916,7 +1881,7 @@ mod tests { #[test] fn test_raw_annotation_unaligned_multiline() { - let dl = DisplayList::from(vec![ + let dl = from_display_lines(vec![ DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { annotation_type: DisplayAnnotationType::Warning, @@ -1951,7 +1916,7 @@ mod tests { #[test] fn test_raw_annotation_aligned() { - let dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Annotation { + let dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { annotation_type: DisplayAnnotationType::Error, id: Some("E0001"), @@ -1969,7 +1934,7 @@ mod tests { #[test] fn test_raw_annotation_aligned_multiline() { - let dl = DisplayList::from(vec![ + let dl = from_display_lines(vec![ DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { annotation_type: DisplayAnnotationType::Warning, @@ -2004,7 +1969,7 @@ mod tests { #[test] fn test_different_annotation_types() { - let dl = DisplayList::from(vec![ + let dl = from_display_lines(vec![ DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { annotation_type: DisplayAnnotationType::Note, @@ -2051,7 +2016,7 @@ mod tests { #[test] fn test_inline_marks_empty_line() { - let dl = DisplayList::from(vec![DisplayLine::Source { + let dl = from_display_lines(vec![DisplayLine::Source { lineno: None, inline_marks: vec![DisplayMark { mark_type: DisplayMarkType::AnnotationThrough, @@ -2065,7 +2030,7 @@ mod tests { #[test] fn test_anon_lines() { - let mut dl = DisplayList::from(vec![ + let mut dl = from_display_lines(vec![ DisplayLine::Source { lineno: Some(56), inline_marks: vec![], @@ -2106,7 +2071,7 @@ mod tests { #[test] fn test_raw_origin_initial_pos_anon_lines() { - let mut dl = DisplayList::from(vec![DisplayLine::Raw(DisplayRawLine::Origin { + let mut dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Origin { path: "src/test.rs", pos: Some((23, 15)), header_type: DisplayHeaderType::Initial, diff --git a/src/renderer/stylesheet.rs b/src/renderer/stylesheet.rs index b3dbbc3d..ee1ab937 100644 --- a/src/renderer/stylesheet.rs +++ b/src/renderer/stylesheet.rs @@ -14,6 +14,12 @@ pub(crate) struct Stylesheet { impl Default for Stylesheet { fn default() -> Self { + Self::plain() + } +} + +impl Stylesheet { + pub(crate) const fn plain() -> Self { Self { error: Style::new(), warning: Style::new(), From a9bf3857a4d66e3186870d2375125a86275d2721 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Sat, 2 Dec 2023 14:40:42 -0700 Subject: [PATCH 098/455] refactor(display_list): Take a reference to `StyleSheet` --- src/display_list/mod.rs | 20 ++++++++++---------- src/renderer/mod.rs | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/display_list/mod.rs b/src/display_list/mod.rs index b88b2138..02e14a50 100644 --- a/src/display_list/mod.rs +++ b/src/display_list/mod.rs @@ -40,7 +40,7 @@ use crate::renderer::{stylesheet::Stylesheet, Margin, Style}; /// List of lines to be displayed. pub(crate) struct DisplayList<'a> { pub body: Vec<DisplayLine<'a>>, - pub stylesheet: Stylesheet, + pub stylesheet: &'a Stylesheet, pub anonymized_line_numbers: bool, pub margin: Option<Margin>, } @@ -108,7 +108,7 @@ impl<'a> DisplayList<'a> { footer, slices, }: snippet::Snippet<'a>, - stylesheet: Stylesheet, + stylesheet: &'a Stylesheet, anonymized_line_numbers: bool, margin: Option<Margin>, ) -> DisplayList<'a> { @@ -1217,7 +1217,7 @@ mod tests { fn from_display_lines(lines: Vec<DisplayLine<'_>>) -> DisplayList<'_> { DisplayList { body: lines, - stylesheet: STYLESHEET, + stylesheet: &STYLESHEET, anonymized_line_numbers: false, margin: None, } @@ -1246,7 +1246,7 @@ mod tests { source_aligned: false, continuation: false, })]); - assert_eq!(DisplayList::new(input, STYLESHEET, false, None), output); + assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output); } #[test] @@ -1293,7 +1293,7 @@ mod tests { line: DisplaySourceLine::Empty, }, ]); - assert_eq!(DisplayList::new(input, STYLESHEET, false, None), output); + assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output); } #[test] @@ -1370,7 +1370,7 @@ mod tests { line: DisplaySourceLine::Empty, }, ]); - assert_eq!(DisplayList::new(input, STYLESHEET, false, None), output); + assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output); } #[test] @@ -1440,7 +1440,7 @@ mod tests { line: DisplaySourceLine::Empty, }, ]); - assert_eq!(DisplayList::new(input, STYLESHEET, false, None), output); + assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output); } #[test] @@ -1466,7 +1466,7 @@ mod tests { source_aligned: true, continuation: false, })]); - assert_eq!(DisplayList::new(input, STYLESHEET, false, None), output); + assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output); } #[test] @@ -1489,7 +1489,7 @@ mod tests { fold: false, }], }; - let _ = DisplayList::new(input, STYLESHEET, false, None); + let _ = DisplayList::new(input, &STYLESHEET, false, None); } #[test] @@ -1577,7 +1577,7 @@ mod tests { }, ]); assert_eq!( - DisplayList::new(snippets, STYLESHEET, false, None), + DisplayList::new(snippets, &STYLESHEET, false, None), expected ); } diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 2f033418..69f1cf41 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -95,7 +95,7 @@ impl Renderer { pub fn render<'a>(&'a self, snippet: Snippet<'a>) -> impl Display + 'a { DisplayList::new( snippet, - self.stylesheet, + &self.stylesheet, self.anonymized_line_numbers, self.margin, ) From 748b79263dc59ea3c8604fd9e3f0657d10c76c90 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Sat, 2 Dec 2023 14:46:46 -0700 Subject: [PATCH 099/455] refactor(renderer): Make functions `const` --- src/renderer/mod.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 69f1cf41..bf6bf696 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -17,16 +17,16 @@ pub struct Renderer { impl Renderer { /// No terminal styling - pub fn plain() -> Self { + pub const fn plain() -> Self { Self { anonymized_line_numbers: false, margin: None, - stylesheet: Stylesheet::default(), + stylesheet: Stylesheet::plain(), } } /// Default terminal styling - pub fn styled() -> Self { + pub const fn styled() -> Self { Self { stylesheet: Stylesheet { error: AnsiColor::BrightRed.on_default().effects(Effects::BOLD), @@ -42,52 +42,52 @@ impl Renderer { } } - pub fn anonymized_line_numbers(mut self, anonymized_line_numbers: bool) -> Self { + pub const fn anonymized_line_numbers(mut self, anonymized_line_numbers: bool) -> Self { self.anonymized_line_numbers = anonymized_line_numbers; self } - pub fn margin(mut self, margin: Option<Margin>) -> Self { + pub const fn margin(mut self, margin: Option<Margin>) -> Self { self.margin = margin; self } - pub fn error(mut self, style: Style) -> Self { + pub const fn error(mut self, style: Style) -> Self { self.stylesheet.error = style; self } - pub fn warning(mut self, style: Style) -> Self { + pub const fn warning(mut self, style: Style) -> Self { self.stylesheet.warning = style; self } - pub fn info(mut self, style: Style) -> Self { + pub const fn info(mut self, style: Style) -> Self { self.stylesheet.info = style; self } - pub fn note(mut self, style: Style) -> Self { + pub const fn note(mut self, style: Style) -> Self { self.stylesheet.note = style; self } - pub fn help(mut self, style: Style) -> Self { + pub const fn help(mut self, style: Style) -> Self { self.stylesheet.help = style; self } - pub fn line_no(mut self, style: Style) -> Self { + pub const fn line_no(mut self, style: Style) -> Self { self.stylesheet.line_no = style; self } - pub fn emphasis(mut self, style: Style) -> Self { + pub const fn emphasis(mut self, style: Style) -> Self { self.stylesheet.emphasis = style; self } - pub fn none(mut self, style: Style) -> Self { + pub const fn none(mut self, style: Style) -> Self { self.stylesheet.none = style; self } From b0848f70cdf54b4601bd01d4ae99ce4c63a80be9 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Mon, 4 Dec 2023 16:21:30 -0700 Subject: [PATCH 100/455] chore(renderer): Add doc comments --- src/renderer/mod.rs | 75 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index bf6bf696..ce080d36 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -1,3 +1,37 @@ +//! The renderer for [`Snippet`]s +//! +//! # Example +//! ``` +//! use annotate_snippets::renderer::Renderer; +//! use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet}; +//! let snippet = Snippet { +//! title: Some(Annotation { +//! label: Some("mismatched types"), +//! id: None, +//! annotation_type: AnnotationType::Error, +//! }), +//! footer: vec![], +//! slices: vec![ +//! Slice { +//! source: "Foo", +//! line_start: 51, +//! origin: Some("src/format.rs"), +//! fold: false, +//! annotations: vec![], +//! }, +//! Slice { +//! source: "Faa", +//! line_start: 129, +//! origin: Some("src/display.rs"), +//! fold: false, +//! annotations: vec![], +//! }, +//! ], +//! }; +//! +//! let renderer = Renderer::styled(); +//! println!("{}", renderer.render(snippet)); + mod margin; pub(crate) mod stylesheet; @@ -8,6 +42,7 @@ pub use margin::Margin; use std::fmt::Display; use stylesheet::Stylesheet; +/// A renderer for [`Snippet`]s #[derive(Clone)] pub struct Renderer { anonymized_line_numbers: bool, @@ -42,56 +77,96 @@ impl Renderer { } } + /// Anonymize line numbers + /// + /// This enables (or disables) line number anonymization. When enabled, line numbers are replaced + /// with `LL`. + /// + /// # Example + /// + /// ```text + /// --> $DIR/whitespace-trimming.rs:4:193 + /// | + /// LL | ... let _: () = 42; + /// | ^^ expected (), found integer + /// | + /// ``` pub const fn anonymized_line_numbers(mut self, anonymized_line_numbers: bool) -> Self { self.anonymized_line_numbers = anonymized_line_numbers; self } + /// Set the margin for the output + /// + /// This controls the various margins of the output. + /// + /// # Example + /// + /// ```text + /// error: expected type, found `22` + /// --> examples/footer.rs:29:25 + /// | + /// 26 | ... annotations: vec![SourceAnnotation { + /// | ---------------- info: while parsing this struct + /// ... + /// 29 | ... range: <22, 25>, + /// | ^^ + /// | + /// ``` pub const fn margin(mut self, margin: Option<Margin>) -> Self { self.margin = margin; self } + /// Set the output style for `error` pub const fn error(mut self, style: Style) -> Self { self.stylesheet.error = style; self } + /// Set the output style for `warning` pub const fn warning(mut self, style: Style) -> Self { self.stylesheet.warning = style; self } + /// Set the output style for `info` pub const fn info(mut self, style: Style) -> Self { self.stylesheet.info = style; self } + /// Set the output style for `note` pub const fn note(mut self, style: Style) -> Self { self.stylesheet.note = style; self } + /// Set the output style for `help` pub const fn help(mut self, style: Style) -> Self { self.stylesheet.help = style; self } + /// Set the output style for line numbers pub const fn line_no(mut self, style: Style) -> Self { self.stylesheet.line_no = style; self } + /// Set the output style for emphasis pub const fn emphasis(mut self, style: Style) -> Self { self.stylesheet.emphasis = style; self } + /// Set the output style for none pub const fn none(mut self, style: Style) -> Self { self.stylesheet.none = style; self } + /// Render a snippet into a `Display`able object pub fn render<'a>(&'a self, snippet: Snippet<'a>) -> impl Display + 'a { DisplayList::new( snippet, From a1007ddf2fc6f76e960a4fc01207228e64e9fae7 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 5 Dec 2023 11:39:46 -0700 Subject: [PATCH 101/455] fix!: Move around items in the public api BREAKING CHANGE: This moves `snippet::*` and `Renderer` to root --- benches/simple.rs | 3 +-- examples/expected_type.rs | 3 +-- examples/footer.rs | 3 +-- examples/format.rs | 3 +-- examples/multislice.rs | 3 +-- src/lib.rs | 12 ++++++++---- src/renderer/mod.rs | 3 +-- src/snippet.rs | 2 +- tests/deserialize/mod.rs | 4 +--- tests/fixtures_test.rs | 4 ++-- tests/formatter.rs | 41 +++++++++++++++++++-------------------- 11 files changed, 38 insertions(+), 43 deletions(-) diff --git a/benches/simple.rs b/benches/simple.rs index 8ccd18f7..3a40bbf5 100644 --- a/benches/simple.rs +++ b/benches/simple.rs @@ -4,8 +4,7 @@ extern crate criterion; use criterion::{black_box, Criterion}; -use annotate_snippets::renderer::Renderer; -use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}; +use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation}; fn create_snippet(renderer: Renderer) { let snippet = Snippet { diff --git a/examples/expected_type.rs b/examples/expected_type.rs index 959419c2..bbd1fe64 100644 --- a/examples/expected_type.rs +++ b/examples/expected_type.rs @@ -1,5 +1,4 @@ -use annotate_snippets::renderer::Renderer; -use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}; +use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation}; fn main() { let snippet = Snippet { diff --git a/examples/footer.rs b/examples/footer.rs index 0191c1d6..ca021198 100644 --- a/examples/footer.rs +++ b/examples/footer.rs @@ -1,5 +1,4 @@ -use annotate_snippets::renderer::Renderer; -use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}; +use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation}; fn main() { let snippet = Snippet { diff --git a/examples/format.rs b/examples/format.rs index 7302eefe..41f852ee 100644 --- a/examples/format.rs +++ b/examples/format.rs @@ -1,5 +1,4 @@ -use annotate_snippets::renderer::Renderer; -use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}; +use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation}; fn main() { let snippet = Snippet { diff --git a/examples/multislice.rs b/examples/multislice.rs index dc51d4f5..63ebb650 100644 --- a/examples/multislice.rs +++ b/examples/multislice.rs @@ -1,5 +1,4 @@ -use annotate_snippets::renderer::Renderer; -use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet}; +use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet}; fn main() { let snippet = Snippet { diff --git a/src/lib.rs b/src/lib.rs index 342db23c..5da3575a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,18 +29,22 @@ //! Snippet --> Renderer --> impl Display //! ``` //! -//! The input type - [Snippet](self::snippet) is a structure designed +//! The input type - [Snippet] is a structure designed //! to align with likely output from any parser whose code snippet is to be //! annotated. //! -//! The middle structure - [Renderer](self::renderer) is a structure designed +//! The middle structure - [Renderer] is a structure designed //! to convert a snippet into an internal structure that is designed to store //! the snippet data in a way that is easy to format. -//! [Renderer](self::renderer) also handles the user-configurable formatting +//! [Renderer] also handles the user-configurable formatting //! options, such as color, or margins. //! //! Finally, `impl Display` into a final `String` output. mod display_list; pub mod renderer; -pub mod snippet; +mod snippet; + +#[doc(inline)] +pub use renderer::Renderer; +pub use snippet::*; diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index ce080d36..5f655c89 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -2,8 +2,7 @@ //! //! # Example //! ``` -//! use annotate_snippets::renderer::Renderer; -//! use annotate_snippets::snippet::{Annotation, AnnotationType, Slice, Snippet}; +//! use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet}; //! let snippet = Snippet { //! title: Some(Annotation { //! label: Some("mismatched types"), diff --git a/src/snippet.rs b/src/snippet.rs index c1914c96..02e70cc1 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -3,7 +3,7 @@ //! Example: //! //! ``` -//! use annotate_snippets::snippet::*; +//! use annotate_snippets::*; //! //! Snippet { //! title: Some(Annotation { diff --git a/tests/deserialize/mod.rs b/tests/deserialize/mod.rs index af959cbf..1763005a 100644 --- a/tests/deserialize/mod.rs +++ b/tests/deserialize/mod.rs @@ -1,9 +1,7 @@ use serde::{Deserialize, Deserializer, Serialize}; -use annotate_snippets::renderer::Renderer; use annotate_snippets::{ - renderer::Margin, - snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, + renderer::Margin, Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation, }; #[derive(Deserialize)] diff --git a/tests/fixtures_test.rs b/tests/fixtures_test.rs index 854719ff..063829a6 100644 --- a/tests/fixtures_test.rs +++ b/tests/fixtures_test.rs @@ -2,8 +2,8 @@ mod deserialize; mod diff; use crate::deserialize::Fixture; -use annotate_snippets::renderer::Renderer; -use annotate_snippets::snippet::Snippet; +use annotate_snippets::Renderer; +use annotate_snippets::Snippet; use glob::glob; use std::{error::Error, fs::File, io, io::prelude::*}; diff --git a/tests/formatter.rs b/tests/formatter.rs index 3f85b695..97c7be3b 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -1,23 +1,22 @@ -use annotate_snippets::renderer::Renderer; -use annotate_snippets::snippet::{self, Snippet}; +use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation}; #[test] fn test_i_29() { let snippets = Snippet { - title: Some(snippet::Annotation { + title: Some(Annotation { id: None, label: Some("oops"), - annotation_type: snippet::AnnotationType::Error, + annotation_type: AnnotationType::Error, }), footer: vec![], - slices: vec![snippet::Slice { + slices: vec![Slice { source: "First line\r\nSecond oops line", line_start: 1, origin: Some("<current file>"), - annotations: vec![snippet::SourceAnnotation { + annotations: vec![SourceAnnotation { range: (19, 23), label: "oops", - annotation_type: snippet::AnnotationType::Error, + annotation_type: AnnotationType::Error, }], fold: true, }], @@ -37,14 +36,14 @@ fn test_i_29() { #[test] fn test_point_to_double_width_characters() { let snippets = Snippet { - slices: vec![snippet::Slice { + slices: vec![Slice { source: "こんにちは、世界", line_start: 1, origin: Some("<current file>"), - annotations: vec![snippet::SourceAnnotation { + annotations: vec![SourceAnnotation { range: (6, 8), label: "world", - annotation_type: snippet::AnnotationType::Error, + annotation_type: AnnotationType::Error, }], fold: false, }], @@ -65,14 +64,14 @@ fn test_point_to_double_width_characters() { #[test] fn test_point_to_double_width_characters_across_lines() { let snippets = Snippet { - slices: vec![snippet::Slice { + slices: vec![Slice { source: "おはよう\nございます", line_start: 1, origin: Some("<current file>"), - annotations: vec![snippet::SourceAnnotation { + annotations: vec![SourceAnnotation { range: (2, 8), label: "Good morning", - annotation_type: snippet::AnnotationType::Error, + annotation_type: AnnotationType::Error, }], fold: false, }], @@ -95,20 +94,20 @@ fn test_point_to_double_width_characters_across_lines() { #[test] fn test_point_to_double_width_characters_multiple() { let snippets = Snippet { - slices: vec![snippet::Slice { + slices: vec![Slice { source: "お寿司\n食べたい🍣", line_start: 1, origin: Some("<current file>"), annotations: vec![ - snippet::SourceAnnotation { + SourceAnnotation { range: (0, 3), label: "Sushi1", - annotation_type: snippet::AnnotationType::Error, + annotation_type: AnnotationType::Error, }, - snippet::SourceAnnotation { + SourceAnnotation { range: (6, 8), label: "Sushi2", - annotation_type: snippet::AnnotationType::Note, + annotation_type: AnnotationType::Note, }, ], fold: false, @@ -132,14 +131,14 @@ fn test_point_to_double_width_characters_multiple() { #[test] fn test_point_to_double_width_characters_mixed() { let snippets = Snippet { - slices: vec![snippet::Slice { + slices: vec![Slice { source: "こんにちは、新しいWorld!", line_start: 1, origin: Some("<current file>"), - annotations: vec![snippet::SourceAnnotation { + annotations: vec![SourceAnnotation { range: (6, 14), label: "New world", - annotation_type: snippet::AnnotationType::Error, + annotation_type: AnnotationType::Error, }], fold: false, }], From 71b1eb527b41da38fb6d4a70a1fc53afea4c3785 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 5 Dec 2023 11:58:27 -0700 Subject: [PATCH 102/455] refactor: Move `display_list` under `renderer` --- src/lib.rs | 1 - src/{display_list/mod.rs => renderer/display_list.rs} | 0 src/renderer/mod.rs | 3 ++- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/{display_list/mod.rs => renderer/display_list.rs} (100%) diff --git a/src/lib.rs b/src/lib.rs index 5da3575a..80eac530 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,7 +41,6 @@ //! //! Finally, `impl Display` into a final `String` output. -mod display_list; pub mod renderer; mod snippet; diff --git a/src/display_list/mod.rs b/src/renderer/display_list.rs similarity index 100% rename from src/display_list/mod.rs rename to src/renderer/display_list.rs diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 5f655c89..0e19c08b 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -31,12 +31,13 @@ //! let renderer = Renderer::styled(); //! println!("{}", renderer.render(snippet)); +mod display_list; mod margin; pub(crate) mod stylesheet; -use crate::display_list::DisplayList; use crate::snippet::Snippet; pub use anstyle::*; +use display_list::DisplayList; pub use margin::Margin; use std::fmt::Display; use stylesheet::Stylesheet; From 5419c9c4bad72b3e9a5123d83ad9a02d415fff8d Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 12 Dec 2023 13:53:10 -0700 Subject: [PATCH 103/455] chore: Update CHANGELOG.md for `0.10.0` --- CHANGELOG.md | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1ab9621..0e55c8d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,34 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] - ReleaseDate - + +## [0.10.0] - December 12, 2023 + +### Added + +- `Renderer` is now used for displaying a `Snippet` [#67](https://github.com/rust-lang/annotate-snippets-rs/pull/67/commits/9076cbf66336e5137b47dc7a52df2999b6c82598) + - `Renderer` also controls the color scheme and formatting of the snippet + +### Changed + +- Moved everything in the `snippet` to be in the crate root [#67](https://github.com/rust-lang/annotate-snippets-rs/pull/67/commits/a1007ddf2fc6f76e960a4fc01207228e64e9fae7) + +### Breaking Changes + +- `Renderer` now controls the color scheme and formatting of `Snippet`s [#67](https://github.com/rust-lang/annotate-snippets-rs/pull/67/commits/d0c65b26493d60f86a82c5919ef736b35808c23a) +- Removed the `Style` and `Stylesheet` traits, as color is controlled by `Renderer` [#67](https://github.com/rust-lang/annotate-snippets-rs/pull/67/commits/4affdfb50ea0670d85e52737c082c03f89ae8ada) +- Replaced [`yansi-term`](https://crates.io/crates/yansi-term) with [`anstyle`](https://crates.io/crates/anstyle) [#67](https://github.com/rust-lang/annotate-snippets-rs/pull/67/commits/dfd4e87d6f31ec50d29af26d7310cff5e66ca978) + - `anstyle` is designed primarily to exist in public APIs for interoperability + - `anstyle` is re-exported under `annotate_snippets::renderer` +- Removed the `color` feature in favor of `Renderer::plain()` [#67](https://github.com/rust-lang/annotate-snippets-rs/pull/67/commits/dfd4e87d6f31ec50d29af26d7310cff5e66ca978) +- Moved `Margin` to `renderer` module [#67](https://github.com/rust-lang/annotate-snippets-rs/pull/67/commits/79f657ea252c3c0ce55fa69894ee520f8820b4bf) +- Made the `display_list` module private [#67](https://github.com/rust-lang/annotate-snippets-rs/pull/67/commits/da45f4858af3ec4c0d792ecc40225e27fdd2bac8) + +### Compatibility + +- Changed the edition to `2021` [#61](https://github.com/rust-lang/annotate-snippets-rs/pull/61) +- Set the minimum supported Rust version to `1.70.0` [#61](https://github.com/rust-lang/annotate-snippets-rs/pull/61) + ## [0.9.2] - October 30, 2023 - Remove parsing of __ in title strings, fixes (#53) @@ -46,7 +73,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Update the syntax to Rust 2018 idioms. (#4) <!-- next-url --> -[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.9.2...HEAD +[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.0...HEAD +[0.10.0]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.9.2...0.10.0 [0.9.2]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.9.1...0.9.2 [0.9.1]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.9.0...0.9.1 [0.9.0]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.8.0...0.9.0 From 17a0d37c5c398a5654567f7a7b9308c8b34d14c7 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 12 Dec 2023 13:55:46 -0700 Subject: [PATCH 104/455] chore: Update version to `0.10.0` --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0d9659ec..149d5c5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,7 +19,7 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "annotate-snippets" -version = "0.9.2" +version = "0.10.0" dependencies = [ "anstyle", "criterion", diff --git a/Cargo.toml b/Cargo.toml index ec39e99d..51e52a05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "annotate-snippets" -version = "0.9.2" +version = "0.10.0" edition = "2021" rust-version = "1.70" # MSRV authors = ["Zibi Braniecki <gandalf@mozilla.com>"] From 7aee27897ee6391257016a0831b0d17f7ec741ba Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 12 Dec 2023 15:13:03 -0700 Subject: [PATCH 105/455] chore: Update `README.md` example to use `Renderer` --- README.md | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 2e5a20fd..f1b03522 100644 --- a/README.md +++ b/README.md @@ -33,10 +33,7 @@ Usage ----- ```rust -use annotate_snippets::{ - display_list::{DisplayList, FormatOptions}, - snippet::{Annotation, AnnotationType, Slice, Snippet, SourceAnnotation}, -}; +use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation}; fn main() { let snippet = Snippet { @@ -58,7 +55,7 @@ fn main() { SourceAnnotation { label: "", annotation_type: AnnotationType::Error, - range: (187, 189), + range: (193, 195), }, SourceAnnotation { label: "while parsing this struct", @@ -67,14 +64,10 @@ fn main() { }, ], }], - opt: FormatOptions { - color: true, - ..Default::default() - }, }; - let dl = DisplayList::from(snippet); - println!("{}", dl); + let renderer = Renderer::plain(); + println!("{}", renderer.render(snippet)); } ``` From a6a3a0ef43f70ad86c7ce7ca33f9ee0f5840bd51 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 29 Dec 2023 05:30:13 +0000 Subject: [PATCH 106/455] chore(deps): update msrv to v1.73 --- .github/workflows/ci.yml | 6 +++--- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3a3d8128..efa54a0e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,7 +49,7 @@ jobs: - name: No-default features run: cargo test --workspace --no-default-features msrv: - name: "Check MSRV: 1.70" # MSRV + name: "Check MSRV: 1.73" # MSRV runs-on: ubuntu-latest steps: - name: Checkout repository @@ -57,7 +57,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.70" # MSRV + toolchain: "1.73" # MSRV - uses: Swatinem/rust-cache@v2 - name: Default features run: cargo check --workspace --all-targets @@ -119,7 +119,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.70" # MSRV + toolchain: "1.73" # MSRV components: clippy - uses: Swatinem/rust-cache@v2 - name: Install SARIF tools diff --git a/Cargo.toml b/Cargo.toml index 51e52a05..2cdd75e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "annotate-snippets" version = "0.10.0" edition = "2021" -rust-version = "1.70" # MSRV +rust-version = "1.73" # MSRV authors = ["Zibi Braniecki <gandalf@mozilla.com>"] description = "Library for building code annotations" license = "Apache-2.0/MIT" From 69fa0268a44a9859c4db98a6692369c0e8719146 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 00:59:55 +0000 Subject: [PATCH 107/455] chore(deps): update actions/setup-python action to v5 --- .github/workflows/pre-commit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 80447507..95514075 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -19,5 +19,5 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 - uses: pre-commit/action@v3.0.0 From 82c9aa7bddef20e9705d3d404403e59b2cd7e8e1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 00:59:58 +0000 Subject: [PATCH 108/455] chore(deps): update github/codeql-action action to v3 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 19efcf9f..1b09d217 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -134,7 +134,7 @@ jobs: | sarif-fmt continue-on-error: true - name: Upload - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: clippy-results.sarif wait-for-processing: true From a4c062667d3b0c94928d285052795637de0a7227 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Tue, 2 Jan 2024 09:56:35 -0600 Subject: [PATCH 109/455] chore: Make renovate commits to match --- .github/renovate.json5 | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 3119c425..e5a5de0f 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -3,6 +3,7 @@ 'before 5am on the first day of the month', ], semanticCommits: 'enabled', + commitMessageLowerCase: 'never', configMigration: true, dependencyDashboard: true, customManagers: [ From 929a279ec4fbd97f3d828369aa0e5ba697f2de00 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 2 Jan 2024 14:14:52 -0700 Subject: [PATCH 110/455] chore: Apply clippy's suggestions --- src/renderer/display_list.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index 02e14a50..e7784db9 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -766,7 +766,7 @@ fn format_slice( has_footer: bool, margin: Option<Margin>, ) -> Vec<DisplayLine<'_>> { - let main_range = slice.annotations.get(0).map(|x| x.range.0); + let main_range = slice.annotations.first().map(|x| x.range.0); let origin = slice.origin; let need_empty_header = origin.is_some() || is_first; let mut body = format_body(slice, need_empty_header, has_footer, margin); From 61250a36135d0032ceda4316b43d3a62d8b07643 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 3 Jan 2024 08:27:59 -0600 Subject: [PATCH 111/455] chore(ci): Optimize CI runs --- .github/workflows/ci.yml | 2 +- .github/workflows/rust-next.yml | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1b09d217..943becfb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,7 @@ jobs: toolchain: ${{ matrix.rust }} - uses: Swatinem/rust-cache@v2 - name: Build - run: cargo test --no-run --workspace --all-features + run: cargo test --workspace --no-run - name: Default features run: cargo test --workspace - name: All features diff --git a/.github/workflows/rust-next.yml b/.github/workflows/rust-next.yml index d8c2d257..43b4ec8f 100644 --- a/.github/workflows/rust-next.yml +++ b/.github/workflows/rust-next.yml @@ -32,6 +32,8 @@ jobs: with: toolchain: ${{ matrix.rust }} - uses: Swatinem/rust-cache@v2 + - name: Build + run: cargo test --workspace --no-run - name: Default features run: cargo test --workspace - name: All features @@ -51,9 +53,11 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: Update dependencues run: cargo update + - name: Build + run: cargo test --workspace --no-run - name: Default features - run: cargo test --workspace --all-targets + run: cargo test --workspace - name: All features - run: cargo test --workspace --all-targets --all-features + run: cargo test --workspace --all-features - name: No-default features - run: cargo test --workspace --all-targets --no-default-features + run: cargo test --workspace --no-default-features From 49ef459a49f9294b7c6947611c2b2524713d31bf Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 2 Jan 2024 14:07:15 -0700 Subject: [PATCH 112/455] fix: Match rustc's colors --- src/renderer/mod.rs | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 0e19c08b..be81ebcc 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -62,15 +62,30 @@ impl Renderer { /// Default terminal styling pub const fn styled() -> Self { + const BRIGHT_BLUE: Style = if cfg!(windows) { + AnsiColor::BrightCyan.on_default() + } else { + AnsiColor::BrightBlue.on_default() + }; Self { stylesheet: Stylesheet { error: AnsiColor::BrightRed.on_default().effects(Effects::BOLD), - warning: AnsiColor::BrightYellow.on_default().effects(Effects::BOLD), - info: AnsiColor::BrightBlue.on_default().effects(Effects::BOLD), - note: Style::new().effects(Effects::BOLD), + warning: if cfg!(windows) { + AnsiColor::BrightYellow.on_default() + } else { + AnsiColor::Yellow.on_default() + } + .effects(Effects::BOLD), + info: BRIGHT_BLUE.effects(Effects::BOLD), + note: AnsiColor::BrightGreen.on_default().effects(Effects::BOLD), help: AnsiColor::BrightCyan.on_default().effects(Effects::BOLD), - line_no: AnsiColor::BrightBlue.on_default().effects(Effects::BOLD), - emphasis: Style::new().effects(Effects::BOLD), + line_no: BRIGHT_BLUE.effects(Effects::BOLD), + emphasis: if cfg!(windows) { + AnsiColor::BrightWhite.on_default() + } else { + Style::new() + } + .effects(Effects::BOLD), none: Style::new(), }, ..Self::plain() From bc5398f5f54e70a50deb0e7c2bb67cbff29a5ed8 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 4 Jan 2024 14:17:11 -0700 Subject: [PATCH 113/455] fix: Allow highlighting one past end --- src/renderer/display_list.rs | 7 ++++--- tests/fixtures/no-color/one_past.toml | 12 ++++++++++++ tests/fixtures/no-color/one_past.txt | 6 ++++++ 3 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 tests/fixtures/no-color/one_past.toml create mode 100644 tests/fixtures/no-color/one_past.txt diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index e7784db9..f283e522 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -934,14 +934,15 @@ fn format_body( ) -> Vec<DisplayLine<'_>> { let source_len = slice.source.chars().count(); if let Some(bigger) = slice.annotations.iter().find_map(|x| { - if source_len < x.range.1 { + // Allow highlighting one past the last character in the source. + if source_len + 1 < x.range.1 { Some(x.range) } else { None } }) { panic!( - "SourceAnnotation range `{:?}` is bigger than source length `{}`", + "SourceAnnotation range `{:?}` is beyond the end of buffer `{}`", bigger, source_len ) } @@ -1479,7 +1480,7 @@ mod tests { footer: vec![], slices: vec![snippet::Slice { annotations: vec![snippet::SourceAnnotation { - range: (0, source.len() + 1), + range: (0, source.len() + 2), label, annotation_type: snippet::AnnotationType::Error, }], diff --git a/tests/fixtures/no-color/one_past.toml b/tests/fixtures/no-color/one_past.toml new file mode 100644 index 00000000..62d1d42c --- /dev/null +++ b/tests/fixtures/no-color/one_past.toml @@ -0,0 +1,12 @@ +[snippet.title] +label = "expected `.`, `=`" +annotation_type = "Error" + +[[snippet.slices]] +source = "asdf" +line_start = 1 +origin = "Cargo.toml" +[[snippet.slices.annotations]] +label = "" +annotation_type = "Error" +range = [4, 5] diff --git a/tests/fixtures/no-color/one_past.txt b/tests/fixtures/no-color/one_past.txt new file mode 100644 index 00000000..7f255b88 --- /dev/null +++ b/tests/fixtures/no-color/one_past.txt @@ -0,0 +1,6 @@ +error: expected `.`, `=` + --> Cargo.toml:1:5 + | +1 | asdf + | ^ + | From e400fcbeabf72c5518f39d07a52cf9f78b387768 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 4 Jan 2024 16:26:35 -0600 Subject: [PATCH 114/455] chore: Add release process --- CHANGELOG.md | 1 + Cargo.toml | 10 ++++++++++ release.toml | 1 + 3 files changed, 12 insertions(+) create mode 100644 release.toml diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e55c8d7..45fa915a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +<!-- next-header --> ## [Unreleased] - ReleaseDate ## [0.10.0] - December 12, 2023 diff --git a/Cargo.toml b/Cargo.toml index 2cdd75e2..16d8cf99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,16 @@ repository = "https://github.com/rust-lang/annotate-snippets-rs" readme = "README.md" keywords = ["code", "analysis", "ascii", "errors", "debug"] +[package.metadata.release] +tag-name = "{{version}}" +pre-release-replacements = [ + {file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1}, + {file="CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}", exactly=1}, + {file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1}, + {file="CHANGELOG.md", search="<!-- next-header -->", replace="<!-- next-header -->\n## [Unreleased] - ReleaseDate\n", exactly=1}, + {file="CHANGELOG.md", search="<!-- next-url -->", replace="<!-- next-url -->\n[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/{{tag_name}}...HEAD", exactly=1}, +] + [badges] maintenance = { status = "actively-developed" } diff --git a/release.toml b/release.toml new file mode 100644 index 00000000..dac7b6ba --- /dev/null +++ b/release.toml @@ -0,0 +1 @@ +allow-branch = ["master"] From f285fc97a1bcba269ca25f84f994d325a4ae6a79 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 4 Jan 2024 15:35:35 -0700 Subject: [PATCH 115/455] chore: Update CHANGELOG.md --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e55c8d7..fe12b3d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] - ReleaseDate +### Fixed + +- Match `rustc`'s colors [#73](https://github.com/rust-lang/annotate-snippets-rs/pull/73) +- Allow highlighting one past the end of `source` [#74](https://github.com/rust-lang/annotate-snippets-rs/pull/74) + +### Compatibility + +- Set the minimum supported Rust version to `1.73.0` [#71](https://github.com/rust-lang/annotate-snippets-rs/pull/71) + ## [0.10.0] - December 12, 2023 ### Added From b6d8d79f2a447fb7f45baa7a64727f9af81fb84b Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 4 Jan 2024 15:46:23 -0700 Subject: [PATCH 116/455] chore: Release annotate-snippets version 0.10.1 --- CHANGELOG.md | 5 ++++- Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 963d58a4..b6605193 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). <!-- next-header --> ## [Unreleased] - ReleaseDate +## [0.10.1] - 2024-01-04 + ### Fixed - Match `rustc`'s colors [#73](https://github.com/rust-lang/annotate-snippets-rs/pull/73) @@ -83,7 +85,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Update the syntax to Rust 2018 idioms. (#4) <!-- next-url --> -[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.0...HEAD +[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.1...HEAD +[0.10.1]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.0...0.10.1 [0.10.0]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.9.2...0.10.0 [0.9.2]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.9.1...0.9.2 [0.9.1]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.9.0...0.9.1 diff --git a/Cargo.lock b/Cargo.lock index 149d5c5e..221841ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,7 +19,7 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "annotate-snippets" -version = "0.10.0" +version = "0.10.1" dependencies = [ "anstyle", "criterion", diff --git a/Cargo.toml b/Cargo.toml index 16d8cf99..a39fd9da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "annotate-snippets" -version = "0.10.0" +version = "0.10.1" edition = "2021" rust-version = "1.73" # MSRV authors = ["Zibi Braniecki <gandalf@mozilla.com>"] From e819db4af62e231bcedd976faa488b4e6f46f312 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 18 Jan 2024 09:22:25 -0600 Subject: [PATCH 117/455] chore(ci): Cancel prior CI runs --- .github/workflows/audit.yml | 4 ++++ .github/workflows/ci.yml | 4 ++++ .github/workflows/committed.yml | 4 ++++ .github/workflows/pre-commit.yml | 4 ++++ .github/workflows/rust-next.yml | 4 ++++ .github/workflows/spelling.yml | 4 ++++ 6 files changed, 24 insertions(+) diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index ccee1fef..07c70eeb 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -17,6 +17,10 @@ env: CARGO_TERM_COLOR: always CLICOLOR: 1 +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + jobs: security_audit: permissions: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 943becfb..2bbd5a57 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,6 +14,10 @@ env: CARGO_TERM_COLOR: always CLICOLOR: 1 +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + jobs: ci: permissions: diff --git a/.github/workflows/committed.yml b/.github/workflows/committed.yml index 04625584..e7a50fbb 100644 --- a/.github/workflows/committed.yml +++ b/.github/workflows/committed.yml @@ -11,6 +11,10 @@ env: CARGO_TERM_COLOR: always CLICOLOR: 1 +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + jobs: committed: name: Lint Commits diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 95514075..7d773285 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -12,6 +12,10 @@ env: CARGO_TERM_COLOR: always CLICOLOR: 1 +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + jobs: pre-commit: permissions: diff --git a/.github/workflows/rust-next.yml b/.github/workflows/rust-next.yml index 43b4ec8f..f0febc9f 100644 --- a/.github/workflows/rust-next.yml +++ b/.github/workflows/rust-next.yml @@ -12,6 +12,10 @@ env: CARGO_TERM_COLOR: always CLICOLOR: 1 +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + jobs: test: name: Test diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml index 12f75859..8e58d9ec 100644 --- a/.github/workflows/spelling.yml +++ b/.github/workflows/spelling.yml @@ -10,6 +10,10 @@ env: CARGO_TERM_COLOR: always CLICOLOR: 1 +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + jobs: spelling: name: Spell Check with Typos From 0b029063fe83d818e3a79819b88bee8b8314f752 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 24 Jan 2024 08:40:56 -0600 Subject: [PATCH 118/455] chore(ci): Be explicit in renovate updates --- .github/renovate.json5 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index e5a5de0f..32d3628d 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -73,6 +73,7 @@ matchCurrentVersion: '>=1.0.0', matchUpdateTypes: [ 'minor', + 'patch', ], enabled: false, }, @@ -100,6 +101,7 @@ matchCurrentVersion: '>=1.0.0', matchUpdateTypes: [ 'minor', + 'patch', ], automerge: true, groupName: 'compatible (dev)', From 131de55d50284af7a54287d34699347b74d75709 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 31 Jan 2024 12:07:12 -0600 Subject: [PATCH 119/455] chore(ci): Add m1 runners See https://github.blog/changelog/2024-01-30-github-actions-introducing-the-new-m1-macos-runner-available-to-open-source/ --- .github/workflows/ci.yml | 2 +- .github/workflows/rust-next.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2bbd5a57..a8826b05 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: name: Test strategy: matrix: - os: ["ubuntu-latest", "windows-latest", "macos-latest"] + os: ["ubuntu-latest", "windows-latest", "macos-latest", "macos-14"] rust: ["stable"] continue-on-error: ${{ matrix.rust != 'stable' }} runs-on: ${{ matrix.os }} diff --git a/.github/workflows/rust-next.yml b/.github/workflows/rust-next.yml index f0febc9f..49e5d8c3 100644 --- a/.github/workflows/rust-next.yml +++ b/.github/workflows/rust-next.yml @@ -21,7 +21,7 @@ jobs: name: Test strategy: matrix: - os: ["ubuntu-latest", "windows-latest", "macos-latest"] + os: ["ubuntu-latest", "windows-latest", "macos-latest", "macos-14"] rust: ["stable", "beta"] include: - os: ubuntu-latest From 1293b88a110e2448512407a6d5d48e31684cc327 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 1 Feb 2024 00:35:37 +0000 Subject: [PATCH 120/455] chore(deps): update rust crate serde to 1.0.196 --- Cargo.lock | 20 ++++++++++---------- Cargo.toml | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 221841ad..6261065f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -346,18 +346,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -447,18 +447,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.192" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.192" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" dependencies = [ "proc-macro2", "quote", @@ -478,9 +478,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.39" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index a39fd9da..77a251a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ unicode-width = "0.1.11" criterion = "0.5.1" difference = "2.0.0" glob = "0.3.1" -serde = { version = "1.0.192", features = ["derive"] } +serde = { version = "1.0.196", features = ["derive"] } toml = "0.5.11" [[bench]] From 9a5af5c8d21d549a7eb785343ae055d931952075 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 8 Feb 2024 07:45:48 -0600 Subject: [PATCH 121/455] chore(ci): Only check intel mac on schedule --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8826b05..af065d56 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: name: Test strategy: matrix: - os: ["ubuntu-latest", "windows-latest", "macos-latest", "macos-14"] + os: ["ubuntu-latest", "windows-latest", "macos-14"] rust: ["stable"] continue-on-error: ${{ matrix.rust != 'stable' }} runs-on: ${{ matrix.os }} From da56001fd6cabdb744f25b021c9efd230472f671 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 8 Feb 2024 07:48:29 -0600 Subject: [PATCH 122/455] chore(ci): Gather coverage --- .github/workflows/ci.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index af065d56..7849a735 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -144,3 +144,22 @@ jobs: wait-for-processing: true - name: Report status run: cargo clippy --workspace --all-features --all-targets -- -D warnings --allow deprecated + coverage: + name: Coverage + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: "1.75" # STABLE + - uses: Swatinem/rust-cache@v2 + - name: Install cargo-tarpaulin + run: cargo install cargo-tarpaulin + - name: Gather coverage + run: cargo tarpaulin --output-dir coverage --out lcov + - name: Publish to Coveralls + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} From 1313256db3e9a31a3e0647abaacc9a8e4edb51b1 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 8 Feb 2024 09:07:52 -0600 Subject: [PATCH 123/455] chore(ci): Use latest for coverage --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7849a735..2e6597e9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -153,7 +153,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.75" # STABLE + toolchain: stable - uses: Swatinem/rust-cache@v2 - name: Install cargo-tarpaulin run: cargo install cargo-tarpaulin From 51a98a25b6c30dd2fbdd74795432006525d1d84a Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 15 Feb 2024 09:57:26 -0600 Subject: [PATCH 124/455] chore(ci): Defer to package.rust-version for clippy --- .clippy.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/.clippy.toml b/.clippy.toml index 090e2bec..293c14f3 100644 --- a/.clippy.toml +++ b/.clippy.toml @@ -1,4 +1,3 @@ -msrv = "1.65.0" # MSRV warn-on-all-wildcard-imports = true allow-expect-in-tests = true allow-unwrap-in-tests = true From 4db293d99b81e9c7da8fb030b1471e4e96dc91ef Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 15 Feb 2024 09:58:01 -0600 Subject: [PATCH 125/455] chore(ci): Only verify MSRV for published packages --- .github/workflows/ci.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e6597e9..03a4fc0b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,7 +53,7 @@ jobs: - name: No-default features run: cargo test --workspace --no-default-features msrv: - name: "Check MSRV: 1.65.0" + name: "Check MSRV" runs-on: ubuntu-latest steps: - name: Checkout repository @@ -61,14 +61,15 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.65.0" # MSRV + toolchain: stable - uses: Swatinem/rust-cache@v2 + - uses: taiki-e/install-action@cargo-hack - name: Default features - run: cargo check --workspace --all-targets + run: cargo hack check --rust-version --ignore-private --workspace --all-targets - name: All features - run: cargo check --workspace --all-targets --all-features + run: cargo hack check --rust-version --ignore-private --workspace --all-targets --all-features - name: No-default features - run: cargo check --workspace --all-targets --no-default-features + run: cargo hack check --rust-version --ignore-private --workspace --all-targets --no-default-features lockfile: runs-on: ubuntu-latest steps: From 779496bb002837bf4e115ae3a33e7c819abdf293 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 15 Feb 2024 10:03:14 -0600 Subject: [PATCH 126/455] chore(ci): Run the latest clippy --- .github/renovate.json5 | 37 +++++++++++++++++++++++++++++++++++-- .github/workflows/ci.yml | 2 +- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 32d3628d..06c1d639 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -21,7 +21,25 @@ 'MSRV.*?(?<currentValue>\\d+\\.\\d+(\\.\\d+)?)', '(?<currentValue>\\d+\\.\\d+(\\.\\d+)?).*?MSRV', ], - depNameTemplate: 'rust', + depNameTemplate: 'MSRV', + packageNameTemplate: 'rust-lang/rust', + datasourceTemplate: 'github-releases', + }, + { + customType: 'regex', + fileMatch: [ + '^rust-toolchain\\.toml$', + 'Cargo.toml$', + 'clippy.toml$', + '\\.clippy.toml$', + '^\\.github/workflows/ci.yml$', + '^\\.github/workflows/rust-next.yml$', + ], + matchStrings: [ + 'STABLE.*?(?<currentValue>\\d+\\.\\d+(\\.\\d+)?)', + '(?<currentValue>\\d+\\.\\d+(\\.\\d+)?).*?STABLE', + ], + depNameTemplate: 'STABLE', packageNameTemplate: 'rust-lang/rust', datasourceTemplate: 'github-releases', }, @@ -33,7 +51,7 @@ 'custom.regex', ], matchPackageNames: [ - 'rust', + 'MSRV', ], minimumReleaseAge: '336 days', // 8 releases * 6 weeks per release * 7 days per week internalChecksFilter: 'strict', @@ -41,6 +59,21 @@ schedule: [ '* * * * *', ], + groupName: 'rust-version', + }, + { + commitMessageTopic: 'STABLE', + matchManagers: [ + 'custom.regex', + ], + matchPackageNames: [ + 'STABLE', + ], + extractVersion: '^(?<version>\\d+\\.\\d+)', // Drop the patch version + schedule: [ + '* * * * *', + ], + groupName: 'rust-version', }, // Goals: // - Keep version reqs low, ignoring compatible normal/build dependencies diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 03a4fc0b..1c18ca65 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -124,7 +124,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.65.0" # MSRV + toolchain: "1.76" # STABLE components: clippy - uses: Swatinem/rust-cache@v2 - name: Install SARIF tools From c977df514987a625772ca04df9fc100ba8b7576f Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Tue, 20 Feb 2024 20:22:05 -0600 Subject: [PATCH 127/455] chore(ci): Prevent cargo-hack from blowing away our lockfile See taiki-e/cargo-hack#234 --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1c18ca65..12d398cd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,11 +65,11 @@ jobs: - uses: Swatinem/rust-cache@v2 - uses: taiki-e/install-action@cargo-hack - name: Default features - run: cargo hack check --rust-version --ignore-private --workspace --all-targets + run: cargo hack check --locked --rust-version --ignore-private --workspace --all-targets - name: All features - run: cargo hack check --rust-version --ignore-private --workspace --all-targets --all-features + run: cargo hack check --locked --rust-version --ignore-private --workspace --all-targets --all-features - name: No-default features - run: cargo hack check --rust-version --ignore-private --workspace --all-targets --no-default-features + run: cargo hack check --locked --rust-version --ignore-private --workspace --all-targets --no-default-features lockfile: runs-on: ubuntu-latest steps: From 8a29fa8b4d7ac4734e71a90bb17783f55465da22 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 29 Feb 2024 08:56:51 -0700 Subject: [PATCH 128/455] feat: Add feature for testing colored output easier --- Cargo.toml | 1 + src/lib.rs | 9 +++++++++ src/renderer/mod.rs | 10 +++++++--- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a39fd9da..45744cd3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,3 +40,4 @@ harness = false [features] default = [] +testing-colors = [] diff --git a/src/lib.rs b/src/lib.rs index 80eac530..03ffbb64 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,6 +40,15 @@ //! options, such as color, or margins. //! //! Finally, `impl Display` into a final `String` output. +//! +//! # features +//! - `testing-colors` - Makes [Renderer::styled] colors OS independent, which +//! allows for easier testing when testing colored output. It should be added as +//! a feature in `[dev-dependencies]`, which can be done with the following command: +//! ```text +//! cargo add annotate-snippets --dev --feature testing-colors +//! ``` +//! pub mod renderer; mod snippet; diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index be81ebcc..b6108ce7 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -61,8 +61,12 @@ impl Renderer { } /// Default terminal styling + /// + /// # Note + /// When testing styled terminal output, see the [`testing-colors` feature](crate#features) pub const fn styled() -> Self { - const BRIGHT_BLUE: Style = if cfg!(windows) { + const USE_WINDOWS_COLORS: bool = cfg!(windows) && !cfg!(feature = "testing-colors"); + const BRIGHT_BLUE: Style = if USE_WINDOWS_COLORS { AnsiColor::BrightCyan.on_default() } else { AnsiColor::BrightBlue.on_default() @@ -70,7 +74,7 @@ impl Renderer { Self { stylesheet: Stylesheet { error: AnsiColor::BrightRed.on_default().effects(Effects::BOLD), - warning: if cfg!(windows) { + warning: if USE_WINDOWS_COLORS { AnsiColor::BrightYellow.on_default() } else { AnsiColor::Yellow.on_default() @@ -80,7 +84,7 @@ impl Renderer { note: AnsiColor::BrightGreen.on_default().effects(Effects::BOLD), help: AnsiColor::BrightCyan.on_default().effects(Effects::BOLD), line_no: BRIGHT_BLUE.effects(Effects::BOLD), - emphasis: if cfg!(windows) { + emphasis: if USE_WINDOWS_COLORS { AnsiColor::BrightWhite.on_default() } else { Style::new() From 6dab14f3c1014c71975d2e92e905ee55b87f6cf5 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 29 Feb 2024 14:54:00 -0700 Subject: [PATCH 129/455] chore: Update CHANGELOG.md --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6605193..f2ffa6f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). <!-- next-header --> + ## [Unreleased] - ReleaseDate +### Added + +- Added `testing-colors` feature to remove platform-specific colors when testing + [#82](https://github.com/rust-lang/annotate-snippets-rs/pull/82) + ## [0.10.1] - 2024-01-04 ### Fixed From 9c373d4ab2545effe772169c502f6a1bcf019113 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 29 Feb 2024 14:56:51 -0700 Subject: [PATCH 130/455] chore: Release annotate-snippets version 0.10.2 --- CHANGELOG.md | 7 +++++-- Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2ffa6f6..0f460225 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). <!-- next-header --> - ## [Unreleased] - ReleaseDate + +## [0.10.2] - 2024-02-29 + ### Added - Added `testing-colors` feature to remove platform-specific colors when testing @@ -91,7 +93,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Update the syntax to Rust 2018 idioms. (#4) <!-- next-url --> -[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.1...HEAD +[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.2...HEAD +[0.10.2]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.1...0.10.2 [0.10.1]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.0...0.10.1 [0.10.0]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.9.2...0.10.0 [0.9.2]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.9.1...0.9.2 diff --git a/Cargo.lock b/Cargo.lock index 6261065f..86766fc3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,7 +19,7 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "annotate-snippets" -version = "0.10.1" +version = "0.10.2" dependencies = [ "anstyle", "criterion", diff --git a/Cargo.toml b/Cargo.toml index 650d8eb7..05a34828 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "annotate-snippets" -version = "0.10.1" +version = "0.10.2" edition = "2021" rust-version = "1.73" # MSRV authors = ["Zibi Braniecki <gandalf@mozilla.com>"] From 7846c5130e5459ce452bd4fdb17373d83f45dff3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 1 Mar 2024 00:30:14 +0000 Subject: [PATCH 131/455] chore(deps): Update pre-commit/action action to v3.0.1 --- .github/workflows/pre-commit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 7d773285..1b000abf 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -24,4 +24,4 @@ jobs: steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 - - uses: pre-commit/action@v3.0.0 + - uses: pre-commit/action@v3.0.1 From 4171d18ca006ec0f99fcf2dcfb1d93d1f6faea96 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 1 Mar 2024 00:40:16 +0000 Subject: [PATCH 132/455] chore(deps): update rust crate serde to 1.0.197 --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 86766fc3..53b8e3c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -447,18 +447,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.196" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.196" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 05a34828..3f5bf82b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ unicode-width = "0.1.11" criterion = "0.5.1" difference = "2.0.0" glob = "0.3.1" -serde = { version = "1.0.196", features = ["derive"] } +serde = { version = "1.0.197", features = ["derive"] } toml = "0.5.11" [[bench]] From 2ed735f9cbb5b27b36ed0b28eda7db58d8054df1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 1 Mar 2024 04:54:49 +0000 Subject: [PATCH 133/455] chore(deps): update pre-commit/action action to v3.0.1 --- .github/workflows/pre-commit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 80447507..9a5c7634 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -20,4 +20,4 @@ jobs: steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v4 - - uses: pre-commit/action@v3.0.0 + - uses: pre-commit/action@v3.0.1 From 2687807093a5f658d4241a900f823e1a48ef7c25 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 1 Mar 2024 04:55:30 +0000 Subject: [PATCH 134/455] chore(deps): update github/codeql-action action to v3 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index efa54a0e..f10a0db1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -134,7 +134,7 @@ jobs: | sarif-fmt continue-on-error: true - name: Upload - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: clippy-results.sarif wait-for-processing: true From ad801833687d72c45f1346b402bba32afc6bb28e Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Sat, 2 Mar 2024 16:24:16 -0700 Subject: [PATCH 135/455] refactor(fixtures): Move to snapbox with SVGs --- Cargo.lock | 422 +++++++++++++++++- Cargo.toml | 5 + tests/diff/mod.rs | 63 --- .../mod.rs => fixtures/deserialize.rs} | 0 tests/fixtures/main.rs | 33 ++ tests/fixtures/no-color/issue_52.svg | 35 ++ tests/fixtures/no-color/issue_52.txt | 7 - tests/fixtures/no-color/issue_9.svg | 45 ++ tests/fixtures/no-color/issue_9.txt | 12 - .../no-color/multiline_annotation.svg | 49 ++ .../no-color/multiline_annotation.txt | 14 - .../no-color/multiline_annotation2.svg | 39 ++ .../no-color/multiline_annotation2.txt | 9 - .../no-color/multiline_annotation3.svg | 39 ++ .../no-color/multiline_annotation3.txt | 9 - .../no-color/multiple_annotations.svg | 49 ++ .../no-color/multiple_annotations.txt | 14 - tests/fixtures/no-color/one_past.svg | 33 ++ tests/fixtures/no-color/one_past.txt | 6 - tests/fixtures/no-color/simple.svg | 39 ++ tests/fixtures/no-color/simple.txt | 9 - tests/fixtures/no-color/strip_line.svg | 33 ++ tests/fixtures/no-color/strip_line.txt | 6 - tests/fixtures/no-color/strip_line_char.svg | 33 ++ tests/fixtures/no-color/strip_line_char.txt | 6 - tests/fixtures/no-color/strip_line_non_ws.svg | 33 ++ tests/fixtures/no-color/strip_line_non_ws.txt | 6 - tests/fixtures_test.rs | 46 -- 28 files changed, 867 insertions(+), 227 deletions(-) delete mode 100644 tests/diff/mod.rs rename tests/{deserialize/mod.rs => fixtures/deserialize.rs} (100%) create mode 100644 tests/fixtures/main.rs create mode 100644 tests/fixtures/no-color/issue_52.svg delete mode 100644 tests/fixtures/no-color/issue_52.txt create mode 100644 tests/fixtures/no-color/issue_9.svg delete mode 100644 tests/fixtures/no-color/issue_9.txt create mode 100644 tests/fixtures/no-color/multiline_annotation.svg delete mode 100644 tests/fixtures/no-color/multiline_annotation.txt create mode 100644 tests/fixtures/no-color/multiline_annotation2.svg delete mode 100644 tests/fixtures/no-color/multiline_annotation2.txt create mode 100644 tests/fixtures/no-color/multiline_annotation3.svg delete mode 100644 tests/fixtures/no-color/multiline_annotation3.txt create mode 100644 tests/fixtures/no-color/multiple_annotations.svg delete mode 100644 tests/fixtures/no-color/multiple_annotations.txt create mode 100644 tests/fixtures/no-color/one_past.svg delete mode 100644 tests/fixtures/no-color/one_past.txt create mode 100644 tests/fixtures/no-color/simple.svg delete mode 100644 tests/fixtures/no-color/simple.txt create mode 100644 tests/fixtures/no-color/strip_line.svg delete mode 100644 tests/fixtures/no-color/strip_line.txt create mode 100644 tests/fixtures/no-color/strip_line_char.svg delete mode 100644 tests/fixtures/no-color/strip_line_char.txt create mode 100644 tests/fixtures/no-color/strip_line_non_ws.svg delete mode 100644 tests/fixtures/no-color/strip_line_non_ws.txt delete mode 100644 tests/fixtures_test.rs diff --git a/Cargo.lock b/Cargo.lock index 53b8e3c7..034e18cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,28 +26,109 @@ dependencies = [ "difference", "glob", "serde", + "snapbox", "toml", "unicode-width", ] +[[package]] +name = "anstream" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + [[package]] name = "anstyle" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +[[package]] +name = "anstyle-lossy" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a0444767dbd4aea9355cb47a370eb184dbfe918875e127eff52cb9d1638181" +dependencies = [ + "anstyle", +] + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-svg" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6ddad447b448d6d5db36b31cbd3ff27c7af071619501998eeceab01968287a" +dependencies = [ + "anstream", + "anstyle", + "anstyle-lossy", + "html-escape", + "unicode-width", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +[[package]] +name = "bstr" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.14.0" @@ -100,6 +181,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b" dependencies = [ "clap_builder", + "clap_derive", ] [[package]] @@ -108,8 +190,22 @@ version = "4.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663" dependencies = [ + "anstream", "anstyle", "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -118,6 +214,21 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "content_inspector" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7bda66e858c683005a53a9a60c69a4aca7eeaa45d124526e389f7aec8e62f38" +dependencies = [ + "memchr", +] + [[package]] name = "criterion" version = "0.5.1" @@ -193,6 +304,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + [[package]] name = "either" version = "1.9.0" @@ -201,12 +318,39 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "errno" -version = "0.3.6" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "escape8259" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4f4911e3666fcd7826997b4745c8224295a6f3072f1418c3067b97a67557ee" +dependencies = [ + "rustversion", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "filetime" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" dependencies = [ + "cfg-if", "libc", - "windows-sys", + "redox_syscall", + "windows-sys 0.52.0", ] [[package]] @@ -215,18 +359,62 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "globset" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + [[package]] name = "half" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +[[package]] +name = "html-escape" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" +dependencies = [ + "utf8-width", +] + +[[package]] +name = "ignore" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + [[package]] name = "is-terminal" version = "0.4.9" @@ -235,7 +423,7 @@ checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -264,15 +452,27 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.150" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "libtest-mimic" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f0f4c6f44ecfd52e8b443f2ad18f2b996540135771561283c2352ce56a1c70b" +dependencies = [ + "clap", + "escape8259", + "termcolor", + "threadpool", +] [[package]] name = "linux-raw-sys" -version = "0.4.11" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "log" @@ -295,6 +495,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + [[package]] name = "num-traits" version = "0.2.17" @@ -304,6 +510,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "once_cell" version = "1.18.0" @@ -382,6 +598,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "regex" version = "1.10.2" @@ -413,17 +638,23 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rustix" -version = "0.38.21" +version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ - "bitflags", + "bitflags 2.4.1", "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.52.0", ] +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + [[package]] name = "ryu" version = "1.0.15" @@ -476,6 +707,49 @@ dependencies = [ "serde", ] +[[package]] +name = "similar" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21" + +[[package]] +name = "snapbox" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6de4a00e16c1e859fbc29a6484c5aad30f18fb9f4c2c29033c28aef7e965b82e" +dependencies = [ + "anstream", + "anstyle", + "anstyle-svg", + "content_inspector", + "dunce", + "filetime", + "ignore", + "libtest-mimic", + "normalize-line-endings", + "serde_json", + "similar", + "snapbox-macros", + "tempfile", + "walkdir", +] + +[[package]] +name = "snapbox-macros" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1c4b838b05d15ab22754068cb73500b2f3b07bf09d310e15b27f88160f1de40" +dependencies = [ + "anstream", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" version = "2.0.48" @@ -487,6 +761,36 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -518,6 +822,18 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +[[package]] +name = "utf8-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "walkdir" version = "2.4.0" @@ -629,7 +945,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.4", ] [[package]] @@ -638,13 +963,28 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", ] [[package]] @@ -653,38 +993,80 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" diff --git a/Cargo.toml b/Cargo.toml index 3f5bf82b..8763c11c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,12 +32,17 @@ criterion = "0.5.1" difference = "2.0.0" glob = "0.3.1" serde = { version = "1.0.197", features = ["derive"] } +snapbox = { version = "0.5.8", features = ["diff", "harness", "path", "term-svg"] } toml = "0.5.11" [[bench]] name = "simple" harness = false +[[test]] +name = "fixtures" +harness = false + [features] default = [] testing-colors = [] diff --git a/tests/diff/mod.rs b/tests/diff/mod.rs deleted file mode 100644 index 60ccae19..00000000 --- a/tests/diff/mod.rs +++ /dev/null @@ -1,63 +0,0 @@ -use annotate_snippets::renderer::{AnsiColor, Color, Style}; -use difference::{Changeset, Difference}; - -const GREEN: Style = AnsiColor::Green.on_default(); -pub fn get_diff(left: &str, right: &str) -> String { - let mut output = String::new(); - - let Changeset { diffs, .. } = Changeset::new(left, right, "\n"); - - for i in 0..diffs.len() { - match diffs[i] { - Difference::Same(ref x) => { - output += &format!(" {}\n", x); - } - Difference::Add(ref x) => { - match diffs[i - 1] { - Difference::Rem(ref y) => { - output += &format!("{}+{}", GREEN.render(), GREEN.render_reset()); - let Changeset { diffs, .. } = Changeset::new(y, x, " "); - for c in diffs { - match c { - Difference::Same(ref z) => { - output += &format!( - "{}{}{} ", - GREEN.render(), - z.as_str(), - GREEN.render_reset() - ); - } - Difference::Add(ref z) => { - let black_on_green = Style::new() - .bg_color(Some(Color::Ansi(AnsiColor::Green))) - .fg_color(Some(Color::Ansi(AnsiColor::Black))); - output += &format!( - "{}{}{} ", - black_on_green.render(), - z.as_str(), - black_on_green.render_reset() - ); - } - _ => (), - } - } - output += "\n"; - } - _ => { - output += &format!( - "+{}{}{}\n", - GREEN.render(), - x.as_str(), - GREEN.render_reset() - ); - } - }; - } - Difference::Rem(ref x) => { - let red = AnsiColor::Red.on_default(); - output += &format!("-{}{}{}\n", red.render(), x.as_str(), red.render_reset()); - } - } - } - output -} diff --git a/tests/deserialize/mod.rs b/tests/fixtures/deserialize.rs similarity index 100% rename from tests/deserialize/mod.rs rename to tests/fixtures/deserialize.rs diff --git a/tests/fixtures/main.rs b/tests/fixtures/main.rs new file mode 100644 index 00000000..c320407a --- /dev/null +++ b/tests/fixtures/main.rs @@ -0,0 +1,33 @@ +mod deserialize; + +use crate::deserialize::Fixture; +use annotate_snippets::{Renderer, Snippet}; +use snapbox::data::DataFormat; +use snapbox::Data; +use std::error::Error; + +fn main() { + #[cfg(not(windows))] + snapbox::harness::Harness::new("tests/fixtures/", setup, test) + .select(["*/*.toml"]) + .action_env("SNAPSHOTS") + .test(); +} + +fn setup(input_path: std::path::PathBuf) -> snapbox::harness::Case { + let name = input_path.file_name().unwrap().to_str().unwrap().to_owned(); + let expected = input_path.with_extension("svg"); + snapbox::harness::Case { + name, + fixture: input_path, + expected, + } +} + +fn test(input_path: &std::path::Path) -> Result<Data, Box<dyn Error>> { + let src = std::fs::read_to_string(input_path)?; + let (renderer, snippet): (Renderer, Snippet<'_>) = + toml::from_str(&src).map(|a: Fixture| (a.renderer.into(), a.snippet.into()))?; + let actual = renderer.render(snippet).to_string(); + Ok(Data::from(actual).coerce_to(DataFormat::TermSvg)) +} diff --git a/tests/fixtures/no-color/issue_52.svg b/tests/fixtures/no-color/issue_52.svg new file mode 100644 index 00000000..5ed22289 --- /dev/null +++ b/tests/fixtures/no-color/issue_52.svg @@ -0,0 +1,35 @@ +<svg width="740px" height="146px" xmlns="http://www.w3.org/2000/svg"> + <style> + .fg { fill: #AAAAAA } + .bg { background: #000000 } + .container { + padding: 0 10px; + line-height: 18px; + } + tspan { + font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + white-space: pre; + line-height: 18px; + } + </style> + + <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> + + <text xml:space="preserve" class="container fg"> + <tspan x="10px" y="28px"><tspan>error</tspan> +</tspan> + <tspan x="10px" y="46px"><tspan> --> path/to/error.rs:3:1</tspan> +</tspan> + <tspan x="10px" y="64px"><tspan> |</tspan> +</tspan> + <tspan x="10px" y="82px"><tspan>...</tspan> +</tspan> + <tspan x="10px" y="100px"><tspan>3 | invalid syntax</tspan> +</tspan> + <tspan x="10px" y="118px"><tspan> | -------------- error here</tspan> +</tspan> + <tspan x="10px" y="136px"><tspan> |</tspan> +</tspan> + </text> + +</svg> diff --git a/tests/fixtures/no-color/issue_52.txt b/tests/fixtures/no-color/issue_52.txt deleted file mode 100644 index b1c6bf21..00000000 --- a/tests/fixtures/no-color/issue_52.txt +++ /dev/null @@ -1,7 +0,0 @@ -error - --> path/to/error.rs:3:1 - | -... -3 | invalid syntax - | -------------- error here - | \ No newline at end of file diff --git a/tests/fixtures/no-color/issue_9.svg b/tests/fixtures/no-color/issue_9.svg new file mode 100644 index 00000000..af22d82d --- /dev/null +++ b/tests/fixtures/no-color/issue_9.svg @@ -0,0 +1,45 @@ +<svg width="911px" height="236px" xmlns="http://www.w3.org/2000/svg"> + <style> + .fg { fill: #AAAAAA } + .bg { background: #000000 } + .container { + padding: 0 10px; + line-height: 18px; + } + tspan { + font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + white-space: pre; + line-height: 18px; + } + </style> + + <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> + + <text xml:space="preserve" class="container fg"> + <tspan x="10px" y="28px"><tspan>error: expected one of `.`, `;`, `?`, or an operator, found `for`</tspan> +</tspan> + <tspan x="10px" y="46px"><tspan> --> /code/rust/src/test/ui/annotate-snippet/suggestion.rs:4:5</tspan> +</tspan> + <tspan x="10px" y="64px"><tspan> |</tspan> +</tspan> + <tspan x="10px" y="82px"><tspan>4 | let x = vec![1];</tspan> +</tspan> + <tspan x="10px" y="100px"><tspan> | - move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait</tspan> +</tspan> + <tspan x="10px" y="118px"><tspan> |</tspan> +</tspan> + <tspan x="10px" y="136px"><tspan>7 | let y = x;</tspan> +</tspan> + <tspan x="10px" y="154px"><tspan> | - value moved here</tspan> +</tspan> + <tspan x="10px" y="172px"><tspan> |</tspan> +</tspan> + <tspan x="10px" y="190px"><tspan>9 | x;</tspan> +</tspan> + <tspan x="10px" y="208px"><tspan> | ^ value used here after move</tspan> +</tspan> + <tspan x="10px" y="226px"><tspan> |</tspan> +</tspan> + </text> + +</svg> diff --git a/tests/fixtures/no-color/issue_9.txt b/tests/fixtures/no-color/issue_9.txt deleted file mode 100644 index affe6bc4..00000000 --- a/tests/fixtures/no-color/issue_9.txt +++ /dev/null @@ -1,12 +0,0 @@ -error: expected one of `.`, `;`, `?`, or an operator, found `for` - --> /code/rust/src/test/ui/annotate-snippet/suggestion.rs:4:5 - | -4 | let x = vec![1]; - | - move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait - | -7 | let y = x; - | - value moved here - | -9 | x; - | ^ value used here after move - | \ No newline at end of file diff --git a/tests/fixtures/no-color/multiline_annotation.svg b/tests/fixtures/no-color/multiline_annotation.svg new file mode 100644 index 00000000..5fa6e81a --- /dev/null +++ b/tests/fixtures/no-color/multiline_annotation.svg @@ -0,0 +1,49 @@ +<svg width="869px" height="272px" xmlns="http://www.w3.org/2000/svg"> + <style> + .fg { fill: #AAAAAA } + .bg { background: #000000 } + .container { + padding: 0 10px; + line-height: 18px; + } + tspan { + font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + white-space: pre; + line-height: 18px; + } + </style> + + <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> + + <text xml:space="preserve" class="container fg"> + <tspan x="10px" y="28px"><tspan>error[E0308]: mismatched types</tspan> +</tspan> + <tspan x="10px" y="46px"><tspan> --> src/format.rs:51:6</tspan> +</tspan> + <tspan x="10px" y="64px"><tspan> |</tspan> +</tspan> + <tspan x="10px" y="82px"><tspan>51 | ) -> Option<String> {</tspan> +</tspan> + <tspan x="10px" y="100px"><tspan> | -------------- expected `std::option::Option<std::string::String>` because of return type</tspan> +</tspan> + <tspan x="10px" y="118px"><tspan>52 | / for ann in annotations {</tspan> +</tspan> + <tspan x="10px" y="136px"><tspan>53 | | match (ann.range.0, ann.range.1) {</tspan> +</tspan> + <tspan x="10px" y="154px"><tspan>54 | | (None, None) => continue,</tspan> +</tspan> + <tspan x="10px" y="172px"><tspan>55 | | (Some(start), Some(end)) if start > end_index || end < start_index => continue,</tspan> +</tspan> + <tspan x="10px" y="190px"><tspan>... |</tspan> +</tspan> + <tspan x="10px" y="208px"><tspan>71 | | }</tspan> +</tspan> + <tspan x="10px" y="226px"><tspan>72 | | }</tspan> +</tspan> + <tspan x="10px" y="244px"><tspan> | |_____^ expected enum `std::option::Option`, found ()</tspan> +</tspan> + <tspan x="10px" y="262px"><tspan> |</tspan> +</tspan> + </text> + +</svg> diff --git a/tests/fixtures/no-color/multiline_annotation.txt b/tests/fixtures/no-color/multiline_annotation.txt deleted file mode 100644 index bacdec10..00000000 --- a/tests/fixtures/no-color/multiline_annotation.txt +++ /dev/null @@ -1,14 +0,0 @@ -error[E0308]: mismatched types - --> src/format.rs:51:6 - | -51 | ) -> Option<String> { - | -------------- expected `std::option::Option<std::string::String>` because of return type -52 | / for ann in annotations { -53 | | match (ann.range.0, ann.range.1) { -54 | | (None, None) => continue, -55 | | (Some(start), Some(end)) if start > end_index || end < start_index => continue, -... | -71 | | } -72 | | } - | |_____^ expected enum `std::option::Option`, found () - | diff --git a/tests/fixtures/no-color/multiline_annotation2.svg b/tests/fixtures/no-color/multiline_annotation2.svg new file mode 100644 index 00000000..f4b4433b --- /dev/null +++ b/tests/fixtures/no-color/multiline_annotation2.svg @@ -0,0 +1,39 @@ +<svg width="740px" height="182px" xmlns="http://www.w3.org/2000/svg"> + <style> + .fg { fill: #AAAAAA } + .bg { background: #000000 } + .container { + padding: 0 10px; + line-height: 18px; + } + tspan { + font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + white-space: pre; + line-height: 18px; + } + </style> + + <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> + + <text xml:space="preserve" class="container fg"> + <tspan x="10px" y="28px"><tspan>error[E0027]: pattern does not mention fields `lineno`, `content`</tspan> +</tspan> + <tspan x="10px" y="46px"><tspan> --> src/display_list.rs:139:32</tspan> +</tspan> + <tspan x="10px" y="64px"><tspan> |</tspan> +</tspan> + <tspan x="10px" y="82px"><tspan>139 | if let DisplayLine::Source {</tspan> +</tspan> + <tspan x="10px" y="100px"><tspan> | ________________________________^</tspan> +</tspan> + <tspan x="10px" y="118px"><tspan>140 | | ref mut inline_marks,</tspan> +</tspan> + <tspan x="10px" y="136px"><tspan>141 | | } = body[body_idx]</tspan> +</tspan> + <tspan x="10px" y="154px"><tspan> | |_________________________^ missing fields `lineno`, `content`</tspan> +</tspan> + <tspan x="10px" y="172px"><tspan> |</tspan> +</tspan> + </text> + +</svg> diff --git a/tests/fixtures/no-color/multiline_annotation2.txt b/tests/fixtures/no-color/multiline_annotation2.txt deleted file mode 100644 index 8a00bfa2..00000000 --- a/tests/fixtures/no-color/multiline_annotation2.txt +++ /dev/null @@ -1,9 +0,0 @@ -error[E0027]: pattern does not mention fields `lineno`, `content` - --> src/display_list.rs:139:32 - | -139 | if let DisplayLine::Source { - | ________________________________^ -140 | | ref mut inline_marks, -141 | | } = body[body_idx] - | |_________________________^ missing fields `lineno`, `content` - | diff --git a/tests/fixtures/no-color/multiline_annotation3.svg b/tests/fixtures/no-color/multiline_annotation3.svg new file mode 100644 index 00000000..18a9bf6d --- /dev/null +++ b/tests/fixtures/no-color/multiline_annotation3.svg @@ -0,0 +1,39 @@ +<svg width="740px" height="182px" xmlns="http://www.w3.org/2000/svg"> + <style> + .fg { fill: #AAAAAA } + .bg { background: #000000 } + .container { + padding: 0 10px; + line-height: 18px; + } + tspan { + font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + white-space: pre; + line-height: 18px; + } + </style> + + <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> + + <text xml:space="preserve" class="container fg"> + <tspan x="10px" y="28px"><tspan>error[E####]: spacing error found</tspan> +</tspan> + <tspan x="10px" y="46px"><tspan> --> foo.txt:26:12</tspan> +</tspan> + <tspan x="10px" y="64px"><tspan> |</tspan> +</tspan> + <tspan x="10px" y="82px"><tspan>26 | This is an exampl</tspan> +</tspan> + <tspan x="10px" y="100px"><tspan> | ____________^</tspan> +</tspan> + <tspan x="10px" y="118px"><tspan>27 | | e of an edge case of an annotation overflowing</tspan> +</tspan> + <tspan x="10px" y="136px"><tspan> | |_^ this should not be on separate lines</tspan> +</tspan> + <tspan x="10px" y="154px"><tspan>28 | to exactly one character on next line.</tspan> +</tspan> + <tspan x="10px" y="172px"><tspan> |</tspan> +</tspan> + </text> + +</svg> diff --git a/tests/fixtures/no-color/multiline_annotation3.txt b/tests/fixtures/no-color/multiline_annotation3.txt deleted file mode 100644 index 12e174c5..00000000 --- a/tests/fixtures/no-color/multiline_annotation3.txt +++ /dev/null @@ -1,9 +0,0 @@ -error[E####]: spacing error found - --> foo.txt:26:12 - | -26 | This is an exampl - | ____________^ -27 | | e of an edge case of an annotation overflowing - | |_^ this should not be on separate lines -28 | to exactly one character on next line. - | \ No newline at end of file diff --git a/tests/fixtures/no-color/multiple_annotations.svg b/tests/fixtures/no-color/multiple_annotations.svg new file mode 100644 index 00000000..3f151449 --- /dev/null +++ b/tests/fixtures/no-color/multiple_annotations.svg @@ -0,0 +1,49 @@ +<svg width="768px" height="272px" xmlns="http://www.w3.org/2000/svg"> + <style> + .fg { fill: #AAAAAA } + .bg { background: #000000 } + .container { + padding: 0 10px; + line-height: 18px; + } + tspan { + font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + white-space: pre; + line-height: 18px; + } + </style> + + <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> + + <text xml:space="preserve" class="container fg"> + <tspan x="10px" y="28px"><tspan> |</tspan> +</tspan> + <tspan x="10px" y="46px"><tspan> 96 | fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>) {</tspan> +</tspan> + <tspan x="10px" y="64px"><tspan> 97 | if let Some(annotation) = main_annotation {</tspan> +</tspan> + <tspan x="10px" y="82px"><tspan> | ^^^^^^^^^^ Variable defined here</tspan> +</tspan> + <tspan x="10px" y="100px"><tspan> 98 | result.push(format_title_line(</tspan> +</tspan> + <tspan x="10px" y="118px"><tspan> 99 | &annotation.annotation_type,</tspan> +</tspan> + <tspan x="10px" y="136px"><tspan> | ^^^^^^^^^^ Referenced here</tspan> +</tspan> + <tspan x="10px" y="154px"><tspan>100 | None,</tspan> +</tspan> + <tspan x="10px" y="172px"><tspan>101 | &annotation.label,</tspan> +</tspan> + <tspan x="10px" y="190px"><tspan> | ^^^^^^^^^^ Referenced again here</tspan> +</tspan> + <tspan x="10px" y="208px"><tspan>102 | ));</tspan> +</tspan> + <tspan x="10px" y="226px"><tspan>103 | }</tspan> +</tspan> + <tspan x="10px" y="244px"><tspan>104 | }</tspan> +</tspan> + <tspan x="10px" y="262px"><tspan> |</tspan> +</tspan> + </text> + +</svg> diff --git a/tests/fixtures/no-color/multiple_annotations.txt b/tests/fixtures/no-color/multiple_annotations.txt deleted file mode 100644 index 26c677f7..00000000 --- a/tests/fixtures/no-color/multiple_annotations.txt +++ /dev/null @@ -1,14 +0,0 @@ - | - 96 | fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>) { - 97 | if let Some(annotation) = main_annotation { - | ^^^^^^^^^^ Variable defined here - 98 | result.push(format_title_line( - 99 | &annotation.annotation_type, - | ^^^^^^^^^^ Referenced here -100 | None, -101 | &annotation.label, - | ^^^^^^^^^^ Referenced again here -102 | )); -103 | } -104 | } - | diff --git a/tests/fixtures/no-color/one_past.svg b/tests/fixtures/no-color/one_past.svg new file mode 100644 index 00000000..c8900d03 --- /dev/null +++ b/tests/fixtures/no-color/one_past.svg @@ -0,0 +1,33 @@ +<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg"> + <style> + .fg { fill: #AAAAAA } + .bg { background: #000000 } + .container { + padding: 0 10px; + line-height: 18px; + } + tspan { + font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + white-space: pre; + line-height: 18px; + } + </style> + + <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> + + <text xml:space="preserve" class="container fg"> + <tspan x="10px" y="28px"><tspan>error: expected `.`, `=`</tspan> +</tspan> + <tspan x="10px" y="46px"><tspan> --> Cargo.toml:1:5</tspan> +</tspan> + <tspan x="10px" y="64px"><tspan> |</tspan> +</tspan> + <tspan x="10px" y="82px"><tspan>1 | asdf</tspan> +</tspan> + <tspan x="10px" y="100px"><tspan> | ^</tspan> +</tspan> + <tspan x="10px" y="118px"><tspan> |</tspan> +</tspan> + </text> + +</svg> diff --git a/tests/fixtures/no-color/one_past.txt b/tests/fixtures/no-color/one_past.txt deleted file mode 100644 index 7f255b88..00000000 --- a/tests/fixtures/no-color/one_past.txt +++ /dev/null @@ -1,6 +0,0 @@ -error: expected `.`, `=` - --> Cargo.toml:1:5 - | -1 | asdf - | ^ - | diff --git a/tests/fixtures/no-color/simple.svg b/tests/fixtures/no-color/simple.svg new file mode 100644 index 00000000..51a3a65a --- /dev/null +++ b/tests/fixtures/no-color/simple.svg @@ -0,0 +1,39 @@ +<svg width="740px" height="182px" xmlns="http://www.w3.org/2000/svg"> + <style> + .fg { fill: #AAAAAA } + .bg { background: #000000 } + .container { + padding: 0 10px; + line-height: 18px; + } + tspan { + font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + white-space: pre; + line-height: 18px; + } + </style> + + <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> + + <text xml:space="preserve" class="container fg"> + <tspan x="10px" y="28px"><tspan>error: expected one of `.`, `;`, `?`, or an operator, found `for`</tspan> +</tspan> + <tspan x="10px" y="46px"><tspan> --> src/format_color.rs:171:9</tspan> +</tspan> + <tspan x="10px" y="64px"><tspan> |</tspan> +</tspan> + <tspan x="10px" y="82px"><tspan>169 | })</tspan> +</tspan> + <tspan x="10px" y="100px"><tspan> | - expected one of `.`, `;`, `?`, or an operator here</tspan> +</tspan> + <tspan x="10px" y="118px"><tspan>170 | </tspan> +</tspan> + <tspan x="10px" y="136px"><tspan>171 | for line in &self.body {</tspan> +</tspan> + <tspan x="10px" y="154px"><tspan> | ^^^ unexpected token</tspan> +</tspan> + <tspan x="10px" y="172px"><tspan> |</tspan> +</tspan> + </text> + +</svg> diff --git a/tests/fixtures/no-color/simple.txt b/tests/fixtures/no-color/simple.txt deleted file mode 100644 index 752cc890..00000000 --- a/tests/fixtures/no-color/simple.txt +++ /dev/null @@ -1,9 +0,0 @@ -error: expected one of `.`, `;`, `?`, or an operator, found `for` - --> src/format_color.rs:171:9 - | -169 | }) - | - expected one of `.`, `;`, `?`, or an operator here -170 | -171 | for line in &self.body { - | ^^^ unexpected token - | diff --git a/tests/fixtures/no-color/strip_line.svg b/tests/fixtures/no-color/strip_line.svg new file mode 100644 index 00000000..b1fd8a6d --- /dev/null +++ b/tests/fixtures/no-color/strip_line.svg @@ -0,0 +1,33 @@ +<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg"> + <style> + .fg { fill: #AAAAAA } + .bg { background: #000000 } + .container { + padding: 0 10px; + line-height: 18px; + } + tspan { + font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + white-space: pre; + line-height: 18px; + } + </style> + + <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> + + <text xml:space="preserve" class="container fg"> + <tspan x="10px" y="28px"><tspan>error[E0308]: mismatched types</tspan> +</tspan> + <tspan x="10px" y="46px"><tspan> --> $DIR/whitespace-trimming.rs:4:193</tspan> +</tspan> + <tspan x="10px" y="64px"><tspan> |</tspan> +</tspan> + <tspan x="10px" y="82px"><tspan>LL | ... let _: () = 42;</tspan> +</tspan> + <tspan x="10px" y="100px"><tspan> | ^^ expected (), found integer</tspan> +</tspan> + <tspan x="10px" y="118px"><tspan> |</tspan> +</tspan> + </text> + +</svg> diff --git a/tests/fixtures/no-color/strip_line.txt b/tests/fixtures/no-color/strip_line.txt deleted file mode 100644 index 65b05384..00000000 --- a/tests/fixtures/no-color/strip_line.txt +++ /dev/null @@ -1,6 +0,0 @@ -error[E0308]: mismatched types - --> $DIR/whitespace-trimming.rs:4:193 - | -LL | ... let _: () = 42; - | ^^ expected (), found integer - | diff --git a/tests/fixtures/no-color/strip_line_char.svg b/tests/fixtures/no-color/strip_line_char.svg new file mode 100644 index 00000000..15296a14 --- /dev/null +++ b/tests/fixtures/no-color/strip_line_char.svg @@ -0,0 +1,33 @@ +<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg"> + <style> + .fg { fill: #AAAAAA } + .bg { background: #000000 } + .container { + padding: 0 10px; + line-height: 18px; + } + tspan { + font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + white-space: pre; + line-height: 18px; + } + </style> + + <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> + + <text xml:space="preserve" class="container fg"> + <tspan x="10px" y="28px"><tspan>error[E0308]: mismatched types</tspan> +</tspan> + <tspan x="10px" y="46px"><tspan> --> $DIR/whitespace-trimming.rs:4:193</tspan> +</tspan> + <tspan x="10px" y="64px"><tspan> |</tspan> +</tspan> + <tspan x="10px" y="82px"><tspan>LL | ... let _: () = 42ñ</tspan> +</tspan> + <tspan x="10px" y="100px"><tspan> | ^^ expected (), found integer</tspan> +</tspan> + <tspan x="10px" y="118px"><tspan> |</tspan> +</tspan> + </text> + +</svg> diff --git a/tests/fixtures/no-color/strip_line_char.txt b/tests/fixtures/no-color/strip_line_char.txt deleted file mode 100644 index 3d4b700c..00000000 --- a/tests/fixtures/no-color/strip_line_char.txt +++ /dev/null @@ -1,6 +0,0 @@ -error[E0308]: mismatched types - --> $DIR/whitespace-trimming.rs:4:193 - | -LL | ... let _: () = 42ñ - | ^^ expected (), found integer - | diff --git a/tests/fixtures/no-color/strip_line_non_ws.svg b/tests/fixtures/no-color/strip_line_non_ws.svg new file mode 100644 index 00000000..6a72e7c9 --- /dev/null +++ b/tests/fixtures/no-color/strip_line_non_ws.svg @@ -0,0 +1,33 @@ +<svg width="1238px" height="128px" xmlns="http://www.w3.org/2000/svg"> + <style> + .fg { fill: #AAAAAA } + .bg { background: #000000 } + .container { + padding: 0 10px; + line-height: 18px; + } + tspan { + font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + white-space: pre; + line-height: 18px; + } + </style> + + <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> + + <text xml:space="preserve" class="container fg"> + <tspan x="10px" y="28px"><tspan>error[E0308]: mismatched types</tspan> +</tspan> + <tspan x="10px" y="46px"><tspan> --> $DIR/non-whitespace-trimming.rs:4:241</tspan> +</tspan> + <tspan x="10px" y="64px"><tspan> |</tspan> +</tspan> + <tspan x="10px" y="82px"><tspan>LL | ... = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = ();...</tspan> +</tspan> + <tspan x="10px" y="100px"><tspan> | ^^ expected (), found integer</tspan> +</tspan> + <tspan x="10px" y="118px"><tspan> |</tspan> +</tspan> + </text> + +</svg> diff --git a/tests/fixtures/no-color/strip_line_non_ws.txt b/tests/fixtures/no-color/strip_line_non_ws.txt deleted file mode 100644 index 850619ad..00000000 --- a/tests/fixtures/no-color/strip_line_non_ws.txt +++ /dev/null @@ -1,6 +0,0 @@ -error[E0308]: mismatched types - --> $DIR/non-whitespace-trimming.rs:4:241 - | -LL | ... = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = ();... - | ^^ expected (), found integer - | diff --git a/tests/fixtures_test.rs b/tests/fixtures_test.rs deleted file mode 100644 index 063829a6..00000000 --- a/tests/fixtures_test.rs +++ /dev/null @@ -1,46 +0,0 @@ -mod deserialize; -mod diff; - -use crate::deserialize::Fixture; -use annotate_snippets::Renderer; -use annotate_snippets::Snippet; -use glob::glob; -use std::{error::Error, fs::File, io, io::prelude::*}; - -fn read_file(path: &str) -> Result<String, io::Error> { - let mut f = File::open(path)?; - let mut s = String::new(); - (f.read_to_string(&mut s))?; - Ok(s.trim_end().to_string()) -} - -fn read_fixture(src: &str) -> Result<(Renderer, Snippet<'_>), Box<dyn Error>> { - Ok(toml::from_str(src).map(|a: Fixture| (a.renderer.into(), a.snippet.into()))?) -} - -#[test] -#[cfg(not(windows))] // HACK: Not working on windows due to a serde error -fn test_fixtures() { - for entry in glob("./tests/fixtures/no-color/**/*.toml").expect("Failed to read glob pattern") { - let p = entry.expect("Error while getting an entry"); - - let path_in = p.to_str().expect("Can't print path"); - let path_out = path_in.replace(".toml", ".txt"); - - let src = read_file(path_in).expect("Failed to read file"); - let (renderer, snippet) = read_fixture(&src).expect("Failed to read file"); - let expected_out = read_file(&path_out).expect("Failed to read file"); - - let actual_out = renderer.render(snippet).to_string(); - println!("{}", expected_out); - println!("{}", actual_out.trim_end()); - - assert_eq!( - expected_out, - actual_out.trim_end(), - "\n\n\nWhile parsing: {}\nThe diff is:\n\n\n{}\n\n\n", - path_in, - diff::get_diff(expected_out.as_str(), actual_out.as_str()) - ); - } -} From b65b8cabcd34da9fed88490a7a1cd8085777706a Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Fri, 8 Mar 2024 09:16:18 -0700 Subject: [PATCH 136/455] fix!: Take in byte spans BREAKING CHANGE: This switches from char spans to byte spans --- Cargo.lock | 14 +++++++-- Cargo.toml | 1 + src/renderer/display_list.rs | 57 ++++++++++++++++-------------------- src/snippet.rs | 1 + tests/formatter.rs | 10 +++---- 5 files changed, 45 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 034e18cd..e507736f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,6 +25,7 @@ dependencies = [ "criterion", "difference", "glob", + "itertools 0.12.1", "serde", "snapbox", "toml", @@ -241,7 +242,7 @@ dependencies = [ "clap", "criterion-plot", "is-terminal", - "itertools", + "itertools 0.10.5", "num-traits", "once_cell", "oorandom", @@ -262,7 +263,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", - "itertools", + "itertools 0.10.5", ] [[package]] @@ -435,6 +436,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.9" diff --git a/Cargo.toml b/Cargo.toml index 8763c11c..0b19a89f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ maintenance = { status = "actively-developed" } [dependencies] anstyle = "1.0.4" +itertools = "0.12.1" unicode-width = "0.1.11" [dev-dependencies] diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index f283e522..d0339465 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -32,6 +32,8 @@ //! //! The above snippet has been built out of the following structure: use crate::snippet; +use itertools::FoldWhile::{Continue, Done}; +use itertools::Itertools; use std::fmt::{Display, Write}; use std::{cmp, fmt}; @@ -804,13 +806,27 @@ fn format_header<'a>( for item in body { if let DisplayLine::Source { - line: DisplaySourceLine::Content { range, .. }, + line: DisplaySourceLine::Content { text, range }, lineno, .. } = item { if main_range >= range.0 && main_range <= range.1 { - col = main_range - range.0 + 1; + let char_column = text + .chars() + .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) + .chain(std::iter::once(1)) // treat the end of line as single-width + .enumerate() + .fold_while((0, 0), |(count, acc), (i, width)| { + if acc <= main_range - range.0 { + Continue((i, acc + width)) + } else { + Done((count, acc)) + } + }) + .into_inner() + .0; + col = char_column + 1; line_offset = lineno.unwrap_or(1); break; } @@ -932,7 +948,7 @@ fn format_body( has_footer: bool, margin: Option<Margin>, ) -> Vec<DisplayLine<'_>> { - let source_len = slice.source.chars().count(); + let source_len = slice.source.len(); if let Some(bigger) = slice.annotations.iter().find_map(|x| { // Allow highlighting one past the last character in the source. if source_len + 1 < x.range.1 { @@ -955,18 +971,14 @@ fn format_body( struct LineInfo { line_start_index: usize, line_end_index: usize, - // How many spaces each character in the line take up when displayed - char_widths: Vec<usize>, } for (line, end_line) in CursorLines::new(slice.source) { - let line_length = line.chars().count(); - let line_range = (current_index, current_index + line_length); - let char_widths = line + let line_length: usize = line .chars() .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) - .chain(std::iter::once(1)) // treat the end of line as single-width - .collect::<Vec<_>>(); + .sum(); + let line_range = (current_index, current_index + line_length); body.push(DisplayLine::Source { lineno: Some(current_line), inline_marks: vec![], @@ -978,7 +990,6 @@ fn format_body( line_info.push(LineInfo { line_start_index: line_range.0, line_end_index: line_range.1, - char_widths, }); current_line += 1; current_index += line_length + end_line as usize; @@ -991,7 +1002,6 @@ fn format_body( LineInfo { line_start_index, line_end_index, - char_widths, }, ) in line_info.into_iter().enumerate() { @@ -1012,16 +1022,8 @@ fn format_body( if start >= line_start_index && end <= line_end_index || start == line_end_index && end - start <= 1 => { - let annotation_start_col = char_widths - .iter() - .take(start - line_start_index) - .sum::<usize>() - - margin_left; - let annotation_end_col = char_widths - .iter() - .take(end - line_start_index) - .sum::<usize>() - - margin_left; + let annotation_start_col = start - line_start_index - margin_left; + let annotation_end_col = end - line_start_index - margin_left; let range = (annotation_start_col, annotation_end_col); body.insert( body_idx + 1, @@ -1064,10 +1066,7 @@ fn format_body( }); } } else { - let annotation_start_col = char_widths - .iter() - .take(start - line_start_index) - .sum::<usize>(); + let annotation_start_col = start - line_start_index; let range = (annotation_start_col, annotation_start_col + 1); body.insert( body_idx + 1, @@ -1125,11 +1124,7 @@ fn format_body( }); } - let end_mark = char_widths - .iter() - .take(end - line_start_index) - .sum::<usize>() - .saturating_sub(1); + let end_mark = (end - line_start_index).saturating_sub(1); let range = (end_mark - margin_left, (end_mark + 1) - margin_left); body.insert( body_idx + 1, diff --git a/src/snippet.rs b/src/snippet.rs index 02e70cc1..f48eaba9 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -70,6 +70,7 @@ pub enum AnnotationType { /// An annotation for a `Slice`. #[derive(Debug)] pub struct SourceAnnotation<'a> { + /// The byte range of the annotation in the `source` string pub range: (usize, usize), pub label: &'a str, pub annotation_type: AnnotationType, diff --git a/tests/formatter.rs b/tests/formatter.rs index 97c7be3b..2bee0b42 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -41,7 +41,7 @@ fn test_point_to_double_width_characters() { line_start: 1, origin: Some("<current file>"), annotations: vec![SourceAnnotation { - range: (6, 8), + range: (12, 16), label: "world", annotation_type: AnnotationType::Error, }], @@ -69,7 +69,7 @@ fn test_point_to_double_width_characters_across_lines() { line_start: 1, origin: Some("<current file>"), annotations: vec![SourceAnnotation { - range: (2, 8), + range: (4, 15), label: "Good morning", annotation_type: AnnotationType::Error, }], @@ -100,12 +100,12 @@ fn test_point_to_double_width_characters_multiple() { origin: Some("<current file>"), annotations: vec![ SourceAnnotation { - range: (0, 3), + range: (0, 6), label: "Sushi1", annotation_type: AnnotationType::Error, }, SourceAnnotation { - range: (6, 8), + range: (11, 15), label: "Sushi2", annotation_type: AnnotationType::Note, }, @@ -136,7 +136,7 @@ fn test_point_to_double_width_characters_mixed() { line_start: 1, origin: Some("<current file>"), annotations: vec![SourceAnnotation { - range: (6, 14), + range: (12, 23), label: "New world", annotation_type: AnnotationType::Error, }], From c3bd0c3a63f983f5f2b4793a099972b1f6e97a9f Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Fri, 8 Mar 2024 13:35:01 -0700 Subject: [PATCH 137/455] fix!: Use explicit Range<usize> BREAKING CHANGE: This changes (usize, usize) into Range<usize> --- benches/simple.rs | 4 ++-- examples/expected_type.rs | 4 ++-- examples/footer.rs | 2 +- examples/format.rs | 4 ++-- src/renderer/display_list.rs | 30 +++++++++++++++++------------- src/snippet.rs | 4 +++- tests/fixtures/deserialize.rs | 3 ++- tests/formatter.rs | 12 ++++++------ 8 files changed, 35 insertions(+), 28 deletions(-) diff --git a/benches/simple.rs b/benches/simple.rs index 3a40bbf5..f6abcee7 100644 --- a/benches/simple.rs +++ b/benches/simple.rs @@ -38,12 +38,12 @@ fn create_snippet(renderer: Renderer) { SourceAnnotation { label: "expected `Option<String>` because of return type", annotation_type: AnnotationType::Warning, - range: (5, 19), + range: 5..19, }, SourceAnnotation { label: "expected enum `std::option::Option`", annotation_type: AnnotationType::Error, - range: (26, 724), + range: 26..724, }, ], }], diff --git a/examples/expected_type.rs b/examples/expected_type.rs index bbd1fe64..613cf60e 100644 --- a/examples/expected_type.rs +++ b/examples/expected_type.rs @@ -20,12 +20,12 @@ fn main() { SourceAnnotation { label: "", annotation_type: AnnotationType::Error, - range: (193, 195), + range: 193..195, }, SourceAnnotation { label: "while parsing this struct", annotation_type: AnnotationType::Info, - range: (34, 50), + range: 34..50, }, ], }], diff --git a/examples/footer.rs b/examples/footer.rs index ca021198..433aa830 100644 --- a/examples/footer.rs +++ b/examples/footer.rs @@ -21,7 +21,7 @@ fn main() { fold: false, annotations: vec![SourceAnnotation { label: "expected struct `annotate_snippets::snippet::Slice`, found reference", - range: (21, 24), + range: 21..24, annotation_type: AnnotationType::Error, }], }], diff --git a/examples/format.rs b/examples/format.rs index 41f852ee..a699f0a7 100644 --- a/examples/format.rs +++ b/examples/format.rs @@ -32,12 +32,12 @@ fn main() { SourceAnnotation { label: "expected `Option<String>` because of return type", annotation_type: AnnotationType::Warning, - range: (5, 19), + range: 5..19, }, SourceAnnotation { label: "expected enum `std::option::Option`", annotation_type: AnnotationType::Error, - range: (26, 724), + range: 26..724, }, ], }], diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index d0339465..f8241db7 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -35,6 +35,7 @@ use crate::snippet; use itertools::FoldWhile::{Continue, Done}; use itertools::Itertools; use std::fmt::{Display, Write}; +use std::ops::Range; use std::{cmp, fmt}; use crate::renderer::{stylesheet::Stylesheet, Margin, Style}; @@ -768,7 +769,7 @@ fn format_slice( has_footer: bool, margin: Option<Margin>, ) -> Vec<DisplayLine<'_>> { - let main_range = slice.annotations.first().map(|x| x.range.0); + let main_range = slice.annotations.first().map(|x| x.range.start); let origin = slice.origin; let need_empty_header = origin.is_some() || is_first; let mut body = format_body(slice, need_empty_header, has_footer, margin); @@ -951,8 +952,8 @@ fn format_body( let source_len = slice.source.len(); if let Some(bigger) = slice.annotations.iter().find_map(|x| { // Allow highlighting one past the last character in the source. - if source_len + 1 < x.range.1 { - Some(x.range) + if source_len + 1 < x.range.end { + Some(&x.range) } else { None } @@ -1017,8 +1018,8 @@ fn format_body( _ => DisplayAnnotationType::from(annotation.annotation_type), }; match annotation.range { - (start, _) if start > line_end_index => true, - (start, end) + Range { start, .. } if start > line_end_index => true, + Range { start, end } if start >= line_start_index && end <= line_end_index || start == line_end_index && end - start <= 1 => { @@ -1047,7 +1048,7 @@ fn format_body( annotation_line_count += 1; false } - (start, end) + Range { start, end } if start >= line_start_index && start <= line_end_index && end > line_end_index => @@ -1091,7 +1092,7 @@ fn format_body( } true } - (start, end) if start < line_start_index && end > line_end_index => { + Range { start, end } if start < line_start_index && end > line_end_index => { if let DisplayLine::Source { ref mut inline_marks, .. @@ -1106,7 +1107,7 @@ fn format_body( } true } - (start, end) + Range { start, end } if start < line_start_index && end >= line_start_index && end <= line_end_index => @@ -1375,7 +1376,7 @@ mod tests { let line_2 = "This is line 2"; let source = [line_1, line_2].join("\n"); // In line 2 - let range = (22, 24); + let range = 22..24; let input = snippet::Snippet { title: None, footer: vec![], @@ -1384,7 +1385,7 @@ mod tests { line_start: 5402, origin: None, annotations: vec![snippet::SourceAnnotation { - range, + range: range.clone(), label: "Test annotation", annotation_type: snippet::AnnotationType::Info, }], @@ -1425,7 +1426,10 @@ mod tests { style: DisplayTextStyle::Regular, }], }, - range: (range.0 - (line_1.len() + 1), range.1 - (line_1.len() + 1)), + range: ( + range.start - (line_1.len() + 1), + range.end - (line_1.len() + 1), + ), annotation_type: DisplayAnnotationType::Info, annotation_part: DisplayAnnotationPart::Standalone, }, @@ -1475,7 +1479,7 @@ mod tests { footer: vec![], slices: vec![snippet::Slice { annotations: vec![snippet::SourceAnnotation { - range: (0, source.len() + 2), + range: 0..source.len() + 2, label, annotation_type: snippet::AnnotationType::Error, }], @@ -1502,7 +1506,7 @@ mod tests { line_start: 1, origin: Some("<current file>"), annotations: vec![snippet::SourceAnnotation { - range: (19, 23), + range: 19..23, label: "oops", annotation_type: snippet::AnnotationType::Error, }], diff --git a/src/snippet.rs b/src/snippet.rs index f48eaba9..7f052f00 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -31,6 +31,8 @@ //! }; //! ``` +use std::ops::Range; + /// Primary structure provided for formatting #[derive(Debug, Default)] pub struct Snippet<'a> { @@ -71,7 +73,7 @@ pub enum AnnotationType { #[derive(Debug)] pub struct SourceAnnotation<'a> { /// The byte range of the annotation in the `source` string - pub range: (usize, usize), + pub range: Range<usize>, pub label: &'a str, pub annotation_type: AnnotationType, } diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs index 1763005a..70e06ac6 100644 --- a/tests/fixtures/deserialize.rs +++ b/tests/fixtures/deserialize.rs @@ -1,4 +1,5 @@ use serde::{Deserialize, Deserializer, Serialize}; +use std::ops::Range; use annotate_snippets::{ renderer::Margin, Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation, @@ -122,7 +123,7 @@ where #[derive(Serialize, Deserialize)] #[serde(remote = "SourceAnnotation")] pub struct SourceAnnotationDef<'a> { - pub range: (usize, usize), + pub range: Range<usize>, #[serde(borrow)] pub label: &'a str, #[serde(with = "AnnotationTypeDef")] diff --git a/tests/formatter.rs b/tests/formatter.rs index 2bee0b42..954204d2 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -14,7 +14,7 @@ fn test_i_29() { line_start: 1, origin: Some("<current file>"), annotations: vec![SourceAnnotation { - range: (19, 23), + range: 19..23, label: "oops", annotation_type: AnnotationType::Error, }], @@ -41,7 +41,7 @@ fn test_point_to_double_width_characters() { line_start: 1, origin: Some("<current file>"), annotations: vec![SourceAnnotation { - range: (12, 16), + range: 12..16, label: "world", annotation_type: AnnotationType::Error, }], @@ -69,7 +69,7 @@ fn test_point_to_double_width_characters_across_lines() { line_start: 1, origin: Some("<current file>"), annotations: vec![SourceAnnotation { - range: (4, 15), + range: 4..15, label: "Good morning", annotation_type: AnnotationType::Error, }], @@ -100,12 +100,12 @@ fn test_point_to_double_width_characters_multiple() { origin: Some("<current file>"), annotations: vec![ SourceAnnotation { - range: (0, 6), + range: 0..6, label: "Sushi1", annotation_type: AnnotationType::Error, }, SourceAnnotation { - range: (11, 15), + range: 11..15, label: "Sushi2", annotation_type: AnnotationType::Note, }, @@ -136,7 +136,7 @@ fn test_point_to_double_width_characters_mixed() { line_start: 1, origin: Some("<current file>"), annotations: vec![SourceAnnotation { - range: (12, 23), + range: 12..23, label: "New world", annotation_type: AnnotationType::Error, }], From 5c566c0af4d4406b29265eed49dede25531602ef Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Sat, 9 Mar 2024 11:08:18 -0700 Subject: [PATCH 138/455] feat!: Move to builder pattern for snippet creation BREAKING CHANGE: Snippets must be created using the builder pattern --- benches/simple.rs | 39 +--- examples/expected_type.rs | 43 ++-- examples/footer.rs | 42 ++-- examples/format.rs | 39 +--- examples/multislice.rs | 29 +-- src/lib.rs | 1 - src/renderer/display_list.rs | 217 ++++++++---------- src/renderer/mod.rs | 29 +-- src/snippet.rs | 194 ++++++++++++---- tests/fixtures/deserialize.rs | 107 ++++++--- tests/fixtures/no-color/issue_52.toml | 1 + .../no-color/multiline_annotation.toml | 6 +- .../no-color/multiline_annotation2.toml | 5 +- .../no-color/multiline_annotation3.toml | 5 +- .../no-color/multiple_annotations.svg | 32 +-- .../no-color/multiple_annotations.toml | 4 + tests/fixtures/no-color/strip_line.toml | 5 +- tests/fixtures/no-color/strip_line_char.toml | 5 +- .../fixtures/no-color/strip_line_non_ws.toml | 5 +- tests/formatter.rs | 127 +++------- 20 files changed, 442 insertions(+), 493 deletions(-) diff --git a/benches/simple.rs b/benches/simple.rs index f6abcee7..4eacfdaa 100644 --- a/benches/simple.rs +++ b/benches/simple.rs @@ -4,12 +4,10 @@ extern crate criterion; use criterion::{black_box, Criterion}; -use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation}; +use annotate_snippets::{Label, Renderer, Slice, Snippet}; fn create_snippet(renderer: Renderer) { - let snippet = Snippet { - slices: vec![Slice { - source: r#") -> Option<String> { + let source = r#") -> Option<String> { for ann in annotations { match (ann.range.0, ann.range.1) { (None, None) => continue, @@ -30,30 +28,15 @@ fn create_snippet(renderer: Renderer) { } _ => continue, } - }"#, - line_start: 51, - origin: Some("src/format.rs"), - fold: false, - annotations: vec![ - SourceAnnotation { - label: "expected `Option<String>` because of return type", - annotation_type: AnnotationType::Warning, - range: 5..19, - }, - SourceAnnotation { - label: "expected enum `std::option::Option`", - annotation_type: AnnotationType::Error, - range: 26..724, - }, - ], - }], - title: Some(Annotation { - label: Some("mismatched types"), - id: Some("E0308"), - annotation_type: AnnotationType::Error, - }), - footer: vec![], - }; + }"#; + let snippet = Snippet::error("mismatched types").id("E0308").slice( + Slice::new(source, 51) + .origin("src/format.rs") + .annotation( + Label::warning("expected `Option<String>` because of return type").span(5..19), + ) + .annotation(Label::error("expected enum `std::option::Option`").span(26..724)), + ); let _result = renderer.render(snippet).to_string(); } diff --git a/examples/expected_type.rs b/examples/expected_type.rs index 613cf60e..99fb1bf7 100644 --- a/examples/expected_type.rs +++ b/examples/expected_type.rs @@ -1,35 +1,22 @@ -use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation}; +use annotate_snippets::{Label, Renderer, Slice, Snippet}; fn main() { - let snippet = Snippet { - title: Some(Annotation { - label: Some("expected type, found `22`"), - id: None, - annotation_type: AnnotationType::Error, - }), - footer: vec![], - slices: vec![Slice { - source: r#" annotations: vec![SourceAnnotation { + let source = r#" annotations: vec![SourceAnnotation { label: "expected struct `annotate_snippets::snippet::Slice`, found reference" , - range: <22, 25>,"#, - line_start: 26, - origin: Some("examples/footer.rs"), - fold: true, - annotations: vec![ - SourceAnnotation { - label: "", - annotation_type: AnnotationType::Error, - range: 193..195, - }, - SourceAnnotation { - label: "while parsing this struct", - annotation_type: AnnotationType::Info, - range: 34..50, - }, - ], - }], - }; + range: <22, 25>,"#; + let snippet = Snippet::error("expected type, found `22`").slice( + Slice::new(source, 26) + .origin("examples/footer.rs") + .fold(true) + .annotation( + Label::error( + "expected struct `annotate_snippets::snippet::Slice`, found reference", + ) + .span(193..195), + ) + .annotation(Label::info("while parsing this struct").span(34..50)), + ); let renderer = Renderer::plain(); println!("{}", renderer.render(snippet)); diff --git a/examples/footer.rs b/examples/footer.rs index 433aa830..d24b4973 100644 --- a/examples/footer.rs +++ b/examples/footer.rs @@ -1,31 +1,21 @@ -use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation}; +use annotate_snippets::{Label, Renderer, Slice, Snippet}; fn main() { - let snippet = Snippet { - title: Some(Annotation { - label: Some("mismatched types"), - id: Some("E0308"), - annotation_type: AnnotationType::Error, - }), - footer: vec![Annotation { - label: Some( - "expected type: `snippet::Annotation`\n found type: `__&__snippet::Annotation`", - ), - id: None, - annotation_type: AnnotationType::Note, - }], - slices: vec![Slice { - source: " slices: vec![\"A\",", - line_start: 13, - origin: Some("src/multislice.rs"), - fold: false, - annotations: vec![SourceAnnotation { - label: "expected struct `annotate_snippets::snippet::Slice`, found reference", - range: 21..24, - annotation_type: AnnotationType::Error, - }], - }], - }; + let snippet = Snippet::error("mismatched types") + .id("E0308") + .slice( + Slice::new(" slices: vec![\"A\",", 13) + .origin("src/multislice.rs") + .annotation( + Label::error( + "expected struct `annotate_snippets::snippet::Slice`, found reference", + ) + .span(21..24), + ), + ) + .footer(Label::note( + "expected type: `snippet::Annotation`\n found type: `__&__snippet::Annotation`", + )); let renderer = Renderer::plain(); println!("{}", renderer.render(snippet)); diff --git a/examples/format.rs b/examples/format.rs index a699f0a7..5eb5a287 100644 --- a/examples/format.rs +++ b/examples/format.rs @@ -1,9 +1,7 @@ -use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation}; +use annotate_snippets::{Label, Renderer, Slice, Snippet}; fn main() { - let snippet = Snippet { - slices: vec![Slice { - source: r#") -> Option<String> { + let source = r#") -> Option<String> { for ann in annotations { match (ann.range.0, ann.range.1) { (None, None) => continue, @@ -24,30 +22,15 @@ fn main() { } _ => continue, } - }"#, - line_start: 51, - origin: Some("src/format.rs"), - fold: false, - annotations: vec![ - SourceAnnotation { - label: "expected `Option<String>` because of return type", - annotation_type: AnnotationType::Warning, - range: 5..19, - }, - SourceAnnotation { - label: "expected enum `std::option::Option`", - annotation_type: AnnotationType::Error, - range: 26..724, - }, - ], - }], - title: Some(Annotation { - label: Some("mismatched types"), - id: Some("E0308"), - annotation_type: AnnotationType::Error, - }), - footer: vec![], - }; + }"#; + let snippet = Snippet::error("mismatched types").id("E0308").slice( + Slice::new(source, 51) + .origin("src/format.rs") + .annotation( + Label::warning("expected `Option<String>` because of return type").span(5..19), + ) + .annotation(Label::error("expected enum `std::option::Option`").span(26..724)), + ); let renderer = Renderer::plain(); println!("{}", renderer.render(snippet)); diff --git a/examples/multislice.rs b/examples/multislice.rs index 63ebb650..f0de5579 100644 --- a/examples/multislice.rs +++ b/examples/multislice.rs @@ -1,30 +1,9 @@ -use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet}; +use annotate_snippets::{Renderer, Slice, Snippet}; fn main() { - let snippet = Snippet { - title: Some(Annotation { - label: Some("mismatched types"), - id: None, - annotation_type: AnnotationType::Error, - }), - footer: vec![], - slices: vec![ - Slice { - source: "Foo", - line_start: 51, - origin: Some("src/format.rs"), - fold: false, - annotations: vec![], - }, - Slice { - source: "Faa", - line_start: 129, - origin: Some("src/display.rs"), - fold: false, - annotations: vec![], - }, - ], - }; + let snippet = Snippet::error("mismatched types") + .slice(Slice::new("Foo", 51).origin("src/format.rs")) + .slice(Slice::new("Faa", 129).origin("src/display.rs")); let renderer = Renderer::plain(); println!("{}", renderer.render(snippet)); diff --git a/src/lib.rs b/src/lib.rs index 03ffbb64..2066e671 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,7 +48,6 @@ //! ```text //! cargo add annotate-snippets --dev --feature testing-colors //! ``` -//! pub mod renderer; mod snippet; diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index f8241db7..01484d45 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -108,6 +108,7 @@ impl<'a> DisplayList<'a> { pub(crate) fn new( snippet::Snippet { title, + id, footer, slices, }: snippet::Snippet<'a>, @@ -116,9 +117,8 @@ impl<'a> DisplayList<'a> { margin: Option<Margin>, ) -> DisplayList<'a> { let mut body = vec![]; - if let Some(annotation) = title { - body.push(format_title(annotation)); - } + + body.push(format_title(title, id)); for (idx, slice) in slices.into_iter().enumerate() { body.append(&mut format_slice( @@ -130,7 +130,7 @@ impl<'a> DisplayList<'a> { } for annotation in footer { - body.append(&mut format_annotation(annotation)); + body.append(&mut format_footer(annotation)); } Self { @@ -733,26 +733,24 @@ fn format_label( result } -fn format_title(annotation: snippet::Annotation<'_>) -> DisplayLine<'_> { - let label = annotation.label.unwrap_or_default(); +fn format_title<'a>(title: snippet::Label<'a>, id: Option<&'a str>) -> DisplayLine<'a> { DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { - annotation_type: DisplayAnnotationType::from(annotation.annotation_type), - id: annotation.id, - label: format_label(Some(label), Some(DisplayTextStyle::Emphasis)), + annotation_type: DisplayAnnotationType::from(title.annotation_type), + id, + label: format_label(Some(title.label), Some(DisplayTextStyle::Emphasis)), }, source_aligned: false, continuation: false, }) } -fn format_annotation(annotation: snippet::Annotation<'_>) -> Vec<DisplayLine<'_>> { +fn format_footer(footer: snippet::Label<'_>) -> Vec<DisplayLine<'_>> { let mut result = vec![]; - let label = annotation.label.unwrap_or_default(); - for (i, line) in label.lines().enumerate() { + for (i, line) in footer.label.lines().enumerate() { result.push(DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { - annotation_type: DisplayAnnotationType::from(annotation.annotation_type), + annotation_type: DisplayAnnotationType::from(footer.annotation_type), id: None, label: format_label(Some(line), None), }, @@ -1222,15 +1220,7 @@ mod tests { #[test] fn test_format_title() { - let input = snippet::Snippet { - title: Some(snippet::Annotation { - id: Some("E0001"), - label: Some("This is a title"), - annotation_type: snippet::AnnotationType::Error, - }), - footer: vec![], - slices: vec![], - }; + let input = snippet::Snippet::error("This is a title").id("E0001"); let output = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { annotation_type: DisplayAnnotationType::Error, @@ -1251,18 +1241,20 @@ mod tests { let line_1 = "This is line 1"; let line_2 = "This is line 2"; let source = [line_1, line_2].join("\n"); - let input = snippet::Snippet { - title: None, - footer: vec![], - slices: vec![snippet::Slice { - source: &source, - line_start: 5402, - origin: None, - annotations: vec![], - fold: false, - }], - }; + let input = snippet::Snippet::error("").slice(snippet::Slice::new(&source, 5402)); let output = from_display_lines(vec![ + DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::Error, + id: None, + label: vec![DisplayTextFragment { + content: "", + style: DisplayTextStyle::Emphasis, + }], + }, + source_aligned: false, + continuation: false, + }), DisplayLine::Source { lineno: None, inline_marks: vec![], @@ -1299,27 +1291,22 @@ mod tests { let src_0_len = src_0.len(); let src_1 = "This is slice 2"; let src_1_len = src_1.len(); - let input = snippet::Snippet { - title: None, - footer: vec![], - slices: vec![ - snippet::Slice { - source: src_0, - line_start: 5402, - origin: Some("file1.rs"), - annotations: vec![], - fold: false, - }, - snippet::Slice { - source: src_1, - line_start: 2, - origin: Some("file2.rs"), - annotations: vec![], - fold: false, - }, - ], - }; + let input = snippet::Snippet::error("") + .slice(snippet::Slice::new(src_0, 5402).origin("file1.rs")) + .slice(snippet::Slice::new(src_1, 2).origin("file2.rs")); let output = from_display_lines(vec![ + DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::Error, + id: None, + label: vec![DisplayTextFragment { + content: "", + style: DisplayTextStyle::Emphasis, + }], + }, + source_aligned: false, + continuation: false, + }), DisplayLine::Raw(DisplayRawLine::Origin { path: "file1.rs", pos: None, @@ -1377,22 +1364,23 @@ mod tests { let source = [line_1, line_2].join("\n"); // In line 2 let range = 22..24; - let input = snippet::Snippet { - title: None, - footer: vec![], - slices: vec![snippet::Slice { - source: &source, - line_start: 5402, - origin: None, - annotations: vec![snippet::SourceAnnotation { - range: range.clone(), - label: "Test annotation", - annotation_type: snippet::AnnotationType::Info, - }], - fold: false, - }], - }; + let input = snippet::Snippet::error("").slice( + snippet::Slice::new(&source, 5402) + .annotation(snippet::Label::info("Test annotation").span(range.clone())), + ); let output = from_display_lines(vec![ + DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::Error, + id: None, + label: vec![DisplayTextFragment { + content: "", + style: DisplayTextStyle::Emphasis, + }], + }, + source_aligned: false, + continuation: false, + }), DisplayLine::Source { lineno: None, inline_marks: vec![], @@ -1445,27 +1433,34 @@ mod tests { #[test] fn test_format_label() { - let input = snippet::Snippet { - title: None, - footer: vec![snippet::Annotation { - id: None, - label: Some("This __is__ a title"), - annotation_type: snippet::AnnotationType::Error, - }], - slices: vec![], - }; - let output = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Error, - id: None, - label: vec![DisplayTextFragment { - content: "This __is__ a title", - style: DisplayTextStyle::Regular, - }], - }, - source_aligned: true, - continuation: false, - })]); + let input = + snippet::Snippet::error("").footer(snippet::Label::error("This __is__ a title")); + let output = from_display_lines(vec![ + DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::Error, + id: None, + label: vec![DisplayTextFragment { + content: "", + style: DisplayTextStyle::Emphasis, + }], + }, + source_aligned: false, + continuation: false, + }), + DisplayLine::Raw(DisplayRawLine::Annotation { + annotation: Annotation { + annotation_type: DisplayAnnotationType::Error, + id: None, + label: vec![DisplayTextFragment { + content: "This __is__ a title", + style: DisplayTextStyle::Regular, + }], + }, + source_aligned: true, + continuation: false, + }), + ]); assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output); } @@ -1474,45 +1469,21 @@ mod tests { fn test_i26() { let source = "short"; let label = "label"; - let input = snippet::Snippet { - title: None, - footer: vec![], - slices: vec![snippet::Slice { - annotations: vec![snippet::SourceAnnotation { - range: 0..source.len() + 2, - label, - annotation_type: snippet::AnnotationType::Error, - }], - source, - line_start: 0, - origin: None, - fold: false, - }], - }; + let input = snippet::Snippet::error("").slice( + snippet::Slice::new(source, 0) + .annotation(snippet::Label::error(label).span(0..source.len() + 2)), + ); let _ = DisplayList::new(input, &STYLESHEET, false, None); } #[test] fn test_i_29() { - let snippets = snippet::Snippet { - title: Some(snippet::Annotation { - id: None, - label: Some("oops"), - annotation_type: snippet::AnnotationType::Error, - }), - footer: vec![], - slices: vec![snippet::Slice { - source: "First line\r\nSecond oops line", - line_start: 1, - origin: Some("<current file>"), - annotations: vec![snippet::SourceAnnotation { - range: 19..23, - label: "oops", - annotation_type: snippet::AnnotationType::Error, - }], - fold: true, - }], - }; + let snippets = snippet::Snippet::error("oops").slice( + snippet::Slice::new("First line\r\nSecond oops line", 1) + .origin("<current file>") + .fold(true) + .annotation(snippet::Label::error("oops").span(19..23)), + ); let expected = from_display_lines(vec![ DisplayLine::Raw(DisplayRawLine::Annotation { diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index b6108ce7..7046407b 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -2,31 +2,10 @@ //! //! # Example //! ``` -//! use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet}; -//! let snippet = Snippet { -//! title: Some(Annotation { -//! label: Some("mismatched types"), -//! id: None, -//! annotation_type: AnnotationType::Error, -//! }), -//! footer: vec![], -//! slices: vec![ -//! Slice { -//! source: "Foo", -//! line_start: 51, -//! origin: Some("src/format.rs"), -//! fold: false, -//! annotations: vec![], -//! }, -//! Slice { -//! source: "Faa", -//! line_start: 129, -//! origin: Some("src/display.rs"), -//! fold: false, -//! annotations: vec![], -//! }, -//! ], -//! }; +//! use annotate_snippets::{Renderer, Slice, Snippet}; +//! let snippet = Snippet::error("mismatched types") +//! .slice(Slice::new("Foo", 51).origin("src/format.rs")) +//! .slice(Slice::new("Faa", 129).origin("src/display.rs")); //! //! let renderer = Renderer::styled(); //! println!("{}", renderer.render(snippet)); diff --git a/src/snippet.rs b/src/snippet.rs index 7f052f00..e3a0bc04 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -5,40 +5,121 @@ //! ``` //! use annotate_snippets::*; //! -//! Snippet { -//! title: Some(Annotation { -//! label: Some("mismatched types"), -//! id: None, -//! annotation_type: AnnotationType::Error, -//! }), -//! footer: vec![], -//! slices: vec![ -//! Slice { -//! source: "Foo", -//! line_start: 51, -//! origin: Some("src/format.rs"), -//! fold: false, -//! annotations: vec![], -//! }, -//! Slice { -//! source: "Faa", -//! line_start: 129, -//! origin: Some("src/display.rs"), -//! fold: false, -//! annotations: vec![], -//! }, -//! ], -//! }; +//! Snippet::error("mismatched types") +//! .slice(Slice::new("Foo", 51).origin("src/format.rs")) +//! .slice(Slice::new("Faa", 129).origin("src/display.rs")); //! ``` use std::ops::Range; /// Primary structure provided for formatting -#[derive(Debug, Default)] pub struct Snippet<'a> { - pub title: Option<Annotation<'a>>, - pub footer: Vec<Annotation<'a>>, - pub slices: Vec<Slice<'a>>, + pub(crate) title: Label<'a>, + pub(crate) id: Option<&'a str>, + pub(crate) slices: Vec<Slice<'a>>, + pub(crate) footer: Vec<Label<'a>>, +} + +impl<'a> Snippet<'a> { + pub fn title(title: Label<'a>) -> Self { + Self { + title, + id: None, + slices: vec![], + footer: vec![], + } + } + + pub fn error(title: &'a str) -> Self { + Self::title(Label::error(title)) + } + + pub fn warning(title: &'a str) -> Self { + Self::title(Label::warning(title)) + } + + pub fn info(title: &'a str) -> Self { + Self::title(Label::info(title)) + } + + pub fn note(title: &'a str) -> Self { + Self::title(Label::note(title)) + } + + pub fn help(title: &'a str) -> Self { + Self::title(Label::help(title)) + } + + pub fn id(mut self, id: &'a str) -> Self { + self.id = Some(id); + self + } + + pub fn slice(mut self, slice: Slice<'a>) -> Self { + self.slices.push(slice); + self + } + + pub fn footer(mut self, footer: Label<'a>) -> Self { + self.footer.push(footer); + self + } +} + +pub struct Label<'a> { + pub(crate) annotation_type: AnnotationType, + pub(crate) label: &'a str, +} + +impl<'a> Label<'a> { + pub fn new(annotation_type: AnnotationType, label: &'a str) -> Self { + Self { + annotation_type, + label, + } + } + pub fn error(label: &'a str) -> Self { + Self::new(AnnotationType::Error, label) + } + + pub fn warning(label: &'a str) -> Self { + Self::new(AnnotationType::Warning, label) + } + + pub fn info(label: &'a str) -> Self { + Self::new(AnnotationType::Info, label) + } + + pub fn note(label: &'a str) -> Self { + Self::new(AnnotationType::Note, label) + } + + pub fn help(label: &'a str) -> Self { + Self::new(AnnotationType::Help, label) + } + + pub fn label(mut self, label: &'a str) -> Self { + self.label = label; + self + } + + /// Create a [SourceAnnotation] with the given span for a [Slice] + pub fn span(&self, span: Range<usize>) -> SourceAnnotation<'a> { + SourceAnnotation { + range: span, + label: self.label, + annotation_type: self.annotation_type, + } + } +} + +impl From<AnnotationType> for Label<'_> { + fn from(annotation_type: AnnotationType) -> Self { + Label { + annotation_type, + label: "", + } + } } /// Structure containing the slice of text to be annotated and @@ -46,15 +127,39 @@ pub struct Snippet<'a> { /// /// One `Slice` is meant to represent a single, continuous, /// slice of source code that you want to annotate. -#[derive(Debug)] pub struct Slice<'a> { - pub source: &'a str, - pub line_start: usize, - pub origin: Option<&'a str>, - pub annotations: Vec<SourceAnnotation<'a>>, - /// If set explicitly to `true`, the snippet will fold - /// parts of the slice that don't contain any annotations. - pub fold: bool, + pub(crate) source: &'a str, + pub(crate) line_start: usize, + pub(crate) origin: Option<&'a str>, + pub(crate) annotations: Vec<SourceAnnotation<'a>>, + pub(crate) fold: bool, +} + +impl<'a> Slice<'a> { + pub fn new(source: &'a str, line_start: usize) -> Self { + Self { + source, + line_start, + origin: None, + annotations: vec![], + fold: false, + } + } + + pub fn origin(mut self, origin: &'a str) -> Self { + self.origin = Some(origin); + self + } + + pub fn annotation(mut self, annotation: SourceAnnotation<'a>) -> Self { + self.annotations.push(annotation); + self + } + + pub fn fold(mut self, fold: bool) -> Self { + self.fold = fold; + self + } } /// Types of annotations. @@ -70,19 +175,12 @@ pub enum AnnotationType { } /// An annotation for a `Slice`. +/// +/// This gets created by [Label::span]. #[derive(Debug)] pub struct SourceAnnotation<'a> { /// The byte range of the annotation in the `source` string - pub range: Range<usize>, - pub label: &'a str, - pub annotation_type: AnnotationType, -} - -/// An annotation for a `Snippet`. -#[derive(Debug)] -pub struct Annotation<'a> { - /// Identifier of the annotation. Usually error code like "E0308". - pub id: Option<&'a str>, - pub label: Option<&'a str>, - pub annotation_type: AnnotationType, + pub(crate) range: Range<usize>, + pub(crate) label: &'a str, + pub(crate) annotation_type: AnnotationType, } diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs index 70e06ac6..a01c343c 100644 --- a/tests/fixtures/deserialize.rs +++ b/tests/fixtures/deserialize.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Deserializer, Serialize}; use std::ops::Range; use annotate_snippets::{ - renderer::Margin, Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation, + renderer::Margin, AnnotationType, Label, Renderer, Slice, Snippet, SourceAnnotation, }; #[derive(Deserialize)] @@ -15,14 +15,16 @@ pub struct Fixture<'a> { #[derive(Deserialize)] pub struct SnippetDef<'a> { - #[serde(deserialize_with = "deserialize_annotation")] + #[serde(deserialize_with = "deserialize_label")] + #[serde(borrow)] + pub title: Label<'a>, #[serde(default)] #[serde(borrow)] - pub title: Option<Annotation<'a>>, - #[serde(deserialize_with = "deserialize_annotations")] + pub id: Option<&'a str>, + #[serde(deserialize_with = "deserialize_labels")] #[serde(default)] #[serde(borrow)] - pub footer: Vec<Annotation<'a>>, + pub footer: Vec<Label<'a>>, #[serde(deserialize_with = "deserialize_slices")] #[serde(borrow)] pub slices: Vec<Slice<'a>>, @@ -32,64 +34,72 @@ impl<'a> From<SnippetDef<'a>> for Snippet<'a> { fn from(val: SnippetDef<'a>) -> Self { let SnippetDef { title, + id, footer, slices, } = val; - Snippet { - title, - footer, - slices, + let mut snippet = Snippet::title(title); + if let Some(id) = id { + snippet = snippet.id(id); } + snippet = slices + .into_iter() + .fold(snippet, |snippet, slice| snippet.slice(slice)); + snippet = footer + .into_iter() + .fold(snippet, |snippet, label| snippet.footer(label)); + snippet } } -fn deserialize_slices<'de, D>(deserializer: D) -> Result<Vec<Slice<'de>>, D::Error> +fn deserialize_label<'de, D>(deserializer: D) -> Result<Label<'de>, D::Error> where D: Deserializer<'de>, { #[derive(Deserialize)] struct Wrapper<'a>( - #[serde(with = "SliceDef")] + #[serde(with = "LabelDef")] #[serde(borrow)] - Slice<'a>, + LabelDef<'a>, ); - let v = Vec::deserialize(deserializer)?; - Ok(v.into_iter().map(|Wrapper(a)| a).collect()) + Wrapper::deserialize(deserializer) + .map(|Wrapper(label)| Label::new(label.annotation_type, label.label)) } -fn deserialize_annotation<'de, D>(deserializer: D) -> Result<Option<Annotation<'de>>, D::Error> +fn deserialize_labels<'de, D>(deserializer: D) -> Result<Vec<Label<'de>>, D::Error> where D: Deserializer<'de>, { #[derive(Deserialize)] struct Wrapper<'a>( - #[serde(with = "AnnotationDef")] + #[serde(with = "LabelDef")] #[serde(borrow)] - Annotation<'a>, + LabelDef<'a>, ); - Option::<Wrapper>::deserialize(deserializer) - .map(|opt_wrapped: Option<Wrapper>| opt_wrapped.map(|wrapped: Wrapper| wrapped.0)) + let v = Vec::deserialize(deserializer)?; + Ok(v.into_iter() + .map(|Wrapper(a)| Label::new(a.annotation_type, a.label)) + .collect()) } -fn deserialize_annotations<'de, D>(deserializer: D) -> Result<Vec<Annotation<'de>>, D::Error> +fn deserialize_slices<'de, D>(deserializer: D) -> Result<Vec<Slice<'de>>, D::Error> where D: Deserializer<'de>, { #[derive(Deserialize)] struct Wrapper<'a>( - #[serde(with = "AnnotationDef")] + #[serde(with = "SliceDef")] #[serde(borrow)] - Annotation<'a>, + SliceDef<'a>, ); let v = Vec::deserialize(deserializer)?; - Ok(v.into_iter().map(|Wrapper(a)| a).collect()) + Ok(v.into_iter().map(|Wrapper(a)| a.into()).collect()) } #[derive(Deserialize)] -#[serde(remote = "Slice")] pub struct SliceDef<'a> { #[serde(borrow)] pub source: &'a str, @@ -103,6 +113,26 @@ pub struct SliceDef<'a> { pub fold: bool, } +impl<'a> From<SliceDef<'a>> for Slice<'a> { + fn from(val: SliceDef<'a>) -> Self { + let SliceDef { + source, + line_start, + origin, + annotations, + fold, + } = val; + let mut slice = Slice::new(source, line_start).fold(fold); + if let Some(origin) = origin { + slice = slice.origin(origin) + } + slice = annotations + .into_iter() + .fold(slice, |slice, annotation| slice.annotation(annotation)); + slice + } +} + fn deserialize_source_annotations<'de, D>( deserializer: D, ) -> Result<Vec<SourceAnnotation<'de>>, D::Error> @@ -110,18 +140,13 @@ where D: Deserializer<'de>, { #[derive(Deserialize)] - struct Wrapper<'a>( - #[serde(with = "SourceAnnotationDef")] - #[serde(borrow)] - SourceAnnotation<'a>, - ); + struct Wrapper<'a>(#[serde(borrow)] SourceAnnotationDef<'a>); let v = Vec::deserialize(deserializer)?; - Ok(v.into_iter().map(|Wrapper(a)| a).collect()) + Ok(v.into_iter().map(|Wrapper(a)| a.into()).collect()) } #[derive(Serialize, Deserialize)] -#[serde(remote = "SourceAnnotation")] pub struct SourceAnnotationDef<'a> { pub range: Range<usize>, #[serde(borrow)] @@ -130,15 +155,23 @@ pub struct SourceAnnotationDef<'a> { pub annotation_type: AnnotationType, } +impl<'a> From<SourceAnnotationDef<'a>> for SourceAnnotation<'a> { + fn from(val: SourceAnnotationDef<'a>) -> Self { + let SourceAnnotationDef { + range, + label, + annotation_type, + } = val; + Label::new(annotation_type, label).span(range) + } +} + #[derive(Serialize, Deserialize)] -#[serde(remote = "Annotation")] -pub struct AnnotationDef<'a> { - #[serde(borrow)] - pub id: Option<&'a str>, - #[serde(borrow)] - pub label: Option<&'a str>, +pub struct LabelDef<'a> { #[serde(with = "AnnotationTypeDef")] pub annotation_type: AnnotationType, + #[serde(borrow)] + pub label: &'a str, } #[allow(dead_code)] diff --git a/tests/fixtures/no-color/issue_52.toml b/tests/fixtures/no-color/issue_52.toml index d31e0cb2..ea239dd3 100644 --- a/tests/fixtures/no-color/issue_52.toml +++ b/tests/fixtures/no-color/issue_52.toml @@ -1,5 +1,6 @@ [snippet.title] annotation_type = "Error" +label = "" [[snippet.slices]] source = """ diff --git a/tests/fixtures/no-color/multiline_annotation.toml b/tests/fixtures/no-color/multiline_annotation.toml index 604e04b0..48c07250 100644 --- a/tests/fixtures/no-color/multiline_annotation.toml +++ b/tests/fixtures/no-color/multiline_annotation.toml @@ -34,7 +34,7 @@ range = [5, 19] label = "expected enum `std::option::Option`, found ()" annotation_type = "Error" range = [22, 766] -[snippet.title] -label = "mismatched types" + +[snippet] +title = { annotation_type = "Error", label = "mismatched types" } id = "E0308" -annotation_type = "Error" diff --git a/tests/fixtures/no-color/multiline_annotation2.toml b/tests/fixtures/no-color/multiline_annotation2.toml index 3287fdce..89294bcc 100644 --- a/tests/fixtures/no-color/multiline_annotation2.toml +++ b/tests/fixtures/no-color/multiline_annotation2.toml @@ -12,7 +12,6 @@ label = "missing fields `lineno`, `content`" annotation_type = "Error" range = [31, 128] -[snippet.title] -label = "pattern does not mention fields `lineno`, `content`" +[snippet] +title = { annotation_type = "Error", label = "pattern does not mention fields `lineno`, `content`" } id = "E0027" -annotation_type = "Error" diff --git a/tests/fixtures/no-color/multiline_annotation3.toml b/tests/fixtures/no-color/multiline_annotation3.toml index 9fe85fb4..efc1f5f6 100644 --- a/tests/fixtures/no-color/multiline_annotation3.toml +++ b/tests/fixtures/no-color/multiline_annotation3.toml @@ -12,7 +12,6 @@ label = "this should not be on separate lines" annotation_type = "Error" range = [11, 18] -[snippet.title] -label = "spacing error found" +[snippet] +title = { annotation_type = "Error", label = "spacing error found" } id = "E####" -annotation_type = "Error" diff --git a/tests/fixtures/no-color/multiple_annotations.svg b/tests/fixtures/no-color/multiple_annotations.svg index 3f151449..18bca93e 100644 --- a/tests/fixtures/no-color/multiple_annotations.svg +++ b/tests/fixtures/no-color/multiple_annotations.svg @@ -1,4 +1,4 @@ -<svg width="768px" height="272px" xmlns="http://www.w3.org/2000/svg"> +<svg width="768px" height="290px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -16,33 +16,35 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan> |</tspan> + <tspan x="10px" y="28px"><tspan>error</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> 96 | fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>) {</tspan> + <tspan x="10px" y="46px"><tspan> |</tspan> </tspan> - <tspan x="10px" y="64px"><tspan> 97 | if let Some(annotation) = main_annotation {</tspan> + <tspan x="10px" y="64px"><tspan> 96 | fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>) {</tspan> </tspan> - <tspan x="10px" y="82px"><tspan> | ^^^^^^^^^^ Variable defined here</tspan> + <tspan x="10px" y="82px"><tspan> 97 | if let Some(annotation) = main_annotation {</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> 98 | result.push(format_title_line(</tspan> + <tspan x="10px" y="100px"><tspan> | ^^^^^^^^^^ Variable defined here</tspan> </tspan> - <tspan x="10px" y="118px"><tspan> 99 | &annotation.annotation_type,</tspan> + <tspan x="10px" y="118px"><tspan> 98 | result.push(format_title_line(</tspan> </tspan> - <tspan x="10px" y="136px"><tspan> | ^^^^^^^^^^ Referenced here</tspan> + <tspan x="10px" y="136px"><tspan> 99 | &annotation.annotation_type,</tspan> </tspan> - <tspan x="10px" y="154px"><tspan>100 | None,</tspan> + <tspan x="10px" y="154px"><tspan> | ^^^^^^^^^^ Referenced here</tspan> </tspan> - <tspan x="10px" y="172px"><tspan>101 | &annotation.label,</tspan> + <tspan x="10px" y="172px"><tspan>100 | None,</tspan> </tspan> - <tspan x="10px" y="190px"><tspan> | ^^^^^^^^^^ Referenced again here</tspan> + <tspan x="10px" y="190px"><tspan>101 | &annotation.label,</tspan> </tspan> - <tspan x="10px" y="208px"><tspan>102 | ));</tspan> + <tspan x="10px" y="208px"><tspan> | ^^^^^^^^^^ Referenced again here</tspan> </tspan> - <tspan x="10px" y="226px"><tspan>103 | }</tspan> + <tspan x="10px" y="226px"><tspan>102 | ));</tspan> </tspan> - <tspan x="10px" y="244px"><tspan>104 | }</tspan> + <tspan x="10px" y="244px"><tspan>103 | }</tspan> </tspan> - <tspan x="10px" y="262px"><tspan> |</tspan> + <tspan x="10px" y="262px"><tspan>104 | }</tspan> +</tspan> + <tspan x="10px" y="280px"><tspan> |</tspan> </tspan> </text> diff --git a/tests/fixtures/no-color/multiple_annotations.toml b/tests/fixtures/no-color/multiple_annotations.toml index f037c9a1..ae35cef6 100644 --- a/tests/fixtures/no-color/multiple_annotations.toml +++ b/tests/fixtures/no-color/multiple_annotations.toml @@ -1,3 +1,7 @@ +[snippet.title] +annotation_type = "Error" +label = "" + [[snippet.slices]] source = """ fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>) { diff --git a/tests/fixtures/no-color/strip_line.toml b/tests/fixtures/no-color/strip_line.toml index 4c609c45..9de50aac 100644 --- a/tests/fixtures/no-color/strip_line.toml +++ b/tests/fixtures/no-color/strip_line.toml @@ -1,7 +1,6 @@ -[snippet.title] +[snippet] +title = { annotation_type = "Error", label = "mismatched types" } id = "E0308" -label = "mismatched types" -annotation_type = "Error" [[snippet.slices]] source = " let _: () = 42;" diff --git a/tests/fixtures/no-color/strip_line_char.toml b/tests/fixtures/no-color/strip_line_char.toml index 76ea7499..72df2abd 100644 --- a/tests/fixtures/no-color/strip_line_char.toml +++ b/tests/fixtures/no-color/strip_line_char.toml @@ -1,7 +1,6 @@ -[snippet.title] +[snippet] +title = { annotation_type = "Error", label = "mismatched types" } id = "E0308" -label = "mismatched types" -annotation_type = "Error" [[snippet.slices]] source = " let _: () = 42ñ" diff --git a/tests/fixtures/no-color/strip_line_non_ws.toml b/tests/fixtures/no-color/strip_line_non_ws.toml index c46d6e27..236d8f17 100644 --- a/tests/fixtures/no-color/strip_line_non_ws.toml +++ b/tests/fixtures/no-color/strip_line_non_ws.toml @@ -1,7 +1,6 @@ -[snippet.title] +[snippet] +title = { annotation_type = "Error", label = "mismatched types" } id = "E0308" -label = "mismatched types" -annotation_type = "Error" [[snippet.slices]] source = " let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = ();" diff --git a/tests/formatter.rs b/tests/formatter.rs index 954204d2..8f5c09fc 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -1,26 +1,13 @@ -use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation}; +use annotate_snippets::{Label, Renderer, Slice, Snippet}; #[test] fn test_i_29() { - let snippets = Snippet { - title: Some(Annotation { - id: None, - label: Some("oops"), - annotation_type: AnnotationType::Error, - }), - footer: vec![], - slices: vec![Slice { - source: "First line\r\nSecond oops line", - line_start: 1, - origin: Some("<current file>"), - annotations: vec![SourceAnnotation { - range: 19..23, - label: "oops", - annotation_type: AnnotationType::Error, - }], - fold: true, - }], - }; + let snippets = Snippet::error("oops").slice( + Slice::new("First line\r\nSecond oops line", 1) + .origin("<current file>") + .annotation(Label::error("oops").span(19..23)) + .fold(true), + ); let expected = r#"error: oops --> <current file>:2:8 | @@ -35,23 +22,14 @@ fn test_i_29() { #[test] fn test_point_to_double_width_characters() { - let snippets = Snippet { - slices: vec![Slice { - source: "こんにちは、世界", - line_start: 1, - origin: Some("<current file>"), - annotations: vec![SourceAnnotation { - range: 12..16, - label: "world", - annotation_type: AnnotationType::Error, - }], - fold: false, - }], - title: None, - footer: vec![], - }; + let snippets = Snippet::error("").slice( + Slice::new("こんにちは、世界", 1) + .origin("<current file>") + .annotation(Label::error("world").span(12..16)), + ); - let expected = r#" --> <current file>:1:7 + let expected = r#"error + --> <current file>:1:7 | 1 | こんにちは、世界 | ^^^^ world @@ -63,23 +41,14 @@ fn test_point_to_double_width_characters() { #[test] fn test_point_to_double_width_characters_across_lines() { - let snippets = Snippet { - slices: vec![Slice { - source: "おはよう\nございます", - line_start: 1, - origin: Some("<current file>"), - annotations: vec![SourceAnnotation { - range: 4..15, - label: "Good morning", - annotation_type: AnnotationType::Error, - }], - fold: false, - }], - title: None, - footer: vec![], - }; + let snippets = Snippet::error("").slice( + Slice::new("おはよう\nございます", 1) + .origin("<current file>") + .annotation(Label::error("Good morning").span(4..15)), + ); - let expected = r#" --> <current file>:1:3 + let expected = r#"error + --> <current file>:1:3 | 1 | おはよう | _____^ @@ -93,30 +62,15 @@ fn test_point_to_double_width_characters_across_lines() { #[test] fn test_point_to_double_width_characters_multiple() { - let snippets = Snippet { - slices: vec![Slice { - source: "お寿司\n食べたい🍣", - line_start: 1, - origin: Some("<current file>"), - annotations: vec![ - SourceAnnotation { - range: 0..6, - label: "Sushi1", - annotation_type: AnnotationType::Error, - }, - SourceAnnotation { - range: 11..15, - label: "Sushi2", - annotation_type: AnnotationType::Note, - }, - ], - fold: false, - }], - title: None, - footer: vec![], - }; + let snippets = Snippet::error("").slice( + Slice::new("お寿司\n食べたい🍣", 1) + .origin("<current file>") + .annotation(Label::error("Sushi1").span(0..6)) + .annotation(Label::note("Sushi2").span(11..15)), + ); - let expected = r#" --> <current file>:1:1 + let expected = r#"error + --> <current file>:1:1 | 1 | お寿司 | ^^^^^^ Sushi1 @@ -130,23 +84,14 @@ fn test_point_to_double_width_characters_multiple() { #[test] fn test_point_to_double_width_characters_mixed() { - let snippets = Snippet { - slices: vec![Slice { - source: "こんにちは、新しいWorld!", - line_start: 1, - origin: Some("<current file>"), - annotations: vec![SourceAnnotation { - range: 12..23, - label: "New world", - annotation_type: AnnotationType::Error, - }], - fold: false, - }], - title: None, - footer: vec![], - }; + let snippets = Snippet::error("").slice( + Slice::new("こんにちは、新しいWorld!", 1) + .origin("<current file>") + .annotation(Label::error("New world").span(12..23)), + ); - let expected = r#" --> <current file>:1:7 + let expected = r#"error + --> <current file>:1:7 | 1 | こんにちは、新しいWorld! | ^^^^^^^^^^^ New world From faddee328b98ea67fec5781debb15490a72d654b Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 11 Mar 2024 11:32:41 -0500 Subject: [PATCH 139/455] docs: Improve cross-references --- src/snippet.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/snippet.rs b/src/snippet.rs index e3a0bc04..37f431a1 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -103,7 +103,7 @@ impl<'a> Label<'a> { self } - /// Create a [SourceAnnotation] with the given span for a [Slice] + /// Create a [`SourceAnnotation`] with the given span for a [`Slice`] pub fn span(&self, span: Range<usize>) -> SourceAnnotation<'a> { SourceAnnotation { range: span, @@ -174,9 +174,9 @@ pub enum AnnotationType { Help, } -/// An annotation for a `Slice`. +/// An annotation for a [`Slice`]. /// -/// This gets created by [Label::span]. +/// This gets created by [`Label::span`]. #[derive(Debug)] pub struct SourceAnnotation<'a> { /// The byte range of the annotation in the `source` string From 2edd90c0709b1490a3cbc4d2a763b1f6e7eeba28 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Mar 2024 18:16:51 +0000 Subject: [PATCH 140/455] chore(deps): update actions/setup-python action to v5 --- .github/workflows/pre-commit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 9a5c7634..6eb6ca19 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -19,5 +19,5 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 - uses: pre-commit/action@v3.0.1 From c8d8c65232cf988caa1a36d6cda2ee674548bc0b Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 11 Mar 2024 14:33:17 -0500 Subject: [PATCH 141/455] test(examples): Verify output --- Cargo.lock | 37 +++++++++++++++++ Cargo.toml | 3 +- examples/expected_type.rs | 4 +- examples/expected_type.svg | 44 ++++++++++++++++++++ examples/footer.rs | 4 +- examples/footer.svg | 43 +++++++++++++++++++ examples/format.rs | 4 +- examples/format.svg | 85 ++++++++++++++++++++++++++++++++++++++ examples/multislice.rs | 4 +- examples/multislice.svg | 44 ++++++++++++++++++++ tests/examples.rs | 37 +++++++++++++++++ 11 files changed, 300 insertions(+), 9 deletions(-) create mode 100644 examples/expected_type.svg create mode 100644 examples/footer.svg create mode 100644 examples/format.svg create mode 100644 examples/multislice.svg create mode 100644 tests/examples.rs diff --git a/Cargo.lock b/Cargo.lock index e507736f..5672f610 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,6 +21,7 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" name = "annotate-snippets" version = "0.10.2" dependencies = [ + "anstream", "anstyle", "criterion", "difference", @@ -336,6 +337,18 @@ dependencies = [ "rustversion", ] +[[package]] +name = "escargot" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f474c6844cbd04e783d0f25757583db4f491770ca618bedf2fb01815fc79939" +dependencies = [ + "log", + "once_cell", + "serde", + "serde_json", +] + [[package]] name = "fastrand" version = "2.0.1" @@ -542,6 +555,16 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "os_pipe" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57119c3b893986491ec9aa85056780d3a0f3cf4da7cc09dd3650dbd6c6738fb9" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "plotters" version = "0.3.5" @@ -734,15 +757,20 @@ dependencies = [ "anstyle-svg", "content_inspector", "dunce", + "escargot", "filetime", "ignore", + "libc", "libtest-mimic", "normalize-line-endings", + "os_pipe", "serde_json", "similar", "snapbox-macros", "tempfile", + "wait-timeout", "walkdir", + "windows-sys 0.52.0", ] [[package]] @@ -844,6 +872,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "walkdir" version = "2.4.0" diff --git a/Cargo.toml b/Cargo.toml index 0b19a89f..5fbfc0b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,11 +29,12 @@ itertools = "0.12.1" unicode-width = "0.1.11" [dev-dependencies] +anstream = "0.6.13" criterion = "0.5.1" difference = "2.0.0" glob = "0.3.1" serde = { version = "1.0.197", features = ["derive"] } -snapbox = { version = "0.5.8", features = ["diff", "harness", "path", "term-svg"] } +snapbox = { version = "0.5.8", features = ["diff", "harness", "path", "term-svg", "cmd", "examples"] } toml = "0.5.11" [[bench]] diff --git a/examples/expected_type.rs b/examples/expected_type.rs index 99fb1bf7..adcbeff1 100644 --- a/examples/expected_type.rs +++ b/examples/expected_type.rs @@ -18,6 +18,6 @@ fn main() { .annotation(Label::info("while parsing this struct").span(34..50)), ); - let renderer = Renderer::plain(); - println!("{}", renderer.render(snippet)); + let renderer = Renderer::styled(); + anstream::println!("{}", renderer.render(snippet)); } diff --git a/examples/expected_type.svg b/examples/expected_type.svg new file mode 100644 index 00000000..ae065047 --- /dev/null +++ b/examples/expected_type.svg @@ -0,0 +1,44 @@ +<svg width="860px" height="200px" xmlns="http://www.w3.org/2000/svg"> + <style> + .fg { fill: #AAAAAA } + .bg { background: #000000 } + .fg-bright-blue { fill: #5555FF } + .fg-bright-red { fill: #FF5555 } + .container { + padding: 0 10px; + line-height: 18px; + } + .bold { font-weight: bold; } + tspan { + font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + white-space: pre; + line-height: 18px; + } + </style> + + <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> + + <text xml:space="preserve" class="container fg"> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">expected type, found `22`</tspan> +</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> examples/footer.rs:29:25</tspan> +</tspan> + <tspan x="10px" y="64px"><tspan class="fg-bright-blue bold"> |</tspan> +</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">26 |</tspan><tspan> annotations: vec![SourceAnnotation {</tspan> +</tspan> + <tspan x="10px" y="100px"><tspan class="fg-bright-blue bold"> |</tspan><tspan class="fg-bright-blue bold"> ----------------</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">info</tspan><tspan class="fg-bright-blue bold">: while parsing this struct</tspan> +</tspan> + <tspan x="10px" y="118px"><tspan>...</tspan> +</tspan> + <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">29 |</tspan><tspan> range: <22, 25>,</tspan> +</tspan> + <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold"> |</tspan><tspan class="fg-bright-red bold"> ^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan> +</tspan> + <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold"> |</tspan> +</tspan> + <tspan x="10px" y="190px"> +</tspan> + </text> + +</svg> diff --git a/examples/footer.rs b/examples/footer.rs index d24b4973..6c45328a 100644 --- a/examples/footer.rs +++ b/examples/footer.rs @@ -17,6 +17,6 @@ fn main() { "expected type: `snippet::Annotation`\n found type: `__&__snippet::Annotation`", )); - let renderer = Renderer::plain(); - println!("{}", renderer.render(snippet)); + let renderer = Renderer::styled(); + anstream::println!("{}", renderer.render(snippet)); } diff --git a/examples/footer.svg b/examples/footer.svg new file mode 100644 index 00000000..34f81c8a --- /dev/null +++ b/examples/footer.svg @@ -0,0 +1,43 @@ +<svg width="844px" height="182px" xmlns="http://www.w3.org/2000/svg"> + <style> + .fg { fill: #AAAAAA } + .bg { background: #000000 } + .fg-bright-blue { fill: #5555FF } + .fg-bright-green { fill: #55FF55 } + .fg-bright-red { fill: #FF5555 } + .container { + padding: 0 10px; + line-height: 18px; + } + .bold { font-weight: bold; } + tspan { + font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + white-space: pre; + line-height: 18px; + } + </style> + + <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> + + <text xml:space="preserve" class="container fg"> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan> +</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> src/multislice.rs:13:22</tspan> +</tspan> + <tspan x="10px" y="64px"><tspan class="fg-bright-blue bold"> |</tspan> +</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">13 |</tspan><tspan> slices: vec!["A",</tspan> +</tspan> + <tspan x="10px" y="100px"><tspan class="fg-bright-blue bold"> |</tspan><tspan class="fg-bright-red bold"> ^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan> +</tspan> + <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold"> |</tspan> +</tspan> + <tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">=</tspan><tspan> </tspan><tspan class="fg-bright-green bold">note</tspan><tspan>: expected type: `snippet::Annotation`</tspan> +</tspan> + <tspan x="10px" y="154px"><tspan> found type: `__&__snippet::Annotation`</tspan> +</tspan> + <tspan x="10px" y="172px"> +</tspan> + </text> + +</svg> diff --git a/examples/format.rs b/examples/format.rs index 5eb5a287..1337812e 100644 --- a/examples/format.rs +++ b/examples/format.rs @@ -32,6 +32,6 @@ fn main() { .annotation(Label::error("expected enum `std::option::Option`").span(26..724)), ); - let renderer = Renderer::plain(); - println!("{}", renderer.render(snippet)); + let renderer = Renderer::styled(); + anstream::println!("{}", renderer.render(snippet)); } diff --git a/examples/format.svg b/examples/format.svg new file mode 100644 index 00000000..dd6c1c07 --- /dev/null +++ b/examples/format.svg @@ -0,0 +1,85 @@ +<svg width="740px" height="560px" xmlns="http://www.w3.org/2000/svg"> + <style> + .fg { fill: #AAAAAA } + .bg { background: #000000 } + .fg-bright-blue { fill: #5555FF } + .fg-bright-red { fill: #FF5555 } + .fg-yellow { fill: #AA5500 } + .container { + padding: 0 10px; + line-height: 18px; + } + .bold { font-weight: bold; } + tspan { + font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + white-space: pre; + line-height: 18px; + } + </style> + + <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> + + <text xml:space="preserve" class="container fg"> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan> +</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> src/format.rs:51:6</tspan> +</tspan> + <tspan x="10px" y="64px"><tspan class="fg-bright-blue bold"> |</tspan> +</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">51 |</tspan><tspan> ) -> Option<String> {</tspan> +</tspan> + <tspan x="10px" y="100px"><tspan class="fg-bright-blue bold"> |</tspan><tspan> </tspan><tspan class="fg-yellow bold"> --------------</tspan><tspan> </tspan><tspan class="fg-yellow bold">expected `Option<String>` because of return type</tspan> +</tspan> + <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">52 |</tspan><tspan> for ann in annotations {</tspan> +</tspan> + <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold"> |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">_____^</tspan> +</tspan> + <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">53 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> match (ann.range.0, ann.range.1) {</tspan> +</tspan> + <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">54 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> (None, None) => continue,</tspan> +</tspan> + <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">55 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> (Some(start), Some(end)) if start > end_index => continue,</tspan> +</tspan> + <tspan x="10px" y="208px"><tspan class="fg-bright-blue bold">56 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> (Some(start), Some(end)) if start >= start_index => {</tspan> +</tspan> + <tspan x="10px" y="226px"><tspan class="fg-bright-blue bold">57 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> let label = if let Some(ref label) = ann.label {</tspan> +</tspan> + <tspan x="10px" y="244px"><tspan class="fg-bright-blue bold">58 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> format!(" {}", label)</tspan> +</tspan> + <tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">59 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> } else {</tspan> +</tspan> + <tspan x="10px" y="280px"><tspan class="fg-bright-blue bold">60 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> String::from("")</tspan> +</tspan> + <tspan x="10px" y="298px"><tspan class="fg-bright-blue bold">61 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> };</tspan> +</tspan> + <tspan x="10px" y="316px"><tspan class="fg-bright-blue bold">62 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> </tspan> +</tspan> + <tspan x="10px" y="334px"><tspan class="fg-bright-blue bold">63 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> return Some(format!(</tspan> +</tspan> + <tspan x="10px" y="352px"><tspan class="fg-bright-blue bold">64 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> "{}{}{}",</tspan> +</tspan> + <tspan x="10px" y="370px"><tspan class="fg-bright-blue bold">65 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> " ".repeat(start - start_index),</tspan> +</tspan> + <tspan x="10px" y="388px"><tspan class="fg-bright-blue bold">66 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> "^".repeat(end - start),</tspan> +</tspan> + <tspan x="10px" y="406px"><tspan class="fg-bright-blue bold">67 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> label</tspan> +</tspan> + <tspan x="10px" y="424px"><tspan class="fg-bright-blue bold">68 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> ));</tspan> +</tspan> + <tspan x="10px" y="442px"><tspan class="fg-bright-blue bold">69 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan> +</tspan> + <tspan x="10px" y="460px"><tspan class="fg-bright-blue bold">70 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> _ => continue,</tspan> +</tspan> + <tspan x="10px" y="478px"><tspan class="fg-bright-blue bold">71 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan> +</tspan> + <tspan x="10px" y="496px"><tspan class="fg-bright-blue bold">72 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan> +</tspan> + <tspan x="10px" y="514px"><tspan class="fg-bright-blue bold"> |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan class="fg-bright-red bold">____^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected enum `std::option::Option`</tspan> +</tspan> + <tspan x="10px" y="532px"><tspan class="fg-bright-blue bold"> |</tspan> +</tspan> + <tspan x="10px" y="550px"> +</tspan> + </text> + +</svg> diff --git a/examples/multislice.rs b/examples/multislice.rs index f0de5579..b6fcb3f8 100644 --- a/examples/multislice.rs +++ b/examples/multislice.rs @@ -5,6 +5,6 @@ fn main() { .slice(Slice::new("Foo", 51).origin("src/format.rs")) .slice(Slice::new("Faa", 129).origin("src/display.rs")); - let renderer = Renderer::plain(); - println!("{}", renderer.render(snippet)); + let renderer = Renderer::styled(); + anstream::println!("{}", renderer.render(snippet)); } diff --git a/examples/multislice.svg b/examples/multislice.svg new file mode 100644 index 00000000..2ab959a3 --- /dev/null +++ b/examples/multislice.svg @@ -0,0 +1,44 @@ +<svg width="740px" height="200px" xmlns="http://www.w3.org/2000/svg"> + <style> + .fg { fill: #AAAAAA } + .bg { background: #000000 } + .fg-bright-blue { fill: #5555FF } + .fg-bright-red { fill: #FF5555 } + .container { + padding: 0 10px; + line-height: 18px; + } + .bold { font-weight: bold; } + tspan { + font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + white-space: pre; + line-height: 18px; + } + </style> + + <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> + + <text xml:space="preserve" class="container fg"> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan> +</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> src/format.rs</tspan> +</tspan> + <tspan x="10px" y="64px"><tspan class="fg-bright-blue bold"> |</tspan> +</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold"> 51 |</tspan><tspan> Foo</tspan> +</tspan> + <tspan x="10px" y="100px"><tspan class="fg-bright-blue bold"> |</tspan> +</tspan> + <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">:::</tspan><tspan> src/display.rs</tspan> +</tspan> + <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold"> |</tspan> +</tspan> + <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">129 |</tspan><tspan> Faa</tspan> +</tspan> + <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold"> |</tspan> +</tspan> + <tspan x="10px" y="190px"> +</tspan> + </text> + +</svg> diff --git a/tests/examples.rs b/tests/examples.rs new file mode 100644 index 00000000..6025f5e5 --- /dev/null +++ b/tests/examples.rs @@ -0,0 +1,37 @@ +#[test] +fn expected_type() { + let target = "expected_type"; + let expected = snapbox::file!["../examples/expected_type.svg": TermSvg]; + assert_example(target, expected); +} + +#[test] +fn footer() { + let target = "footer"; + let expected = snapbox::file!["../examples/footer.svg": TermSvg]; + assert_example(target, expected); +} + +#[test] +fn format() { + let target = "format"; + let expected = snapbox::file!["../examples/format.svg": TermSvg]; + assert_example(target, expected); +} + +#[test] +fn multislice() { + let target = "multislice"; + let expected = snapbox::file!["../examples/multislice.svg": TermSvg]; + assert_example(target, expected); +} + +#[track_caller] +fn assert_example(target: &str, expected: snapbox::Data) { + let bin_path = snapbox::cmd::compile_example(target, ["--features=testing-colors"]).unwrap(); + snapbox::cmd::Command::new(bin_path) + .env("CLICOLOR_FORCE", "1") + .assert() + .success() + .stdout_eq(expected); +} From 77050eafcfbadf42b5c8e09c892dadfc9e84753f Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 11 Mar 2024 14:39:11 -0500 Subject: [PATCH 142/455] docs: Shift example code from README to lib.rs We now pull from an example - We verify it builds (wasn't doing that with README) - We show the output for the actual code (couldn't do that from a rustdoc code fence) --- README.md | 65 ++++-------------------------------------------------- src/lib.rs | 18 ++++----------- 2 files changed, 8 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index f1b03522..21f95c4b 100644 --- a/README.md +++ b/README.md @@ -3,73 +3,14 @@ `annotate-snippets` is a Rust library for annotation of programming code slices. [](https://crates.io/crates/annotate-snippets) +[][Documentation]  The library helps visualize meta information annotating source code slices. It takes a data structure called `Snippet` on the input and produces a `String` which may look like this: -```text -error[E0308]: mismatched types - --> src/format.rs:52:1 - | -51 | ) -> Option<String> { - | -------------- expected `Option<String>` because of return type -52 | / for ann in annotations { -53 | | match (ann.range.0, ann.range.1) { -54 | | (None, None) => continue, -55 | | (Some(start), Some(end)) if start > end_index => continue, -... | -71 | | } -72 | | } - | |_____^ expected enum `std::option::Option`, found () -``` - -[Documentation][] - -[Documentation]: https://docs.rs/annotate-snippets/ - -Usage ------ - -```rust -use annotate_snippets::{Annotation, AnnotationType, Renderer, Slice, Snippet, SourceAnnotation}; - -fn main() { - let snippet = Snippet { - title: Some(Annotation { - label: Some("expected type, found `22`"), - id: None, - annotation_type: AnnotationType::Error, - }), - footer: vec![], - slices: vec![Slice { - source: r#" annotations: vec![SourceAnnotation { - label: "expected struct `annotate_snippets::snippet::Slice`, found reference" - , - range: <22, 25>,"#, - line_start: 26, - origin: Some("examples/footer.rs"), - fold: true, - annotations: vec![ - SourceAnnotation { - label: "", - annotation_type: AnnotationType::Error, - range: (193, 195), - }, - SourceAnnotation { - label: "while parsing this struct", - annotation_type: AnnotationType::Info, - range: (34, 50), - }, - ], - }], - }; - - let renderer = Renderer::plain(); - println!("{}", renderer.render(snippet)); -} -``` + Local Development ----------------- @@ -80,3 +21,5 @@ Local Development When submitting a PR please use [`cargo fmt`][] (nightly). [`cargo fmt`]: https://github.com/rust-lang/rustfmt + +[Documentation]: https://docs.rs/annotate-snippets/ diff --git a/src/lib.rs b/src/lib.rs index 2066e671..8602c0a4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,22 +7,12 @@ //! //! # Example //! -//! ```text -//! error[E0308]: mismatched types -//! --> src/format.rs:52:1 -//! | -//! 51 | ) -> Option<String> { -//! | -------------- expected `Option<String>` because of return type -//! 52 | / for ann in annotations { -//! 53 | | match (ann.range.0, ann.range.1) { -//! 54 | | (None, None) => continue, -//! 55 | | (Some(start), Some(end)) if start > end_index => continue, -//! ... | -//! 71 | | } -//! 72 | | } -//! | |_____^ expected enum `std::option::Option`, found () +//! ```rust +#![doc = include_str!("../examples/expected_type.rs")] //! ``` //! +#![doc = include_str!("../examples/expected_type.svg")] +//! //! The crate uses a three stage process with two conversions between states: //! //! ```text From 87bc645dcb2c2ccc07cb3b4af74c17103c9f618d Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 11 Mar 2024 11:24:33 -0500 Subject: [PATCH 143/455] fix: Remove AnnotationType to Label conversion I suggested this when a `Label`s title was an `Option`. We shouldn't be creating labels with empty strings. --- src/snippet.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/snippet.rs b/src/snippet.rs index 37f431a1..a2547327 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -113,15 +113,6 @@ impl<'a> Label<'a> { } } -impl From<AnnotationType> for Label<'_> { - fn from(annotation_type: AnnotationType) -> Self { - Label { - annotation_type, - label: "", - } - } -} - /// Structure containing the slice of text to be annotated and /// basic information about the location of the slice. /// From b49f9471d920c7f561fa61970039b0ba44e448ac Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 11 Mar 2024 14:12:18 -0500 Subject: [PATCH 144/455] fix!: Rename AnnotationType to Level --- src/renderer/display_list.rs | 54 +++++++------------ src/snippet.rs | 25 ++++----- tests/fixtures/deserialize.rs | 23 ++++---- tests/fixtures/no-color/issue_52.toml | 4 +- tests/fixtures/no-color/issue_9.toml | 8 +-- .../no-color/multiline_annotation.toml | 6 +-- .../no-color/multiline_annotation2.toml | 4 +- .../no-color/multiline_annotation3.toml | 4 +- .../no-color/multiple_annotations.toml | 8 +-- tests/fixtures/no-color/one_past.toml | 4 +- tests/fixtures/no-color/simple.toml | 6 +-- tests/fixtures/no-color/strip_line.toml | 4 +- tests/fixtures/no-color/strip_line_char.toml | 4 +- .../fixtures/no-color/strip_line_non_ws.toml | 4 +- 14 files changed, 70 insertions(+), 88 deletions(-) diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index 01484d45..f0bc537c 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -649,14 +649,14 @@ pub enum DisplayAnnotationType { Help, } -impl From<snippet::AnnotationType> for DisplayAnnotationType { - fn from(at: snippet::AnnotationType) -> Self { +impl From<snippet::Level> for DisplayAnnotationType { + fn from(at: snippet::Level) -> Self { match at { - snippet::AnnotationType::Error => DisplayAnnotationType::Error, - snippet::AnnotationType::Warning => DisplayAnnotationType::Warning, - snippet::AnnotationType::Info => DisplayAnnotationType::Info, - snippet::AnnotationType::Note => DisplayAnnotationType::Note, - snippet::AnnotationType::Help => DisplayAnnotationType::Help, + snippet::Level::Error => DisplayAnnotationType::Error, + snippet::Level::Warning => DisplayAnnotationType::Warning, + snippet::Level::Info => DisplayAnnotationType::Info, + snippet::Level::Note => DisplayAnnotationType::Note, + snippet::Level::Help => DisplayAnnotationType::Help, } } } @@ -736,7 +736,7 @@ fn format_label( fn format_title<'a>(title: snippet::Label<'a>, id: Option<&'a str>) -> DisplayLine<'a> { DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { - annotation_type: DisplayAnnotationType::from(title.annotation_type), + annotation_type: DisplayAnnotationType::from(title.level), id, label: format_label(Some(title.label), Some(DisplayTextStyle::Emphasis)), }, @@ -750,7 +750,7 @@ fn format_footer(footer: snippet::Label<'_>) -> Vec<DisplayLine<'_>> { for (i, line) in footer.label.lines().enumerate() { result.push(DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { - annotation_type: DisplayAnnotationType::from(footer.annotation_type), + annotation_type: DisplayAnnotationType::from(footer.level), id: None, label: format_label(Some(line), None), }, @@ -1010,10 +1010,10 @@ fn format_body( // It would be nice to use filter_drain here once it's stable. annotations.retain(|annotation| { let body_idx = idx + annotation_line_count; - let annotation_type = match annotation.annotation_type { - snippet::AnnotationType::Error => DisplayAnnotationType::None, - snippet::AnnotationType::Warning => DisplayAnnotationType::None, - _ => DisplayAnnotationType::from(annotation.annotation_type), + let annotation_type = match annotation.level { + snippet::Level::Error => DisplayAnnotationType::None, + snippet::Level::Warning => DisplayAnnotationType::None, + _ => DisplayAnnotationType::from(annotation.level), }; match annotation.range { Range { start, .. } if start > line_end_index => true, @@ -1036,9 +1036,7 @@ fn format_body( label: format_label(Some(annotation.label), None), }, range, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), + annotation_type: DisplayAnnotationType::from(annotation.level), annotation_part: DisplayAnnotationPart::Standalone, }, }, @@ -1059,9 +1057,7 @@ fn format_body( { inline_marks.push(DisplayMark { mark_type: DisplayMarkType::AnnotationStart, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), + annotation_type: DisplayAnnotationType::from(annotation.level), }); } } else { @@ -1079,9 +1075,7 @@ fn format_body( label: vec![], }, range, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), + annotation_type: DisplayAnnotationType::from(annotation.level), annotation_part: DisplayAnnotationPart::MultilineStart, }, }, @@ -1098,9 +1092,7 @@ fn format_body( { inline_marks.push(DisplayMark { mark_type: DisplayMarkType::AnnotationThrough, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), + annotation_type: DisplayAnnotationType::from(annotation.level), }); } true @@ -1117,9 +1109,7 @@ fn format_body( { inline_marks.push(DisplayMark { mark_type: DisplayMarkType::AnnotationThrough, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), + annotation_type: DisplayAnnotationType::from(annotation.level), }); } @@ -1131,9 +1121,7 @@ fn format_body( lineno: None, inline_marks: vec![DisplayMark { mark_type: DisplayMarkType::AnnotationThrough, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), + annotation_type: DisplayAnnotationType::from(annotation.level), }], line: DisplaySourceLine::Annotation { annotation: Annotation { @@ -1142,9 +1130,7 @@ fn format_body( label: format_label(Some(annotation.label), None), }, range, - annotation_type: DisplayAnnotationType::from( - annotation.annotation_type, - ), + annotation_type: DisplayAnnotationType::from(annotation.level), annotation_part: DisplayAnnotationPart::MultilineEnd, }, }, diff --git a/src/snippet.rs b/src/snippet.rs index a2547327..f8907b87 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -67,35 +67,32 @@ impl<'a> Snippet<'a> { } pub struct Label<'a> { - pub(crate) annotation_type: AnnotationType, + pub(crate) level: Level, pub(crate) label: &'a str, } impl<'a> Label<'a> { - pub fn new(annotation_type: AnnotationType, label: &'a str) -> Self { - Self { - annotation_type, - label, - } + pub fn new(level: Level, label: &'a str) -> Self { + Self { level, label } } pub fn error(label: &'a str) -> Self { - Self::new(AnnotationType::Error, label) + Self::new(Level::Error, label) } pub fn warning(label: &'a str) -> Self { - Self::new(AnnotationType::Warning, label) + Self::new(Level::Warning, label) } pub fn info(label: &'a str) -> Self { - Self::new(AnnotationType::Info, label) + Self::new(Level::Info, label) } pub fn note(label: &'a str) -> Self { - Self::new(AnnotationType::Note, label) + Self::new(Level::Note, label) } pub fn help(label: &'a str) -> Self { - Self::new(AnnotationType::Help, label) + Self::new(Level::Help, label) } pub fn label(mut self, label: &'a str) -> Self { @@ -108,7 +105,7 @@ impl<'a> Label<'a> { SourceAnnotation { range: span, label: self.label, - annotation_type: self.annotation_type, + level: self.level, } } } @@ -155,7 +152,7 @@ impl<'a> Slice<'a> { /// Types of annotations. #[derive(Debug, Clone, Copy, PartialEq)] -pub enum AnnotationType { +pub enum Level { /// Error annotations are displayed using red color and "^" character. Error, /// Warning annotations are displayed using blue color and "-" character. @@ -173,5 +170,5 @@ pub struct SourceAnnotation<'a> { /// The byte range of the annotation in the `source` string pub(crate) range: Range<usize>, pub(crate) label: &'a str, - pub(crate) annotation_type: AnnotationType, + pub(crate) level: Level, } diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs index a01c343c..ebdf592f 100644 --- a/tests/fixtures/deserialize.rs +++ b/tests/fixtures/deserialize.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Deserializer, Serialize}; use std::ops::Range; use annotate_snippets::{ - renderer::Margin, AnnotationType, Label, Renderer, Slice, Snippet, SourceAnnotation, + renderer::Margin, Label, Level, Renderer, Slice, Snippet, SourceAnnotation, }; #[derive(Deserialize)] @@ -63,8 +63,7 @@ where LabelDef<'a>, ); - Wrapper::deserialize(deserializer) - .map(|Wrapper(label)| Label::new(label.annotation_type, label.label)) + Wrapper::deserialize(deserializer).map(|Wrapper(label)| Label::new(label.level, label.label)) } fn deserialize_labels<'de, D>(deserializer: D) -> Result<Vec<Label<'de>>, D::Error> @@ -80,7 +79,7 @@ where let v = Vec::deserialize(deserializer)?; Ok(v.into_iter() - .map(|Wrapper(a)| Label::new(a.annotation_type, a.label)) + .map(|Wrapper(a)| Label::new(a.level, a.label)) .collect()) } @@ -151,8 +150,8 @@ pub struct SourceAnnotationDef<'a> { pub range: Range<usize>, #[serde(borrow)] pub label: &'a str, - #[serde(with = "AnnotationTypeDef")] - pub annotation_type: AnnotationType, + #[serde(with = "LevelDef")] + pub level: Level, } impl<'a> From<SourceAnnotationDef<'a>> for SourceAnnotation<'a> { @@ -160,24 +159,24 @@ impl<'a> From<SourceAnnotationDef<'a>> for SourceAnnotation<'a> { let SourceAnnotationDef { range, label, - annotation_type, + level, } = val; - Label::new(annotation_type, label).span(range) + Label::new(level, label).span(range) } } #[derive(Serialize, Deserialize)] pub struct LabelDef<'a> { - #[serde(with = "AnnotationTypeDef")] - pub annotation_type: AnnotationType, + #[serde(with = "LevelDef")] + pub level: Level, #[serde(borrow)] pub label: &'a str, } #[allow(dead_code)] #[derive(Serialize, Deserialize)] -#[serde(remote = "AnnotationType")] -enum AnnotationTypeDef { +#[serde(remote = "Level")] +enum LevelDef { Error, Warning, Info, diff --git a/tests/fixtures/no-color/issue_52.toml b/tests/fixtures/no-color/issue_52.toml index ea239dd3..0c2378e6 100644 --- a/tests/fixtures/no-color/issue_52.toml +++ b/tests/fixtures/no-color/issue_52.toml @@ -1,5 +1,5 @@ [snippet.title] -annotation_type = "Error" +level = "Error" label = "" [[snippet.slices]] @@ -13,5 +13,5 @@ origin = "path/to/error.rs" fold = true [[snippet.slices.annotations]] label = "error here" -annotation_type = "Warning" +level = "Warning" range = [2,16] diff --git a/tests/fixtures/no-color/issue_9.toml b/tests/fixtures/no-color/issue_9.toml index ee1fe27b..c6a1c454 100644 --- a/tests/fixtures/no-color/issue_9.toml +++ b/tests/fixtures/no-color/issue_9.toml @@ -1,6 +1,6 @@ [snippet.title] label = "expected one of `.`, `;`, `?`, or an operator, found `for`" -annotation_type = "Error" +level = "Error" [[snippet.slices]] source = "let x = vec![1];" @@ -8,7 +8,7 @@ line_start = 4 origin = "/code/rust/src/test/ui/annotate-snippet/suggestion.rs" [[snippet.slices.annotations]] label = "move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait" -annotation_type = "Warning" +level = "Warning" range = [4, 5] [[snippet.slices]] @@ -16,7 +16,7 @@ source = "let y = x;" line_start = 7 [[snippet.slices.annotations]] label = "value moved here" -annotation_type = "Warning" +level = "Warning" range = [8, 9] [[snippet.slices]] @@ -24,5 +24,5 @@ source = "x;" line_start = 9 [[snippet.slices.annotations]] label = "value used here after move" -annotation_type = "Error" +level = "Error" range = [0, 1] diff --git a/tests/fixtures/no-color/multiline_annotation.toml b/tests/fixtures/no-color/multiline_annotation.toml index 48c07250..64a3513b 100644 --- a/tests/fixtures/no-color/multiline_annotation.toml +++ b/tests/fixtures/no-color/multiline_annotation.toml @@ -28,13 +28,13 @@ origin = "src/format.rs" fold = true [[snippet.slices.annotations]] label = "expected `std::option::Option<std::string::String>` because of return type" -annotation_type = "Warning" +level = "Warning" range = [5, 19] [[snippet.slices.annotations]] label = "expected enum `std::option::Option`, found ()" -annotation_type = "Error" +level = "Error" range = [22, 766] [snippet] -title = { annotation_type = "Error", label = "mismatched types" } +title = { level = "Error", label = "mismatched types" } id = "E0308" diff --git a/tests/fixtures/no-color/multiline_annotation2.toml b/tests/fixtures/no-color/multiline_annotation2.toml index 89294bcc..7c487ab7 100644 --- a/tests/fixtures/no-color/multiline_annotation2.toml +++ b/tests/fixtures/no-color/multiline_annotation2.toml @@ -9,9 +9,9 @@ origin = "src/display_list.rs" fold = false [[snippet.slices.annotations]] label = "missing fields `lineno`, `content`" -annotation_type = "Error" +level = "Error" range = [31, 128] [snippet] -title = { annotation_type = "Error", label = "pattern does not mention fields `lineno`, `content`" } +title = { level = "Error", label = "pattern does not mention fields `lineno`, `content`" } id = "E0027" diff --git a/tests/fixtures/no-color/multiline_annotation3.toml b/tests/fixtures/no-color/multiline_annotation3.toml index efc1f5f6..3850ecab 100644 --- a/tests/fixtures/no-color/multiline_annotation3.toml +++ b/tests/fixtures/no-color/multiline_annotation3.toml @@ -9,9 +9,9 @@ origin = "foo.txt" fold = false [[snippet.slices.annotations]] label = "this should not be on separate lines" -annotation_type = "Error" +level = "Error" range = [11, 18] [snippet] -title = { annotation_type = "Error", label = "spacing error found" } +title = { level = "Error", label = "spacing error found" } id = "E####" diff --git a/tests/fixtures/no-color/multiple_annotations.toml b/tests/fixtures/no-color/multiple_annotations.toml index ae35cef6..bbb2e285 100644 --- a/tests/fixtures/no-color/multiple_annotations.toml +++ b/tests/fixtures/no-color/multiple_annotations.toml @@ -1,5 +1,5 @@ [snippet.title] -annotation_type = "Error" +level = "Error" label = "" [[snippet.slices]] @@ -17,13 +17,13 @@ fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation> line_start = 96 [[snippet.slices.annotations]] label = "Variable defined here" -annotation_type = "Error" +level = "Error" range = [100, 110] [[snippet.slices.annotations]] label = "Referenced here" -annotation_type = "Error" +level = "Error" range = [184, 194] [[snippet.slices.annotations]] label = "Referenced again here" -annotation_type = "Error" +level = "Error" range = [243, 253] diff --git a/tests/fixtures/no-color/one_past.toml b/tests/fixtures/no-color/one_past.toml index 62d1d42c..d7eb0416 100644 --- a/tests/fixtures/no-color/one_past.toml +++ b/tests/fixtures/no-color/one_past.toml @@ -1,6 +1,6 @@ [snippet.title] label = "expected `.`, `=`" -annotation_type = "Error" +level = "Error" [[snippet.slices]] source = "asdf" @@ -8,5 +8,5 @@ line_start = 1 origin = "Cargo.toml" [[snippet.slices.annotations]] label = "" -annotation_type = "Error" +level = "Error" range = [4, 5] diff --git a/tests/fixtures/no-color/simple.toml b/tests/fixtures/no-color/simple.toml index 2e9b6d89..0d592c1e 100644 --- a/tests/fixtures/no-color/simple.toml +++ b/tests/fixtures/no-color/simple.toml @@ -7,12 +7,12 @@ line_start = 169 origin = "src/format_color.rs" [[snippet.slices.annotations]] label = "unexpected token" -annotation_type = "Error" +level = "Error" range = [20, 23] [[snippet.slices.annotations]] label = "expected one of `.`, `;`, `?`, or an operator here" -annotation_type = "Warning" +level = "Warning" range = [10, 11] [snippet.title] label = "expected one of `.`, `;`, `?`, or an operator, found `for`" -annotation_type = "Error" \ No newline at end of file +level = "Error" diff --git a/tests/fixtures/no-color/strip_line.toml b/tests/fixtures/no-color/strip_line.toml index 9de50aac..eedfcbba 100644 --- a/tests/fixtures/no-color/strip_line.toml +++ b/tests/fixtures/no-color/strip_line.toml @@ -1,5 +1,5 @@ [snippet] -title = { annotation_type = "Error", label = "mismatched types" } +title = { level = "Error", label = "mismatched types" } id = "E0308" [[snippet.slices]] @@ -9,7 +9,7 @@ origin = "$DIR/whitespace-trimming.rs" [[snippet.slices.annotations]] label = "expected (), found integer" -annotation_type = "Error" +level = "Error" range = [192, 194] [renderer] diff --git a/tests/fixtures/no-color/strip_line_char.toml b/tests/fixtures/no-color/strip_line_char.toml index 72df2abd..dc51bb6b 100644 --- a/tests/fixtures/no-color/strip_line_char.toml +++ b/tests/fixtures/no-color/strip_line_char.toml @@ -1,5 +1,5 @@ [snippet] -title = { annotation_type = "Error", label = "mismatched types" } +title = { level = "Error", label = "mismatched types" } id = "E0308" [[snippet.slices]] @@ -9,7 +9,7 @@ origin = "$DIR/whitespace-trimming.rs" [[snippet.slices.annotations]] label = "expected (), found integer" -annotation_type = "Error" +level = "Error" range = [192, 194] [renderer] diff --git a/tests/fixtures/no-color/strip_line_non_ws.toml b/tests/fixtures/no-color/strip_line_non_ws.toml index 236d8f17..f56f55d1 100644 --- a/tests/fixtures/no-color/strip_line_non_ws.toml +++ b/tests/fixtures/no-color/strip_line_non_ws.toml @@ -1,5 +1,5 @@ [snippet] -title = { annotation_type = "Error", label = "mismatched types" } +title = { level = "Error", label = "mismatched types" } id = "E0308" [[snippet.slices]] @@ -9,7 +9,7 @@ origin = "$DIR/non-whitespace-trimming.rs" [[snippet.slices.annotations]] label = "expected (), found integer" -annotation_type = "Error" +level = "Error" range = [240, 242] [renderer] From bbf9c5fe27e83652433151cbfc7d6cafc02a8c47 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 11 Mar 2024 14:13:31 -0500 Subject: [PATCH 145/455] fix!: Rename SourceAnnotation to Annotation --- src/snippet.rs | 12 ++++++------ tests/fixtures/deserialize.rs | 22 +++++++++------------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/snippet.rs b/src/snippet.rs index f8907b87..dde50186 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -100,9 +100,9 @@ impl<'a> Label<'a> { self } - /// Create a [`SourceAnnotation`] with the given span for a [`Slice`] - pub fn span(&self, span: Range<usize>) -> SourceAnnotation<'a> { - SourceAnnotation { + /// Create a [`Annotation`] with the given span for a [`Slice`] + pub fn span(&self, span: Range<usize>) -> Annotation<'a> { + Annotation { range: span, label: self.label, level: self.level, @@ -119,7 +119,7 @@ pub struct Slice<'a> { pub(crate) source: &'a str, pub(crate) line_start: usize, pub(crate) origin: Option<&'a str>, - pub(crate) annotations: Vec<SourceAnnotation<'a>>, + pub(crate) annotations: Vec<Annotation<'a>>, pub(crate) fold: bool, } @@ -139,7 +139,7 @@ impl<'a> Slice<'a> { self } - pub fn annotation(mut self, annotation: SourceAnnotation<'a>) -> Self { + pub fn annotation(mut self, annotation: Annotation<'a>) -> Self { self.annotations.push(annotation); self } @@ -166,7 +166,7 @@ pub enum Level { /// /// This gets created by [`Label::span`]. #[derive(Debug)] -pub struct SourceAnnotation<'a> { +pub struct Annotation<'a> { /// The byte range of the annotation in the `source` string pub(crate) range: Range<usize>, pub(crate) label: &'a str, diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs index ebdf592f..bd94770c 100644 --- a/tests/fixtures/deserialize.rs +++ b/tests/fixtures/deserialize.rs @@ -1,9 +1,7 @@ use serde::{Deserialize, Deserializer, Serialize}; use std::ops::Range; -use annotate_snippets::{ - renderer::Margin, Label, Level, Renderer, Slice, Snippet, SourceAnnotation, -}; +use annotate_snippets::{renderer::Margin, Annotation, Label, Level, Renderer, Slice, Snippet}; #[derive(Deserialize)] pub struct Fixture<'a> { @@ -105,9 +103,9 @@ pub struct SliceDef<'a> { pub line_start: usize, #[serde(borrow)] pub origin: Option<&'a str>, - #[serde(deserialize_with = "deserialize_source_annotations")] + #[serde(deserialize_with = "deserialize_annotations")] #[serde(borrow)] - pub annotations: Vec<SourceAnnotation<'a>>, + pub annotations: Vec<Annotation<'a>>, #[serde(default)] pub fold: bool, } @@ -132,21 +130,19 @@ impl<'a> From<SliceDef<'a>> for Slice<'a> { } } -fn deserialize_source_annotations<'de, D>( - deserializer: D, -) -> Result<Vec<SourceAnnotation<'de>>, D::Error> +fn deserialize_annotations<'de, D>(deserializer: D) -> Result<Vec<Annotation<'de>>, D::Error> where D: Deserializer<'de>, { #[derive(Deserialize)] - struct Wrapper<'a>(#[serde(borrow)] SourceAnnotationDef<'a>); + struct Wrapper<'a>(#[serde(borrow)] AnnotationDef<'a>); let v = Vec::deserialize(deserializer)?; Ok(v.into_iter().map(|Wrapper(a)| a.into()).collect()) } #[derive(Serialize, Deserialize)] -pub struct SourceAnnotationDef<'a> { +pub struct AnnotationDef<'a> { pub range: Range<usize>, #[serde(borrow)] pub label: &'a str, @@ -154,9 +150,9 @@ pub struct SourceAnnotationDef<'a> { pub level: Level, } -impl<'a> From<SourceAnnotationDef<'a>> for SourceAnnotation<'a> { - fn from(val: SourceAnnotationDef<'a>) -> Self { - let SourceAnnotationDef { +impl<'a> From<AnnotationDef<'a>> for Annotation<'a> { + fn from(val: AnnotationDef<'a>) -> Self { + let AnnotationDef { range, label, level, From 105da760b6e1bd4cfce4c642ac679ecf6011f511 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 11 Mar 2024 15:13:58 -0500 Subject: [PATCH 146/455] fix!: Rename Snippet to Message --- benches/simple.rs | 6 ++--- examples/expected_type.rs | 6 ++--- examples/footer.rs | 6 ++--- examples/format.rs | 6 ++--- examples/multislice.rs | 6 ++--- src/lib.rs | 4 +-- src/renderer/display_list.rs | 18 ++++++------- src/renderer/mod.rs | 14 +++++----- src/snippet.rs | 6 ++--- tests/fixtures/deserialize.rs | 26 +++++++++---------- tests/fixtures/main.rs | 8 +++--- tests/fixtures/no-color/issue_52.toml | 6 ++--- tests/fixtures/no-color/issue_9.toml | 14 +++++----- .../no-color/multiline_annotation.toml | 8 +++--- .../no-color/multiline_annotation2.toml | 6 ++--- .../no-color/multiline_annotation3.toml | 6 ++--- .../no-color/multiple_annotations.toml | 10 +++---- tests/fixtures/no-color/one_past.toml | 6 ++--- tests/fixtures/no-color/simple.toml | 8 +++--- tests/fixtures/no-color/strip_line.toml | 6 ++--- tests/fixtures/no-color/strip_line_char.toml | 6 ++--- .../fixtures/no-color/strip_line_non_ws.toml | 6 ++--- tests/formatter.rs | 12 ++++----- 23 files changed, 100 insertions(+), 100 deletions(-) diff --git a/benches/simple.rs b/benches/simple.rs index 4eacfdaa..75a852f0 100644 --- a/benches/simple.rs +++ b/benches/simple.rs @@ -4,7 +4,7 @@ extern crate criterion; use criterion::{black_box, Criterion}; -use annotate_snippets::{Label, Renderer, Slice, Snippet}; +use annotate_snippets::{Label, Message, Renderer, Slice}; fn create_snippet(renderer: Renderer) { let source = r#") -> Option<String> { @@ -29,7 +29,7 @@ fn create_snippet(renderer: Renderer) { _ => continue, } }"#; - let snippet = Snippet::error("mismatched types").id("E0308").slice( + let message = Message::error("mismatched types").id("E0308").slice( Slice::new(source, 51) .origin("src/format.rs") .annotation( @@ -38,7 +38,7 @@ fn create_snippet(renderer: Renderer) { .annotation(Label::error("expected enum `std::option::Option`").span(26..724)), ); - let _result = renderer.render(snippet).to_string(); + let _result = renderer.render(message).to_string(); } pub fn criterion_benchmark(c: &mut Criterion) { diff --git a/examples/expected_type.rs b/examples/expected_type.rs index adcbeff1..ccdb8351 100644 --- a/examples/expected_type.rs +++ b/examples/expected_type.rs @@ -1,11 +1,11 @@ -use annotate_snippets::{Label, Renderer, Slice, Snippet}; +use annotate_snippets::{Label, Message, Renderer, Slice}; fn main() { let source = r#" annotations: vec![SourceAnnotation { label: "expected struct `annotate_snippets::snippet::Slice`, found reference" , range: <22, 25>,"#; - let snippet = Snippet::error("expected type, found `22`").slice( + let message = Message::error("expected type, found `22`").slice( Slice::new(source, 26) .origin("examples/footer.rs") .fold(true) @@ -19,5 +19,5 @@ fn main() { ); let renderer = Renderer::styled(); - anstream::println!("{}", renderer.render(snippet)); + anstream::println!("{}", renderer.render(message)); } diff --git a/examples/footer.rs b/examples/footer.rs index 6c45328a..924fae2f 100644 --- a/examples/footer.rs +++ b/examples/footer.rs @@ -1,7 +1,7 @@ -use annotate_snippets::{Label, Renderer, Slice, Snippet}; +use annotate_snippets::{Label, Message, Renderer, Slice}; fn main() { - let snippet = Snippet::error("mismatched types") + let message = Message::error("mismatched types") .id("E0308") .slice( Slice::new(" slices: vec![\"A\",", 13) @@ -18,5 +18,5 @@ fn main() { )); let renderer = Renderer::styled(); - anstream::println!("{}", renderer.render(snippet)); + anstream::println!("{}", renderer.render(message)); } diff --git a/examples/format.rs b/examples/format.rs index 1337812e..85e9a82b 100644 --- a/examples/format.rs +++ b/examples/format.rs @@ -1,4 +1,4 @@ -use annotate_snippets::{Label, Renderer, Slice, Snippet}; +use annotate_snippets::{Label, Message, Renderer, Slice}; fn main() { let source = r#") -> Option<String> { @@ -23,7 +23,7 @@ fn main() { _ => continue, } }"#; - let snippet = Snippet::error("mismatched types").id("E0308").slice( + let message = Message::error("mismatched types").id("E0308").slice( Slice::new(source, 51) .origin("src/format.rs") .annotation( @@ -33,5 +33,5 @@ fn main() { ); let renderer = Renderer::styled(); - anstream::println!("{}", renderer.render(snippet)); + anstream::println!("{}", renderer.render(message)); } diff --git a/examples/multislice.rs b/examples/multislice.rs index b6fcb3f8..48e73726 100644 --- a/examples/multislice.rs +++ b/examples/multislice.rs @@ -1,10 +1,10 @@ -use annotate_snippets::{Renderer, Slice, Snippet}; +use annotate_snippets::{Message, Renderer, Slice}; fn main() { - let snippet = Snippet::error("mismatched types") + let message = Message::error("mismatched types") .slice(Slice::new("Foo", 51).origin("src/format.rs")) .slice(Slice::new("Faa", 129).origin("src/display.rs")); let renderer = Renderer::styled(); - anstream::println!("{}", renderer.render(snippet)); + anstream::println!("{}", renderer.render(message)); } diff --git a/src/lib.rs b/src/lib.rs index 8602c0a4..a2e4231d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,10 +16,10 @@ //! The crate uses a three stage process with two conversions between states: //! //! ```text -//! Snippet --> Renderer --> impl Display +//! Message --> Renderer --> impl Display //! ``` //! -//! The input type - [Snippet] is a structure designed +//! The input type - [Message] is a structure designed //! to align with likely output from any parser whose code snippet is to be //! annotated. //! diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index f0bc537c..07216c39 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -106,12 +106,12 @@ impl<'a> DisplayList<'a> { const WARNING_TXT: &'static str = "warning"; pub(crate) fn new( - snippet::Snippet { + snippet::Message { title, id, footer, slices, - }: snippet::Snippet<'a>, + }: snippet::Message<'a>, stylesheet: &'a Stylesheet, anonymized_line_numbers: bool, margin: Option<Margin>, @@ -1206,7 +1206,7 @@ mod tests { #[test] fn test_format_title() { - let input = snippet::Snippet::error("This is a title").id("E0001"); + let input = snippet::Message::error("This is a title").id("E0001"); let output = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { annotation_type: DisplayAnnotationType::Error, @@ -1227,7 +1227,7 @@ mod tests { let line_1 = "This is line 1"; let line_2 = "This is line 2"; let source = [line_1, line_2].join("\n"); - let input = snippet::Snippet::error("").slice(snippet::Slice::new(&source, 5402)); + let input = snippet::Message::error("").slice(snippet::Slice::new(&source, 5402)); let output = from_display_lines(vec![ DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { @@ -1277,7 +1277,7 @@ mod tests { let src_0_len = src_0.len(); let src_1 = "This is slice 2"; let src_1_len = src_1.len(); - let input = snippet::Snippet::error("") + let input = snippet::Message::error("") .slice(snippet::Slice::new(src_0, 5402).origin("file1.rs")) .slice(snippet::Slice::new(src_1, 2).origin("file2.rs")); let output = from_display_lines(vec![ @@ -1350,7 +1350,7 @@ mod tests { let source = [line_1, line_2].join("\n"); // In line 2 let range = 22..24; - let input = snippet::Snippet::error("").slice( + let input = snippet::Message::error("").slice( snippet::Slice::new(&source, 5402) .annotation(snippet::Label::info("Test annotation").span(range.clone())), ); @@ -1420,7 +1420,7 @@ mod tests { #[test] fn test_format_label() { let input = - snippet::Snippet::error("").footer(snippet::Label::error("This __is__ a title")); + snippet::Message::error("").footer(snippet::Label::error("This __is__ a title")); let output = from_display_lines(vec![ DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { @@ -1455,7 +1455,7 @@ mod tests { fn test_i26() { let source = "short"; let label = "label"; - let input = snippet::Snippet::error("").slice( + let input = snippet::Message::error("").slice( snippet::Slice::new(source, 0) .annotation(snippet::Label::error(label).span(0..source.len() + 2)), ); @@ -1464,7 +1464,7 @@ mod tests { #[test] fn test_i_29() { - let snippets = snippet::Snippet::error("oops").slice( + let snippets = snippet::Message::error("oops").slice( snippet::Slice::new("First line\r\nSecond oops line", 1) .origin("<current file>") .fold(true) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 7046407b..ef24cfd5 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -1,9 +1,9 @@ -//! The renderer for [`Snippet`]s +//! The renderer for [`Message`]s //! //! # Example //! ``` -//! use annotate_snippets::{Renderer, Slice, Snippet}; -//! let snippet = Snippet::error("mismatched types") +//! use annotate_snippets::{Renderer, Slice, Message}; +//! let snippet = Message::error("mismatched types") //! .slice(Slice::new("Foo", 51).origin("src/format.rs")) //! .slice(Slice::new("Faa", 129).origin("src/display.rs")); //! @@ -14,14 +14,14 @@ mod display_list; mod margin; pub(crate) mod stylesheet; -use crate::snippet::Snippet; +use crate::snippet::Message; pub use anstyle::*; use display_list::DisplayList; pub use margin::Margin; use std::fmt::Display; use stylesheet::Stylesheet; -/// A renderer for [`Snippet`]s +/// A renderer for [`Message`]s #[derive(Clone)] pub struct Renderer { anonymized_line_numbers: bool, @@ -165,9 +165,9 @@ impl Renderer { } /// Render a snippet into a `Display`able object - pub fn render<'a>(&'a self, snippet: Snippet<'a>) -> impl Display + 'a { + pub fn render<'a>(&'a self, msg: Message<'a>) -> impl Display + 'a { DisplayList::new( - snippet, + msg, &self.stylesheet, self.anonymized_line_numbers, self.margin, diff --git a/src/snippet.rs b/src/snippet.rs index dde50186..61253caa 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -5,7 +5,7 @@ //! ``` //! use annotate_snippets::*; //! -//! Snippet::error("mismatched types") +//! Message::error("mismatched types") //! .slice(Slice::new("Foo", 51).origin("src/format.rs")) //! .slice(Slice::new("Faa", 129).origin("src/display.rs")); //! ``` @@ -13,14 +13,14 @@ use std::ops::Range; /// Primary structure provided for formatting -pub struct Snippet<'a> { +pub struct Message<'a> { pub(crate) title: Label<'a>, pub(crate) id: Option<&'a str>, pub(crate) slices: Vec<Slice<'a>>, pub(crate) footer: Vec<Label<'a>>, } -impl<'a> Snippet<'a> { +impl<'a> Message<'a> { pub fn title(title: Label<'a>) -> Self { Self { title, diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs index bd94770c..6bee4199 100644 --- a/tests/fixtures/deserialize.rs +++ b/tests/fixtures/deserialize.rs @@ -1,18 +1,18 @@ use serde::{Deserialize, Deserializer, Serialize}; use std::ops::Range; -use annotate_snippets::{renderer::Margin, Annotation, Label, Level, Renderer, Slice, Snippet}; +use annotate_snippets::{renderer::Margin, Annotation, Label, Level, Message, Renderer, Slice}; #[derive(Deserialize)] pub struct Fixture<'a> { #[serde(default)] pub renderer: RendererDef, #[serde(borrow)] - pub snippet: SnippetDef<'a>, + pub message: MessageDef<'a>, } #[derive(Deserialize)] -pub struct SnippetDef<'a> { +pub struct MessageDef<'a> { #[serde(deserialize_with = "deserialize_label")] #[serde(borrow)] pub title: Label<'a>, @@ -28,25 +28,25 @@ pub struct SnippetDef<'a> { pub slices: Vec<Slice<'a>>, } -impl<'a> From<SnippetDef<'a>> for Snippet<'a> { - fn from(val: SnippetDef<'a>) -> Self { - let SnippetDef { +impl<'a> From<MessageDef<'a>> for Message<'a> { + fn from(val: MessageDef<'a>) -> Self { + let MessageDef { title, id, footer, slices, } = val; - let mut snippet = Snippet::title(title); + let mut message = Message::title(title); if let Some(id) = id { - snippet = snippet.id(id); + message = message.id(id); } - snippet = slices + message = slices .into_iter() - .fold(snippet, |snippet, slice| snippet.slice(slice)); - snippet = footer + .fold(message, |message, slice| message.slice(slice)); + message = footer .into_iter() - .fold(snippet, |snippet, label| snippet.footer(label)); - snippet + .fold(message, |message, label| message.footer(label)); + message } } diff --git a/tests/fixtures/main.rs b/tests/fixtures/main.rs index c320407a..841b3639 100644 --- a/tests/fixtures/main.rs +++ b/tests/fixtures/main.rs @@ -1,7 +1,7 @@ mod deserialize; use crate::deserialize::Fixture; -use annotate_snippets::{Renderer, Snippet}; +use annotate_snippets::{Message, Renderer}; use snapbox::data::DataFormat; use snapbox::Data; use std::error::Error; @@ -26,8 +26,8 @@ fn setup(input_path: std::path::PathBuf) -> snapbox::harness::Case { fn test(input_path: &std::path::Path) -> Result<Data, Box<dyn Error>> { let src = std::fs::read_to_string(input_path)?; - let (renderer, snippet): (Renderer, Snippet<'_>) = - toml::from_str(&src).map(|a: Fixture| (a.renderer.into(), a.snippet.into()))?; - let actual = renderer.render(snippet).to_string(); + let (renderer, message): (Renderer, Message<'_>) = + toml::from_str(&src).map(|a: Fixture| (a.renderer.into(), a.message.into()))?; + let actual = renderer.render(message).to_string(); Ok(Data::from(actual).coerce_to(DataFormat::TermSvg)) } diff --git a/tests/fixtures/no-color/issue_52.toml b/tests/fixtures/no-color/issue_52.toml index 0c2378e6..749324e7 100644 --- a/tests/fixtures/no-color/issue_52.toml +++ b/tests/fixtures/no-color/issue_52.toml @@ -1,8 +1,8 @@ -[snippet.title] +[message.title] level = "Error" label = "" -[[snippet.slices]] +[[message.slices]] source = """ @@ -11,7 +11,7 @@ invalid syntax line_start = 1 origin = "path/to/error.rs" fold = true -[[snippet.slices.annotations]] +[[message.slices.annotations]] label = "error here" level = "Warning" range = [2,16] diff --git a/tests/fixtures/no-color/issue_9.toml b/tests/fixtures/no-color/issue_9.toml index c6a1c454..e534505d 100644 --- a/tests/fixtures/no-color/issue_9.toml +++ b/tests/fixtures/no-color/issue_9.toml @@ -1,28 +1,28 @@ -[snippet.title] +[message.title] label = "expected one of `.`, `;`, `?`, or an operator, found `for`" level = "Error" -[[snippet.slices]] +[[message.slices]] source = "let x = vec![1];" line_start = 4 origin = "/code/rust/src/test/ui/annotate-snippet/suggestion.rs" -[[snippet.slices.annotations]] +[[message.slices.annotations]] label = "move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait" level = "Warning" range = [4, 5] -[[snippet.slices]] +[[message.slices]] source = "let y = x;" line_start = 7 -[[snippet.slices.annotations]] +[[message.slices.annotations]] label = "value moved here" level = "Warning" range = [8, 9] -[[snippet.slices]] +[[message.slices]] source = "x;" line_start = 9 -[[snippet.slices.annotations]] +[[message.slices.annotations]] label = "value used here after move" level = "Error" range = [0, 1] diff --git a/tests/fixtures/no-color/multiline_annotation.toml b/tests/fixtures/no-color/multiline_annotation.toml index 64a3513b..55b0513a 100644 --- a/tests/fixtures/no-color/multiline_annotation.toml +++ b/tests/fixtures/no-color/multiline_annotation.toml @@ -1,4 +1,4 @@ -[[snippet.slices]] +[[message.slices]] source = """ ) -> Option<String> { for ann in annotations { @@ -26,15 +26,15 @@ source = """ line_start = 51 origin = "src/format.rs" fold = true -[[snippet.slices.annotations]] +[[message.slices.annotations]] label = "expected `std::option::Option<std::string::String>` because of return type" level = "Warning" range = [5, 19] -[[snippet.slices.annotations]] +[[message.slices.annotations]] label = "expected enum `std::option::Option`, found ()" level = "Error" range = [22, 766] -[snippet] +[message] title = { level = "Error", label = "mismatched types" } id = "E0308" diff --git a/tests/fixtures/no-color/multiline_annotation2.toml b/tests/fixtures/no-color/multiline_annotation2.toml index 7c487ab7..b1506ef0 100644 --- a/tests/fixtures/no-color/multiline_annotation2.toml +++ b/tests/fixtures/no-color/multiline_annotation2.toml @@ -1,4 +1,4 @@ -[[snippet.slices]] +[[message.slices]] source = """ if let DisplayLine::Source { ref mut inline_marks, @@ -7,11 +7,11 @@ source = """ line_start = 139 origin = "src/display_list.rs" fold = false -[[snippet.slices.annotations]] +[[message.slices.annotations]] label = "missing fields `lineno`, `content`" level = "Error" range = [31, 128] -[snippet] +[message] title = { level = "Error", label = "pattern does not mention fields `lineno`, `content`" } id = "E0027" diff --git a/tests/fixtures/no-color/multiline_annotation3.toml b/tests/fixtures/no-color/multiline_annotation3.toml index 3850ecab..a5f0400e 100644 --- a/tests/fixtures/no-color/multiline_annotation3.toml +++ b/tests/fixtures/no-color/multiline_annotation3.toml @@ -1,4 +1,4 @@ -[[snippet.slices]] +[[message.slices]] source = """ This is an exampl e of an edge case of an annotation overflowing @@ -7,11 +7,11 @@ to exactly one character on next line. line_start = 26 origin = "foo.txt" fold = false -[[snippet.slices.annotations]] +[[message.slices.annotations]] label = "this should not be on separate lines" level = "Error" range = [11, 18] -[snippet] +[message] title = { level = "Error", label = "spacing error found" } id = "E####" diff --git a/tests/fixtures/no-color/multiple_annotations.toml b/tests/fixtures/no-color/multiple_annotations.toml index bbb2e285..1c97a27c 100644 --- a/tests/fixtures/no-color/multiple_annotations.toml +++ b/tests/fixtures/no-color/multiple_annotations.toml @@ -1,8 +1,8 @@ -[snippet.title] +[message.title] level = "Error" label = "" -[[snippet.slices]] +[[message.slices]] source = """ fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>) { if let Some(annotation) = main_annotation { @@ -15,15 +15,15 @@ fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation> } """ line_start = 96 -[[snippet.slices.annotations]] +[[message.slices.annotations]] label = "Variable defined here" level = "Error" range = [100, 110] -[[snippet.slices.annotations]] +[[message.slices.annotations]] label = "Referenced here" level = "Error" range = [184, 194] -[[snippet.slices.annotations]] +[[message.slices.annotations]] label = "Referenced again here" level = "Error" range = [243, 253] diff --git a/tests/fixtures/no-color/one_past.toml b/tests/fixtures/no-color/one_past.toml index d7eb0416..6dbee4bf 100644 --- a/tests/fixtures/no-color/one_past.toml +++ b/tests/fixtures/no-color/one_past.toml @@ -1,12 +1,12 @@ -[snippet.title] +[message.title] label = "expected `.`, `=`" level = "Error" -[[snippet.slices]] +[[message.slices]] source = "asdf" line_start = 1 origin = "Cargo.toml" -[[snippet.slices.annotations]] +[[message.slices.annotations]] label = "" level = "Error" range = [4, 5] diff --git a/tests/fixtures/no-color/simple.toml b/tests/fixtures/no-color/simple.toml index 0d592c1e..c511ef92 100644 --- a/tests/fixtures/no-color/simple.toml +++ b/tests/fixtures/no-color/simple.toml @@ -1,18 +1,18 @@ -[[snippet.slices]] +[[message.slices]] source = """ }) for line in &self.body {""" line_start = 169 origin = "src/format_color.rs" -[[snippet.slices.annotations]] +[[message.slices.annotations]] label = "unexpected token" level = "Error" range = [20, 23] -[[snippet.slices.annotations]] +[[message.slices.annotations]] label = "expected one of `.`, `;`, `?`, or an operator here" level = "Warning" range = [10, 11] -[snippet.title] +[message.title] label = "expected one of `.`, `;`, `?`, or an operator, found `for`" level = "Error" diff --git a/tests/fixtures/no-color/strip_line.toml b/tests/fixtures/no-color/strip_line.toml index eedfcbba..02601257 100644 --- a/tests/fixtures/no-color/strip_line.toml +++ b/tests/fixtures/no-color/strip_line.toml @@ -1,13 +1,13 @@ -[snippet] +[message] title = { level = "Error", label = "mismatched types" } id = "E0308" -[[snippet.slices]] +[[message.slices]] source = " let _: () = 42;" line_start = 4 origin = "$DIR/whitespace-trimming.rs" -[[snippet.slices.annotations]] +[[message.slices.annotations]] label = "expected (), found integer" level = "Error" range = [192, 194] diff --git a/tests/fixtures/no-color/strip_line_char.toml b/tests/fixtures/no-color/strip_line_char.toml index dc51bb6b..70ee2e34 100644 --- a/tests/fixtures/no-color/strip_line_char.toml +++ b/tests/fixtures/no-color/strip_line_char.toml @@ -1,13 +1,13 @@ -[snippet] +[message] title = { level = "Error", label = "mismatched types" } id = "E0308" -[[snippet.slices]] +[[message.slices]] source = " let _: () = 42ñ" line_start = 4 origin = "$DIR/whitespace-trimming.rs" -[[snippet.slices.annotations]] +[[message.slices.annotations]] label = "expected (), found integer" level = "Error" range = [192, 194] diff --git a/tests/fixtures/no-color/strip_line_non_ws.toml b/tests/fixtures/no-color/strip_line_non_ws.toml index f56f55d1..e133bbc0 100644 --- a/tests/fixtures/no-color/strip_line_non_ws.toml +++ b/tests/fixtures/no-color/strip_line_non_ws.toml @@ -1,13 +1,13 @@ -[snippet] +[message] title = { level = "Error", label = "mismatched types" } id = "E0308" -[[snippet.slices]] +[[message.slices]] source = " let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = ();" line_start = 4 origin = "$DIR/non-whitespace-trimming.rs" -[[snippet.slices.annotations]] +[[message.slices.annotations]] label = "expected (), found integer" level = "Error" range = [240, 242] diff --git a/tests/formatter.rs b/tests/formatter.rs index 8f5c09fc..4b903665 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -1,8 +1,8 @@ -use annotate_snippets::{Label, Renderer, Slice, Snippet}; +use annotate_snippets::{Label, Message, Renderer, Slice}; #[test] fn test_i_29() { - let snippets = Snippet::error("oops").slice( + let snippets = Message::error("oops").slice( Slice::new("First line\r\nSecond oops line", 1) .origin("<current file>") .annotation(Label::error("oops").span(19..23)) @@ -22,7 +22,7 @@ fn test_i_29() { #[test] fn test_point_to_double_width_characters() { - let snippets = Snippet::error("").slice( + let snippets = Message::error("").slice( Slice::new("こんにちは、世界", 1) .origin("<current file>") .annotation(Label::error("world").span(12..16)), @@ -41,7 +41,7 @@ fn test_point_to_double_width_characters() { #[test] fn test_point_to_double_width_characters_across_lines() { - let snippets = Snippet::error("").slice( + let snippets = Message::error("").slice( Slice::new("おはよう\nございます", 1) .origin("<current file>") .annotation(Label::error("Good morning").span(4..15)), @@ -62,7 +62,7 @@ fn test_point_to_double_width_characters_across_lines() { #[test] fn test_point_to_double_width_characters_multiple() { - let snippets = Snippet::error("").slice( + let snippets = Message::error("").slice( Slice::new("お寿司\n食べたい🍣", 1) .origin("<current file>") .annotation(Label::error("Sushi1").span(0..6)) @@ -84,7 +84,7 @@ fn test_point_to_double_width_characters_multiple() { #[test] fn test_point_to_double_width_characters_mixed() { - let snippets = Snippet::error("").slice( + let snippets = Message::error("").slice( Slice::new("こんにちは、新しいWorld!", 1) .origin("<current file>") .annotation(Label::error("New world").span(12..23)), From 1c18950300cf8b93d92d89e9797ed0bae02c0a37 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 11 Mar 2024 15:18:05 -0500 Subject: [PATCH 147/455] fix!: Rename Slice to Snippet --- benches/simple.rs | 6 +-- examples/expected_type.rs | 6 +-- examples/footer.rs | 6 +-- examples/format.rs | 6 +-- examples/multislice.rs | 6 +-- src/renderer/display_list.rs | 48 +++++++++---------- src/renderer/mod.rs | 6 +-- src/snippet.rs | 22 ++++----- tests/fixtures/deserialize.rs | 38 ++++++++------- tests/fixtures/no-color/issue_52.toml | 4 +- tests/fixtures/no-color/issue_9.toml | 12 ++--- .../no-color/multiline_annotation.toml | 6 +-- .../no-color/multiline_annotation2.toml | 4 +- .../no-color/multiline_annotation3.toml | 4 +- .../no-color/multiple_annotations.toml | 8 ++-- tests/fixtures/no-color/one_past.toml | 4 +- tests/fixtures/no-color/simple.toml | 6 +-- tests/fixtures/no-color/strip_line.toml | 4 +- tests/fixtures/no-color/strip_line_char.toml | 4 +- .../fixtures/no-color/strip_line_non_ws.toml | 4 +- tests/formatter.rs | 22 ++++----- 21 files changed, 114 insertions(+), 112 deletions(-) diff --git a/benches/simple.rs b/benches/simple.rs index 75a852f0..a22b42de 100644 --- a/benches/simple.rs +++ b/benches/simple.rs @@ -4,7 +4,7 @@ extern crate criterion; use criterion::{black_box, Criterion}; -use annotate_snippets::{Label, Message, Renderer, Slice}; +use annotate_snippets::{Label, Message, Renderer, Snippet}; fn create_snippet(renderer: Renderer) { let source = r#") -> Option<String> { @@ -29,8 +29,8 @@ fn create_snippet(renderer: Renderer) { _ => continue, } }"#; - let message = Message::error("mismatched types").id("E0308").slice( - Slice::new(source, 51) + let message = Message::error("mismatched types").id("E0308").snippet( + Snippet::new(source, 51) .origin("src/format.rs") .annotation( Label::warning("expected `Option<String>` because of return type").span(5..19), diff --git a/examples/expected_type.rs b/examples/expected_type.rs index ccdb8351..d15cabb1 100644 --- a/examples/expected_type.rs +++ b/examples/expected_type.rs @@ -1,12 +1,12 @@ -use annotate_snippets::{Label, Message, Renderer, Slice}; +use annotate_snippets::{Label, Message, Renderer, Snippet}; fn main() { let source = r#" annotations: vec![SourceAnnotation { label: "expected struct `annotate_snippets::snippet::Slice`, found reference" , range: <22, 25>,"#; - let message = Message::error("expected type, found `22`").slice( - Slice::new(source, 26) + let message = Message::error("expected type, found `22`").snippet( + Snippet::new(source, 26) .origin("examples/footer.rs") .fold(true) .annotation( diff --git a/examples/footer.rs b/examples/footer.rs index 924fae2f..e82d1a99 100644 --- a/examples/footer.rs +++ b/examples/footer.rs @@ -1,10 +1,10 @@ -use annotate_snippets::{Label, Message, Renderer, Slice}; +use annotate_snippets::{Label, Message, Renderer, Snippet}; fn main() { let message = Message::error("mismatched types") .id("E0308") - .slice( - Slice::new(" slices: vec![\"A\",", 13) + .snippet( + Snippet::new(" slices: vec![\"A\",", 13) .origin("src/multislice.rs") .annotation( Label::error( diff --git a/examples/format.rs b/examples/format.rs index 85e9a82b..ad6b6dee 100644 --- a/examples/format.rs +++ b/examples/format.rs @@ -1,4 +1,4 @@ -use annotate_snippets::{Label, Message, Renderer, Slice}; +use annotate_snippets::{Label, Message, Renderer, Snippet}; fn main() { let source = r#") -> Option<String> { @@ -23,8 +23,8 @@ fn main() { _ => continue, } }"#; - let message = Message::error("mismatched types").id("E0308").slice( - Slice::new(source, 51) + let message = Message::error("mismatched types").id("E0308").snippet( + Snippet::new(source, 51) .origin("src/format.rs") .annotation( Label::warning("expected `Option<String>` because of return type").span(5..19), diff --git a/examples/multislice.rs b/examples/multislice.rs index 48e73726..28e8fdef 100644 --- a/examples/multislice.rs +++ b/examples/multislice.rs @@ -1,9 +1,9 @@ -use annotate_snippets::{Message, Renderer, Slice}; +use annotate_snippets::{Message, Renderer, Snippet}; fn main() { let message = Message::error("mismatched types") - .slice(Slice::new("Foo", 51).origin("src/format.rs")) - .slice(Slice::new("Faa", 129).origin("src/display.rs")); + .snippet(Snippet::new("Foo", 51).origin("src/format.rs")) + .snippet(Snippet::new("Faa", 129).origin("src/display.rs")); let renderer = Renderer::styled(); anstream::println!("{}", renderer.render(message)); diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index 07216c39..e02e1760 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -110,7 +110,7 @@ impl<'a> DisplayList<'a> { title, id, footer, - slices, + snippets, }: snippet::Message<'a>, stylesheet: &'a Stylesheet, anonymized_line_numbers: bool, @@ -120,9 +120,9 @@ impl<'a> DisplayList<'a> { body.push(format_title(title, id)); - for (idx, slice) in slices.into_iter().enumerate() { + for (idx, snippet) in snippets.into_iter().enumerate() { body.append(&mut format_slice( - slice, + snippet, idx == 0, !footer.is_empty(), margin, @@ -542,7 +542,7 @@ pub enum DisplayLine<'a> { /// A source line. #[derive(Debug, PartialEq)] pub enum DisplaySourceLine<'a> { - /// A line with the content of the Slice. + /// A line with the content of the Snippet. Content { text: &'a str, range: (usize, usize), // meta information for annotation placement. @@ -762,15 +762,15 @@ fn format_footer(footer: snippet::Label<'_>) -> Vec<DisplayLine<'_>> { } fn format_slice( - slice: snippet::Slice<'_>, + snippet: snippet::Snippet<'_>, is_first: bool, has_footer: bool, margin: Option<Margin>, ) -> Vec<DisplayLine<'_>> { - let main_range = slice.annotations.first().map(|x| x.range.start); - let origin = slice.origin; + let main_range = snippet.annotations.first().map(|x| x.range.start); + let origin = snippet.origin; let need_empty_header = origin.is_some() || is_first; - let mut body = format_body(slice, need_empty_header, has_footer, margin); + let mut body = format_body(snippet, need_empty_header, has_footer, margin); let header = format_header(origin, main_range, &body, is_first); let mut result = vec![]; @@ -942,13 +942,13 @@ fn fold_body(mut body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> { } fn format_body( - slice: snippet::Slice<'_>, + snippet: snippet::Snippet<'_>, need_empty_header: bool, has_footer: bool, margin: Option<Margin>, ) -> Vec<DisplayLine<'_>> { - let source_len = slice.source.len(); - if let Some(bigger) = slice.annotations.iter().find_map(|x| { + let source_len = snippet.source.len(); + if let Some(bigger) = snippet.annotations.iter().find_map(|x| { // Allow highlighting one past the last character in the source. if source_len + 1 < x.range.end { Some(&x.range) @@ -963,7 +963,7 @@ fn format_body( } let mut body = vec![]; - let mut current_line = slice.line_start; + let mut current_line = snippet.line_start; let mut current_index = 0; let mut line_info = vec![]; @@ -972,7 +972,7 @@ fn format_body( line_end_index: usize, } - for (line, end_line) in CursorLines::new(slice.source) { + for (line, end_line) in CursorLines::new(snippet.source) { let line_length: usize = line .chars() .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) @@ -995,7 +995,7 @@ fn format_body( } let mut annotation_line_count = 0; - let mut annotations = slice.annotations; + let mut annotations = snippet.annotations; for ( idx, LineInfo { @@ -1143,7 +1143,7 @@ fn format_body( }); } - if slice.fold { + if snippet.fold { body = fold_body(body); } @@ -1227,7 +1227,7 @@ mod tests { let line_1 = "This is line 1"; let line_2 = "This is line 2"; let source = [line_1, line_2].join("\n"); - let input = snippet::Message::error("").slice(snippet::Slice::new(&source, 5402)); + let input = snippet::Message::error("").snippet(snippet::Snippet::new(&source, 5402)); let output = from_display_lines(vec![ DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { @@ -1278,8 +1278,8 @@ mod tests { let src_1 = "This is slice 2"; let src_1_len = src_1.len(); let input = snippet::Message::error("") - .slice(snippet::Slice::new(src_0, 5402).origin("file1.rs")) - .slice(snippet::Slice::new(src_1, 2).origin("file2.rs")); + .snippet(snippet::Snippet::new(src_0, 5402).origin("file1.rs")) + .snippet(snippet::Snippet::new(src_1, 2).origin("file2.rs")); let output = from_display_lines(vec![ DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { @@ -1350,8 +1350,8 @@ mod tests { let source = [line_1, line_2].join("\n"); // In line 2 let range = 22..24; - let input = snippet::Message::error("").slice( - snippet::Slice::new(&source, 5402) + let input = snippet::Message::error("").snippet( + snippet::Snippet::new(&source, 5402) .annotation(snippet::Label::info("Test annotation").span(range.clone())), ); let output = from_display_lines(vec![ @@ -1455,8 +1455,8 @@ mod tests { fn test_i26() { let source = "short"; let label = "label"; - let input = snippet::Message::error("").slice( - snippet::Slice::new(source, 0) + let input = snippet::Message::error("").snippet( + snippet::Snippet::new(source, 0) .annotation(snippet::Label::error(label).span(0..source.len() + 2)), ); let _ = DisplayList::new(input, &STYLESHEET, false, None); @@ -1464,8 +1464,8 @@ mod tests { #[test] fn test_i_29() { - let snippets = snippet::Message::error("oops").slice( - snippet::Slice::new("First line\r\nSecond oops line", 1) + let snippets = snippet::Message::error("oops").snippet( + snippet::Snippet::new("First line\r\nSecond oops line", 1) .origin("<current file>") .fold(true) .annotation(snippet::Label::error("oops").span(19..23)), diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index ef24cfd5..5ce75081 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -2,10 +2,10 @@ //! //! # Example //! ``` -//! use annotate_snippets::{Renderer, Slice, Message}; +//! use annotate_snippets::{Renderer, Snippet, Message}; //! let snippet = Message::error("mismatched types") -//! .slice(Slice::new("Foo", 51).origin("src/format.rs")) -//! .slice(Slice::new("Faa", 129).origin("src/display.rs")); +//! .snippet(Snippet::new("Foo", 51).origin("src/format.rs")) +//! .snippet(Snippet::new("Faa", 129).origin("src/display.rs")); //! //! let renderer = Renderer::styled(); //! println!("{}", renderer.render(snippet)); diff --git a/src/snippet.rs b/src/snippet.rs index 61253caa..f8832c6c 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -6,8 +6,8 @@ //! use annotate_snippets::*; //! //! Message::error("mismatched types") -//! .slice(Slice::new("Foo", 51).origin("src/format.rs")) -//! .slice(Slice::new("Faa", 129).origin("src/display.rs")); +//! .snippet(Snippet::new("Foo", 51).origin("src/format.rs")) +//! .snippet(Snippet::new("Faa", 129).origin("src/display.rs")); //! ``` use std::ops::Range; @@ -16,7 +16,7 @@ use std::ops::Range; pub struct Message<'a> { pub(crate) title: Label<'a>, pub(crate) id: Option<&'a str>, - pub(crate) slices: Vec<Slice<'a>>, + pub(crate) snippets: Vec<Snippet<'a>>, pub(crate) footer: Vec<Label<'a>>, } @@ -25,7 +25,7 @@ impl<'a> Message<'a> { Self { title, id: None, - slices: vec![], + snippets: vec![], footer: vec![], } } @@ -55,8 +55,8 @@ impl<'a> Message<'a> { self } - pub fn slice(mut self, slice: Slice<'a>) -> Self { - self.slices.push(slice); + pub fn snippet(mut self, slice: Snippet<'a>) -> Self { + self.snippets.push(slice); self } @@ -100,7 +100,7 @@ impl<'a> Label<'a> { self } - /// Create a [`Annotation`] with the given span for a [`Slice`] + /// Create a [`Annotation`] with the given span for a [`Snippet`] pub fn span(&self, span: Range<usize>) -> Annotation<'a> { Annotation { range: span, @@ -113,9 +113,9 @@ impl<'a> Label<'a> { /// Structure containing the slice of text to be annotated and /// basic information about the location of the slice. /// -/// One `Slice` is meant to represent a single, continuous, +/// One `Snippet` is meant to represent a single, continuous, /// slice of source code that you want to annotate. -pub struct Slice<'a> { +pub struct Snippet<'a> { pub(crate) source: &'a str, pub(crate) line_start: usize, pub(crate) origin: Option<&'a str>, @@ -123,7 +123,7 @@ pub struct Slice<'a> { pub(crate) fold: bool, } -impl<'a> Slice<'a> { +impl<'a> Snippet<'a> { pub fn new(source: &'a str, line_start: usize) -> Self { Self { source, @@ -162,7 +162,7 @@ pub enum Level { Help, } -/// An annotation for a [`Slice`]. +/// An annotation for a [`Snippet`]. /// /// This gets created by [`Label::span`]. #[derive(Debug)] diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs index 6bee4199..99d14674 100644 --- a/tests/fixtures/deserialize.rs +++ b/tests/fixtures/deserialize.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Deserializer, Serialize}; use std::ops::Range; -use annotate_snippets::{renderer::Margin, Annotation, Label, Level, Message, Renderer, Slice}; +use annotate_snippets::{renderer::Margin, Annotation, Label, Level, Message, Renderer, Snippet}; #[derive(Deserialize)] pub struct Fixture<'a> { @@ -23,9 +23,9 @@ pub struct MessageDef<'a> { #[serde(default)] #[serde(borrow)] pub footer: Vec<Label<'a>>, - #[serde(deserialize_with = "deserialize_slices")] + #[serde(deserialize_with = "deserialize_snippets")] #[serde(borrow)] - pub slices: Vec<Slice<'a>>, + pub snippets: Vec<Snippet<'a>>, } impl<'a> From<MessageDef<'a>> for Message<'a> { @@ -34,15 +34,15 @@ impl<'a> From<MessageDef<'a>> for Message<'a> { title, id, footer, - slices, + snippets, } = val; let mut message = Message::title(title); if let Some(id) = id { message = message.id(id); } - message = slices + message = snippets .into_iter() - .fold(message, |message, slice| message.slice(slice)); + .fold(message, |message, snippet| message.snippet(snippet)); message = footer .into_iter() .fold(message, |message, label| message.footer(label)); @@ -81,15 +81,15 @@ where .collect()) } -fn deserialize_slices<'de, D>(deserializer: D) -> Result<Vec<Slice<'de>>, D::Error> +fn deserialize_snippets<'de, D>(deserializer: D) -> Result<Vec<Snippet<'de>>, D::Error> where D: Deserializer<'de>, { #[derive(Deserialize)] struct Wrapper<'a>( - #[serde(with = "SliceDef")] + #[serde(with = "SnippetDef")] #[serde(borrow)] - SliceDef<'a>, + SnippetDef<'a>, ); let v = Vec::deserialize(deserializer)?; @@ -97,7 +97,7 @@ where } #[derive(Deserialize)] -pub struct SliceDef<'a> { +pub struct SnippetDef<'a> { #[serde(borrow)] pub source: &'a str, pub line_start: usize, @@ -110,23 +110,25 @@ pub struct SliceDef<'a> { pub fold: bool, } -impl<'a> From<SliceDef<'a>> for Slice<'a> { - fn from(val: SliceDef<'a>) -> Self { - let SliceDef { +impl<'a> From<SnippetDef<'a>> for Snippet<'a> { + fn from(val: SnippetDef<'a>) -> Self { + let SnippetDef { source, line_start, origin, annotations, fold, } = val; - let mut slice = Slice::new(source, line_start).fold(fold); + let mut snippet = Snippet::new(source, line_start).fold(fold); if let Some(origin) = origin { - slice = slice.origin(origin) + snippet = snippet.origin(origin) } - slice = annotations + snippet = annotations .into_iter() - .fold(slice, |slice, annotation| slice.annotation(annotation)); - slice + .fold(snippet, |snippet, annotation| { + snippet.annotation(annotation) + }); + snippet } } diff --git a/tests/fixtures/no-color/issue_52.toml b/tests/fixtures/no-color/issue_52.toml index 749324e7..9729dcc6 100644 --- a/tests/fixtures/no-color/issue_52.toml +++ b/tests/fixtures/no-color/issue_52.toml @@ -2,7 +2,7 @@ level = "Error" label = "" -[[message.slices]] +[[message.snippets]] source = """ @@ -11,7 +11,7 @@ invalid syntax line_start = 1 origin = "path/to/error.rs" fold = true -[[message.slices.annotations]] +[[message.snippets.annotations]] label = "error here" level = "Warning" range = [2,16] diff --git a/tests/fixtures/no-color/issue_9.toml b/tests/fixtures/no-color/issue_9.toml index e534505d..04ca88cf 100644 --- a/tests/fixtures/no-color/issue_9.toml +++ b/tests/fixtures/no-color/issue_9.toml @@ -2,27 +2,27 @@ label = "expected one of `.`, `;`, `?`, or an operator, found `for`" level = "Error" -[[message.slices]] +[[message.snippets]] source = "let x = vec![1];" line_start = 4 origin = "/code/rust/src/test/ui/annotate-snippet/suggestion.rs" -[[message.slices.annotations]] +[[message.snippets.annotations]] label = "move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait" level = "Warning" range = [4, 5] -[[message.slices]] +[[message.snippets]] source = "let y = x;" line_start = 7 -[[message.slices.annotations]] +[[message.snippets.annotations]] label = "value moved here" level = "Warning" range = [8, 9] -[[message.slices]] +[[message.snippets]] source = "x;" line_start = 9 -[[message.slices.annotations]] +[[message.snippets.annotations]] label = "value used here after move" level = "Error" range = [0, 1] diff --git a/tests/fixtures/no-color/multiline_annotation.toml b/tests/fixtures/no-color/multiline_annotation.toml index 55b0513a..d20716d5 100644 --- a/tests/fixtures/no-color/multiline_annotation.toml +++ b/tests/fixtures/no-color/multiline_annotation.toml @@ -1,4 +1,4 @@ -[[message.slices]] +[[message.snippets]] source = """ ) -> Option<String> { for ann in annotations { @@ -26,11 +26,11 @@ source = """ line_start = 51 origin = "src/format.rs" fold = true -[[message.slices.annotations]] +[[message.snippets.annotations]] label = "expected `std::option::Option<std::string::String>` because of return type" level = "Warning" range = [5, 19] -[[message.slices.annotations]] +[[message.snippets.annotations]] label = "expected enum `std::option::Option`, found ()" level = "Error" range = [22, 766] diff --git a/tests/fixtures/no-color/multiline_annotation2.toml b/tests/fixtures/no-color/multiline_annotation2.toml index b1506ef0..db7da7b4 100644 --- a/tests/fixtures/no-color/multiline_annotation2.toml +++ b/tests/fixtures/no-color/multiline_annotation2.toml @@ -1,4 +1,4 @@ -[[message.slices]] +[[message.snippets]] source = """ if let DisplayLine::Source { ref mut inline_marks, @@ -7,7 +7,7 @@ source = """ line_start = 139 origin = "src/display_list.rs" fold = false -[[message.slices.annotations]] +[[message.snippets.annotations]] label = "missing fields `lineno`, `content`" level = "Error" range = [31, 128] diff --git a/tests/fixtures/no-color/multiline_annotation3.toml b/tests/fixtures/no-color/multiline_annotation3.toml index a5f0400e..76c6bc52 100644 --- a/tests/fixtures/no-color/multiline_annotation3.toml +++ b/tests/fixtures/no-color/multiline_annotation3.toml @@ -1,4 +1,4 @@ -[[message.slices]] +[[message.snippets]] source = """ This is an exampl e of an edge case of an annotation overflowing @@ -7,7 +7,7 @@ to exactly one character on next line. line_start = 26 origin = "foo.txt" fold = false -[[message.slices.annotations]] +[[message.snippets.annotations]] label = "this should not be on separate lines" level = "Error" range = [11, 18] diff --git a/tests/fixtures/no-color/multiple_annotations.toml b/tests/fixtures/no-color/multiple_annotations.toml index 1c97a27c..d9adbfac 100644 --- a/tests/fixtures/no-color/multiple_annotations.toml +++ b/tests/fixtures/no-color/multiple_annotations.toml @@ -2,7 +2,7 @@ level = "Error" label = "" -[[message.slices]] +[[message.snippets]] source = """ fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>) { if let Some(annotation) = main_annotation { @@ -15,15 +15,15 @@ fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation> } """ line_start = 96 -[[message.slices.annotations]] +[[message.snippets.annotations]] label = "Variable defined here" level = "Error" range = [100, 110] -[[message.slices.annotations]] +[[message.snippets.annotations]] label = "Referenced here" level = "Error" range = [184, 194] -[[message.slices.annotations]] +[[message.snippets.annotations]] label = "Referenced again here" level = "Error" range = [243, 253] diff --git a/tests/fixtures/no-color/one_past.toml b/tests/fixtures/no-color/one_past.toml index 6dbee4bf..a84fa88a 100644 --- a/tests/fixtures/no-color/one_past.toml +++ b/tests/fixtures/no-color/one_past.toml @@ -2,11 +2,11 @@ label = "expected `.`, `=`" level = "Error" -[[message.slices]] +[[message.snippets]] source = "asdf" line_start = 1 origin = "Cargo.toml" -[[message.slices.annotations]] +[[message.snippets.annotations]] label = "" level = "Error" range = [4, 5] diff --git a/tests/fixtures/no-color/simple.toml b/tests/fixtures/no-color/simple.toml index c511ef92..70a65df7 100644 --- a/tests/fixtures/no-color/simple.toml +++ b/tests/fixtures/no-color/simple.toml @@ -1,15 +1,15 @@ -[[message.slices]] +[[message.snippets]] source = """ }) for line in &self.body {""" line_start = 169 origin = "src/format_color.rs" -[[message.slices.annotations]] +[[message.snippets.annotations]] label = "unexpected token" level = "Error" range = [20, 23] -[[message.slices.annotations]] +[[message.snippets.annotations]] label = "expected one of `.`, `;`, `?`, or an operator here" level = "Warning" range = [10, 11] diff --git a/tests/fixtures/no-color/strip_line.toml b/tests/fixtures/no-color/strip_line.toml index 02601257..21eb8973 100644 --- a/tests/fixtures/no-color/strip_line.toml +++ b/tests/fixtures/no-color/strip_line.toml @@ -2,12 +2,12 @@ title = { level = "Error", label = "mismatched types" } id = "E0308" -[[message.slices]] +[[message.snippets]] source = " let _: () = 42;" line_start = 4 origin = "$DIR/whitespace-trimming.rs" -[[message.slices.annotations]] +[[message.snippets.annotations]] label = "expected (), found integer" level = "Error" range = [192, 194] diff --git a/tests/fixtures/no-color/strip_line_char.toml b/tests/fixtures/no-color/strip_line_char.toml index 70ee2e34..d7daae0f 100644 --- a/tests/fixtures/no-color/strip_line_char.toml +++ b/tests/fixtures/no-color/strip_line_char.toml @@ -2,12 +2,12 @@ title = { level = "Error", label = "mismatched types" } id = "E0308" -[[message.slices]] +[[message.snippets]] source = " let _: () = 42ñ" line_start = 4 origin = "$DIR/whitespace-trimming.rs" -[[message.slices.annotations]] +[[message.snippets.annotations]] label = "expected (), found integer" level = "Error" range = [192, 194] diff --git a/tests/fixtures/no-color/strip_line_non_ws.toml b/tests/fixtures/no-color/strip_line_non_ws.toml index e133bbc0..3afb6ba0 100644 --- a/tests/fixtures/no-color/strip_line_non_ws.toml +++ b/tests/fixtures/no-color/strip_line_non_ws.toml @@ -2,12 +2,12 @@ title = { level = "Error", label = "mismatched types" } id = "E0308" -[[message.slices]] +[[message.snippets]] source = " let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = ();" line_start = 4 origin = "$DIR/non-whitespace-trimming.rs" -[[message.slices.annotations]] +[[message.snippets.annotations]] label = "expected (), found integer" level = "Error" range = [240, 242] diff --git a/tests/formatter.rs b/tests/formatter.rs index 4b903665..54663cb5 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -1,9 +1,9 @@ -use annotate_snippets::{Label, Message, Renderer, Slice}; +use annotate_snippets::{Label, Message, Renderer, Snippet}; #[test] fn test_i_29() { - let snippets = Message::error("oops").slice( - Slice::new("First line\r\nSecond oops line", 1) + let snippets = Message::error("oops").snippet( + Snippet::new("First line\r\nSecond oops line", 1) .origin("<current file>") .annotation(Label::error("oops").span(19..23)) .fold(true), @@ -22,8 +22,8 @@ fn test_i_29() { #[test] fn test_point_to_double_width_characters() { - let snippets = Message::error("").slice( - Slice::new("こんにちは、世界", 1) + let snippets = Message::error("").snippet( + Snippet::new("こんにちは、世界", 1) .origin("<current file>") .annotation(Label::error("world").span(12..16)), ); @@ -41,8 +41,8 @@ fn test_point_to_double_width_characters() { #[test] fn test_point_to_double_width_characters_across_lines() { - let snippets = Message::error("").slice( - Slice::new("おはよう\nございます", 1) + let snippets = Message::error("").snippet( + Snippet::new("おはよう\nございます", 1) .origin("<current file>") .annotation(Label::error("Good morning").span(4..15)), ); @@ -62,8 +62,8 @@ fn test_point_to_double_width_characters_across_lines() { #[test] fn test_point_to_double_width_characters_multiple() { - let snippets = Message::error("").slice( - Slice::new("お寿司\n食べたい🍣", 1) + let snippets = Message::error("").snippet( + Snippet::new("お寿司\n食べたい🍣", 1) .origin("<current file>") .annotation(Label::error("Sushi1").span(0..6)) .annotation(Label::note("Sushi2").span(11..15)), @@ -84,8 +84,8 @@ fn test_point_to_double_width_characters_multiple() { #[test] fn test_point_to_double_width_characters_mixed() { - let snippets = Message::error("").slice( - Slice::new("こんにちは、新しいWorld!", 1) + let snippets = Message::error("").snippet( + Snippet::new("こんにちは、新しいWorld!", 1) .origin("<current file>") .annotation(Label::error("New world").span(12..23)), ); From 95645d15d89fe29f43c8737e4f6791f11a55b934 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 11 Mar 2024 15:19:10 -0500 Subject: [PATCH 148/455] refactor: Re-order Snippet fields according to render layout --- src/snippet.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/snippet.rs b/src/snippet.rs index f8832c6c..153fc7fb 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -116,19 +116,21 @@ impl<'a> Label<'a> { /// One `Snippet` is meant to represent a single, continuous, /// slice of source code that you want to annotate. pub struct Snippet<'a> { - pub(crate) source: &'a str, - pub(crate) line_start: usize, pub(crate) origin: Option<&'a str>, + pub(crate) line_start: usize, + + pub(crate) source: &'a str, pub(crate) annotations: Vec<Annotation<'a>>, + pub(crate) fold: bool, } impl<'a> Snippet<'a> { pub fn new(source: &'a str, line_start: usize) -> Self { Self { - source, - line_start, origin: None, + line_start, + source, annotations: vec![], fold: false, } From 91bd796dde322ef4573c1ca707ccf99898938c07 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 11 Mar 2024 15:22:01 -0500 Subject: [PATCH 149/455] refactor: Re-order structs logically --- src/snippet.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/snippet.rs b/src/snippet.rs index 153fc7fb..bfe849be 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -152,6 +152,17 @@ impl<'a> Snippet<'a> { } } +/// An annotation for a [`Snippet`]. +/// +/// This gets created by [`Label::span`]. +#[derive(Debug)] +pub struct Annotation<'a> { + /// The byte range of the annotation in the `source` string + pub(crate) range: Range<usize>, + pub(crate) label: &'a str, + pub(crate) level: Level, +} + /// Types of annotations. #[derive(Debug, Clone, Copy, PartialEq)] pub enum Level { @@ -163,14 +174,3 @@ pub enum Level { Note, Help, } - -/// An annotation for a [`Snippet`]. -/// -/// This gets created by [`Label::span`]. -#[derive(Debug)] -pub struct Annotation<'a> { - /// The byte range of the annotation in the `source` string - pub(crate) range: Range<usize>, - pub(crate) label: &'a str, - pub(crate) level: Level, -} From b821bd397e2118a4d786231e1ca9ace28c2632a9 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 11 Mar 2024 15:28:58 -0500 Subject: [PATCH 150/455] fix!: Default Snippet::line_start --- benches/simple.rs | 3 ++- examples/expected_type.rs | 3 ++- examples/footer.rs | 3 ++- examples/format.rs | 3 ++- examples/multislice.rs | 4 ++-- src/renderer/display_list.rs | 24 ++++++++++++++++++------ src/renderer/mod.rs | 4 ++-- src/snippet.rs | 13 +++++++++---- tests/fixtures/deserialize.rs | 2 +- tests/formatter.rs | 10 +++++----- 10 files changed, 45 insertions(+), 24 deletions(-) diff --git a/benches/simple.rs b/benches/simple.rs index a22b42de..26ff1518 100644 --- a/benches/simple.rs +++ b/benches/simple.rs @@ -30,7 +30,8 @@ fn create_snippet(renderer: Renderer) { } }"#; let message = Message::error("mismatched types").id("E0308").snippet( - Snippet::new(source, 51) + Snippet::new(source) + .line_start(51) .origin("src/format.rs") .annotation( Label::warning("expected `Option<String>` because of return type").span(5..19), diff --git a/examples/expected_type.rs b/examples/expected_type.rs index d15cabb1..73d3425c 100644 --- a/examples/expected_type.rs +++ b/examples/expected_type.rs @@ -6,7 +6,8 @@ fn main() { , range: <22, 25>,"#; let message = Message::error("expected type, found `22`").snippet( - Snippet::new(source, 26) + Snippet::new(source) + .line_start(26) .origin("examples/footer.rs") .fold(true) .annotation( diff --git a/examples/footer.rs b/examples/footer.rs index e82d1a99..482487d6 100644 --- a/examples/footer.rs +++ b/examples/footer.rs @@ -4,7 +4,8 @@ fn main() { let message = Message::error("mismatched types") .id("E0308") .snippet( - Snippet::new(" slices: vec![\"A\",", 13) + Snippet::new(" slices: vec![\"A\",") + .line_start(13) .origin("src/multislice.rs") .annotation( Label::error( diff --git a/examples/format.rs b/examples/format.rs index ad6b6dee..646eefd2 100644 --- a/examples/format.rs +++ b/examples/format.rs @@ -24,7 +24,8 @@ fn main() { } }"#; let message = Message::error("mismatched types").id("E0308").snippet( - Snippet::new(source, 51) + Snippet::new(source) + .line_start(51) .origin("src/format.rs") .annotation( Label::warning("expected `Option<String>` because of return type").span(5..19), diff --git a/examples/multislice.rs b/examples/multislice.rs index 28e8fdef..39ca1a09 100644 --- a/examples/multislice.rs +++ b/examples/multislice.rs @@ -2,8 +2,8 @@ use annotate_snippets::{Message, Renderer, Snippet}; fn main() { let message = Message::error("mismatched types") - .snippet(Snippet::new("Foo", 51).origin("src/format.rs")) - .snippet(Snippet::new("Faa", 129).origin("src/display.rs")); + .snippet(Snippet::new("Foo").line_start(51).origin("src/format.rs")) + .snippet(Snippet::new("Faa").line_start(129).origin("src/display.rs")); let renderer = Renderer::styled(); anstream::println!("{}", renderer.render(message)); diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index e02e1760..13e97c85 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -1227,7 +1227,8 @@ mod tests { let line_1 = "This is line 1"; let line_2 = "This is line 2"; let source = [line_1, line_2].join("\n"); - let input = snippet::Message::error("").snippet(snippet::Snippet::new(&source, 5402)); + let input = + snippet::Message::error("").snippet(snippet::Snippet::new(&source).line_start(5402)); let output = from_display_lines(vec![ DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { @@ -1278,8 +1279,16 @@ mod tests { let src_1 = "This is slice 2"; let src_1_len = src_1.len(); let input = snippet::Message::error("") - .snippet(snippet::Snippet::new(src_0, 5402).origin("file1.rs")) - .snippet(snippet::Snippet::new(src_1, 2).origin("file2.rs")); + .snippet( + snippet::Snippet::new(src_0) + .line_start(5402) + .origin("file1.rs"), + ) + .snippet( + snippet::Snippet::new(src_1) + .line_start(2) + .origin("file2.rs"), + ); let output = from_display_lines(vec![ DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { @@ -1351,7 +1360,8 @@ mod tests { // In line 2 let range = 22..24; let input = snippet::Message::error("").snippet( - snippet::Snippet::new(&source, 5402) + snippet::Snippet::new(&source) + .line_start(5402) .annotation(snippet::Label::info("Test annotation").span(range.clone())), ); let output = from_display_lines(vec![ @@ -1456,7 +1466,8 @@ mod tests { let source = "short"; let label = "label"; let input = snippet::Message::error("").snippet( - snippet::Snippet::new(source, 0) + snippet::Snippet::new(source) + .line_start(0) .annotation(snippet::Label::error(label).span(0..source.len() + 2)), ); let _ = DisplayList::new(input, &STYLESHEET, false, None); @@ -1465,7 +1476,8 @@ mod tests { #[test] fn test_i_29() { let snippets = snippet::Message::error("oops").snippet( - snippet::Snippet::new("First line\r\nSecond oops line", 1) + snippet::Snippet::new("First line\r\nSecond oops line") + .line_start(1) .origin("<current file>") .fold(true) .annotation(snippet::Label::error("oops").span(19..23)), diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 5ce75081..1974b6fb 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -4,8 +4,8 @@ //! ``` //! use annotate_snippets::{Renderer, Snippet, Message}; //! let snippet = Message::error("mismatched types") -//! .snippet(Snippet::new("Foo", 51).origin("src/format.rs")) -//! .snippet(Snippet::new("Faa", 129).origin("src/display.rs")); +//! .snippet(Snippet::new("Foo").line_start(51).origin("src/format.rs")) +//! .snippet(Snippet::new("Faa").line_start(129).origin("src/display.rs")); //! //! let renderer = Renderer::styled(); //! println!("{}", renderer.render(snippet)); diff --git a/src/snippet.rs b/src/snippet.rs index bfe849be..e32a5a30 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -6,8 +6,8 @@ //! use annotate_snippets::*; //! //! Message::error("mismatched types") -//! .snippet(Snippet::new("Foo", 51).origin("src/format.rs")) -//! .snippet(Snippet::new("Faa", 129).origin("src/display.rs")); +//! .snippet(Snippet::new("Foo").line_start(51).origin("src/format.rs")) +//! .snippet(Snippet::new("Faa").line_start(129).origin("src/display.rs")); //! ``` use std::ops::Range; @@ -126,16 +126,21 @@ pub struct Snippet<'a> { } impl<'a> Snippet<'a> { - pub fn new(source: &'a str, line_start: usize) -> Self { + pub fn new(source: &'a str) -> Self { Self { origin: None, - line_start, + line_start: 1, source, annotations: vec![], fold: false, } } + pub fn line_start(mut self, line_start: usize) -> Self { + self.line_start = line_start; + self + } + pub fn origin(mut self, origin: &'a str) -> Self { self.origin = Some(origin); self diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs index 99d14674..36349a94 100644 --- a/tests/fixtures/deserialize.rs +++ b/tests/fixtures/deserialize.rs @@ -119,7 +119,7 @@ impl<'a> From<SnippetDef<'a>> for Snippet<'a> { annotations, fold, } = val; - let mut snippet = Snippet::new(source, line_start).fold(fold); + let mut snippet = Snippet::new(source).line_start(line_start).fold(fold); if let Some(origin) = origin { snippet = snippet.origin(origin) } diff --git a/tests/formatter.rs b/tests/formatter.rs index 54663cb5..ebf7604e 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -3,7 +3,7 @@ use annotate_snippets::{Label, Message, Renderer, Snippet}; #[test] fn test_i_29() { let snippets = Message::error("oops").snippet( - Snippet::new("First line\r\nSecond oops line", 1) + Snippet::new("First line\r\nSecond oops line") .origin("<current file>") .annotation(Label::error("oops").span(19..23)) .fold(true), @@ -23,7 +23,7 @@ fn test_i_29() { #[test] fn test_point_to_double_width_characters() { let snippets = Message::error("").snippet( - Snippet::new("こんにちは、世界", 1) + Snippet::new("こんにちは、世界") .origin("<current file>") .annotation(Label::error("world").span(12..16)), ); @@ -42,7 +42,7 @@ fn test_point_to_double_width_characters() { #[test] fn test_point_to_double_width_characters_across_lines() { let snippets = Message::error("").snippet( - Snippet::new("おはよう\nございます", 1) + Snippet::new("おはよう\nございます") .origin("<current file>") .annotation(Label::error("Good morning").span(4..15)), ); @@ -63,7 +63,7 @@ fn test_point_to_double_width_characters_across_lines() { #[test] fn test_point_to_double_width_characters_multiple() { let snippets = Message::error("").snippet( - Snippet::new("お寿司\n食べたい🍣", 1) + Snippet::new("お寿司\n食べたい🍣") .origin("<current file>") .annotation(Label::error("Sushi1").span(0..6)) .annotation(Label::note("Sushi2").span(11..15)), @@ -85,7 +85,7 @@ fn test_point_to_double_width_characters_multiple() { #[test] fn test_point_to_double_width_characters_mixed() { let snippets = Message::error("").snippet( - Snippet::new("こんにちは、新しいWorld!", 1) + Snippet::new("こんにちは、新しいWorld!") .origin("<current file>") .annotation(Label::error("New world").span(12..23)), ); From 39672b2b5da27ac820af3a12db78bf8995c82e5f Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 11 Mar 2024 15:50:02 -0500 Subject: [PATCH 151/455] fix!: Move Message contruction to Level This fits with the session-type / type-state style builder and allows Diagnoatic adapters, like rustc, to leverage it vs the hard-coded functions. --- benches/simple.rs | 4 +- examples/expected_type.rs | 4 +- examples/footer.rs | 5 +- examples/format.rs | 4 +- examples/multislice.rs | 5 +- src/renderer/display_list.rs | 32 ++++++++----- src/renderer/mod.rs | 4 +- src/snippet.rs | 48 +++++++------------ tests/fixtures/deserialize.rs | 22 ++------- tests/fixtures/no-color/issue_52.toml | 4 +- tests/fixtures/no-color/issue_9.toml | 4 +- .../no-color/multiline_annotation.toml | 9 ++-- .../no-color/multiline_annotation2.toml | 9 ++-- .../no-color/multiline_annotation3.toml | 9 ++-- .../no-color/multiple_annotations.toml | 4 +- tests/fixtures/no-color/one_past.toml | 4 +- tests/fixtures/no-color/simple.toml | 7 +-- tests/fixtures/no-color/strip_line.toml | 3 +- tests/fixtures/no-color/strip_line_char.toml | 3 +- .../fixtures/no-color/strip_line_non_ws.toml | 3 +- tests/formatter.rs | 12 ++--- 21 files changed, 96 insertions(+), 103 deletions(-) diff --git a/benches/simple.rs b/benches/simple.rs index 26ff1518..59d02a08 100644 --- a/benches/simple.rs +++ b/benches/simple.rs @@ -4,7 +4,7 @@ extern crate criterion; use criterion::{black_box, Criterion}; -use annotate_snippets::{Label, Message, Renderer, Snippet}; +use annotate_snippets::{Label, Level, Renderer, Snippet}; fn create_snippet(renderer: Renderer) { let source = r#") -> Option<String> { @@ -29,7 +29,7 @@ fn create_snippet(renderer: Renderer) { _ => continue, } }"#; - let message = Message::error("mismatched types").id("E0308").snippet( + let message = Level::Error.title("mismatched types").id("E0308").snippet( Snippet::new(source) .line_start(51) .origin("src/format.rs") diff --git a/examples/expected_type.rs b/examples/expected_type.rs index 73d3425c..3938d5f5 100644 --- a/examples/expected_type.rs +++ b/examples/expected_type.rs @@ -1,11 +1,11 @@ -use annotate_snippets::{Label, Message, Renderer, Snippet}; +use annotate_snippets::{Label, Level, Renderer, Snippet}; fn main() { let source = r#" annotations: vec![SourceAnnotation { label: "expected struct `annotate_snippets::snippet::Slice`, found reference" , range: <22, 25>,"#; - let message = Message::error("expected type, found `22`").snippet( + let message = Level::Error.title("expected type, found `22`").snippet( Snippet::new(source) .line_start(26) .origin("examples/footer.rs") diff --git a/examples/footer.rs b/examples/footer.rs index 482487d6..e873546f 100644 --- a/examples/footer.rs +++ b/examples/footer.rs @@ -1,7 +1,8 @@ -use annotate_snippets::{Label, Message, Renderer, Snippet}; +use annotate_snippets::{Label, Level, Renderer, Snippet}; fn main() { - let message = Message::error("mismatched types") + let message = Level::Error + .title("mismatched types") .id("E0308") .snippet( Snippet::new(" slices: vec![\"A\",") diff --git a/examples/format.rs b/examples/format.rs index 646eefd2..7d19b87a 100644 --- a/examples/format.rs +++ b/examples/format.rs @@ -1,4 +1,4 @@ -use annotate_snippets::{Label, Message, Renderer, Snippet}; +use annotate_snippets::{Label, Level, Renderer, Snippet}; fn main() { let source = r#") -> Option<String> { @@ -23,7 +23,7 @@ fn main() { _ => continue, } }"#; - let message = Message::error("mismatched types").id("E0308").snippet( + let message = Level::Error.title("mismatched types").id("E0308").snippet( Snippet::new(source) .line_start(51) .origin("src/format.rs") diff --git a/examples/multislice.rs b/examples/multislice.rs index 39ca1a09..38afbd48 100644 --- a/examples/multislice.rs +++ b/examples/multislice.rs @@ -1,7 +1,8 @@ -use annotate_snippets::{Message, Renderer, Snippet}; +use annotate_snippets::{Level, Renderer, Snippet}; fn main() { - let message = Message::error("mismatched types") + let message = Level::Error + .title("mismatched types") .snippet(Snippet::new("Foo").line_start(51).origin("src/format.rs")) .snippet(Snippet::new("Faa").line_start(129).origin("src/display.rs")); diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index 13e97c85..2ba7f832 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -107,8 +107,9 @@ impl<'a> DisplayList<'a> { pub(crate) fn new( snippet::Message { - title, + level, id, + title, footer, snippets, }: snippet::Message<'a>, @@ -118,7 +119,13 @@ impl<'a> DisplayList<'a> { ) -> DisplayList<'a> { let mut body = vec![]; - body.push(format_title(title, id)); + body.push(format_title( + snippet::Label { + level, + label: title, + }, + id, + )); for (idx, snippet) in snippets.into_iter().enumerate() { body.append(&mut format_slice( @@ -1206,7 +1213,7 @@ mod tests { #[test] fn test_format_title() { - let input = snippet::Message::error("This is a title").id("E0001"); + let input = snippet::Level::Error.title("This is a title").id("E0001"); let output = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { annotation_type: DisplayAnnotationType::Error, @@ -1227,8 +1234,9 @@ mod tests { let line_1 = "This is line 1"; let line_2 = "This is line 2"; let source = [line_1, line_2].join("\n"); - let input = - snippet::Message::error("").snippet(snippet::Snippet::new(&source).line_start(5402)); + let input = snippet::Level::Error + .title("") + .snippet(snippet::Snippet::new(&source).line_start(5402)); let output = from_display_lines(vec![ DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { @@ -1278,7 +1286,8 @@ mod tests { let src_0_len = src_0.len(); let src_1 = "This is slice 2"; let src_1_len = src_1.len(); - let input = snippet::Message::error("") + let input = snippet::Level::Error + .title("") .snippet( snippet::Snippet::new(src_0) .line_start(5402) @@ -1359,7 +1368,7 @@ mod tests { let source = [line_1, line_2].join("\n"); // In line 2 let range = 22..24; - let input = snippet::Message::error("").snippet( + let input = snippet::Level::Error.title("").snippet( snippet::Snippet::new(&source) .line_start(5402) .annotation(snippet::Label::info("Test annotation").span(range.clone())), @@ -1429,8 +1438,9 @@ mod tests { #[test] fn test_format_label() { - let input = - snippet::Message::error("").footer(snippet::Label::error("This __is__ a title")); + let input = snippet::Level::Error + .title("") + .footer(snippet::Label::error("This __is__ a title")); let output = from_display_lines(vec![ DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { @@ -1465,7 +1475,7 @@ mod tests { fn test_i26() { let source = "short"; let label = "label"; - let input = snippet::Message::error("").snippet( + let input = snippet::Level::Error.title("").snippet( snippet::Snippet::new(source) .line_start(0) .annotation(snippet::Label::error(label).span(0..source.len() + 2)), @@ -1475,7 +1485,7 @@ mod tests { #[test] fn test_i_29() { - let snippets = snippet::Message::error("oops").snippet( + let snippets = snippet::Level::Error.title("oops").snippet( snippet::Snippet::new("First line\r\nSecond oops line") .line_start(1) .origin("<current file>") diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 1974b6fb..29f3dae7 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -2,8 +2,8 @@ //! //! # Example //! ``` -//! use annotate_snippets::{Renderer, Snippet, Message}; -//! let snippet = Message::error("mismatched types") +//! use annotate_snippets::{Renderer, Snippet, Level}; +//! let snippet = Level::Error.title("mismatched types") //! .snippet(Snippet::new("Foo").line_start(51).origin("src/format.rs")) //! .snippet(Snippet::new("Faa").line_start(129).origin("src/display.rs")); //! diff --git a/src/snippet.rs b/src/snippet.rs index e32a5a30..d2906d49 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -5,7 +5,7 @@ //! ``` //! use annotate_snippets::*; //! -//! Message::error("mismatched types") +//! Level::Error.title("mismatched types") //! .snippet(Snippet::new("Foo").line_start(51).origin("src/format.rs")) //! .snippet(Snippet::new("Faa").line_start(129).origin("src/display.rs")); //! ``` @@ -13,43 +13,17 @@ use std::ops::Range; /// Primary structure provided for formatting +/// +/// See [`Level::title`] to create a [`Message`] pub struct Message<'a> { - pub(crate) title: Label<'a>, + pub(crate) level: Level, pub(crate) id: Option<&'a str>, + pub(crate) title: &'a str, pub(crate) snippets: Vec<Snippet<'a>>, pub(crate) footer: Vec<Label<'a>>, } impl<'a> Message<'a> { - pub fn title(title: Label<'a>) -> Self { - Self { - title, - id: None, - snippets: vec![], - footer: vec![], - } - } - - pub fn error(title: &'a str) -> Self { - Self::title(Label::error(title)) - } - - pub fn warning(title: &'a str) -> Self { - Self::title(Label::warning(title)) - } - - pub fn info(title: &'a str) -> Self { - Self::title(Label::info(title)) - } - - pub fn note(title: &'a str) -> Self { - Self::title(Label::note(title)) - } - - pub fn help(title: &'a str) -> Self { - Self::title(Label::help(title)) - } - pub fn id(mut self, id: &'a str) -> Self { self.id = Some(id); self @@ -179,3 +153,15 @@ pub enum Level { Note, Help, } + +impl Level { + pub fn title(self, title: &str) -> Message<'_> { + Message { + level: self, + id: None, + title, + snippets: vec![], + footer: vec![], + } + } +} diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs index 36349a94..7e64945a 100644 --- a/tests/fixtures/deserialize.rs +++ b/tests/fixtures/deserialize.rs @@ -13,9 +13,10 @@ pub struct Fixture<'a> { #[derive(Deserialize)] pub struct MessageDef<'a> { - #[serde(deserialize_with = "deserialize_label")] + #[serde(with = "LevelDef")] + pub level: Level, #[serde(borrow)] - pub title: Label<'a>, + pub title: &'a str, #[serde(default)] #[serde(borrow)] pub id: Option<&'a str>, @@ -31,12 +32,13 @@ pub struct MessageDef<'a> { impl<'a> From<MessageDef<'a>> for Message<'a> { fn from(val: MessageDef<'a>) -> Self { let MessageDef { + level, title, id, footer, snippets, } = val; - let mut message = Message::title(title); + let mut message = level.title(title); if let Some(id) = id { message = message.id(id); } @@ -50,20 +52,6 @@ impl<'a> From<MessageDef<'a>> for Message<'a> { } } -fn deserialize_label<'de, D>(deserializer: D) -> Result<Label<'de>, D::Error> -where - D: Deserializer<'de>, -{ - #[derive(Deserialize)] - struct Wrapper<'a>( - #[serde(with = "LabelDef")] - #[serde(borrow)] - LabelDef<'a>, - ); - - Wrapper::deserialize(deserializer).map(|Wrapper(label)| Label::new(label.level, label.label)) -} - fn deserialize_labels<'de, D>(deserializer: D) -> Result<Vec<Label<'de>>, D::Error> where D: Deserializer<'de>, diff --git a/tests/fixtures/no-color/issue_52.toml b/tests/fixtures/no-color/issue_52.toml index 9729dcc6..1e81a713 100644 --- a/tests/fixtures/no-color/issue_52.toml +++ b/tests/fixtures/no-color/issue_52.toml @@ -1,6 +1,6 @@ -[message.title] +[message] level = "Error" -label = "" +title = "" [[message.snippets]] source = """ diff --git a/tests/fixtures/no-color/issue_9.toml b/tests/fixtures/no-color/issue_9.toml index 04ca88cf..1f35243c 100644 --- a/tests/fixtures/no-color/issue_9.toml +++ b/tests/fixtures/no-color/issue_9.toml @@ -1,6 +1,6 @@ -[message.title] -label = "expected one of `.`, `;`, `?`, or an operator, found `for`" +[message] level = "Error" +title = "expected one of `.`, `;`, `?`, or an operator, found `for`" [[message.snippets]] source = "let x = vec![1];" diff --git a/tests/fixtures/no-color/multiline_annotation.toml b/tests/fixtures/no-color/multiline_annotation.toml index d20716d5..09fc7d44 100644 --- a/tests/fixtures/no-color/multiline_annotation.toml +++ b/tests/fixtures/no-color/multiline_annotation.toml @@ -1,3 +1,8 @@ +[message] +level = "Error" +id = "E0308" +title = "mismatched types" + [[message.snippets]] source = """ ) -> Option<String> { @@ -34,7 +39,3 @@ range = [5, 19] label = "expected enum `std::option::Option`, found ()" level = "Error" range = [22, 766] - -[message] -title = { level = "Error", label = "mismatched types" } -id = "E0308" diff --git a/tests/fixtures/no-color/multiline_annotation2.toml b/tests/fixtures/no-color/multiline_annotation2.toml index db7da7b4..671b5344 100644 --- a/tests/fixtures/no-color/multiline_annotation2.toml +++ b/tests/fixtures/no-color/multiline_annotation2.toml @@ -1,3 +1,8 @@ +[message] +level = "Error" +id = "E0027" +title = "pattern does not mention fields `lineno`, `content`" + [[message.snippets]] source = """ if let DisplayLine::Source { @@ -11,7 +16,3 @@ fold = false label = "missing fields `lineno`, `content`" level = "Error" range = [31, 128] - -[message] -title = { level = "Error", label = "pattern does not mention fields `lineno`, `content`" } -id = "E0027" diff --git a/tests/fixtures/no-color/multiline_annotation3.toml b/tests/fixtures/no-color/multiline_annotation3.toml index 76c6bc52..dd853324 100644 --- a/tests/fixtures/no-color/multiline_annotation3.toml +++ b/tests/fixtures/no-color/multiline_annotation3.toml @@ -1,3 +1,8 @@ +[message] +level = "Error" +id = "E####" +title = "spacing error found" + [[message.snippets]] source = """ This is an exampl @@ -11,7 +16,3 @@ fold = false label = "this should not be on separate lines" level = "Error" range = [11, 18] - -[message] -title = { level = "Error", label = "spacing error found" } -id = "E####" diff --git a/tests/fixtures/no-color/multiple_annotations.toml b/tests/fixtures/no-color/multiple_annotations.toml index d9adbfac..842b137e 100644 --- a/tests/fixtures/no-color/multiple_annotations.toml +++ b/tests/fixtures/no-color/multiple_annotations.toml @@ -1,6 +1,6 @@ -[message.title] +[message] level = "Error" -label = "" +title = "" [[message.snippets]] source = """ diff --git a/tests/fixtures/no-color/one_past.toml b/tests/fixtures/no-color/one_past.toml index a84fa88a..b681c293 100644 --- a/tests/fixtures/no-color/one_past.toml +++ b/tests/fixtures/no-color/one_past.toml @@ -1,6 +1,6 @@ -[message.title] -label = "expected `.`, `=`" +[message] level = "Error" +title = "expected `.`, `=`" [[message.snippets]] source = "asdf" diff --git a/tests/fixtures/no-color/simple.toml b/tests/fixtures/no-color/simple.toml index 70a65df7..76b5bac6 100644 --- a/tests/fixtures/no-color/simple.toml +++ b/tests/fixtures/no-color/simple.toml @@ -1,3 +1,7 @@ +[message] +level = "Error" +title = "expected one of `.`, `;`, `?`, or an operator, found `for`" + [[message.snippets]] source = """ }) @@ -13,6 +17,3 @@ range = [20, 23] label = "expected one of `.`, `;`, `?`, or an operator here" level = "Warning" range = [10, 11] -[message.title] -label = "expected one of `.`, `;`, `?`, or an operator, found `for`" -level = "Error" diff --git a/tests/fixtures/no-color/strip_line.toml b/tests/fixtures/no-color/strip_line.toml index 21eb8973..d44024bd 100644 --- a/tests/fixtures/no-color/strip_line.toml +++ b/tests/fixtures/no-color/strip_line.toml @@ -1,6 +1,7 @@ [message] -title = { level = "Error", label = "mismatched types" } +level = "Error" id = "E0308" +title = "mismatched types" [[message.snippets]] source = " let _: () = 42;" diff --git a/tests/fixtures/no-color/strip_line_char.toml b/tests/fixtures/no-color/strip_line_char.toml index d7daae0f..e3f74822 100644 --- a/tests/fixtures/no-color/strip_line_char.toml +++ b/tests/fixtures/no-color/strip_line_char.toml @@ -1,6 +1,7 @@ [message] -title = { level = "Error", label = "mismatched types" } +level = "Error" id = "E0308" +title = "mismatched types" [[message.snippets]] source = " let _: () = 42ñ" diff --git a/tests/fixtures/no-color/strip_line_non_ws.toml b/tests/fixtures/no-color/strip_line_non_ws.toml index 3afb6ba0..2985177b 100644 --- a/tests/fixtures/no-color/strip_line_non_ws.toml +++ b/tests/fixtures/no-color/strip_line_non_ws.toml @@ -1,6 +1,7 @@ [message] -title = { level = "Error", label = "mismatched types" } +level = "Error" id = "E0308" +title = "mismatched types" [[message.snippets]] source = " let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = ();" diff --git a/tests/formatter.rs b/tests/formatter.rs index ebf7604e..3224175a 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -1,8 +1,8 @@ -use annotate_snippets::{Label, Message, Renderer, Snippet}; +use annotate_snippets::{Label, Level, Renderer, Snippet}; #[test] fn test_i_29() { - let snippets = Message::error("oops").snippet( + let snippets = Level::Error.title("oops").snippet( Snippet::new("First line\r\nSecond oops line") .origin("<current file>") .annotation(Label::error("oops").span(19..23)) @@ -22,7 +22,7 @@ fn test_i_29() { #[test] fn test_point_to_double_width_characters() { - let snippets = Message::error("").snippet( + let snippets = Level::Error.title("").snippet( Snippet::new("こんにちは、世界") .origin("<current file>") .annotation(Label::error("world").span(12..16)), @@ -41,7 +41,7 @@ fn test_point_to_double_width_characters() { #[test] fn test_point_to_double_width_characters_across_lines() { - let snippets = Message::error("").snippet( + let snippets = Level::Error.title("").snippet( Snippet::new("おはよう\nございます") .origin("<current file>") .annotation(Label::error("Good morning").span(4..15)), @@ -62,7 +62,7 @@ fn test_point_to_double_width_characters_across_lines() { #[test] fn test_point_to_double_width_characters_multiple() { - let snippets = Message::error("").snippet( + let snippets = Level::Error.title("").snippet( Snippet::new("お寿司\n食べたい🍣") .origin("<current file>") .annotation(Label::error("Sushi1").span(0..6)) @@ -84,7 +84,7 @@ fn test_point_to_double_width_characters_multiple() { #[test] fn test_point_to_double_width_characters_mixed() { - let snippets = Message::error("").snippet( + let snippets = Level::Error.title("").snippet( Snippet::new("こんにちは、新しいWorld!") .origin("<current file>") .annotation(Label::error("New world").span(12..23)), From 86823c4b3aedf5cad62b871d33b6aa6be56a1323 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 11 Mar 2024 15:59:45 -0500 Subject: [PATCH 152/455] fix!: Rename Snippet::new to Snippet::source --- benches/simple.rs | 2 +- examples/expected_type.rs | 2 +- examples/footer.rs | 2 +- examples/format.rs | 2 +- examples/multislice.rs | 12 ++++++++++-- src/renderer/display_list.rs | 12 ++++++------ src/renderer/mod.rs | 4 ++-- src/snippet.rs | 6 +++--- tests/fixtures/deserialize.rs | 2 +- tests/formatter.rs | 10 +++++----- 10 files changed, 31 insertions(+), 23 deletions(-) diff --git a/benches/simple.rs b/benches/simple.rs index 59d02a08..ab573024 100644 --- a/benches/simple.rs +++ b/benches/simple.rs @@ -30,7 +30,7 @@ fn create_snippet(renderer: Renderer) { } }"#; let message = Level::Error.title("mismatched types").id("E0308").snippet( - Snippet::new(source) + Snippet::source(source) .line_start(51) .origin("src/format.rs") .annotation( diff --git a/examples/expected_type.rs b/examples/expected_type.rs index 3938d5f5..942728f8 100644 --- a/examples/expected_type.rs +++ b/examples/expected_type.rs @@ -6,7 +6,7 @@ fn main() { , range: <22, 25>,"#; let message = Level::Error.title("expected type, found `22`").snippet( - Snippet::new(source) + Snippet::source(source) .line_start(26) .origin("examples/footer.rs") .fold(true) diff --git a/examples/footer.rs b/examples/footer.rs index e873546f..858f811a 100644 --- a/examples/footer.rs +++ b/examples/footer.rs @@ -5,7 +5,7 @@ fn main() { .title("mismatched types") .id("E0308") .snippet( - Snippet::new(" slices: vec![\"A\",") + Snippet::source(" slices: vec![\"A\",") .line_start(13) .origin("src/multislice.rs") .annotation( diff --git a/examples/format.rs b/examples/format.rs index 7d19b87a..34a76f40 100644 --- a/examples/format.rs +++ b/examples/format.rs @@ -24,7 +24,7 @@ fn main() { } }"#; let message = Level::Error.title("mismatched types").id("E0308").snippet( - Snippet::new(source) + Snippet::source(source) .line_start(51) .origin("src/format.rs") .annotation( diff --git a/examples/multislice.rs b/examples/multislice.rs index 38afbd48..ea31bbd0 100644 --- a/examples/multislice.rs +++ b/examples/multislice.rs @@ -3,8 +3,16 @@ use annotate_snippets::{Level, Renderer, Snippet}; fn main() { let message = Level::Error .title("mismatched types") - .snippet(Snippet::new("Foo").line_start(51).origin("src/format.rs")) - .snippet(Snippet::new("Faa").line_start(129).origin("src/display.rs")); + .snippet( + Snippet::source("Foo") + .line_start(51) + .origin("src/format.rs"), + ) + .snippet( + Snippet::source("Faa") + .line_start(129) + .origin("src/display.rs"), + ); let renderer = Renderer::styled(); anstream::println!("{}", renderer.render(message)); diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index 2ba7f832..b4c50a90 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -1236,7 +1236,7 @@ mod tests { let source = [line_1, line_2].join("\n"); let input = snippet::Level::Error .title("") - .snippet(snippet::Snippet::new(&source).line_start(5402)); + .snippet(snippet::Snippet::source(&source).line_start(5402)); let output = from_display_lines(vec![ DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { @@ -1289,12 +1289,12 @@ mod tests { let input = snippet::Level::Error .title("") .snippet( - snippet::Snippet::new(src_0) + snippet::Snippet::source(src_0) .line_start(5402) .origin("file1.rs"), ) .snippet( - snippet::Snippet::new(src_1) + snippet::Snippet::source(src_1) .line_start(2) .origin("file2.rs"), ); @@ -1369,7 +1369,7 @@ mod tests { // In line 2 let range = 22..24; let input = snippet::Level::Error.title("").snippet( - snippet::Snippet::new(&source) + snippet::Snippet::source(&source) .line_start(5402) .annotation(snippet::Label::info("Test annotation").span(range.clone())), ); @@ -1476,7 +1476,7 @@ mod tests { let source = "short"; let label = "label"; let input = snippet::Level::Error.title("").snippet( - snippet::Snippet::new(source) + snippet::Snippet::source(source) .line_start(0) .annotation(snippet::Label::error(label).span(0..source.len() + 2)), ); @@ -1486,7 +1486,7 @@ mod tests { #[test] fn test_i_29() { let snippets = snippet::Level::Error.title("oops").snippet( - snippet::Snippet::new("First line\r\nSecond oops line") + snippet::Snippet::source("First line\r\nSecond oops line") .line_start(1) .origin("<current file>") .fold(true) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 29f3dae7..5f9394d5 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -4,8 +4,8 @@ //! ``` //! use annotate_snippets::{Renderer, Snippet, Level}; //! let snippet = Level::Error.title("mismatched types") -//! .snippet(Snippet::new("Foo").line_start(51).origin("src/format.rs")) -//! .snippet(Snippet::new("Faa").line_start(129).origin("src/display.rs")); +//! .snippet(Snippet::source("Foo").line_start(51).origin("src/format.rs")) +//! .snippet(Snippet::source("Faa").line_start(129).origin("src/display.rs")); //! //! let renderer = Renderer::styled(); //! println!("{}", renderer.render(snippet)); diff --git a/src/snippet.rs b/src/snippet.rs index d2906d49..e0a05ea8 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -6,8 +6,8 @@ //! use annotate_snippets::*; //! //! Level::Error.title("mismatched types") -//! .snippet(Snippet::new("Foo").line_start(51).origin("src/format.rs")) -//! .snippet(Snippet::new("Faa").line_start(129).origin("src/display.rs")); +//! .snippet(Snippet::source("Foo").line_start(51).origin("src/format.rs")) +//! .snippet(Snippet::source("Faa").line_start(129).origin("src/display.rs")); //! ``` use std::ops::Range; @@ -100,7 +100,7 @@ pub struct Snippet<'a> { } impl<'a> Snippet<'a> { - pub fn new(source: &'a str) -> Self { + pub fn source(source: &'a str) -> Self { Self { origin: None, line_start: 1, diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs index 7e64945a..f5bb411f 100644 --- a/tests/fixtures/deserialize.rs +++ b/tests/fixtures/deserialize.rs @@ -107,7 +107,7 @@ impl<'a> From<SnippetDef<'a>> for Snippet<'a> { annotations, fold, } = val; - let mut snippet = Snippet::new(source).line_start(line_start).fold(fold); + let mut snippet = Snippet::source(source).line_start(line_start).fold(fold); if let Some(origin) = origin { snippet = snippet.origin(origin) } diff --git a/tests/formatter.rs b/tests/formatter.rs index 3224175a..c905b1cf 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -3,7 +3,7 @@ use annotate_snippets::{Label, Level, Renderer, Snippet}; #[test] fn test_i_29() { let snippets = Level::Error.title("oops").snippet( - Snippet::new("First line\r\nSecond oops line") + Snippet::source("First line\r\nSecond oops line") .origin("<current file>") .annotation(Label::error("oops").span(19..23)) .fold(true), @@ -23,7 +23,7 @@ fn test_i_29() { #[test] fn test_point_to_double_width_characters() { let snippets = Level::Error.title("").snippet( - Snippet::new("こんにちは、世界") + Snippet::source("こんにちは、世界") .origin("<current file>") .annotation(Label::error("world").span(12..16)), ); @@ -42,7 +42,7 @@ fn test_point_to_double_width_characters() { #[test] fn test_point_to_double_width_characters_across_lines() { let snippets = Level::Error.title("").snippet( - Snippet::new("おはよう\nございます") + Snippet::source("おはよう\nございます") .origin("<current file>") .annotation(Label::error("Good morning").span(4..15)), ); @@ -63,7 +63,7 @@ fn test_point_to_double_width_characters_across_lines() { #[test] fn test_point_to_double_width_characters_multiple() { let snippets = Level::Error.title("").snippet( - Snippet::new("お寿司\n食べたい🍣") + Snippet::source("お寿司\n食べたい🍣") .origin("<current file>") .annotation(Label::error("Sushi1").span(0..6)) .annotation(Label::note("Sushi2").span(11..15)), @@ -85,7 +85,7 @@ fn test_point_to_double_width_characters_multiple() { #[test] fn test_point_to_double_width_characters_mixed() { let snippets = Level::Error.title("").snippet( - Snippet::new("こんにちは、新しいWorld!") + Snippet::source("こんにちは、新しいWorld!") .origin("<current file>") .annotation(Label::error("New world").span(12..23)), ); From c821084068a1acd2688b6c8d0b3423e143d359e2 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 11 Mar 2024 16:13:40 -0500 Subject: [PATCH 153/455] fix!: Make Annotation labels optional --- benches/simple.rs | 12 +++++++++--- examples/expected_type.rs | 11 +++++------ examples/footer.rs | 30 ++++++++++++++---------------- examples/format.rs | 12 +++++++++--- src/renderer/display_list.rs | 14 +++++++++----- src/snippet.rs | 29 ++++++++++++++++++----------- tests/fixtures/deserialize.rs | 2 +- tests/formatter.rs | 14 +++++++------- 8 files changed, 72 insertions(+), 52 deletions(-) diff --git a/benches/simple.rs b/benches/simple.rs index ab573024..fccb70be 100644 --- a/benches/simple.rs +++ b/benches/simple.rs @@ -4,7 +4,7 @@ extern crate criterion; use criterion::{black_box, Criterion}; -use annotate_snippets::{Label, Level, Renderer, Snippet}; +use annotate_snippets::{Level, Renderer, Snippet}; fn create_snippet(renderer: Renderer) { let source = r#") -> Option<String> { @@ -34,9 +34,15 @@ fn create_snippet(renderer: Renderer) { .line_start(51) .origin("src/format.rs") .annotation( - Label::warning("expected `Option<String>` because of return type").span(5..19), + Level::Warning + .span(5..19) + .label("expected `Option<String>` because of return type"), ) - .annotation(Label::error("expected enum `std::option::Option`").span(26..724)), + .annotation( + Level::Error + .span(26..724) + .label("expected enum `std::option::Option`"), + ), ); let _result = renderer.render(message).to_string(); diff --git a/examples/expected_type.rs b/examples/expected_type.rs index 942728f8..0184deeb 100644 --- a/examples/expected_type.rs +++ b/examples/expected_type.rs @@ -1,4 +1,4 @@ -use annotate_snippets::{Label, Level, Renderer, Snippet}; +use annotate_snippets::{Level, Renderer, Snippet}; fn main() { let source = r#" annotations: vec![SourceAnnotation { @@ -11,12 +11,11 @@ fn main() { .origin("examples/footer.rs") .fold(true) .annotation( - Label::error( - "expected struct `annotate_snippets::snippet::Slice`, found reference", - ) - .span(193..195), + Level::Error + .span(193..195) + .label("expected struct `annotate_snippets::snippet::Slice`, found reference"), ) - .annotation(Label::info("while parsing this struct").span(34..50)), + .annotation(Level::Info.span(34..50).label("while parsing this struct")), ); let renderer = Renderer::styled(); diff --git a/examples/footer.rs b/examples/footer.rs index 858f811a..8b4d0780 100644 --- a/examples/footer.rs +++ b/examples/footer.rs @@ -1,23 +1,21 @@ use annotate_snippets::{Label, Level, Renderer, Snippet}; fn main() { - let message = Level::Error - .title("mismatched types") - .id("E0308") - .snippet( - Snippet::source(" slices: vec![\"A\",") - .line_start(13) - .origin("src/multislice.rs") - .annotation( - Label::error( + let message = + Level::Error + .title("mismatched types") + .id("E0308") + .snippet( + Snippet::source(" slices: vec![\"A\",") + .line_start(13) + .origin("src/multislice.rs") + .annotation(Level::Error.span(21..24).label( "expected struct `annotate_snippets::snippet::Slice`, found reference", - ) - .span(21..24), - ), - ) - .footer(Label::note( - "expected type: `snippet::Annotation`\n found type: `__&__snippet::Annotation`", - )); + )), + ) + .footer(Label::note( + "expected type: `snippet::Annotation`\n found type: `__&__snippet::Annotation`", + )); let renderer = Renderer::styled(); anstream::println!("{}", renderer.render(message)); diff --git a/examples/format.rs b/examples/format.rs index 34a76f40..1606777b 100644 --- a/examples/format.rs +++ b/examples/format.rs @@ -1,4 +1,4 @@ -use annotate_snippets::{Label, Level, Renderer, Snippet}; +use annotate_snippets::{Level, Renderer, Snippet}; fn main() { let source = r#") -> Option<String> { @@ -28,9 +28,15 @@ fn main() { .line_start(51) .origin("src/format.rs") .annotation( - Label::warning("expected `Option<String>` because of return type").span(5..19), + Level::Warning + .span(5..19) + .label("expected `Option<String>` because of return type"), ) - .annotation(Label::error("expected enum `std::option::Option`").span(26..724)), + .annotation( + Level::Error + .span(26..724) + .label("expected enum `std::option::Option`"), + ), ); let renderer = Renderer::styled(); diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index b4c50a90..d54282fe 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -1040,7 +1040,7 @@ fn format_body( annotation: Annotation { annotation_type, id: None, - label: format_label(Some(annotation.label), None), + label: format_label(annotation.label, None), }, range, annotation_type: DisplayAnnotationType::from(annotation.level), @@ -1134,7 +1134,7 @@ fn format_body( annotation: Annotation { annotation_type, id: None, - label: format_label(Some(annotation.label), None), + label: format_label(annotation.label, None), }, range, annotation_type: DisplayAnnotationType::from(annotation.level), @@ -1371,7 +1371,11 @@ mod tests { let input = snippet::Level::Error.title("").snippet( snippet::Snippet::source(&source) .line_start(5402) - .annotation(snippet::Label::info("Test annotation").span(range.clone())), + .annotation( + snippet::Level::Info + .span(range.clone()) + .label("Test annotation"), + ), ); let output = from_display_lines(vec![ DisplayLine::Raw(DisplayRawLine::Annotation { @@ -1478,7 +1482,7 @@ mod tests { let input = snippet::Level::Error.title("").snippet( snippet::Snippet::source(source) .line_start(0) - .annotation(snippet::Label::error(label).span(0..source.len() + 2)), + .annotation(snippet::Level::Error.span(0..source.len() + 2).label(label)), ); let _ = DisplayList::new(input, &STYLESHEET, false, None); } @@ -1490,7 +1494,7 @@ mod tests { .line_start(1) .origin("<current file>") .fold(true) - .annotation(snippet::Label::error("oops").span(19..23)), + .annotation(snippet::Level::Error.span(19..23).label("oops")), ); let expected = from_display_lines(vec![ diff --git a/src/snippet.rs b/src/snippet.rs index e0a05ea8..7b214028 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -73,15 +73,6 @@ impl<'a> Label<'a> { self.label = label; self } - - /// Create a [`Annotation`] with the given span for a [`Snippet`] - pub fn span(&self, span: Range<usize>) -> Annotation<'a> { - Annotation { - range: span, - label: self.label, - level: self.level, - } - } } /// Structure containing the slice of text to be annotated and @@ -133,15 +124,22 @@ impl<'a> Snippet<'a> { /// An annotation for a [`Snippet`]. /// -/// This gets created by [`Label::span`]. +/// See [`Level::span`] to create a [`Annotation`] #[derive(Debug)] pub struct Annotation<'a> { /// The byte range of the annotation in the `source` string pub(crate) range: Range<usize>, - pub(crate) label: &'a str, + pub(crate) label: Option<&'a str>, pub(crate) level: Level, } +impl<'a> Annotation<'a> { + pub fn label(mut self, label: &'a str) -> Self { + self.label = Some(label); + self + } +} + /// Types of annotations. #[derive(Debug, Clone, Copy, PartialEq)] pub enum Level { @@ -164,4 +162,13 @@ impl Level { footer: vec![], } } + + /// Create a [`Annotation`] with the given span for a [`Snippet`] + pub fn span<'a>(self, span: Range<usize>) -> Annotation<'a> { + Annotation { + range: span, + label: None, + level: self, + } + } } diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs index f5bb411f..a22f2157 100644 --- a/tests/fixtures/deserialize.rs +++ b/tests/fixtures/deserialize.rs @@ -147,7 +147,7 @@ impl<'a> From<AnnotationDef<'a>> for Annotation<'a> { label, level, } = val; - Label::new(level, label).span(range) + level.span(range).label(label) } } diff --git a/tests/formatter.rs b/tests/formatter.rs index c905b1cf..76dcb27c 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -1,11 +1,11 @@ -use annotate_snippets::{Label, Level, Renderer, Snippet}; +use annotate_snippets::{Level, Renderer, Snippet}; #[test] fn test_i_29() { let snippets = Level::Error.title("oops").snippet( Snippet::source("First line\r\nSecond oops line") .origin("<current file>") - .annotation(Label::error("oops").span(19..23)) + .annotation(Level::Error.span(19..23).label("oops")) .fold(true), ); let expected = r#"error: oops @@ -25,7 +25,7 @@ fn test_point_to_double_width_characters() { let snippets = Level::Error.title("").snippet( Snippet::source("こんにちは、世界") .origin("<current file>") - .annotation(Label::error("world").span(12..16)), + .annotation(Level::Error.span(12..16).label("world")), ); let expected = r#"error @@ -44,7 +44,7 @@ fn test_point_to_double_width_characters_across_lines() { let snippets = Level::Error.title("").snippet( Snippet::source("おはよう\nございます") .origin("<current file>") - .annotation(Label::error("Good morning").span(4..15)), + .annotation(Level::Error.span(4..15).label("Good morning")), ); let expected = r#"error @@ -65,8 +65,8 @@ fn test_point_to_double_width_characters_multiple() { let snippets = Level::Error.title("").snippet( Snippet::source("お寿司\n食べたい🍣") .origin("<current file>") - .annotation(Label::error("Sushi1").span(0..6)) - .annotation(Label::note("Sushi2").span(11..15)), + .annotation(Level::Error.span(0..6).label("Sushi1")) + .annotation(Level::Note.span(11..15).label("Sushi2")), ); let expected = r#"error @@ -87,7 +87,7 @@ fn test_point_to_double_width_characters_mixed() { let snippets = Level::Error.title("").snippet( Snippet::source("こんにちは、新しいWorld!") .origin("<current file>") - .annotation(Label::error("New world").span(12..23)), + .annotation(Level::Error.span(12..23).label("New world")), ); let expected = r#"error From 4ceb316f8d9e9aefdc84c6dfe33d2c6618ee881b Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 11 Mar 2024 16:24:07 -0500 Subject: [PATCH 154/455] feat: Allow bulk-adding of snippets, footers, annotations This will make it easier for diagnostic systems to convert their messages to a `Message` --- src/snippet.rs | 15 +++++++++++++++ tests/fixtures/deserialize.rs | 14 +++----------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/snippet.rs b/src/snippet.rs index 7b214028..9ce10318 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -34,10 +34,20 @@ impl<'a> Message<'a> { self } + pub fn snippets(mut self, slice: impl IntoIterator<Item = Snippet<'a>>) -> Self { + self.snippets.extend(slice); + self + } + pub fn footer(mut self, footer: Label<'a>) -> Self { self.footer.push(footer); self } + + pub fn footers(mut self, footer: impl IntoIterator<Item = Label<'a>>) -> Self { + self.footer.extend(footer); + self + } } pub struct Label<'a> { @@ -116,6 +126,11 @@ impl<'a> Snippet<'a> { self } + pub fn annotations(mut self, annotation: impl IntoIterator<Item = Annotation<'a>>) -> Self { + self.annotations.extend(annotation); + self + } + pub fn fold(mut self, fold: bool) -> Self { self.fold = fold; self diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs index a22f2157..6bfe76f9 100644 --- a/tests/fixtures/deserialize.rs +++ b/tests/fixtures/deserialize.rs @@ -42,12 +42,8 @@ impl<'a> From<MessageDef<'a>> for Message<'a> { if let Some(id) = id { message = message.id(id); } - message = snippets - .into_iter() - .fold(message, |message, snippet| message.snippet(snippet)); - message = footer - .into_iter() - .fold(message, |message, label| message.footer(label)); + message = message.snippets(snippets); + message = message.footers(footer); message } } @@ -111,11 +107,7 @@ impl<'a> From<SnippetDef<'a>> for Snippet<'a> { if let Some(origin) = origin { snippet = snippet.origin(origin) } - snippet = annotations - .into_iter() - .fold(snippet, |snippet, annotation| { - snippet.annotation(annotation) - }); + snippet = snippet.annotations(annotations); snippet } } From 9cdd503a0a52961b9536a460e3bbc325f6efdc69 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Tue, 12 Mar 2024 11:13:12 -0500 Subject: [PATCH 155/455] docs: Describe fold --- src/snippet.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/snippet.rs b/src/snippet.rs index 9ce10318..6ba2d2d8 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -131,6 +131,7 @@ impl<'a> Snippet<'a> { self } + /// Hide lines without [`Annotation`]s pub fn fold(mut self, fold: bool) -> Self { self.fold = fold; self From 24c305668ceefca18633eca5f8c317fd3d10676f Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Tue, 12 Mar 2024 14:49:02 -0500 Subject: [PATCH 156/455] refactor(render): Decouple title from Label --- src/renderer/display_list.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index d54282fe..b75c809a 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -119,13 +119,7 @@ impl<'a> DisplayList<'a> { ) -> DisplayList<'a> { let mut body = vec![]; - body.push(format_title( - snippet::Label { - level, - label: title, - }, - id, - )); + body.push(format_title(level, id, title)); for (idx, snippet) in snippets.into_iter().enumerate() { body.append(&mut format_slice( @@ -740,12 +734,12 @@ fn format_label( result } -fn format_title<'a>(title: snippet::Label<'a>, id: Option<&'a str>) -> DisplayLine<'a> { +fn format_title<'a>(level: crate::Level, id: Option<&'a str>, label: &'a str) -> DisplayLine<'a> { DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { - annotation_type: DisplayAnnotationType::from(title.level), + annotation_type: DisplayAnnotationType::from(level), id, - label: format_label(Some(title.label), Some(DisplayTextStyle::Emphasis)), + label: format_label(Some(label), Some(DisplayTextStyle::Emphasis)), }, source_aligned: false, continuation: false, From 568b1a30d93f9067ea20e1a61256b6484ba6b5d2 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Tue, 12 Mar 2024 14:52:28 -0500 Subject: [PATCH 157/455] refactor(render): Emphasize top-down order --- src/renderer/display_list.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index b75c809a..2bbc2902 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -719,21 +719,6 @@ impl<'a> Iterator for CursorLines<'a> { } } -fn format_label( - label: Option<&str>, - style: Option<DisplayTextStyle>, -) -> Vec<DisplayTextFragment<'_>> { - let mut result = vec![]; - if let Some(label) = label { - let element_style = style.unwrap_or(DisplayTextStyle::Regular); - result.push(DisplayTextFragment { - content: label, - style: element_style, - }); - } - result -} - fn format_title<'a>(level: crate::Level, id: Option<&'a str>, label: &'a str) -> DisplayLine<'a> { DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { @@ -762,6 +747,21 @@ fn format_footer(footer: snippet::Label<'_>) -> Vec<DisplayLine<'_>> { result } +fn format_label( + label: Option<&str>, + style: Option<DisplayTextStyle>, +) -> Vec<DisplayTextFragment<'_>> { + let mut result = vec![]; + if let Some(label) = label { + let element_style = style.unwrap_or(DisplayTextStyle::Regular); + result.push(DisplayTextFragment { + content: label, + style: element_style, + }); + } + result +} + fn format_slice( snippet: snippet::Snippet<'_>, is_first: bool, From 20842e5afa1540fb25e3dadab643bdd69c32c47c Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Tue, 12 Mar 2024 14:53:11 -0500 Subject: [PATCH 158/455] refactor(render): Update fn name for slice -> snippet --- src/renderer/display_list.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index 2bbc2902..5c2581c1 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -122,7 +122,7 @@ impl<'a> DisplayList<'a> { body.push(format_title(level, id, title)); for (idx, snippet) in snippets.into_iter().enumerate() { - body.append(&mut format_slice( + body.append(&mut format_snippet( snippet, idx == 0, !footer.is_empty(), @@ -762,7 +762,7 @@ fn format_label( result } -fn format_slice( +fn format_snippet( snippet: snippet::Snippet<'_>, is_first: bool, has_footer: bool, From 25d519a5071a2175631fef5ce10ade7f72b22192 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Tue, 12 Mar 2024 15:01:52 -0500 Subject: [PATCH 159/455] refactor(render): Pull out Message formatting --- src/renderer/display_list.rs | 55 +++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index 5c2581c1..c3890ba8 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -106,33 +106,12 @@ impl<'a> DisplayList<'a> { const WARNING_TXT: &'static str = "warning"; pub(crate) fn new( - snippet::Message { - level, - id, - title, - footer, - snippets, - }: snippet::Message<'a>, + message: snippet::Message<'a>, stylesheet: &'a Stylesheet, anonymized_line_numbers: bool, margin: Option<Margin>, ) -> DisplayList<'a> { - let mut body = vec![]; - - body.push(format_title(level, id, title)); - - for (idx, snippet) in snippets.into_iter().enumerate() { - body.append(&mut format_snippet( - snippet, - idx == 0, - !footer.is_empty(), - margin, - )); - } - - for annotation in footer { - body.append(&mut format_footer(annotation)); - } + let body = format_message(message, margin); Self { body, @@ -719,6 +698,36 @@ impl<'a> Iterator for CursorLines<'a> { } } +fn format_message<'a>( + snippet::Message { + level, + id, + title, + footer, + snippets, + }: snippet::Message<'a>, + margin: Option<Margin>, +) -> Vec<DisplayLine<'a>> { + let mut body = vec![]; + + body.push(format_title(level, id, title)); + + for (idx, snippet) in snippets.into_iter().enumerate() { + body.append(&mut format_snippet( + snippet, + idx == 0, + !footer.is_empty(), + margin, + )); + } + + for annotation in footer { + body.append(&mut format_footer(annotation)); + } + + body +} + fn format_title<'a>(level: crate::Level, id: Option<&'a str>, label: &'a str) -> DisplayLine<'a> { DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { From ce736be5742f873a03627cad0340964e2d44c61d Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Tue, 12 Mar 2024 15:13:10 -0500 Subject: [PATCH 160/455] fix!: Generalize footer to be any `Message` Fixes #96 --- examples/footer.rs | 4 ++-- src/renderer/display_list.rs | 31 ++++++++++++++++---------- src/snippet.rs | 41 +++-------------------------------- tests/fixtures/deserialize.rs | 24 +++----------------- 4 files changed, 28 insertions(+), 72 deletions(-) diff --git a/examples/footer.rs b/examples/footer.rs index 8b4d0780..35809050 100644 --- a/examples/footer.rs +++ b/examples/footer.rs @@ -1,4 +1,4 @@ -use annotate_snippets::{Label, Level, Renderer, Snippet}; +use annotate_snippets::{Level, Renderer, Snippet}; fn main() { let message = @@ -13,7 +13,7 @@ fn main() { "expected struct `annotate_snippets::snippet::Slice`, found reference", )), ) - .footer(Label::note( + .footer(Level::Note.title( "expected type: `snippet::Annotation`\n found type: `__&__snippet::Annotation`", )); diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index c3890ba8..74036360 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -111,7 +111,7 @@ impl<'a> DisplayList<'a> { anonymized_line_numbers: bool, margin: Option<Margin>, ) -> DisplayList<'a> { - let body = format_message(message, margin); + let body = format_message(message, margin, true); Self { body, @@ -698,19 +698,24 @@ impl<'a> Iterator for CursorLines<'a> { } } -fn format_message<'a>( +fn format_message( snippet::Message { level, id, title, footer, snippets, - }: snippet::Message<'a>, + }: snippet::Message<'_>, margin: Option<Margin>, -) -> Vec<DisplayLine<'a>> { + primary: bool, +) -> Vec<DisplayLine<'_>> { let mut body = vec![]; - body.push(format_title(level, id, title)); + if !snippets.is_empty() || primary { + body.push(format_title(level, id, title)); + } else { + body.append(&mut format_footer(level, id, title)); + } for (idx, snippet) in snippets.into_iter().enumerate() { body.append(&mut format_snippet( @@ -722,7 +727,7 @@ fn format_message<'a>( } for annotation in footer { - body.append(&mut format_footer(annotation)); + body.append(&mut format_message(annotation, margin, false)); } body @@ -740,13 +745,17 @@ fn format_title<'a>(level: crate::Level, id: Option<&'a str>, label: &'a str) -> }) } -fn format_footer(footer: snippet::Label<'_>) -> Vec<DisplayLine<'_>> { +fn format_footer<'a>( + level: crate::Level, + id: Option<&'a str>, + label: &'a str, +) -> Vec<DisplayLine<'a>> { let mut result = vec![]; - for (i, line) in footer.label.lines().enumerate() { + for (i, line) in label.lines().enumerate() { result.push(DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { - annotation_type: DisplayAnnotationType::from(footer.level), - id: None, + annotation_type: DisplayAnnotationType::from(level), + id, label: format_label(Some(line), None), }, source_aligned: true, @@ -1447,7 +1456,7 @@ mod tests { fn test_format_label() { let input = snippet::Level::Error .title("") - .footer(snippet::Label::error("This __is__ a title")); + .footer(snippet::Level::Error.title("This __is__ a title")); let output = from_display_lines(vec![ DisplayLine::Raw(DisplayRawLine::Annotation { annotation: Annotation { diff --git a/src/snippet.rs b/src/snippet.rs index 6ba2d2d8..e7d4bef6 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -20,7 +20,7 @@ pub struct Message<'a> { pub(crate) id: Option<&'a str>, pub(crate) title: &'a str, pub(crate) snippets: Vec<Snippet<'a>>, - pub(crate) footer: Vec<Label<'a>>, + pub(crate) footer: Vec<Message<'a>>, } impl<'a> Message<'a> { @@ -39,52 +39,17 @@ impl<'a> Message<'a> { self } - pub fn footer(mut self, footer: Label<'a>) -> Self { + pub fn footer(mut self, footer: Message<'a>) -> Self { self.footer.push(footer); self } - pub fn footers(mut self, footer: impl IntoIterator<Item = Label<'a>>) -> Self { + pub fn footers(mut self, footer: impl IntoIterator<Item = Message<'a>>) -> Self { self.footer.extend(footer); self } } -pub struct Label<'a> { - pub(crate) level: Level, - pub(crate) label: &'a str, -} - -impl<'a> Label<'a> { - pub fn new(level: Level, label: &'a str) -> Self { - Self { level, label } - } - pub fn error(label: &'a str) -> Self { - Self::new(Level::Error, label) - } - - pub fn warning(label: &'a str) -> Self { - Self::new(Level::Warning, label) - } - - pub fn info(label: &'a str) -> Self { - Self::new(Level::Info, label) - } - - pub fn note(label: &'a str) -> Self { - Self::new(Level::Note, label) - } - - pub fn help(label: &'a str) -> Self { - Self::new(Level::Help, label) - } - - pub fn label(mut self, label: &'a str) -> Self { - self.label = label; - self - } -} - /// Structure containing the slice of text to be annotated and /// basic information about the location of the slice. /// diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs index 6bfe76f9..165c3418 100644 --- a/tests/fixtures/deserialize.rs +++ b/tests/fixtures/deserialize.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Deserializer, Serialize}; use std::ops::Range; -use annotate_snippets::{renderer::Margin, Annotation, Label, Level, Message, Renderer, Snippet}; +use annotate_snippets::{renderer::Margin, Annotation, Level, Message, Renderer, Snippet}; #[derive(Deserialize)] pub struct Fixture<'a> { @@ -20,10 +20,9 @@ pub struct MessageDef<'a> { #[serde(default)] #[serde(borrow)] pub id: Option<&'a str>, - #[serde(deserialize_with = "deserialize_labels")] #[serde(default)] #[serde(borrow)] - pub footer: Vec<Label<'a>>, + pub footer: Vec<MessageDef<'a>>, #[serde(deserialize_with = "deserialize_snippets")] #[serde(borrow)] pub snippets: Vec<Snippet<'a>>, @@ -43,28 +42,11 @@ impl<'a> From<MessageDef<'a>> for Message<'a> { message = message.id(id); } message = message.snippets(snippets); - message = message.footers(footer); + message = message.footers(footer.into_iter().map(Into::into)); message } } -fn deserialize_labels<'de, D>(deserializer: D) -> Result<Vec<Label<'de>>, D::Error> -where - D: Deserializer<'de>, -{ - #[derive(Deserialize)] - struct Wrapper<'a>( - #[serde(with = "LabelDef")] - #[serde(borrow)] - LabelDef<'a>, - ); - - let v = Vec::deserialize(deserializer)?; - Ok(v.into_iter() - .map(|Wrapper(a)| Label::new(a.level, a.label)) - .collect()) -} - fn deserialize_snippets<'de, D>(deserializer: D) -> Result<Vec<Snippet<'de>>, D::Error> where D: Deserializer<'de>, From 535e146651e68f183682083cca6bb58c37e8b481 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Tue, 12 Mar 2024 15:16:05 -0500 Subject: [PATCH 161/455] refactor(render): Prefer 'extend' over 'append' --- src/renderer/display_list.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index 74036360..d0444a31 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -714,11 +714,11 @@ fn format_message( if !snippets.is_empty() || primary { body.push(format_title(level, id, title)); } else { - body.append(&mut format_footer(level, id, title)); + body.extend(format_footer(level, id, title)); } for (idx, snippet) in snippets.into_iter().enumerate() { - body.append(&mut format_snippet( + body.extend(format_snippet( snippet, idx == 0, !footer.is_empty(), @@ -727,7 +727,7 @@ fn format_message( } for annotation in footer { - body.append(&mut format_message(annotation, margin, false)); + body.extend(format_message(annotation, margin, false)); } body @@ -789,14 +789,14 @@ fn format_snippet( let main_range = snippet.annotations.first().map(|x| x.range.start); let origin = snippet.origin; let need_empty_header = origin.is_some() || is_first; - let mut body = format_body(snippet, need_empty_header, has_footer, margin); + let body = format_body(snippet, need_empty_header, has_footer, margin); let header = format_header(origin, main_range, &body, is_first); let mut result = vec![]; if let Some(header) = header { result.push(header); } - result.append(&mut body); + result.extend(body); result } From aed54bb789f9830996d08181634542aa0b53b1b8 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Sun, 10 Mar 2024 12:45:03 -0600 Subject: [PATCH 162/455] fix: Make spans work with single width emojis --- Cargo.lock | 14 +--- Cargo.toml | 1 - src/renderer/display_list.rs | 76 ++++++++----------- .../no-color/ensure-emoji-highlight-width.svg | 33 ++++++++ .../ensure-emoji-highlight-width.toml | 15 ++++ tests/formatter.rs | 10 +-- 6 files changed, 85 insertions(+), 64 deletions(-) create mode 100644 tests/fixtures/no-color/ensure-emoji-highlight-width.svg create mode 100644 tests/fixtures/no-color/ensure-emoji-highlight-width.toml diff --git a/Cargo.lock b/Cargo.lock index 5672f610..3bfb779a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,7 +26,6 @@ dependencies = [ "criterion", "difference", "glob", - "itertools 0.12.1", "serde", "snapbox", "toml", @@ -243,7 +242,7 @@ dependencies = [ "clap", "criterion-plot", "is-terminal", - "itertools 0.10.5", + "itertools", "num-traits", "once_cell", "oorandom", @@ -264,7 +263,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", - "itertools 0.10.5", + "itertools", ] [[package]] @@ -449,15 +448,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.9" diff --git a/Cargo.toml b/Cargo.toml index 5fbfc0b6..f97dddb8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,6 @@ maintenance = { status = "actively-developed" } [dependencies] anstyle = "1.0.4" -itertools = "0.12.1" unicode-width = "0.1.11" [dev-dependencies] diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index d0444a31..5cc1e2c3 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -32,8 +32,6 @@ //! //! The above snippet has been built out of the following structure: use crate::snippet; -use itertools::FoldWhile::{Continue, Done}; -use itertools::Itertools; use std::fmt::{Display, Write}; use std::ops::Range; use std::{cmp, fmt}; @@ -830,20 +828,7 @@ fn format_header<'a>( } = item { if main_range >= range.0 && main_range <= range.1 { - let char_column = text - .chars() - .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) - .chain(std::iter::once(1)) // treat the end of line as single-width - .enumerate() - .fold_while((0, 0), |(count, acc), (i, width)| { - if acc <= main_range - range.0 { - Continue((i, acc + width)) - } else { - Done((count, acc)) - } - }) - .into_inner() - .0; + let char_column = text[0..(main_range - range.0)].chars().count(); col = char_column + 1; line_offset = lineno.unwrap_or(1); break; @@ -984,18 +969,11 @@ fn format_body( let mut body = vec![]; let mut current_line = snippet.line_start; let mut current_index = 0; - let mut line_info = vec![]; - struct LineInfo { - line_start_index: usize, - line_end_index: usize, - } - - for (line, end_line) in CursorLines::new(snippet.source) { - let line_length: usize = line - .chars() - .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) - .sum(); + let mut annotation_line_count = 0; + let mut annotations = snippet.annotations; + for (idx, (line, end_line)) in CursorLines::new(snippet.source).enumerate() { + let line_length: usize = line.len(); let line_range = (current_index, current_index + line_length); body.push(DisplayLine::Source { lineno: Some(current_line), @@ -1005,24 +983,11 @@ fn format_body( range: line_range, }, }); - line_info.push(LineInfo { - line_start_index: line_range.0, - line_end_index: line_range.1, - }); + let line_start_index = line_range.0; + let line_end_index = line_range.1; current_line += 1; current_index += line_length + end_line as usize; - } - let mut annotation_line_count = 0; - let mut annotations = snippet.annotations; - for ( - idx, - LineInfo { - line_start_index, - line_end_index, - }, - ) in line_info.into_iter().enumerate() - { let margin_left = margin .map(|m| m.left(line_end_index - line_start_index)) .unwrap_or_default(); @@ -1040,8 +1005,20 @@ fn format_body( if start >= line_start_index && end <= line_end_index || start == line_end_index && end - start <= 1 => { - let annotation_start_col = start - line_start_index - margin_left; - let annotation_end_col = end - line_start_index - margin_left; + let annotation_start_col = line[0..(start - line_start_index)] + .chars() + .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) + .sum::<usize>() + - margin_left; + // This allows for annotations to be placed one past the + // last character + let safe_end = (end - line_start_index).saturating_sub(line_length); + let annotation_end_col = line[0..(end - line_start_index) - safe_end] + .chars() + .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) + .sum::<usize>() + + safe_end + - margin_left; let range = (annotation_start_col, annotation_end_col); body.insert( body_idx + 1, @@ -1080,7 +1057,10 @@ fn format_body( }); } } else { - let annotation_start_col = start - line_start_index; + let annotation_start_col = line[0..(start - line_start_index)] + .chars() + .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) + .sum::<usize>(); let range = (annotation_start_col, annotation_start_col + 1); body.insert( body_idx + 1, @@ -1132,7 +1112,11 @@ fn format_body( }); } - let end_mark = (end - line_start_index).saturating_sub(1); + let end_mark = line[0..(end - line_start_index)] + .chars() + .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) + .sum::<usize>() + .saturating_sub(1); let range = (end_mark - margin_left, (end_mark + 1) - margin_left); body.insert( body_idx + 1, diff --git a/tests/fixtures/no-color/ensure-emoji-highlight-width.svg b/tests/fixtures/no-color/ensure-emoji-highlight-width.svg new file mode 100644 index 00000000..0840805e --- /dev/null +++ b/tests/fixtures/no-color/ensure-emoji-highlight-width.svg @@ -0,0 +1,33 @@ +<svg width="1356px" height="128px" xmlns="http://www.w3.org/2000/svg"> + <style> + .fg { fill: #AAAAAA } + .bg { background: #000000 } + .container { + padding: 0 10px; + line-height: 18px; + } + tspan { + font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + white-space: pre; + line-height: 18px; + } + </style> + + <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> + + <text xml:space="preserve" class="container fg"> + <tspan x="10px" y="28px"><tspan>error: invalid character ` ` in package name: `haha this isn't a valid name 🐛`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)</tspan> +</tspan> + <tspan x="10px" y="46px"><tspan> --> <file>:7:1</tspan> +</tspan> + <tspan x="10px" y="64px"><tspan> |</tspan> +</tspan> + <tspan x="10px" y="82px"><tspan>7 | "haha this isn't a valid name 🐛" = { package = "libc", version = "0.1" }</tspan> +</tspan> + <tspan x="10px" y="100px"><tspan> | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</tspan> +</tspan> + <tspan x="10px" y="118px"><tspan> |</tspan> +</tspan> + </text> + +</svg> diff --git a/tests/fixtures/no-color/ensure-emoji-highlight-width.toml b/tests/fixtures/no-color/ensure-emoji-highlight-width.toml new file mode 100644 index 00000000..7af05ff1 --- /dev/null +++ b/tests/fixtures/no-color/ensure-emoji-highlight-width.toml @@ -0,0 +1,15 @@ +[message] +title = "invalid character ` ` in package name: `haha this isn't a valid name 🐛`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)" +level = "Error" + + +[[message.snippets]] +source = """ +"haha this isn't a valid name 🐛" = { package = "libc", version = "0.1" } +""" +line_start = 7 +origin = "<file>" +[[message.snippets.annotations]] +label = "" +level = "Error" +range = [0, 35] diff --git a/tests/formatter.rs b/tests/formatter.rs index 76dcb27c..035492a9 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -25,7 +25,7 @@ fn test_point_to_double_width_characters() { let snippets = Level::Error.title("").snippet( Snippet::source("こんにちは、世界") .origin("<current file>") - .annotation(Level::Error.span(12..16).label("world")), + .annotation(Level::Error.span(18..24).label("world")), ); let expected = r#"error @@ -44,7 +44,7 @@ fn test_point_to_double_width_characters_across_lines() { let snippets = Level::Error.title("").snippet( Snippet::source("おはよう\nございます") .origin("<current file>") - .annotation(Level::Error.span(4..15).label("Good morning")), + .annotation(Level::Error.span(6..22).label("Good morning")), ); let expected = r#"error @@ -65,8 +65,8 @@ fn test_point_to_double_width_characters_multiple() { let snippets = Level::Error.title("").snippet( Snippet::source("お寿司\n食べたい🍣") .origin("<current file>") - .annotation(Level::Error.span(0..6).label("Sushi1")) - .annotation(Level::Note.span(11..15).label("Sushi2")), + .annotation(Level::Error.span(0..9).label("Sushi1")) + .annotation(Level::Note.span(16..22).label("Sushi2")), ); let expected = r#"error @@ -87,7 +87,7 @@ fn test_point_to_double_width_characters_mixed() { let snippets = Level::Error.title("").snippet( Snippet::source("こんにちは、新しいWorld!") .origin("<current file>") - .annotation(Level::Error.span(12..23).label("New world")), + .annotation(Level::Error.span(18..32).label("New world")), ); let expected = r#"error From 449feec0a82dd1da8cca2d8da7882aa3808a5f15 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Tue, 12 Mar 2024 20:24:09 -0500 Subject: [PATCH 163/455] test(render): Migrate to snapshot tests --- Cargo.lock | 4 +- Cargo.toml | 2 +- src/renderer/display_list.rs | 754 +++++++++++++++++++++-------------- 3 files changed, 463 insertions(+), 297 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5672f610..0fb37c3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -748,9 +748,9 @@ checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21" [[package]] name = "snapbox" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6de4a00e16c1e859fbc29a6484c5aad30f18fb9f4c2c29033c28aef7e965b82e" +checksum = "8ac441e1ecf678f68423d47f376d53fabce1afba92c8f68e31508eb27df8562a" dependencies = [ "anstream", "anstyle", diff --git a/Cargo.toml b/Cargo.toml index 5fbfc0b6..908478b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ criterion = "0.5.1" difference = "2.0.0" glob = "0.3.1" serde = { version = "1.0.197", features = ["derive"] } -snapbox = { version = "0.5.8", features = ["diff", "harness", "path", "term-svg", "cmd", "examples"] } +snapbox = { version = "0.5.9", features = ["diff", "harness", "path", "term-svg", "cmd", "examples"] } toml = "0.5.11" [[bench]] diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index d0444a31..95517dfa 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -1212,6 +1212,10 @@ fn is_annotation_empty(annotation: &Annotation<'_>) -> bool { mod tests { use super::*; + use snapbox::assert_eq; + use snapbox::str; + use snapbox::ToDebug as _; + const STYLESHEET: Stylesheet = Stylesheet::plain(); fn from_display_lines(lines: Vec<DisplayLine<'_>>) -> DisplayList<'_> { @@ -1226,19 +1230,35 @@ mod tests { #[test] fn test_format_title() { let input = snippet::Level::Error.title("This is a title").id("E0001"); - let output = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Error, - id: Some("E0001"), - label: vec![DisplayTextFragment { - content: "This is a title", - style: DisplayTextStyle::Emphasis, - }], - }, - source_aligned: false, - continuation: false, - })]); - assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output); + let output = str![[r#" + DisplayList { + body: [ + Raw( + Annotation { + annotation: Annotation { + annotation_type: Error, + id: Some( + "E0001", + ), + label: [ + DisplayTextFragment { + content: "This is a title", + style: Emphasis, + }, + ], + }, + source_aligned: false, + continuation: false, + }, + ), + ], + anonymized_line_numbers: false, + } + "#]]; + assert_eq( + output, + DisplayList::new(input, &STYLESHEET, false, None).to_debug(), + ); } #[test] @@ -1249,55 +1269,75 @@ mod tests { let input = snippet::Level::Error .title("") .snippet(snippet::Snippet::source(&source).line_start(5402)); - let output = from_display_lines(vec![ - DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Error, - id: None, - label: vec![DisplayTextFragment { - content: "", - style: DisplayTextStyle::Emphasis, - }], - }, - source_aligned: false, - continuation: false, - }), - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }, - DisplayLine::Source { - lineno: Some(5402), - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: line_1, - range: (0, line_1.len()), - }, - }, - DisplayLine::Source { - lineno: Some(5403), - inline_marks: vec![], - line: DisplaySourceLine::Content { - range: (line_1.len() + 1, source.len()), - text: line_2, - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }, - ]); - assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output); + let output = str![[r#" + DisplayList { + body: [ + Raw( + Annotation { + annotation: Annotation { + annotation_type: Error, + id: None, + label: [ + DisplayTextFragment { + content: "", + style: Emphasis, + }, + ], + }, + source_aligned: false, + continuation: false, + }, + ), + Source { + lineno: None, + inline_marks: [], + line: Empty, + }, + Source { + lineno: Some( + 5402, + ), + inline_marks: [], + line: Content { + text: "This is line 1", + range: ( + 0, + 14, + ), + }, + }, + Source { + lineno: Some( + 5403, + ), + inline_marks: [], + line: Content { + text: "This is line 2", + range: ( + 15, + 29, + ), + }, + }, + Source { + lineno: None, + inline_marks: [], + line: Empty, + }, + ], + anonymized_line_numbers: false, + } + "#]]; + assert_eq( + output, + DisplayList::new(input, &STYLESHEET, false, None).to_debug(), + ); } #[test] fn test_format_slices_continuation() { let src_0 = "This is slice 1"; - let src_0_len = src_0.len(); let src_1 = "This is slice 2"; - let src_1_len = src_1.len(); let input = snippet::Level::Error .title("") .snippet( @@ -1310,67 +1350,93 @@ mod tests { .line_start(2) .origin("file2.rs"), ); - let output = from_display_lines(vec![ - DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Error, - id: None, - label: vec![DisplayTextFragment { - content: "", - style: DisplayTextStyle::Emphasis, - }], - }, - source_aligned: false, - continuation: false, - }), - DisplayLine::Raw(DisplayRawLine::Origin { - path: "file1.rs", - pos: None, - header_type: DisplayHeaderType::Initial, - }), - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }, - DisplayLine::Source { - lineno: Some(5402), - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: src_0, - range: (0, src_0_len), - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }, - DisplayLine::Raw(DisplayRawLine::Origin { - path: "file2.rs", - pos: None, - header_type: DisplayHeaderType::Continuation, - }), - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }, - DisplayLine::Source { - lineno: Some(2), - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: src_1, - range: (0, src_1_len), - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }, - ]); - assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output); + let output = str![[r#" + DisplayList { + body: [ + Raw( + Annotation { + annotation: Annotation { + annotation_type: Error, + id: None, + label: [ + DisplayTextFragment { + content: "", + style: Emphasis, + }, + ], + }, + source_aligned: false, + continuation: false, + }, + ), + Raw( + Origin { + path: "file1.rs", + pos: None, + header_type: Initial, + }, + ), + Source { + lineno: None, + inline_marks: [], + line: Empty, + }, + Source { + lineno: Some( + 5402, + ), + inline_marks: [], + line: Content { + text: "This is slice 1", + range: ( + 0, + 15, + ), + }, + }, + Source { + lineno: None, + inline_marks: [], + line: Empty, + }, + Raw( + Origin { + path: "file2.rs", + pos: None, + header_type: Continuation, + }, + ), + Source { + lineno: None, + inline_marks: [], + line: Empty, + }, + Source { + lineno: Some( + 2, + ), + inline_marks: [], + line: Content { + text: "This is slice 2", + range: ( + 0, + 15, + ), + }, + }, + Source { + lineno: None, + inline_marks: [], + line: Empty, + }, + ], + anonymized_line_numbers: false, + } + "#]]; + assert_eq( + output, + DisplayList::new(input, &STYLESHEET, false, None).to_debug(), + ); } #[test] @@ -1389,67 +1455,91 @@ mod tests { .label("Test annotation"), ), ); - let output = from_display_lines(vec![ - DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Error, - id: None, - label: vec![DisplayTextFragment { - content: "", - style: DisplayTextStyle::Emphasis, - }], - }, - source_aligned: false, - continuation: false, - }), - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }, - DisplayLine::Source { - lineno: Some(5402), - inline_marks: vec![], - line: DisplaySourceLine::Content { - range: (0, line_1.len()), - text: line_1, - }, - }, - DisplayLine::Source { - lineno: Some(5403), - inline_marks: vec![], - line: DisplaySourceLine::Content { - range: (line_1.len() + 1, source.len()), - text: line_2, - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Info, - id: None, - label: vec![DisplayTextFragment { - content: "Test annotation", - style: DisplayTextStyle::Regular, - }], - }, - range: ( - range.start - (line_1.len() + 1), - range.end - (line_1.len() + 1), + let output = str![[r#" + DisplayList { + body: [ + Raw( + Annotation { + annotation: Annotation { + annotation_type: Error, + id: None, + label: [ + DisplayTextFragment { + content: "", + style: Emphasis, + }, + ], + }, + source_aligned: false, + continuation: false, + }, ), - annotation_type: DisplayAnnotationType::Info, - annotation_part: DisplayAnnotationPart::Standalone, - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }, - ]); - assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output); + Source { + lineno: None, + inline_marks: [], + line: Empty, + }, + Source { + lineno: Some( + 5402, + ), + inline_marks: [], + line: Content { + text: "This is line 1", + range: ( + 0, + 14, + ), + }, + }, + Source { + lineno: Some( + 5403, + ), + inline_marks: [], + line: Content { + text: "This is line 2", + range: ( + 15, + 29, + ), + }, + }, + Source { + lineno: None, + inline_marks: [], + line: Annotation { + annotation: Annotation { + annotation_type: Info, + id: None, + label: [ + DisplayTextFragment { + content: "Test annotation", + style: Regular, + }, + ], + }, + range: ( + 7, + 9, + ), + annotation_type: Info, + annotation_part: Standalone, + }, + }, + Source { + lineno: None, + inline_marks: [], + line: Empty, + }, + ], + anonymized_line_numbers: false, + } + "#]]; + assert_eq( + output, + DisplayList::new(input, &STYLESHEET, false, None).to_debug(), + ); } #[test] @@ -1457,33 +1547,49 @@ mod tests { let input = snippet::Level::Error .title("") .footer(snippet::Level::Error.title("This __is__ a title")); - let output = from_display_lines(vec![ - DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Error, - id: None, - label: vec![DisplayTextFragment { - content: "", - style: DisplayTextStyle::Emphasis, - }], - }, - source_aligned: false, - continuation: false, - }), - DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Error, - id: None, - label: vec![DisplayTextFragment { - content: "This __is__ a title", - style: DisplayTextStyle::Regular, - }], - }, - source_aligned: true, - continuation: false, - }), - ]); - assert_eq!(DisplayList::new(input, &STYLESHEET, false, None), output); + let output = str![[r#" + DisplayList { + body: [ + Raw( + Annotation { + annotation: Annotation { + annotation_type: Error, + id: None, + label: [ + DisplayTextFragment { + content: "", + style: Emphasis, + }, + ], + }, + source_aligned: false, + continuation: false, + }, + ), + Raw( + Annotation { + annotation: Annotation { + annotation_type: Error, + id: None, + label: [ + DisplayTextFragment { + content: "This __is__ a title", + style: Regular, + }, + ], + }, + source_aligned: true, + continuation: false, + }, + ), + ], + anonymized_line_numbers: false, + } + "#]]; + assert_eq( + output, + DisplayList::new(input, &STYLESHEET, false, None).to_debug(), + ); } #[test] @@ -1501,79 +1607,109 @@ mod tests { #[test] fn test_i_29() { - let snippets = snippet::Level::Error.title("oops").snippet( + let input = snippet::Level::Error.title("oops").snippet( snippet::Snippet::source("First line\r\nSecond oops line") .line_start(1) .origin("<current file>") .fold(true) .annotation(snippet::Level::Error.span(19..23).label("oops")), ); - - let expected = from_display_lines(vec![ - DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Error, - id: None, - label: vec![DisplayTextFragment { - content: "oops", - style: DisplayTextStyle::Emphasis, - }], - }, - source_aligned: false, - continuation: false, - }), - DisplayLine::Raw(DisplayRawLine::Origin { - path: "<current file>", - pos: Some((2, 8)), - header_type: DisplayHeaderType::Initial, - }), - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }, - DisplayLine::Source { - lineno: Some(1), - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: "First line", - range: (0, 10), - }, - }, - DisplayLine::Source { - lineno: Some(2), - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: "Second oops line", - range: (12, 28), - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::None, - id: None, - label: vec![DisplayTextFragment { - content: "oops", - style: DisplayTextStyle::Regular, - }], + let output = str![[r#" + DisplayList { + body: [ + Raw( + Annotation { + annotation: Annotation { + annotation_type: Error, + id: None, + label: [ + DisplayTextFragment { + content: "oops", + style: Emphasis, + }, + ], + }, + source_aligned: false, + continuation: false, + }, + ), + Raw( + Origin { + path: "<current file>", + pos: Some( + ( + 2, + 8, + ), + ), + header_type: Initial, + }, + ), + Source { + lineno: None, + inline_marks: [], + line: Empty, }, - range: (7, 11), - annotation_type: DisplayAnnotationType::Error, - annotation_part: DisplayAnnotationPart::Standalone, - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }, - ]); - assert_eq!( - DisplayList::new(snippets, &STYLESHEET, false, None), - expected + Source { + lineno: Some( + 1, + ), + inline_marks: [], + line: Content { + text: "First line", + range: ( + 0, + 10, + ), + }, + }, + Source { + lineno: Some( + 2, + ), + inline_marks: [], + line: Content { + text: "Second oops line", + range: ( + 12, + 28, + ), + }, + }, + Source { + lineno: None, + inline_marks: [], + line: Annotation { + annotation: Annotation { + annotation_type: None, + id: None, + label: [ + DisplayTextFragment { + content: "oops", + style: Regular, + }, + ], + }, + range: ( + 7, + 11, + ), + annotation_type: Error, + annotation_part: Standalone, + }, + }, + Source { + lineno: None, + inline_marks: [], + line: Empty, + }, + ], + anonymized_line_numbers: false, + } + "#]]; + assert_eq( + output, + DisplayList::new(input, &STYLESHEET, false, None).to_debug(), ); } @@ -1585,7 +1721,7 @@ mod tests { line: DisplaySourceLine::Empty, }]); - assert_eq!(dl.to_string(), " |"); + assert_eq(str![" |"], dl.to_string()); } #[test] @@ -1609,9 +1745,11 @@ mod tests { }, ]); - assert_eq!( + assert_eq( + str![[r#" + 56 | This is an example + 57 | of content lines"#]], dl.to_string(), - "56 | This is an example\n57 | of content lines" ); } @@ -1635,7 +1773,7 @@ mod tests { }, }]); - assert_eq!(dl.to_string(), " | ^^^^^ Example string"); + assert_eq(str![" | ^^^^^ Example string"], dl.to_string()); } #[test] @@ -1677,9 +1815,12 @@ mod tests { }, ]); - assert_eq!( + assert_eq( + str![[r#" + | ----- help: Example string + | Second line"#]] + .indent(false), dl.to_string(), - " | ----- help: Example string\n | Second line" ); } @@ -1790,7 +1931,17 @@ mod tests { }, ]); - assert_eq!(dl.to_string(), " | ----- info: Example string\n | Second line\n | Second line of the warning\n | ----- info: This is an info\n | ----- help: This is help\n | This is an annotation of type none"); + assert_eq( + str![[r#" + | ----- info: Example string + | Second line + | Second line of the warning + | ----- info: This is an info + | ----- help: This is help + | This is an annotation of type none"#]] + .indent(false), + dl.to_string(), + ); } #[test] @@ -1817,9 +1968,12 @@ mod tests { }, ]); - assert_eq!( + assert_eq( + str![[r#" + 5 | This is line 5 + ... + 10021 | ... and now we're at line 10021"#]], dl.to_string(), - " 5 | This is line 5\n...\n10021 | ... and now we're at line 10021" ); } @@ -1831,7 +1985,7 @@ mod tests { header_type: DisplayHeaderType::Initial, })]); - assert_eq!(dl.to_string(), "--> src/test.rs"); + assert_eq(str!["--> src/test.rs"], dl.to_string()); } #[test] @@ -1842,7 +1996,7 @@ mod tests { header_type: DisplayHeaderType::Initial, })]); - assert_eq!(dl.to_string(), "--> src/test.rs:23:15"); + assert_eq(str!["--> src/test.rs:23:15"], dl.to_string()); } #[test] @@ -1853,7 +2007,7 @@ mod tests { header_type: DisplayHeaderType::Continuation, })]); - assert_eq!(dl.to_string(), "::: src/test.rs:23:15"); + assert_eq(str!["::: src/test.rs:23:15"], dl.to_string()); } #[test] @@ -1871,7 +2025,7 @@ mod tests { continuation: false, })]); - assert_eq!(dl.to_string(), "error[E0001]: This is an error"); + assert_eq(str!["error[E0001]: This is an error"], dl.to_string()); } #[test] @@ -1903,9 +2057,11 @@ mod tests { }), ]); - assert_eq!( + assert_eq( + str![[r#" + warning[E0001]: This is an error + Second line of the error"#]], dl.to_string(), - "warning[E0001]: This is an error\n Second line of the error" ); } @@ -1924,7 +2080,7 @@ mod tests { continuation: false, })]); - assert_eq!(dl.to_string(), " = error[E0001]: This is an error"); + assert_eq(str![" = error[E0001]: This is an error"], dl.to_string()); } #[test] @@ -1956,9 +2112,12 @@ mod tests { }), ]); - assert_eq!( + assert_eq( + str![[r#" + = warning[E0001]: This is an error + Second line of the error"#]] + .indent(false), dl.to_string(), - " = warning[E0001]: This is an error\n Second line of the error" ); } @@ -2003,9 +2162,12 @@ mod tests { }), ]); - assert_eq!( + assert_eq( + str![[r#" + note: This is a note + This is just a string + Second line of none type annotation"#]], dl.to_string(), - "note: This is a note\nThis is just a string\n Second line of none type annotation", ); } @@ -2020,7 +2182,7 @@ mod tests { line: DisplaySourceLine::Empty, }]); - assert_eq!(dl.to_string(), " | |",); + assert_eq(str![[" | |"]], dl.to_string()); } #[test] @@ -2058,9 +2220,13 @@ mod tests { ]); dl.anonymized_line_numbers = true; - assert_eq!( + assert_eq( + str![[r#" + LL | This is an example + LL | of content lines + | + | abc"#]], dl.to_string(), - "LL | This is an example\nLL | of content lines\n |\n | abc" ); } @@ -2074,6 +2240,6 @@ mod tests { // Using anonymized_line_numbers should not affect the initial position dl.anonymized_line_numbers = true; - assert_eq!(dl.to_string(), "--> src/test.rs:23:15"); + assert_eq(str!["--> src/test.rs:23:15"], dl.to_string()); } } From 1aa344705b6648c741f6e26918c8f23569e72cdc Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 14 Mar 2024 14:59:16 -0600 Subject: [PATCH 164/455] refactor: Move display_list tests to formatter --- src/renderer/display_list.rs | 1036 ---------------------------------- tests/formatter.rs | 174 ++++++ 2 files changed, 174 insertions(+), 1036 deletions(-) diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index 89a779fb..05c19100 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -1191,1039 +1191,3 @@ fn is_annotation_empty(annotation: &Annotation<'_>) -> bool { .iter() .all(|fragment| fragment.content.is_empty()) } - -#[cfg(test)] -mod tests { - use super::*; - - use snapbox::assert_eq; - use snapbox::str; - use snapbox::ToDebug as _; - - const STYLESHEET: Stylesheet = Stylesheet::plain(); - - fn from_display_lines(lines: Vec<DisplayLine<'_>>) -> DisplayList<'_> { - DisplayList { - body: lines, - stylesheet: &STYLESHEET, - anonymized_line_numbers: false, - margin: None, - } - } - - #[test] - fn test_format_title() { - let input = snippet::Level::Error.title("This is a title").id("E0001"); - let output = str![[r#" - DisplayList { - body: [ - Raw( - Annotation { - annotation: Annotation { - annotation_type: Error, - id: Some( - "E0001", - ), - label: [ - DisplayTextFragment { - content: "This is a title", - style: Emphasis, - }, - ], - }, - source_aligned: false, - continuation: false, - }, - ), - ], - anonymized_line_numbers: false, - } - "#]]; - assert_eq( - output, - DisplayList::new(input, &STYLESHEET, false, None).to_debug(), - ); - } - - #[test] - fn test_format_slice() { - let line_1 = "This is line 1"; - let line_2 = "This is line 2"; - let source = [line_1, line_2].join("\n"); - let input = snippet::Level::Error - .title("") - .snippet(snippet::Snippet::source(&source).line_start(5402)); - let output = str![[r#" - DisplayList { - body: [ - Raw( - Annotation { - annotation: Annotation { - annotation_type: Error, - id: None, - label: [ - DisplayTextFragment { - content: "", - style: Emphasis, - }, - ], - }, - source_aligned: false, - continuation: false, - }, - ), - Source { - lineno: None, - inline_marks: [], - line: Empty, - }, - Source { - lineno: Some( - 5402, - ), - inline_marks: [], - line: Content { - text: "This is line 1", - range: ( - 0, - 14, - ), - }, - }, - Source { - lineno: Some( - 5403, - ), - inline_marks: [], - line: Content { - text: "This is line 2", - range: ( - 15, - 29, - ), - }, - }, - Source { - lineno: None, - inline_marks: [], - line: Empty, - }, - ], - anonymized_line_numbers: false, - } - "#]]; - assert_eq( - output, - DisplayList::new(input, &STYLESHEET, false, None).to_debug(), - ); - } - - #[test] - fn test_format_slices_continuation() { - let src_0 = "This is slice 1"; - let src_1 = "This is slice 2"; - let input = snippet::Level::Error - .title("") - .snippet( - snippet::Snippet::source(src_0) - .line_start(5402) - .origin("file1.rs"), - ) - .snippet( - snippet::Snippet::source(src_1) - .line_start(2) - .origin("file2.rs"), - ); - let output = str![[r#" - DisplayList { - body: [ - Raw( - Annotation { - annotation: Annotation { - annotation_type: Error, - id: None, - label: [ - DisplayTextFragment { - content: "", - style: Emphasis, - }, - ], - }, - source_aligned: false, - continuation: false, - }, - ), - Raw( - Origin { - path: "file1.rs", - pos: None, - header_type: Initial, - }, - ), - Source { - lineno: None, - inline_marks: [], - line: Empty, - }, - Source { - lineno: Some( - 5402, - ), - inline_marks: [], - line: Content { - text: "This is slice 1", - range: ( - 0, - 15, - ), - }, - }, - Source { - lineno: None, - inline_marks: [], - line: Empty, - }, - Raw( - Origin { - path: "file2.rs", - pos: None, - header_type: Continuation, - }, - ), - Source { - lineno: None, - inline_marks: [], - line: Empty, - }, - Source { - lineno: Some( - 2, - ), - inline_marks: [], - line: Content { - text: "This is slice 2", - range: ( - 0, - 15, - ), - }, - }, - Source { - lineno: None, - inline_marks: [], - line: Empty, - }, - ], - anonymized_line_numbers: false, - } - "#]]; - assert_eq( - output, - DisplayList::new(input, &STYLESHEET, false, None).to_debug(), - ); - } - - #[test] - fn test_format_slice_annotation_standalone() { - let line_1 = "This is line 1"; - let line_2 = "This is line 2"; - let source = [line_1, line_2].join("\n"); - // In line 2 - let range = 22..24; - let input = snippet::Level::Error.title("").snippet( - snippet::Snippet::source(&source) - .line_start(5402) - .annotation( - snippet::Level::Info - .span(range.clone()) - .label("Test annotation"), - ), - ); - let output = str![[r#" - DisplayList { - body: [ - Raw( - Annotation { - annotation: Annotation { - annotation_type: Error, - id: None, - label: [ - DisplayTextFragment { - content: "", - style: Emphasis, - }, - ], - }, - source_aligned: false, - continuation: false, - }, - ), - Source { - lineno: None, - inline_marks: [], - line: Empty, - }, - Source { - lineno: Some( - 5402, - ), - inline_marks: [], - line: Content { - text: "This is line 1", - range: ( - 0, - 14, - ), - }, - }, - Source { - lineno: Some( - 5403, - ), - inline_marks: [], - line: Content { - text: "This is line 2", - range: ( - 15, - 29, - ), - }, - }, - Source { - lineno: None, - inline_marks: [], - line: Annotation { - annotation: Annotation { - annotation_type: Info, - id: None, - label: [ - DisplayTextFragment { - content: "Test annotation", - style: Regular, - }, - ], - }, - range: ( - 7, - 9, - ), - annotation_type: Info, - annotation_part: Standalone, - }, - }, - Source { - lineno: None, - inline_marks: [], - line: Empty, - }, - ], - anonymized_line_numbers: false, - } - "#]]; - assert_eq( - output, - DisplayList::new(input, &STYLESHEET, false, None).to_debug(), - ); - } - - #[test] - fn test_format_label() { - let input = snippet::Level::Error - .title("") - .footer(snippet::Level::Error.title("This __is__ a title")); - let output = str![[r#" - DisplayList { - body: [ - Raw( - Annotation { - annotation: Annotation { - annotation_type: Error, - id: None, - label: [ - DisplayTextFragment { - content: "", - style: Emphasis, - }, - ], - }, - source_aligned: false, - continuation: false, - }, - ), - Raw( - Annotation { - annotation: Annotation { - annotation_type: Error, - id: None, - label: [ - DisplayTextFragment { - content: "This __is__ a title", - style: Regular, - }, - ], - }, - source_aligned: true, - continuation: false, - }, - ), - ], - anonymized_line_numbers: false, - } - "#]]; - assert_eq( - output, - DisplayList::new(input, &STYLESHEET, false, None).to_debug(), - ); - } - - #[test] - #[should_panic] - fn test_i26() { - let source = "short"; - let label = "label"; - let input = snippet::Level::Error.title("").snippet( - snippet::Snippet::source(source) - .line_start(0) - .annotation(snippet::Level::Error.span(0..source.len() + 2).label(label)), - ); - let _ = DisplayList::new(input, &STYLESHEET, false, None); - } - - #[test] - fn test_i_29() { - let input = snippet::Level::Error.title("oops").snippet( - snippet::Snippet::source("First line\r\nSecond oops line") - .line_start(1) - .origin("<current file>") - .fold(true) - .annotation(snippet::Level::Error.span(19..23).label("oops")), - ); - let output = str![[r#" - DisplayList { - body: [ - Raw( - Annotation { - annotation: Annotation { - annotation_type: Error, - id: None, - label: [ - DisplayTextFragment { - content: "oops", - style: Emphasis, - }, - ], - }, - source_aligned: false, - continuation: false, - }, - ), - Raw( - Origin { - path: "<current file>", - pos: Some( - ( - 2, - 8, - ), - ), - header_type: Initial, - }, - ), - Source { - lineno: None, - inline_marks: [], - line: Empty, - }, - Source { - lineno: Some( - 1, - ), - inline_marks: [], - line: Content { - text: "First line", - range: ( - 0, - 10, - ), - }, - }, - Source { - lineno: Some( - 2, - ), - inline_marks: [], - line: Content { - text: "Second oops line", - range: ( - 12, - 28, - ), - }, - }, - Source { - lineno: None, - inline_marks: [], - line: Annotation { - annotation: Annotation { - annotation_type: None, - id: None, - label: [ - DisplayTextFragment { - content: "oops", - style: Regular, - }, - ], - }, - range: ( - 7, - 11, - ), - annotation_type: Error, - annotation_part: Standalone, - }, - }, - Source { - lineno: None, - inline_marks: [], - line: Empty, - }, - ], - anonymized_line_numbers: false, - } - "#]]; - assert_eq( - output, - DisplayList::new(input, &STYLESHEET, false, None).to_debug(), - ); - } - - #[test] - fn test_source_empty() { - let dl = from_display_lines(vec![DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }]); - - assert_eq(str![" |"], dl.to_string()); - } - - #[test] - fn test_source_content() { - let dl = from_display_lines(vec![ - DisplayLine::Source { - lineno: Some(56), - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: "This is an example", - range: (0, 19), - }, - }, - DisplayLine::Source { - lineno: Some(57), - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: "of content lines", - range: (0, 19), - }, - }, - ]); - - assert_eq( - str![[r#" - 56 | This is an example - 57 | of content lines"#]], - dl.to_string(), - ); - } - - #[test] - fn test_source_annotation_standalone_singleline() { - let dl = from_display_lines(vec![DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - range: (0, 5), - annotation: Annotation { - annotation_type: DisplayAnnotationType::None, - id: None, - label: vec![DisplayTextFragment { - content: "Example string", - style: DisplayTextStyle::Regular, - }], - }, - annotation_type: DisplayAnnotationType::Error, - annotation_part: DisplayAnnotationPart::Standalone, - }, - }]); - - assert_eq(str![" | ^^^^^ Example string"], dl.to_string()); - } - - #[test] - fn test_source_annotation_standalone_multiline() { - let dl = from_display_lines(vec![ - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - range: (0, 5), - annotation: Annotation { - annotation_type: DisplayAnnotationType::Help, - id: None, - label: vec![DisplayTextFragment { - content: "Example string", - style: DisplayTextStyle::Regular, - }], - }, - annotation_type: DisplayAnnotationType::Warning, - annotation_part: DisplayAnnotationPart::Standalone, - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - range: (0, 5), - annotation: Annotation { - annotation_type: DisplayAnnotationType::Help, - id: None, - label: vec![DisplayTextFragment { - content: "Second line", - style: DisplayTextStyle::Regular, - }], - }, - annotation_type: DisplayAnnotationType::Warning, - annotation_part: DisplayAnnotationPart::LabelContinuation, - }, - }, - ]); - - assert_eq( - str![[r#" - | ----- help: Example string - | Second line"#]] - .indent(false), - dl.to_string(), - ); - } - - #[test] - fn test_source_annotation_standalone_multi_annotation() { - let dl = from_display_lines(vec![ - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - range: (0, 5), - annotation: Annotation { - annotation_type: DisplayAnnotationType::Info, - id: None, - label: vec![DisplayTextFragment { - content: "Example string", - style: DisplayTextStyle::Regular, - }], - }, - annotation_type: DisplayAnnotationType::Note, - annotation_part: DisplayAnnotationPart::Standalone, - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - range: (0, 5), - annotation: Annotation { - annotation_type: DisplayAnnotationType::Info, - id: None, - label: vec![DisplayTextFragment { - content: "Second line", - style: DisplayTextStyle::Regular, - }], - }, - annotation_type: DisplayAnnotationType::Note, - annotation_part: DisplayAnnotationPart::LabelContinuation, - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - range: (0, 5), - annotation: Annotation { - annotation_type: DisplayAnnotationType::Warning, - id: None, - label: vec![DisplayTextFragment { - content: "Second line of the warning", - style: DisplayTextStyle::Regular, - }], - }, - annotation_type: DisplayAnnotationType::Note, - annotation_part: DisplayAnnotationPart::LabelContinuation, - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - range: (0, 5), - annotation: Annotation { - annotation_type: DisplayAnnotationType::Info, - id: None, - label: vec![DisplayTextFragment { - content: "This is an info", - style: DisplayTextStyle::Regular, - }], - }, - annotation_type: DisplayAnnotationType::Info, - annotation_part: DisplayAnnotationPart::Standalone, - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - range: (0, 5), - annotation: Annotation { - annotation_type: DisplayAnnotationType::Help, - id: None, - label: vec![DisplayTextFragment { - content: "This is help", - style: DisplayTextStyle::Regular, - }], - }, - annotation_type: DisplayAnnotationType::Help, - annotation_part: DisplayAnnotationPart::Standalone, - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - range: (0, 0), - annotation: Annotation { - annotation_type: DisplayAnnotationType::None, - id: None, - label: vec![DisplayTextFragment { - content: "This is an annotation of type none", - style: DisplayTextStyle::Regular, - }], - }, - annotation_type: DisplayAnnotationType::None, - annotation_part: DisplayAnnotationPart::Standalone, - }, - }, - ]); - - assert_eq( - str![[r#" - | ----- info: Example string - | Second line - | Second line of the warning - | ----- info: This is an info - | ----- help: This is help - | This is an annotation of type none"#]] - .indent(false), - dl.to_string(), - ); - } - - #[test] - fn test_fold_line() { - let dl = from_display_lines(vec![ - DisplayLine::Source { - lineno: Some(5), - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: "This is line 5", - range: (0, 19), - }, - }, - DisplayLine::Fold { - inline_marks: vec![], - }, - DisplayLine::Source { - lineno: Some(10021), - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: "... and now we're at line 10021", - range: (0, 19), - }, - }, - ]); - - assert_eq( - str![[r#" - 5 | This is line 5 - ... - 10021 | ... and now we're at line 10021"#]], - dl.to_string(), - ); - } - - #[test] - fn test_raw_origin_initial_nopos() { - let dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Origin { - path: "src/test.rs", - pos: None, - header_type: DisplayHeaderType::Initial, - })]); - - assert_eq(str!["--> src/test.rs"], dl.to_string()); - } - - #[test] - fn test_raw_origin_initial_pos() { - let dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Origin { - path: "src/test.rs", - pos: Some((23, 15)), - header_type: DisplayHeaderType::Initial, - })]); - - assert_eq(str!["--> src/test.rs:23:15"], dl.to_string()); - } - - #[test] - fn test_raw_origin_continuation() { - let dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Origin { - path: "src/test.rs", - pos: Some((23, 15)), - header_type: DisplayHeaderType::Continuation, - })]); - - assert_eq(str!["::: src/test.rs:23:15"], dl.to_string()); - } - - #[test] - fn test_raw_annotation_unaligned() { - let dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Error, - id: Some("E0001"), - label: vec![DisplayTextFragment { - content: "This is an error", - style: DisplayTextStyle::Regular, - }], - }, - source_aligned: false, - continuation: false, - })]); - - assert_eq(str!["error[E0001]: This is an error"], dl.to_string()); - } - - #[test] - fn test_raw_annotation_unaligned_multiline() { - let dl = from_display_lines(vec![ - DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Warning, - id: Some("E0001"), - label: vec![DisplayTextFragment { - content: "This is an error", - style: DisplayTextStyle::Regular, - }], - }, - source_aligned: false, - continuation: false, - }), - DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Warning, - id: Some("E0001"), - label: vec![DisplayTextFragment { - content: "Second line of the error", - style: DisplayTextStyle::Regular, - }], - }, - source_aligned: false, - continuation: true, - }), - ]); - - assert_eq( - str![[r#" - warning[E0001]: This is an error - Second line of the error"#]], - dl.to_string(), - ); - } - - #[test] - fn test_raw_annotation_aligned() { - let dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Error, - id: Some("E0001"), - label: vec![DisplayTextFragment { - content: "This is an error", - style: DisplayTextStyle::Regular, - }], - }, - source_aligned: true, - continuation: false, - })]); - - assert_eq(str![" = error[E0001]: This is an error"], dl.to_string()); - } - - #[test] - fn test_raw_annotation_aligned_multiline() { - let dl = from_display_lines(vec![ - DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Warning, - id: Some("E0001"), - label: vec![DisplayTextFragment { - content: "This is an error", - style: DisplayTextStyle::Regular, - }], - }, - source_aligned: true, - continuation: false, - }), - DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Warning, - id: Some("E0001"), - label: vec![DisplayTextFragment { - content: "Second line of the error", - style: DisplayTextStyle::Regular, - }], - }, - source_aligned: true, - continuation: true, - }), - ]); - - assert_eq( - str![[r#" - = warning[E0001]: This is an error - Second line of the error"#]] - .indent(false), - dl.to_string(), - ); - } - - #[test] - fn test_different_annotation_types() { - let dl = from_display_lines(vec![ - DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::Note, - id: None, - label: vec![DisplayTextFragment { - content: "This is a note", - style: DisplayTextStyle::Regular, - }], - }, - source_aligned: false, - continuation: false, - }), - DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::None, - id: None, - label: vec![DisplayTextFragment { - content: "This is just a string", - style: DisplayTextStyle::Regular, - }], - }, - source_aligned: false, - continuation: false, - }), - DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::None, - id: None, - label: vec![DisplayTextFragment { - content: "Second line of none type annotation", - style: DisplayTextStyle::Regular, - }], - }, - source_aligned: false, - continuation: true, - }), - ]); - - assert_eq( - str![[r#" - note: This is a note - This is just a string - Second line of none type annotation"#]], - dl.to_string(), - ); - } - - #[test] - fn test_inline_marks_empty_line() { - let dl = from_display_lines(vec![DisplayLine::Source { - lineno: None, - inline_marks: vec![DisplayMark { - mark_type: DisplayMarkType::AnnotationThrough, - annotation_type: DisplayAnnotationType::Error, - }], - line: DisplaySourceLine::Empty, - }]); - - assert_eq(str![[" | |"]], dl.to_string()); - } - - #[test] - fn test_anon_lines() { - let mut dl = from_display_lines(vec![ - DisplayLine::Source { - lineno: Some(56), - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: "This is an example", - range: (0, 19), - }, - }, - DisplayLine::Source { - lineno: Some(57), - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: "of content lines", - range: (0, 19), - }, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: "abc", - range: (0, 19), - }, - }, - ]); - - dl.anonymized_line_numbers = true; - assert_eq( - str![[r#" - LL | This is an example - LL | of content lines - | - | abc"#]], - dl.to_string(), - ); - } - - #[test] - fn test_raw_origin_initial_pos_anon_lines() { - let mut dl = from_display_lines(vec![DisplayLine::Raw(DisplayRawLine::Origin { - path: "src/test.rs", - pos: Some((23, 15)), - header_type: DisplayHeaderType::Initial, - })]); - - // Using anonymized_line_numbers should not affect the initial position - dl.anonymized_line_numbers = true; - assert_eq(str!["--> src/test.rs:23:15"], dl.to_string()); - } -} diff --git a/tests/formatter.rs b/tests/formatter.rs index 035492a9..b1a53af3 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -100,3 +100,177 @@ fn test_point_to_double_width_characters_mixed() { let renderer = Renderer::plain(); assert_eq!(renderer.render(snippets).to_string(), expected); } + +#[test] +fn test_format_title() { + let input = Level::Error.title("This is a title").id("E0001"); + + let expected = r#"error[E0001]: This is a title"#; + let renderer = Renderer::plain(); + assert_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn test_format_snippet_only() { + let source = "This is line 1\nThis is line 2"; + let input = Level::Error + .title("") + .snippet(Snippet::source(source).line_start(5402)); + + let expected = r#"error + | +5402 | This is line 1 +5403 | This is line 2 + |"#; + let renderer = Renderer::plain(); + assert_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn test_format_snippets_continuation() { + let src_0 = "This is slice 1"; + let src_1 = "This is slice 2"; + let input = Level::Error + .title("") + .snippet(Snippet::source(src_0).line_start(5402).origin("file1.rs")) + .snippet(Snippet::source(src_1).line_start(2).origin("file2.rs")); + let expected = r#"error + --> file1.rs + | +5402 | This is slice 1 + | + ::: file2.rs + | + 2 | This is slice 2 + |"#; + let renderer = Renderer::plain(); + assert_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn test_format_snippet_annotation_standalone() { + let line_1 = "This is line 1"; + let line_2 = "This is line 2"; + let source = [line_1, line_2].join("\n"); + // In line 2 + let range = 22..24; + let input = Level::Error.title("").snippet( + Snippet::source(&source) + .line_start(5402) + .annotation(Level::Info.span(range.clone()).label("Test annotation")), + ); + let expected = r#"error + | +5402 | This is line 1 +5403 | This is line 2 + | -- info: Test annotation + |"#; + let renderer = Renderer::plain(); + assert_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn test_format_footer_title() { + let input = Level::Error + .title("") + .footer(Level::Error.title("This __is__ a title")); + let expected = r#"error + = error: This __is__ a title"#; + let renderer = Renderer::plain(); + assert_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +#[should_panic] +fn test_i26() { + let source = "short"; + let label = "label"; + let input = Level::Error.title("").snippet( + Snippet::source(source) + .line_start(0) + .annotation(Level::Error.span(0..source.len() + 2).label(label)), + ); + let renderer = Renderer::plain(); + let _ = renderer.render(input).to_string(); +} + +#[test] +fn test_source_content() { + let source = "This is an example\nof content lines"; + let input = Level::Error + .title("") + .snippet(Snippet::source(source).line_start(56)); + let expected = r#"error + | +56 | This is an example +57 | of content lines + |"#; + let renderer = Renderer::plain(); + assert_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn test_source_annotation_standalone_singleline() { + let source = "tests"; + let input = Level::Error.title("").snippet( + Snippet::source(source) + .line_start(1) + .annotation(Level::Help.span(0..5).label("Example string")), + ); + let expected = r#"error + | +1 | tests + | ----- help: Example string + |"#; + let renderer = Renderer::plain(); + assert_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn test_source_annotation_standalone_multiline() { + let source = "tests"; + let input = Level::Error.title("").snippet( + Snippet::source(source) + .line_start(1) + .annotation(Level::Help.span(0..5).label("Example string")) + .annotation(Level::Help.span(0..5).label("Second line")), + ); + let expected = r#"error + | +1 | tests + | ----- help: Example string + | ----- help: Second line + |"#; + let renderer = Renderer::plain(); + assert_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn test_only_source() { + let input = Level::Error + .title("") + .snippet(Snippet::source("").origin("file.rs")); + let expected = r#"error +--> file.rs + | + |"#; + let renderer = Renderer::plain(); + assert_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn test_anon_lines() { + let source = "This is an example\nof content lines\n\nabc"; + let input = Level::Error + .title("") + .snippet(Snippet::source(source).line_start(56)); + let expected = r#"error + | +LL | This is an example +LL | of content lines +LL | +LL | abc + |"#; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_eq!(renderer.render(input).to_string(), expected); +} From da9ea864f95ab3d58f8dbe2d131a6d16c41a8821 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 14 Mar 2024 15:13:14 -0600 Subject: [PATCH 165/455] refactor: Use snapbox::str to auto-update tests --- tests/formatter.rs | 118 ++++++++++++++++++++++++++++----------------- 1 file changed, 74 insertions(+), 44 deletions(-) diff --git a/tests/formatter.rs b/tests/formatter.rs index b1a53af3..3b024896 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -1,5 +1,7 @@ use annotate_snippets::{Level, Renderer, Snippet}; +use snapbox::{assert_eq, str}; + #[test] fn test_i_29() { let snippets = Level::Error.title("oops").snippet( @@ -8,16 +10,18 @@ fn test_i_29() { .annotation(Level::Error.span(19..23).label("oops")) .fold(true), ); - let expected = r#"error: oops + let expected = str![[r#" +error: oops --> <current file>:2:8 | 1 | First line 2 | Second oops line | ^^^^ oops - |"#; + |"#]] + .indent(false); let renderer = Renderer::plain(); - assert_eq!(renderer.render(snippets).to_string(), expected); + assert_eq(expected, renderer.render(snippets).to_string()); } #[test] @@ -28,15 +32,17 @@ fn test_point_to_double_width_characters() { .annotation(Level::Error.span(18..24).label("world")), ); - let expected = r#"error + let expected = str![[r#" +error --> <current file>:1:7 | 1 | こんにちは、世界 | ^^^^ world - |"#; + |"#]] + .indent(false); let renderer = Renderer::plain(); - assert_eq!(renderer.render(snippets).to_string(), expected); + assert_eq(expected, renderer.render(snippets).to_string()); } #[test] @@ -47,17 +53,19 @@ fn test_point_to_double_width_characters_across_lines() { .annotation(Level::Error.span(6..22).label("Good morning")), ); - let expected = r#"error + let expected = str![[r#" +error --> <current file>:1:3 | 1 | おはよう | _____^ 2 | | ございます | |______^ Good morning - |"#; + |"#]] + .indent(false); let renderer = Renderer::plain(); - assert_eq!(renderer.render(snippets).to_string(), expected); + assert_eq(expected, renderer.render(snippets).to_string()); } #[test] @@ -69,17 +77,19 @@ fn test_point_to_double_width_characters_multiple() { .annotation(Level::Note.span(16..22).label("Sushi2")), ); - let expected = r#"error + let expected = str![[r#" +error --> <current file>:1:1 | 1 | お寿司 | ^^^^^^ Sushi1 2 | 食べたい🍣 | ---- note: Sushi2 - |"#; + |"#]] + .indent(false); let renderer = Renderer::plain(); - assert_eq!(renderer.render(snippets).to_string(), expected); + assert_eq(expected, renderer.render(snippets).to_string()); } #[test] @@ -90,24 +100,26 @@ fn test_point_to_double_width_characters_mixed() { .annotation(Level::Error.span(18..32).label("New world")), ); - let expected = r#"error + let expected = str![[r#" +error --> <current file>:1:7 | 1 | こんにちは、新しいWorld! | ^^^^^^^^^^^ New world - |"#; + |"#]] + .indent(false); let renderer = Renderer::plain(); - assert_eq!(renderer.render(snippets).to_string(), expected); + assert_eq(expected, renderer.render(snippets).to_string()); } #[test] fn test_format_title() { let input = Level::Error.title("This is a title").id("E0001"); - let expected = r#"error[E0001]: This is a title"#; + let expected = str![r#"error[E0001]: This is a title"#]; let renderer = Renderer::plain(); - assert_eq!(renderer.render(input).to_string(), expected); + assert_eq(expected, renderer.render(input).to_string()); } #[test] @@ -117,13 +129,15 @@ fn test_format_snippet_only() { .title("") .snippet(Snippet::source(source).line_start(5402)); - let expected = r#"error + let expected = str![[r#" +error | 5402 | This is line 1 5403 | This is line 2 - |"#; + |"#]] + .indent(false); let renderer = Renderer::plain(); - assert_eq!(renderer.render(input).to_string(), expected); + assert_eq(expected, renderer.render(input).to_string()); } #[test] @@ -134,7 +148,8 @@ fn test_format_snippets_continuation() { .title("") .snippet(Snippet::source(src_0).line_start(5402).origin("file1.rs")) .snippet(Snippet::source(src_1).line_start(2).origin("file2.rs")); - let expected = r#"error + let expected = str![[r#" +error --> file1.rs | 5402 | This is slice 1 @@ -142,9 +157,10 @@ fn test_format_snippets_continuation() { ::: file2.rs | 2 | This is slice 2 - |"#; + |"#]] + .indent(false); let renderer = Renderer::plain(); - assert_eq!(renderer.render(input).to_string(), expected); + assert_eq(expected, renderer.render(input).to_string()); } #[test] @@ -159,14 +175,16 @@ fn test_format_snippet_annotation_standalone() { .line_start(5402) .annotation(Level::Info.span(range.clone()).label("Test annotation")), ); - let expected = r#"error + let expected = str![[r#" +error | 5402 | This is line 1 5403 | This is line 2 | -- info: Test annotation - |"#; + |"#]] + .indent(false); let renderer = Renderer::plain(); - assert_eq!(renderer.render(input).to_string(), expected); + assert_eq(expected, renderer.render(input).to_string()); } #[test] @@ -174,10 +192,12 @@ fn test_format_footer_title() { let input = Level::Error .title("") .footer(Level::Error.title("This __is__ a title")); - let expected = r#"error - = error: This __is__ a title"#; + let expected = str![[r#" +error + = error: This __is__ a title"#]] + .indent(false); let renderer = Renderer::plain(); - assert_eq!(renderer.render(input).to_string(), expected); + assert_eq(expected, renderer.render(input).to_string()); } #[test] @@ -200,13 +220,15 @@ fn test_source_content() { let input = Level::Error .title("") .snippet(Snippet::source(source).line_start(56)); - let expected = r#"error + let expected = str![[r#" +error | 56 | This is an example 57 | of content lines - |"#; + |"#]] + .indent(false); let renderer = Renderer::plain(); - assert_eq!(renderer.render(input).to_string(), expected); + assert_eq(expected, renderer.render(input).to_string()); } #[test] @@ -217,13 +239,15 @@ fn test_source_annotation_standalone_singleline() { .line_start(1) .annotation(Level::Help.span(0..5).label("Example string")), ); - let expected = r#"error + let expected = str![[r#" +error | 1 | tests | ----- help: Example string - |"#; + |"#]] + .indent(false); let renderer = Renderer::plain(); - assert_eq!(renderer.render(input).to_string(), expected); + assert_eq(expected, renderer.render(input).to_string()); } #[test] @@ -235,14 +259,16 @@ fn test_source_annotation_standalone_multiline() { .annotation(Level::Help.span(0..5).label("Example string")) .annotation(Level::Help.span(0..5).label("Second line")), ); - let expected = r#"error + let expected = str![[r#" +error | 1 | tests | ----- help: Example string | ----- help: Second line - |"#; + |"#]] + .indent(false); let renderer = Renderer::plain(); - assert_eq!(renderer.render(input).to_string(), expected); + assert_eq(expected, renderer.render(input).to_string()); } #[test] @@ -250,12 +276,14 @@ fn test_only_source() { let input = Level::Error .title("") .snippet(Snippet::source("").origin("file.rs")); - let expected = r#"error + let expected = str![[r#" +error --> file.rs | - |"#; + |"#]] + .indent(false); let renderer = Renderer::plain(); - assert_eq!(renderer.render(input).to_string(), expected); + assert_eq(expected, renderer.render(input).to_string()); } #[test] @@ -264,13 +292,15 @@ fn test_anon_lines() { let input = Level::Error .title("") .snippet(Snippet::source(source).line_start(56)); - let expected = r#"error + let expected = str![[r#" +error | LL | This is an example LL | of content lines LL | LL | abc - |"#; + |"#]] + .indent(false); let renderer = Renderer::plain().anonymized_line_numbers(true); - assert_eq!(renderer.render(input).to_string(), expected); + assert_eq(expected, renderer.render(input).to_string()); } From 0efbba4000c013543d903e6c87f4806e18d18d10 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 13 Mar 2024 14:15:03 -0600 Subject: [PATCH 166/455] refactor: Add a DisplaySet for each Snippet --- src/renderer/display_list.rs | 242 +++++++++++++++++++++-------------- src/renderer/margin.rs | 2 +- 2 files changed, 150 insertions(+), 94 deletions(-) diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index 05c19100..81a7be12 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -38,12 +38,18 @@ use std::{cmp, fmt}; use crate::renderer::{stylesheet::Stylesheet, Margin, Style}; +const ANONYMIZED_LINE_NUM: &str = "LL"; +const ERROR_TXT: &str = "error"; +const HELP_TXT: &str = "help"; +const INFO_TXT: &str = "info"; +const NOTE_TXT: &str = "note"; +const WARNING_TXT: &str = "warning"; + /// List of lines to be displayed. pub(crate) struct DisplayList<'a> { - pub body: Vec<DisplayLine<'a>>, + pub body: Vec<DisplaySet<'a>>, pub stylesheet: &'a Stylesheet, pub anonymized_line_numbers: bool, - pub margin: Option<Margin>, } impl<'a> PartialEq for DisplayList<'a> { @@ -63,46 +69,36 @@ impl<'a> fmt::Debug for DisplayList<'a> { impl<'a> Display for DisplayList<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let lineno_width = self.body.iter().fold(0, |max, line| match line { - DisplayLine::Source { - lineno: Some(lineno), - .. - } => { - // The largest line is the largest width. - cmp::max(*lineno, max) - } - _ => max, + let lineno_width = self.body.iter().fold(0, |max, set| { + set.display_lines.iter().fold(max, |max, line| match line { + DisplayLine::Source { lineno, .. } => cmp::max(lineno.unwrap_or(0), max), + _ => max, + }) }); let lineno_width = if lineno_width == 0 { lineno_width } else if self.anonymized_line_numbers { - Self::ANONYMIZED_LINE_NUM.len() + ANONYMIZED_LINE_NUM.len() } else { ((lineno_width as f64).log10().floor() as usize) + 1 }; - let inline_marks_width = self.body.iter().fold(0, |max, line| match line { - DisplayLine::Source { inline_marks, .. } => cmp::max(inline_marks.len(), max), - _ => max, + let inline_marks_width = self.body.iter().fold(0, |max, set| { + set.display_lines.iter().fold(max, |max, line| match line { + DisplayLine::Source { inline_marks, .. } => cmp::max(inline_marks.len(), max), + _ => max, + }) }); - for (i, line) in self.body.iter().enumerate() { - self.format_line(line, lineno_width, inline_marks_width, f)?; - if i + 1 < self.body.len() { - f.write_char('\n')?; - } + let mut count_offset = 0; + for set in self.body.iter() { + self.format_set(set, lineno_width, inline_marks_width, count_offset, f)?; + count_offset += set.display_lines.len(); } Ok(()) } } impl<'a> DisplayList<'a> { - const ANONYMIZED_LINE_NUM: &'static str = "LL"; - const ERROR_TXT: &'static str = "error"; - const HELP_TXT: &'static str = "help"; - const INFO_TXT: &'static str = "info"; - const NOTE_TXT: &'static str = "note"; - const WARNING_TXT: &'static str = "warning"; - pub(crate) fn new( message: snippet::Message<'a>, stylesheet: &'a Stylesheet, @@ -115,53 +111,53 @@ impl<'a> DisplayList<'a> { body, stylesheet, anonymized_line_numbers, - margin, } } - #[inline] - fn format_annotation_type( - annotation_type: &DisplayAnnotationType, + fn format_set( + &self, + set: &DisplaySet<'_>, + lineno_width: usize, + inline_marks_width: usize, + count_offset: usize, f: &mut fmt::Formatter<'_>, ) -> fmt::Result { - match annotation_type { - DisplayAnnotationType::Error => f.write_str(Self::ERROR_TXT), - DisplayAnnotationType::Help => f.write_str(Self::HELP_TXT), - DisplayAnnotationType::Info => f.write_str(Self::INFO_TXT), - DisplayAnnotationType::Note => f.write_str(Self::NOTE_TXT), - DisplayAnnotationType::Warning => f.write_str(Self::WARNING_TXT), - DisplayAnnotationType::None => Ok(()), - } - } - - fn annotation_type_len(annotation_type: &DisplayAnnotationType) -> usize { - match annotation_type { - DisplayAnnotationType::Error => Self::ERROR_TXT.len(), - DisplayAnnotationType::Help => Self::HELP_TXT.len(), - DisplayAnnotationType::Info => Self::INFO_TXT.len(), - DisplayAnnotationType::Note => Self::NOTE_TXT.len(), - DisplayAnnotationType::Warning => Self::WARNING_TXT.len(), - DisplayAnnotationType::None => 0, + let body_len = self + .body + .iter() + .map(|set| set.display_lines.len()) + .sum::<usize>(); + for (i, line) in set.display_lines.iter().enumerate() { + set.format_line( + line, + lineno_width, + inline_marks_width, + self.stylesheet, + self.anonymized_line_numbers, + f, + )?; + if i + count_offset + 1 < body_len { + f.write_char('\n')?; + } } + Ok(()) } +} - fn get_annotation_style(&self, annotation_type: &DisplayAnnotationType) -> &Style { - match annotation_type { - DisplayAnnotationType::Error => self.stylesheet.error(), - DisplayAnnotationType::Warning => self.stylesheet.warning(), - DisplayAnnotationType::Info => self.stylesheet.info(), - DisplayAnnotationType::Note => self.stylesheet.note(), - DisplayAnnotationType::Help => self.stylesheet.help(), - DisplayAnnotationType::None => self.stylesheet.none(), - } - } +#[derive(Debug, PartialEq)] +pub(crate) struct DisplaySet<'a> { + pub display_lines: Vec<DisplayLine<'a>>, + pub margin: Option<Margin>, +} +impl<'a> DisplaySet<'a> { fn format_label( &self, label: &[DisplayTextFragment<'_>], + stylesheet: &Stylesheet, f: &mut fmt::Formatter<'_>, ) -> fmt::Result { - let emphasis_style = self.stylesheet.emphasis(); + let emphasis_style = stylesheet.emphasis(); for fragment in label { match fragment.style { @@ -185,24 +181,25 @@ impl<'a> DisplayList<'a> { annotation: &Annotation<'_>, continuation: bool, in_source: bool, + stylesheet: &Stylesheet, f: &mut fmt::Formatter<'_>, ) -> fmt::Result { - let color = self.get_annotation_style(&annotation.annotation_type); + let color = get_annotation_style(&annotation.annotation_type, stylesheet); let formatted_len = if let Some(id) = &annotation.id { - 2 + id.len() + Self::annotation_type_len(&annotation.annotation_type) + 2 + id.len() + annotation_type_len(&annotation.annotation_type) } else { - Self::annotation_type_len(&annotation.annotation_type) + annotation_type_len(&annotation.annotation_type) }; if continuation { format_repeat_char(' ', formatted_len + 2, f)?; - return self.format_label(&annotation.label, f); + return self.format_label(&annotation.label, stylesheet, f); } if formatted_len == 0 { - self.format_label(&annotation.label, f) + self.format_label(&annotation.label, stylesheet, f) } else { write!(f, "{}", color.render())?; - Self::format_annotation_type(&annotation.annotation_type, f)?; + format_annotation_type(&annotation.annotation_type, f)?; if let Some(id) = &annotation.id { f.write_char('[')?; f.write_str(id)?; @@ -214,11 +211,11 @@ impl<'a> DisplayList<'a> { if in_source { write!(f, "{}", color.render())?; f.write_str(": ")?; - self.format_label(&annotation.label, f)?; + self.format_label(&annotation.label, stylesheet, f)?; write!(f, "{}", color.render_reset())?; } else { f.write_str(": ")?; - self.format_label(&annotation.label, f)?; + self.format_label(&annotation.label, stylesheet, f)?; } } Ok(()) @@ -229,6 +226,7 @@ impl<'a> DisplayList<'a> { fn format_source_line( &self, line: &DisplaySourceLine<'_>, + stylesheet: &Stylesheet, f: &mut fmt::Formatter<'_>, ) -> fmt::Result { match line { @@ -318,7 +316,7 @@ impl<'a> DisplayList<'a> { DisplayAnnotationType::Help => '-', DisplayAnnotationType::None => ' ', }; - let color = self.get_annotation_style(annotation_type); + let color = get_annotation_style(annotation_type, stylesheet); let indent_length = match annotation_part { DisplayAnnotationPart::LabelContinuation => range.1, _ => range.0, @@ -336,6 +334,7 @@ impl<'a> DisplayList<'a> { annotation, annotation_part == &DisplayAnnotationPart::LabelContinuation, true, + stylesheet, f, )?; write!(f, "{}", color.render_reset())?; @@ -351,6 +350,7 @@ impl<'a> DisplayList<'a> { &self, line: &DisplayRawLine<'_>, lineno_width: usize, + stylesheet: &Stylesheet, f: &mut fmt::Formatter<'_>, ) -> fmt::Result { match line { @@ -363,7 +363,7 @@ impl<'a> DisplayList<'a> { DisplayHeaderType::Initial => "-->", DisplayHeaderType::Continuation => ":::", }; - let lineno_color = self.stylesheet.line_no(); + let lineno_color = stylesheet.line_no(); if let Some((col, row)) = pos { format_repeat_char(' ', lineno_width, f)?; @@ -402,7 +402,7 @@ impl<'a> DisplayList<'a> { if *continuation { format_repeat_char(' ', lineno_width + 3, f)?; } else { - let lineno_color = self.stylesheet.line_no(); + let lineno_color = stylesheet.line_no(); format_repeat_char(' ', lineno_width, f)?; f.write_char(' ')?; write!( @@ -414,7 +414,7 @@ impl<'a> DisplayList<'a> { f.write_char(' ')?; } } - self.format_annotation(annotation, *continuation, false, f) + self.format_annotation(annotation, *continuation, false, stylesheet, f) } } } @@ -425,6 +425,8 @@ impl<'a> DisplayList<'a> { dl: &DisplayLine<'_>, lineno_width: usize, inline_marks_width: usize, + stylesheet: &Stylesheet, + anonymized_line_numbers: bool, f: &mut fmt::Formatter<'_>, ) -> fmt::Result { match dl { @@ -433,10 +435,10 @@ impl<'a> DisplayList<'a> { inline_marks, line, } => { - let lineno_color = self.stylesheet.line_no(); - if self.anonymized_line_numbers && lineno.is_some() { + let lineno_color = stylesheet.line_no(); + if anonymized_line_numbers && lineno.is_some() { write!(f, "{}", lineno_color.render())?; - f.write_str(Self::ANONYMIZED_LINE_NUM)?; + f.write_str(ANONYMIZED_LINE_NUM)?; f.write_str(" |")?; write!(f, "{}", lineno_color.render_reset())?; } else { @@ -451,12 +453,12 @@ impl<'a> DisplayList<'a> { if *line != DisplaySourceLine::Empty { if !inline_marks.is_empty() || 0 < inline_marks_width { f.write_char(' ')?; - self.format_inline_marks(inline_marks, inline_marks_width, f)?; + self.format_inline_marks(inline_marks, inline_marks_width, stylesheet, f)?; } - self.format_source_line(line, f)?; + self.format_source_line(line, stylesheet, f)?; } else if !inline_marks.is_empty() { f.write_char(' ')?; - self.format_inline_marks(inline_marks, inline_marks_width, f)?; + self.format_inline_marks(inline_marks, inline_marks_width, stylesheet, f)?; } Ok(()) } @@ -464,11 +466,11 @@ impl<'a> DisplayList<'a> { f.write_str("...")?; if !inline_marks.is_empty() || 0 < inline_marks_width { format_repeat_char(' ', lineno_width, f)?; - self.format_inline_marks(inline_marks, inline_marks_width, f)?; + self.format_inline_marks(inline_marks, inline_marks_width, stylesheet, f)?; } Ok(()) } - DisplayLine::Raw(line) => self.format_raw_line(line, lineno_width, f), + DisplayLine::Raw(line) => self.format_raw_line(line, lineno_width, stylesheet, f), } } @@ -476,11 +478,12 @@ impl<'a> DisplayList<'a> { &self, inline_marks: &[DisplayMark], inline_marks_width: usize, + stylesheet: &Stylesheet, f: &mut fmt::Formatter<'_>, ) -> fmt::Result { format_repeat_char(' ', inline_marks_width - inline_marks.len(), f)?; for mark in inline_marks { - let annotation_style = self.get_annotation_style(&mark.annotation_type); + let annotation_style = get_annotation_style(&mark.annotation_type, stylesheet); write!(f, "{}", annotation_style.render())?; f.write_char(match mark.mark_type { DisplayMarkType::AnnotationThrough => '|', @@ -706,17 +709,16 @@ fn format_message( }: snippet::Message<'_>, margin: Option<Margin>, primary: bool, -) -> Vec<DisplayLine<'_>> { - let mut body = vec![]; - - if !snippets.is_empty() || primary { - body.push(format_title(level, id, title)); +) -> Vec<DisplaySet<'_>> { + let mut sets = vec![]; + let body = if !snippets.is_empty() || primary { + vec![format_title(level, id, title)] } else { - body.extend(format_footer(level, id, title)); - } + format_footer(level, id, title) + }; for (idx, snippet) in snippets.into_iter().enumerate() { - body.extend(format_snippet( + sets.push(format_snippet( snippet, idx == 0, !footer.is_empty(), @@ -724,11 +726,22 @@ fn format_message( )); } + if let Some(first) = sets.first_mut() { + body.into_iter().for_each(|line| { + first.display_lines.insert(0, line); + }); + } else { + sets.push(DisplaySet { + display_lines: body, + margin, + }); + } + for annotation in footer { - body.extend(format_message(annotation, margin, false)); + sets.extend(format_message(annotation, margin, false)); } - body + sets } fn format_title<'a>(level: crate::Level, id: Option<&'a str>, label: &'a str) -> DisplayLine<'a> { @@ -783,7 +796,7 @@ fn format_snippet( is_first: bool, has_footer: bool, margin: Option<Margin>, -) -> Vec<DisplayLine<'_>> { +) -> DisplaySet<'_> { let main_range = snippet.annotations.first().map(|x| x.range.start); let origin = snippet.origin; let need_empty_header = origin.is_some() || is_first; @@ -795,7 +808,10 @@ fn format_snippet( result.push(header); } result.extend(body); - result + DisplaySet { + display_lines: result, + margin, + } } #[inline] @@ -1184,6 +1200,46 @@ fn format_repeat_char(c: char, n: usize, f: &mut fmt::Formatter<'_>) -> fmt::Res Ok(()) } +#[inline] +fn format_annotation_type( + annotation_type: &DisplayAnnotationType, + f: &mut fmt::Formatter<'_>, +) -> fmt::Result { + match annotation_type { + DisplayAnnotationType::Error => f.write_str(ERROR_TXT), + DisplayAnnotationType::Help => f.write_str(HELP_TXT), + DisplayAnnotationType::Info => f.write_str(INFO_TXT), + DisplayAnnotationType::Note => f.write_str(NOTE_TXT), + DisplayAnnotationType::Warning => f.write_str(WARNING_TXT), + DisplayAnnotationType::None => Ok(()), + } +} + +fn annotation_type_len(annotation_type: &DisplayAnnotationType) -> usize { + match annotation_type { + DisplayAnnotationType::Error => ERROR_TXT.len(), + DisplayAnnotationType::Help => HELP_TXT.len(), + DisplayAnnotationType::Info => INFO_TXT.len(), + DisplayAnnotationType::Note => NOTE_TXT.len(), + DisplayAnnotationType::Warning => WARNING_TXT.len(), + DisplayAnnotationType::None => 0, + } +} + +fn get_annotation_style<'a>( + annotation_type: &DisplayAnnotationType, + stylesheet: &'a Stylesheet, +) -> &'a Style { + match annotation_type { + DisplayAnnotationType::Error => stylesheet.error(), + DisplayAnnotationType::Warning => stylesheet.warning(), + DisplayAnnotationType::Info => stylesheet.info(), + DisplayAnnotationType::Note => stylesheet.note(), + DisplayAnnotationType::Help => stylesheet.help(), + DisplayAnnotationType::None => stylesheet.none(), + } +} + #[inline] fn is_annotation_empty(annotation: &Annotation<'_>) -> bool { annotation diff --git a/src/renderer/margin.rs b/src/renderer/margin.rs index 361f5f36..7636603b 100644 --- a/src/renderer/margin.rs +++ b/src/renderer/margin.rs @@ -4,7 +4,7 @@ const ELLIPSIS_PASSING: usize = 6; const LONG_WHITESPACE: usize = 20; const LONG_WHITESPACE_PADDING: usize = 4; -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq)] pub struct Margin { /// The available whitespace in the left that can be consumed when centering. whitespace_left: usize, From e88121f1199be38a929b43d6e50c92294566ac70 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 14 Mar 2024 16:24:21 -0600 Subject: [PATCH 167/455] refactor: Keep annotations with their DisplayLine --- src/renderer/display_list.rs | 513 ++++++++++++++++++----------------- 1 file changed, 259 insertions(+), 254 deletions(-) diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index 81a7be12..250cfc45 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -222,129 +222,6 @@ impl<'a> DisplaySet<'a> { } } - #[inline] - fn format_source_line( - &self, - line: &DisplaySourceLine<'_>, - stylesheet: &Stylesheet, - f: &mut fmt::Formatter<'_>, - ) -> fmt::Result { - match line { - DisplaySourceLine::Empty => Ok(()), - DisplaySourceLine::Content { text, .. } => { - f.write_char(' ')?; - if let Some(margin) = self.margin { - let line_len = text.chars().count(); - let mut left = margin.left(line_len); - let right = margin.right(line_len); - - if margin.was_cut_left() { - // We have stripped some code/whitespace from the beginning, make it clear. - "...".fmt(f)?; - left += 3; - } - - // On long lines, we strip the source line, accounting for unicode. - let mut taken = 0; - let cut_right = if margin.was_cut_right(line_len) { - taken += 3; - true - } else { - false - }; - // Specifies that it will end on the next character, so it will return - // until the next one to the final condition. - let mut ended = false; - let range = text - .char_indices() - .skip(left) - // Complete char iterator with final character - .chain(std::iter::once((text.len(), '\0'))) - // Take until the next one to the final condition - .take_while(|(_, ch)| { - // Fast return to iterate over final byte position - if ended { - return false; - } - // Make sure that the trimming on the right will fall within the terminal width. - // FIXME: `unicode_width` sometimes disagrees with terminals on how wide a `char` is. - // For now, just accept that sometimes the code line will be longer than desired. - taken += unicode_width::UnicodeWidthChar::width(*ch).unwrap_or(1); - if taken > right - left { - ended = true; - } - true - }) - // Reduce to start and end byte position - .fold((None, 0), |acc, (i, _)| { - if acc.0.is_some() { - (acc.0, i) - } else { - (Some(i), i) - } - }); - - // Format text with margins - text[range.0.expect("One character at line")..range.1].fmt(f)?; - - if cut_right { - // We have stripped some code after the right-most span end, make it clear we did so. - "...".fmt(f)?; - } - Ok(()) - } else { - text.fmt(f) - } - } - DisplaySourceLine::Annotation { - range, - annotation, - annotation_type, - annotation_part, - } => { - let indent_char = match annotation_part { - DisplayAnnotationPart::Standalone => ' ', - DisplayAnnotationPart::LabelContinuation => ' ', - DisplayAnnotationPart::MultilineStart => '_', - DisplayAnnotationPart::MultilineEnd => '_', - }; - let mark = match annotation_type { - DisplayAnnotationType::Error => '^', - DisplayAnnotationType::Warning => '-', - DisplayAnnotationType::Info => '-', - DisplayAnnotationType::Note => '-', - DisplayAnnotationType::Help => '-', - DisplayAnnotationType::None => ' ', - }; - let color = get_annotation_style(annotation_type, stylesheet); - let indent_length = match annotation_part { - DisplayAnnotationPart::LabelContinuation => range.1, - _ => range.0, - }; - - write!(f, "{}", color.render())?; - format_repeat_char(indent_char, indent_length + 1, f)?; - format_repeat_char(mark, range.1 - indent_length, f)?; - write!(f, "{}", color.render_reset())?; - - if !is_annotation_empty(annotation) { - f.write_char(' ')?; - write!(f, "{}", color.render())?; - self.format_annotation( - annotation, - annotation_part == &DisplayAnnotationPart::LabelContinuation, - true, - stylesheet, - f, - )?; - write!(f, "{}", color.render_reset())?; - } - - Ok(()) - } - } - } - #[inline] fn format_raw_line( &self, @@ -434,6 +311,7 @@ impl<'a> DisplaySet<'a> { lineno, inline_marks, line, + annotations, } => { let lineno_color = stylesheet.line_no(); if anonymized_line_numbers && lineno.is_some() { @@ -450,12 +328,95 @@ impl<'a> DisplaySet<'a> { f.write_str(" |")?; write!(f, "{}", lineno_color.render_reset())?; } - if *line != DisplaySourceLine::Empty { + + if let DisplaySourceLine::Content { text, .. } = line { if !inline_marks.is_empty() || 0 < inline_marks_width { f.write_char(' ')?; self.format_inline_marks(inline_marks, inline_marks_width, stylesheet, f)?; } - self.format_source_line(line, stylesheet, f)?; + f.write_char(' ')?; + if let Some(margin) = self.margin { + let line_len = text.chars().count(); + let mut left = margin.left(line_len); + let right = margin.right(line_len); + + if margin.was_cut_left() { + // We have stripped some code/whitespace from the beginning, make it clear. + "...".fmt(f)?; + left += 3; + } + + // On long lines, we strip the source line, accounting for unicode. + let mut taken = 0; + let cut_right = if margin.was_cut_right(line_len) { + taken += 3; + true + } else { + false + }; + // Specifies that it will end on the next character, so it will return + // until the next one to the final condition. + let mut ended = false; + let range = text + .char_indices() + .skip(left) + // Complete char iterator with final character + .chain(std::iter::once((text.len(), '\0'))) + // Take until the next one to the final condition + .take_while(|(_, ch)| { + // Fast return to iterate over final byte position + if ended { + return false; + } + // Make sure that the trimming on the right will fall within the terminal width. + // FIXME: `unicode_width` sometimes disagrees with terminals on how wide a `char` is. + // For now, just accept that sometimes the code line will be longer than desired. + taken += unicode_width::UnicodeWidthChar::width(*ch).unwrap_or(1); + if taken > right - left { + ended = true; + } + true + }) + // Reduce to start and end byte position + .fold((None, 0), |acc, (i, _)| { + if acc.0.is_some() { + (acc.0, i) + } else { + (Some(i), i) + } + }); + + // Format text with margins + text[range.0.expect("One character at line")..range.1].fmt(f)?; + + if cut_right { + // We have stripped some code after the right-most span end, make it clear we did so. + "...".fmt(f)?; + } + } else { + text.fmt(f)?; + } + + for annotation in annotations { + // Each annotation should be on its own line + f.write_char('\n')?; + // Add the line number and the line number delimiter + write!(f, "{}", stylesheet.line_no.render())?; + format_repeat_char(' ', lineno_width, f)?; + f.write_str(" |")?; + write!(f, "{}", stylesheet.line_no.render_reset())?; + + if !inline_marks.is_empty() || 0 < inline_marks_width { + f.write_char(' ')?; + self.format_inline_marks( + inline_marks, + inline_marks_width, + stylesheet, + f, + )?; + } + self.format_source_annotation(annotation, stylesheet, f)?; + } } else if !inline_marks.is_empty() { f.write_char(' ')?; self.format_inline_marks(inline_marks, inline_marks_width, stylesheet, f)?; @@ -493,6 +454,51 @@ impl<'a> DisplaySet<'a> { } Ok(()) } + + fn format_source_annotation( + &self, + annotation: &DisplaySourceAnnotation<'_>, + stylesheet: &Stylesheet, + f: &mut fmt::Formatter<'_>, + ) -> fmt::Result { + let indent_char = match annotation.annotation_part { + DisplayAnnotationPart::Standalone => ' ', + DisplayAnnotationPart::LabelContinuation => ' ', + DisplayAnnotationPart::MultilineStart => '_', + DisplayAnnotationPart::MultilineEnd => '_', + }; + let mark = match annotation.annotation_type { + DisplayAnnotationType::Error => '^', + DisplayAnnotationType::Warning => '-', + DisplayAnnotationType::Info => '-', + DisplayAnnotationType::Note => '-', + DisplayAnnotationType::Help => '-', + DisplayAnnotationType::None => ' ', + }; + let color = get_annotation_style(&annotation.annotation_type, stylesheet); + let indent_length = match annotation.annotation_part { + DisplayAnnotationPart::LabelContinuation => annotation.range.1, + _ => annotation.range.0, + }; + write!(f, "{}", color.render())?; + format_repeat_char(indent_char, indent_length + 1, f)?; + format_repeat_char(mark, annotation.range.1 - indent_length, f)?; + write!(f, "{}", color.render_reset())?; + + if !is_annotation_empty(&annotation.annotation) { + f.write_char(' ')?; + write!(f, "{}", color.render())?; + self.format_annotation( + &annotation.annotation, + annotation.annotation_part == DisplayAnnotationPart::LabelContinuation, + true, + stylesheet, + f, + )?; + write!(f, "{}", color.render_reset())?; + } + Ok(()) + } } /// Inline annotation which can be used in either Raw or Source line. @@ -511,6 +517,7 @@ pub enum DisplayLine<'a> { lineno: Option<usize>, inline_marks: Vec<DisplayMark>, line: DisplaySourceLine<'a>, + annotations: Vec<DisplaySourceAnnotation<'a>>, }, /// A line indicating a folded part of the slice. @@ -528,19 +535,18 @@ pub enum DisplaySourceLine<'a> { text: &'a str, range: (usize, usize), // meta information for annotation placement. }, - - /// An annotation line which is displayed in context of the slice. - Annotation { - annotation: Annotation<'a>, - range: (usize, usize), - annotation_type: DisplayAnnotationType, - annotation_part: DisplayAnnotationPart, - }, - /// An empty source line. Empty, } +#[derive(Debug, PartialEq)] +pub struct DisplaySourceAnnotation<'a> { + pub annotation: Annotation<'a>, + pub range: (usize, usize), + pub annotation_type: DisplayAnnotationType, + pub annotation_part: DisplayAnnotationPart, +} + /// Raw line - a line which does not have the `lineno` part and is not considered /// a part of the snippet. #[derive(Debug, PartialEq)] @@ -878,49 +884,54 @@ fn fold_body(mut body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> { let mut lines = vec![]; let mut no_annotation_lines_counter = 0; - for (idx, line) in body.iter().enumerate() { match line { DisplayLine::Source { - line: DisplaySourceLine::Annotation { .. }, + line: DisplaySourceLine::Content { .. }, + annotations, .. } => { - let fold_start = idx - no_annotation_lines_counter; - if no_annotation_lines_counter > 2 { - let fold_end = idx; - let pre_len = if no_annotation_lines_counter > 8 { - 4 - } else { - 0 - }; - let post_len = if no_annotation_lines_counter > 8 { - 2 - } else { - 1 - }; - for (i, _) in body - .iter() - .enumerate() - .take(fold_start + pre_len) - .skip(fold_start) - { - lines.push(Line::Source(i)); - } - lines.push(Line::Fold(idx)); - for (i, _) in body - .iter() - .enumerate() - .take(fold_end) - .skip(fold_end - post_len) - { - lines.push(Line::Source(i)); - } + if annotations.is_empty() { + no_annotation_lines_counter += 1; + continue; } else { - for (i, _) in body.iter().enumerate().take(idx).skip(fold_start) { - lines.push(Line::Source(i)); + let fold_start = idx - no_annotation_lines_counter; + if no_annotation_lines_counter >= 2 { + let fold_end = idx; + let pre_len = if no_annotation_lines_counter > 8 { + 4 + } else { + 0 + }; + let post_len = if no_annotation_lines_counter > 8 { + 2 + } else { + 1 + }; + for (i, _) in body + .iter() + .enumerate() + .take(fold_start + pre_len) + .skip(fold_start) + { + lines.push(Line::Source(i)); + } + lines.push(Line::Fold(idx)); + for (i, _) in body + .iter() + .enumerate() + .take(fold_end) + .skip(fold_end + 1 - post_len) + { + lines.push(Line::Source(i)); + } + } else { + for (i, _) in body.iter().enumerate().take(idx).skip(fold_start) { + lines.push(Line::Source(i)); + } } + no_annotation_lines_counter = 0; } - no_annotation_lines_counter = 0; } DisplayLine::Source { .. } => { no_annotation_lines_counter += 1; @@ -943,14 +954,19 @@ fn fold_body(mut body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> { } Line::Fold(i) => { if let DisplayLine::Source { - line: DisplaySourceLine::Annotation { .. }, + line: DisplaySourceLine::Content { .. }, ref inline_marks, + ref annotations, .. } = body.get(i - removed).unwrap() { - new_body.push(DisplayLine::Fold { - inline_marks: inline_marks.clone(), - }) + if !annotations.is_empty() { + new_body.push(DisplayLine::Fold { + inline_marks: inline_marks.clone(), + }); + } else { + unreachable!() + } } else { unreachable!() } @@ -986,7 +1002,6 @@ fn format_body( let mut current_line = snippet.line_start; let mut current_index = 0; - let mut annotation_line_count = 0; let mut annotations = snippet.annotations; for (idx, (line, end_line)) in CursorLines::new(snippet.source).enumerate() { let line_length: usize = line.len(); @@ -998,6 +1013,7 @@ fn format_body( text: line, range: line_range, }, + annotations: vec![], }); let line_start_index = line_range.0; let line_end_index = line_range.1; @@ -1009,7 +1025,7 @@ fn format_body( .unwrap_or_default(); // It would be nice to use filter_drain here once it's stable. annotations.retain(|annotation| { - let body_idx = idx + annotation_line_count; + let body_idx = idx; let annotation_type = match annotation.level { snippet::Level::Error => DisplayAnnotationType::None, snippet::Level::Warning => DisplayAnnotationType::None, @@ -1021,39 +1037,38 @@ fn format_body( if start >= line_start_index && end <= line_end_index || start == line_end_index && end - start <= 1 => { - let annotation_start_col = line[0..(start - line_start_index)] - .chars() - .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) - .sum::<usize>() - - margin_left; - // This allows for annotations to be placed one past the - // last character - let safe_end = (end - line_start_index).saturating_sub(line_length); - let annotation_end_col = line[0..(end - line_start_index) - safe_end] - .chars() - .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) - .sum::<usize>() - + safe_end - - margin_left; - let range = (annotation_start_col, annotation_end_col); - body.insert( - body_idx + 1, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - annotation: Annotation { - annotation_type, - id: None, - label: format_label(annotation.label, None), - }, - range, - annotation_type: DisplayAnnotationType::from(annotation.level), - annotation_part: DisplayAnnotationPart::Standalone, + if let DisplayLine::Source { + ref mut annotations, + .. + } = body[body_idx] + { + let annotation_start_col = line[0..(start - line_start_index)] + .chars() + .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) + .sum::<usize>() + - margin_left; + // This allows for annotations to be placed one past the + // last character + let safe_end = (end - line_start_index).saturating_sub(line_length); + let annotation_end_col = line[0..(end - line_start_index) - safe_end] + .chars() + .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) + .sum::<usize>() + + safe_end + - margin_left; + + let range = (annotation_start_col, annotation_end_col); + annotations.push(DisplaySourceAnnotation { + annotation: Annotation { + annotation_type, + id: None, + label: format_label(annotation.label, None), }, - }, - ); - annotation_line_count += 1; + range, + annotation_type: DisplayAnnotationType::from(annotation.level), + annotation_part: DisplayAnnotationPart::Standalone, + }); + } false } Range { start, end } @@ -1072,30 +1087,27 @@ fn format_body( annotation_type: DisplayAnnotationType::from(annotation.level), }); } - } else { + } else if let DisplayLine::Source { + ref mut annotations, + .. + } = body[body_idx] + { let annotation_start_col = line[0..(start - line_start_index)] .chars() .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) .sum::<usize>(); - let range = (annotation_start_col, annotation_start_col + 1); - body.insert( - body_idx + 1, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::None, - id: None, - label: vec![], - }, - range, - annotation_type: DisplayAnnotationType::from(annotation.level), - annotation_part: DisplayAnnotationPart::MultilineStart, - }, + let annotation_end_col = annotation_start_col + 1; + let range = (annotation_start_col, annotation_end_col); + annotations.push(DisplaySourceAnnotation { + annotation: Annotation { + annotation_type, + id: None, + label: vec![], }, - ); - annotation_line_count += 1; + range, + annotation_type: DisplayAnnotationType::from(annotation.level), + annotation_part: DisplayAnnotationPart::MultilineStart, + }); } true } @@ -1119,6 +1131,7 @@ fn format_body( { if let DisplayLine::Source { ref mut inline_marks, + ref mut annotations, .. } = body[body_idx] { @@ -1126,35 +1139,24 @@ fn format_body( mark_type: DisplayMarkType::AnnotationThrough, annotation_type: DisplayAnnotationType::from(annotation.level), }); - } - - let end_mark = line[0..(end - line_start_index)] - .chars() - .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) - .sum::<usize>() - .saturating_sub(1); - let range = (end_mark - margin_left, (end_mark + 1) - margin_left); - body.insert( - body_idx + 1, - DisplayLine::Source { - lineno: None, - inline_marks: vec![DisplayMark { - mark_type: DisplayMarkType::AnnotationThrough, - annotation_type: DisplayAnnotationType::from(annotation.level), - }], - line: DisplaySourceLine::Annotation { - annotation: Annotation { - annotation_type, - id: None, - label: format_label(annotation.label, None), - }, - range, - annotation_type: DisplayAnnotationType::from(annotation.level), - annotation_part: DisplayAnnotationPart::MultilineEnd, + let end_mark = line[0..(end - line_start_index)] + .chars() + .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) + .sum::<usize>() + .saturating_sub(1) + - margin_left; + let range = (end_mark, end_mark + 1); + annotations.push(DisplaySourceAnnotation { + annotation: Annotation { + annotation_type, + id: None, + label: format_label(annotation.label, None), }, - }, - ); - annotation_line_count += 1; + range, + annotation_type: DisplayAnnotationType::from(annotation.level), + annotation_part: DisplayAnnotationPart::MultilineEnd, + }); + } false } _ => true, @@ -1173,6 +1175,7 @@ fn format_body( lineno: None, inline_marks: vec![], line: DisplaySourceLine::Empty, + annotations: vec![], }, ); } @@ -1182,12 +1185,14 @@ fn format_body( lineno: None, inline_marks: vec![], line: DisplaySourceLine::Empty, + annotations: vec![], }); } else if let Some(DisplayLine::Source { .. }) = body.last() { body.push(DisplayLine::Source { lineno: None, inline_marks: vec![], line: DisplaySourceLine::Empty, + annotations: vec![], }); } body From 73919a734acdfe208086580386b20fa6b9ee5d63 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Fri, 15 Mar 2024 12:05:55 -0600 Subject: [PATCH 168/455] fix!: Create Margin within each DisplaySet BREAKING CHANGE: column_width is exposed instead of Margin --- src/renderer/display_list.rs | 266 ++++++++++++------ src/renderer/margin.rs | 28 +- src/renderer/mod.rs | 30 +- tests/fixtures/deserialize.rs | 46 +-- tests/fixtures/no-color/strip_line.toml | 7 - tests/fixtures/no-color/strip_line_char.toml | 7 - tests/fixtures/no-color/strip_line_non_ws.svg | 12 +- .../fixtures/no-color/strip_line_non_ws.toml | 21 +- 8 files changed, 220 insertions(+), 197 deletions(-) diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index 250cfc45..53caa38b 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -32,11 +32,12 @@ //! //! The above snippet has been built out of the following structure: use crate::snippet; +use std::cmp::{max, min}; use std::fmt::{Display, Write}; use std::ops::Range; use std::{cmp, fmt}; -use crate::renderer::{stylesheet::Stylesheet, Margin, Style}; +use crate::renderer::{stylesheet::Stylesheet, Margin, Style, DEFAULT_TERM_WIDTH}; const ANONYMIZED_LINE_NUM: &str = "LL"; const ERROR_TXT: &str = "error"; @@ -103,9 +104,9 @@ impl<'a> DisplayList<'a> { message: snippet::Message<'a>, stylesheet: &'a Stylesheet, anonymized_line_numbers: bool, - margin: Option<Margin>, + term_width: usize, ) -> DisplayList<'a> { - let body = format_message(message, margin, true); + let body = format_message(message, term_width, anonymized_line_numbers, true); Self { body, @@ -147,7 +148,7 @@ impl<'a> DisplayList<'a> { #[derive(Debug, PartialEq)] pub(crate) struct DisplaySet<'a> { pub display_lines: Vec<DisplayLine<'a>>, - pub margin: Option<Margin>, + pub margin: Margin, } impl<'a> DisplaySet<'a> { @@ -335,66 +336,50 @@ impl<'a> DisplaySet<'a> { self.format_inline_marks(inline_marks, inline_marks_width, stylesheet, f)?; } f.write_char(' ')?; - if let Some(margin) = self.margin { - let line_len = text.chars().count(); - let mut left = margin.left(line_len); - let right = margin.right(line_len); - - if margin.was_cut_left() { - // We have stripped some code/whitespace from the beginning, make it clear. - "...".fmt(f)?; - left += 3; - } - // On long lines, we strip the source line, accounting for unicode. - let mut taken = 0; - let cut_right = if margin.was_cut_right(line_len) { - taken += 3; - true - } else { - false - }; - // Specifies that it will end on the next character, so it will return - // until the next one to the final condition. - let mut ended = false; - let range = text - .char_indices() - .skip(left) - // Complete char iterator with final character - .chain(std::iter::once((text.len(), '\0'))) - // Take until the next one to the final condition - .take_while(|(_, ch)| { - // Fast return to iterate over final byte position - if ended { - return false; - } - // Make sure that the trimming on the right will fall within the terminal width. - // FIXME: `unicode_width` sometimes disagrees with terminals on how wide a `char` is. - // For now, just accept that sometimes the code line will be longer than desired. - taken += unicode_width::UnicodeWidthChar::width(*ch).unwrap_or(1); - if taken > right - left { - ended = true; - } - true - }) - // Reduce to start and end byte position - .fold((None, 0), |acc, (i, _)| { - if acc.0.is_some() { - (acc.0, i) - } else { - (Some(i), i) - } - }); + let text = normalize_whitespace(text); + let line_len = text.as_bytes().len(); + let mut left = self.margin.left(line_len); + let right = self.margin.right(line_len); - // Format text with margins - text[range.0.expect("One character at line")..range.1].fmt(f)?; + if self.margin.was_cut_left() { + "...".fmt(f)?; + left += 3; + } + // On long lines, we strip the source line, accounting for unicode. + let mut taken = 0; + let code: String = text + .chars() + .skip(left) + .take_while(|ch| { + // Make sure that the trimming on the right will fall within the terminal width. + // FIXME: `unicode_width` sometimes disagrees with terminals on how wide a `char` + // is. For now, just accept that sometimes the code line will be longer than + // desired. + let next = unicode_width::UnicodeWidthChar::width(*ch).unwrap_or(1); + if taken + next > right - left { + return false; + } + taken += next; + true + }) + .collect(); - if cut_right { - // We have stripped some code after the right-most span end, make it clear we did so. - "...".fmt(f)?; - } + if self.margin.was_cut_right(line_len) { + code[..taken.saturating_sub(3)].fmt(f)?; + "...".fmt(f)?; } else { - text.fmt(f)?; + code.fmt(f)?; + } + + let mut left: usize = text + .chars() + .take(left) + .map(|ch| unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1)) + .sum(); + + if self.margin.was_cut_left() { + left = left.saturating_sub(3); } for annotation in annotations { @@ -415,7 +400,7 @@ impl<'a> DisplaySet<'a> { f, )?; } - self.format_source_annotation(annotation, stylesheet, f)?; + self.format_source_annotation(annotation, left, stylesheet, f)?; } } else if !inline_marks.is_empty() { f.write_char(' ')?; @@ -458,6 +443,7 @@ impl<'a> DisplaySet<'a> { fn format_source_annotation( &self, annotation: &DisplaySourceAnnotation<'_>, + left: usize, stylesheet: &Stylesheet, f: &mut fmt::Formatter<'_>, ) -> fmt::Result { @@ -476,13 +462,17 @@ impl<'a> DisplaySet<'a> { DisplayAnnotationType::None => ' ', }; let color = get_annotation_style(&annotation.annotation_type, stylesheet); + let range = ( + annotation.range.0.saturating_sub(left), + annotation.range.1.saturating_sub(left), + ); let indent_length = match annotation.annotation_part { - DisplayAnnotationPart::LabelContinuation => annotation.range.1, - _ => annotation.range.0, + DisplayAnnotationPart::LabelContinuation => range.1, + _ => range.0, }; write!(f, "{}", color.render())?; format_repeat_char(indent_char, indent_length + 1, f)?; - format_repeat_char(mark, annotation.range.1 - indent_length, f)?; + format_repeat_char(mark, range.1 - indent_length, f)?; write!(f, "{}", color.render_reset())?; if !is_annotation_empty(&annotation.annotation) { @@ -713,7 +703,8 @@ fn format_message( footer, snippets, }: snippet::Message<'_>, - margin: Option<Margin>, + term_width: usize, + anonymized_line_numbers: bool, primary: bool, ) -> Vec<DisplaySet<'_>> { let mut sets = vec![]; @@ -728,7 +719,8 @@ fn format_message( snippet, idx == 0, !footer.is_empty(), - margin, + term_width, + anonymized_line_numbers, )); } @@ -739,12 +731,17 @@ fn format_message( } else { sets.push(DisplaySet { display_lines: body, - margin, + margin: Margin::new(0, 0, 0, 0, DEFAULT_TERM_WIDTH, 0), }); } for annotation in footer { - sets.extend(format_message(annotation, margin, false)); + sets.extend(format_message( + annotation, + term_width, + anonymized_line_numbers, + false, + )); } sets @@ -801,23 +798,26 @@ fn format_snippet( snippet: snippet::Snippet<'_>, is_first: bool, has_footer: bool, - margin: Option<Margin>, + term_width: usize, + anonymized_line_numbers: bool, ) -> DisplaySet<'_> { let main_range = snippet.annotations.first().map(|x| x.range.start); let origin = snippet.origin; let need_empty_header = origin.is_some() || is_first; - let body = format_body(snippet, need_empty_header, has_footer, margin); - let header = format_header(origin, main_range, &body, is_first); - let mut result = vec![]; + let mut body = format_body( + snippet, + need_empty_header, + has_footer, + term_width, + anonymized_line_numbers, + ); + let header = format_header(origin, main_range, &body.display_lines, is_first); if let Some(header) = header { - result.push(header); - } - result.extend(body); - DisplaySet { - display_lines: result, - margin, + body.display_lines.insert(0, header); } + + body } #[inline] @@ -981,8 +981,9 @@ fn format_body( snippet: snippet::Snippet<'_>, need_empty_header: bool, has_footer: bool, - margin: Option<Margin>, -) -> Vec<DisplayLine<'_>> { + term_width: usize, + anonymized_line_numbers: bool, +) -> DisplaySet<'_> { let source_len = snippet.source.len(); if let Some(bigger) = snippet.annotations.iter().find_map(|x| { // Allow highlighting one past the last character in the source. @@ -1002,6 +1003,12 @@ fn format_body( let mut current_line = snippet.line_start; let mut current_index = 0; + let mut whitespace_margin = usize::MAX; + let mut span_left_margin = usize::MAX; + let mut span_right_margin = 0; + let mut label_right_margin = 0; + let mut max_line_len = 0; + let mut annotations = snippet.annotations; for (idx, (line, end_line)) in CursorLines::new(snippet.source).enumerate() { let line_length: usize = line.len(); @@ -1015,14 +1022,28 @@ fn format_body( }, annotations: vec![], }); + + let leading_whitespace = line + .chars() + .take_while(|c| c.is_whitespace()) + .map(|c| { + match c { + // Tabs are displayed as 4 spaces + '\t' => 4, + _ => 1, + } + }) + .sum(); + if line.chars().any(|c| !c.is_whitespace()) { + whitespace_margin = min(whitespace_margin, leading_whitespace); + } + max_line_len = max(max_line_len, line_length); + let line_start_index = line_range.0; let line_end_index = line_range.1; current_line += 1; current_index += line_length + end_line as usize; - let margin_left = margin - .map(|m| m.left(line_end_index - line_start_index)) - .unwrap_or_default(); // It would be nice to use filter_drain here once it's stable. annotations.retain(|annotation| { let body_idx = idx; @@ -1031,6 +1052,7 @@ fn format_body( snippet::Level::Warning => DisplayAnnotationType::None, _ => DisplayAnnotationType::from(annotation.level), }; + let label_right = annotation.label.map_or(0, |label| label.len() + 1); match annotation.range { Range { start, .. } if start > line_end_index => true, Range { start, end } @@ -1045,8 +1067,7 @@ fn format_body( let annotation_start_col = line[0..(start - line_start_index)] .chars() .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) - .sum::<usize>() - - margin_left; + .sum::<usize>(); // This allows for annotations to be placed one past the // last character let safe_end = (end - line_start_index).saturating_sub(line_length); @@ -1054,8 +1075,12 @@ fn format_body( .chars() .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) .sum::<usize>() - + safe_end - - margin_left; + + safe_end; + + span_left_margin = min(span_left_margin, annotation_start_col); + span_right_margin = max(span_right_margin, annotation_end_col); + label_right_margin = + max(label_right_margin, annotation_end_col + label_right); let range = (annotation_start_col, annotation_end_col); annotations.push(DisplaySourceAnnotation { @@ -1097,6 +1122,12 @@ fn format_body( .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) .sum::<usize>(); let annotation_end_col = annotation_start_col + 1; + + span_left_margin = min(span_left_margin, annotation_start_col); + span_right_margin = max(span_right_margin, annotation_end_col); + label_right_margin = + max(label_right_margin, annotation_end_col + label_right); + let range = (annotation_start_col, annotation_end_col); annotations.push(DisplaySourceAnnotation { annotation: Annotation { @@ -1143,9 +1174,15 @@ fn format_body( .chars() .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) .sum::<usize>() - .saturating_sub(1) - - margin_left; - let range = (end_mark, end_mark + 1); + .saturating_sub(1); + + let end_plus_one = end_mark + 1; + + span_left_margin = min(span_left_margin, end_mark); + span_right_margin = max(span_right_margin, end_plus_one); + label_right_margin = max(label_right_margin, end_plus_one + label_right); + + let range = (end_mark, end_plus_one); annotations.push(DisplaySourceAnnotation { annotation: Annotation { annotation_type, @@ -1195,7 +1232,31 @@ fn format_body( annotations: vec![], }); } - body + let max_line_num_len = if anonymized_line_numbers { + ANONYMIZED_LINE_NUM.len() + } else { + current_line.to_string().len() + }; + + let width_offset = 3 + max_line_num_len; + + if span_left_margin == usize::MAX { + span_left_margin = 0; + } + + let margin = Margin::new( + whitespace_margin, + span_left_margin, + span_right_margin, + label_right_margin, + term_width.saturating_sub(width_offset), + max_line_len, + ); + + DisplaySet { + display_lines: body, + margin, + } } fn format_repeat_char(c: char, n: usize, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -1252,3 +1313,26 @@ fn is_annotation_empty(annotation: &Annotation<'_>) -> bool { .iter() .all(|fragment| fragment.content.is_empty()) } + +// We replace some characters so the CLI output is always consistent and underlines aligned. +const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[ + ('\t', " "), // We do our own tab replacement + ('\u{200D}', ""), // Replace ZWJ with nothing for consistent terminal output of grapheme clusters. + ('\u{202A}', ""), // The following unicode text flow control characters are inconsistently + ('\u{202B}', ""), // supported across CLIs and can cause confusion due to the bytes on disk + ('\u{202D}', ""), // not corresponding to the visible source code, so we replace them always. + ('\u{202E}', ""), + ('\u{2066}', ""), + ('\u{2067}', ""), + ('\u{2068}', ""), + ('\u{202C}', ""), + ('\u{2069}', ""), +]; + +fn normalize_whitespace(str: &str) -> String { + let mut s = str.to_string(); + for (c, replacement) in OUTPUT_REPLACEMENTS { + s = s.replace(*c, replacement); + } + s +} diff --git a/src/renderer/margin.rs b/src/renderer/margin.rs index 7636603b..3f1b28b0 100644 --- a/src/renderer/margin.rs +++ b/src/renderer/margin.rs @@ -17,7 +17,7 @@ pub struct Margin { /// The end of the line to be displayed. computed_right: usize, /// The current width of the terminal. 140 by default and in tests. - column_width: usize, + term_width: usize, /// The end column of a span label, including the span. Doesn't account for labels not in the /// same line as the span. label_right: usize, @@ -29,7 +29,7 @@ impl Margin { span_left: usize, span_right: usize, label_right: usize, - column_width: usize, + term_width: usize, max_line_len: usize, ) -> Self { // The 6 is padding to give a bit of room for `...` when displaying: @@ -47,7 +47,7 @@ impl Margin { span_right: span_right + ELLIPSIS_PASSING, computed_left: 0, computed_right: 0, - column_width, + term_width, label_right: label_right + ELLIPSIS_PASSING, }; m.compute(max_line_len); @@ -67,7 +67,7 @@ impl Margin { } else { self.computed_right }; - right < line_len && self.computed_left + self.column_width < line_len + right < line_len && self.computed_left + self.term_width < line_len } fn compute(&mut self, max_line_len: usize) { @@ -81,22 +81,22 @@ impl Margin { // relevant code. self.computed_right = max(max_line_len, self.computed_left); - if self.computed_right - self.computed_left > self.column_width { + if self.computed_right - self.computed_left > self.term_width { // Trimming only whitespace isn't enough, let's get craftier. - if self.label_right - self.whitespace_left <= self.column_width { + if self.label_right - self.whitespace_left <= self.term_width { // Attempt to fit the code window only trimming whitespace. self.computed_left = self.whitespace_left; - self.computed_right = self.computed_left + self.column_width; - } else if self.label_right - self.span_left <= self.column_width { + self.computed_right = self.computed_left + self.term_width; + } else if self.label_right - self.span_left <= self.term_width { // Attempt to fit the code window considering only the spans and labels. - let padding_left = (self.column_width - (self.label_right - self.span_left)) / 2; + let padding_left = (self.term_width - (self.label_right - self.span_left)) / 2; self.computed_left = self.span_left.saturating_sub(padding_left); - self.computed_right = self.computed_left + self.column_width; - } else if self.span_right - self.span_left <= self.column_width { + self.computed_right = self.computed_left + self.term_width; + } else if self.span_right - self.span_left <= self.term_width { // Attempt to fit the code window considering the spans and labels plus padding. - let padding_left = (self.column_width - (self.span_right - self.span_left)) / 5 * 2; + let padding_left = (self.term_width - (self.span_right - self.span_left)) / 5 * 2; self.computed_left = self.span_left.saturating_sub(padding_left); - self.computed_right = self.computed_left + self.column_width; + self.computed_right = self.computed_left + self.term_width; } else { // Mostly give up but still don't show the full line. self.computed_left = self.span_left; @@ -110,7 +110,7 @@ impl Margin { } pub(crate) fn right(&self, line_len: usize) -> usize { - if line_len.saturating_sub(self.computed_left) <= self.column_width { + if line_len.saturating_sub(self.computed_left) <= self.term_width { line_len } else { min(line_len, self.computed_right) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 5f9394d5..b518cfb5 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -21,11 +21,13 @@ pub use margin::Margin; use std::fmt::Display; use stylesheet::Stylesheet; +pub const DEFAULT_TERM_WIDTH: usize = 140; + /// A renderer for [`Message`]s #[derive(Clone)] pub struct Renderer { anonymized_line_numbers: bool, - margin: Option<Margin>, + term_width: usize, stylesheet: Stylesheet, } @@ -34,7 +36,7 @@ impl Renderer { pub const fn plain() -> Self { Self { anonymized_line_numbers: false, - margin: None, + term_width: DEFAULT_TERM_WIDTH, stylesheet: Stylesheet::plain(), } } @@ -94,25 +96,9 @@ impl Renderer { self } - /// Set the margin for the output - /// - /// This controls the various margins of the output. - /// - /// # Example - /// - /// ```text - /// error: expected type, found `22` - /// --> examples/footer.rs:29:25 - /// | - /// 26 | ... annotations: vec![SourceAnnotation { - /// | ---------------- info: while parsing this struct - /// ... - /// 29 | ... range: <22, 25>, - /// | ^^ - /// | - /// ``` - pub const fn margin(mut self, margin: Option<Margin>) -> Self { - self.margin = margin; + // Set the terminal width + pub const fn term_width(mut self, term_width: usize) -> Self { + self.term_width = term_width; self } @@ -170,7 +156,7 @@ impl Renderer { msg, &self.stylesheet, self.anonymized_line_numbers, - self.margin, + self.term_width, ) } } diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs index 165c3418..2d1452b6 100644 --- a/tests/fixtures/deserialize.rs +++ b/tests/fixtures/deserialize.rs @@ -1,7 +1,8 @@ use serde::{Deserialize, Deserializer, Serialize}; use std::ops::Range; -use annotate_snippets::{renderer::Margin, Annotation, Level, Message, Renderer, Snippet}; +use annotate_snippets::renderer::DEFAULT_TERM_WIDTH; +use annotate_snippets::{Annotation, Level, Message, Renderer, Snippet}; #[derive(Deserialize)] pub struct Fixture<'a> { @@ -148,55 +149,18 @@ enum LevelDef { pub struct RendererDef { #[serde(default)] anonymized_line_numbers: bool, - #[serde(deserialize_with = "deserialize_margin")] #[serde(default)] - margin: Option<Margin>, + term_width: Option<usize>, } impl From<RendererDef> for Renderer { fn from(val: RendererDef) -> Self { let RendererDef { anonymized_line_numbers, - margin, + term_width, } = val; Renderer::plain() .anonymized_line_numbers(anonymized_line_numbers) - .margin(margin) + .term_width(term_width.unwrap_or(DEFAULT_TERM_WIDTH)) } } - -fn deserialize_margin<'de, D>(deserializer: D) -> Result<Option<Margin>, D::Error> -where - D: Deserializer<'de>, -{ - #[derive(Deserialize)] - struct Wrapper { - whitespace_left: usize, - span_left: usize, - span_right: usize, - label_right: usize, - column_width: usize, - max_line_len: usize, - } - - Option::<Wrapper>::deserialize(deserializer).map(|opt_wrapped: Option<Wrapper>| { - opt_wrapped.map(|wrapped: Wrapper| { - let Wrapper { - whitespace_left, - span_left, - span_right, - label_right, - column_width, - max_line_len, - } = wrapped; - Margin::new( - whitespace_left, - span_left, - span_right, - label_right, - column_width, - max_line_len, - ) - }) - }) -} diff --git a/tests/fixtures/no-color/strip_line.toml b/tests/fixtures/no-color/strip_line.toml index d44024bd..459cbe1c 100644 --- a/tests/fixtures/no-color/strip_line.toml +++ b/tests/fixtures/no-color/strip_line.toml @@ -16,10 +16,3 @@ range = [192, 194] [renderer] color = false anonymized_line_numbers = true -[renderer.margin] -whitespace_left = 180 -span_left = 192 -span_right = 194 -label_right = 221 -column_width = 140 -max_line_len = 195 diff --git a/tests/fixtures/no-color/strip_line_char.toml b/tests/fixtures/no-color/strip_line_char.toml index e3f74822..dedefd5a 100644 --- a/tests/fixtures/no-color/strip_line_char.toml +++ b/tests/fixtures/no-color/strip_line_char.toml @@ -16,10 +16,3 @@ range = [192, 194] [renderer] color = false anonymized_line_numbers = true -[renderer.margin] -whitespace_left = 180 -span_left = 192 -span_right = 194 -label_right = 221 -column_width = 140 -max_line_len = 195 diff --git a/tests/fixtures/no-color/strip_line_non_ws.svg b/tests/fixtures/no-color/strip_line_non_ws.svg index 6a72e7c9..2be38901 100644 --- a/tests/fixtures/no-color/strip_line_non_ws.svg +++ b/tests/fixtures/no-color/strip_line_non_ws.svg @@ -1,4 +1,4 @@ -<svg width="1238px" height="128px" xmlns="http://www.w3.org/2000/svg"> +<svg width="1196px" height="146px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -18,15 +18,17 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan>error[E0308]: mismatched types</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> --> $DIR/non-whitespace-trimming.rs:4:241</tspan> + <tspan x="10px" y="46px"><tspan> --> $DIR/non-whitespace-trimming.rs:4:242</tspan> </tspan> <tspan x="10px" y="64px"><tspan> |</tspan> </tspan> - <tspan x="10px" y="82px"><tspan>LL | ... = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = ();...</tspan> + <tspan x="10px" y="82px"><tspan>LL | ... = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () ...</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> | ^^ expected (), found integer</tspan> + <tspan x="10px" y="100px"><tspan> | ^^ expected `()`, found integer</tspan> </tspan> - <tspan x="10px" y="118px"><tspan> |</tspan> + <tspan x="10px" y="118px"><tspan> | ^^ expected due to this</tspan> +</tspan> + <tspan x="10px" y="136px"><tspan> |</tspan> </tspan> </text> diff --git a/tests/fixtures/no-color/strip_line_non_ws.toml b/tests/fixtures/no-color/strip_line_non_ws.toml index 2985177b..06ecad85 100644 --- a/tests/fixtures/no-color/strip_line_non_ws.toml +++ b/tests/fixtures/no-color/strip_line_non_ws.toml @@ -4,21 +4,22 @@ id = "E0308" title = "mismatched types" [[message.snippets]] -source = " let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = ();" +source = """ + let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); +""" line_start = 4 origin = "$DIR/non-whitespace-trimming.rs" [[message.snippets.annotations]] -label = "expected (), found integer" +label = "expected `()`, found integer" level = "Error" -range = [240, 242] +range = [241, 243] + +[[message.snippets.annotations]] +label = "expected due to this" +level = "Error" +range = [236, 238] + [renderer] anonymized_line_numbers = true -[renderer.margin] -whitespace_left = 4 -span_left = 240 -span_right = 242 -label_right = 271 -column_width = 140 -max_line_len = 371 From e89fd82cdcded1a29791f558a2d5bda7712bacb1 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Fri, 15 Mar 2024 12:08:09 -0600 Subject: [PATCH 169/455] fix!: Make Margin private --- src/renderer/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index b518cfb5..ee63da9f 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -17,7 +17,7 @@ pub(crate) mod stylesheet; use crate::snippet::Message; pub use anstyle::*; use display_list::DisplayList; -pub use margin::Margin; +use margin::Margin; use std::fmt::Display; use stylesheet::Stylesheet; From e6b273b2864b54668be271811b07db51b192f3fd Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Fri, 15 Mar 2024 15:15:51 -0600 Subject: [PATCH 170/455] chore: Update CHANGELOG.md --- CHANGELOG.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f460225..268aaf53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](http://semver.org/). <!-- next-header --> ## [Unreleased] - ReleaseDate +### Breaking Changes + +- Switched from char spans to byte spans [#90](https://github.com/rust-lang/annotate-snippets-rs/pull/90/commits/b65b8cabcd34da9fed88490a7a1cd8085777706a) +- Renamed `AnnotationType` to `Level` [#94](https://github.com/rust-lang/annotate-snippets-rs/pull/94/commits/b49f9471d920c7f561fa61970039b0ba44e448ac) +- Renamed `SourceAnnotation` to `Annotation` [#94](https://github.com/rust-lang/annotate-snippets-rs/pull/94/commits/bbf9c5fe27e83652433151cbfc7d6cafc02a8c47) +- Renamed `Snippet` to `Message` [#94](https://github.com/rust-lang/annotate-snippets-rs/pull/94/commits/105da760b6e1bd4cfce4c642ac679ecf6011f511) +- Renamed `Slice` to `Snippet` [#94](https://github.com/rust-lang/annotate-snippets-rs/pull/94/commits/1c18950300cf8b93d92d89e9797ed0bae02c0a37) +- `Message`, `Snippet`, `Annotation` and `Level` can only be built with a builder pattern [#91](https://github.com/rust-lang/annotate-snippets-rs/pull/91) and [#94](https://github.com/rust-lang/annotate-snippets-rs/pull/94) +- `Annotation` labels are now optional [#94](https://github.com/rust-lang/annotate-snippets-rs/pull/94/commits/c821084068a1acd2688b6c8d0b3423e143d359e2) +- `Annotation` now takes in `Range<usize>` instead of `(usize, usize)` [#90](https://github.com/rust-lang/annotate-snippets-rs/pull/90/commits/c3bd0c3a63f983f5f2b4793a099972b1f6e97a9f) +- `Margin` is now an internal detail, only `term_width` is exposed [#105](https://github.com/rust-lang/annotate-snippets-rs/pull/105) +- `footer` was generalized to be a `Message` [#98](https://github.com/rust-lang/annotate-snippets-rs/pull/98) + +### Added +- `term_width` was added to `Renderer` to control the rendering width [#105](https://github.com/rust-lang/annotate-snippets-rs/pull/105) + - defaults to 140 when not set + +### Fixed +- `Margin`s are now calculated per `Snippet`, rather than for the entire `Message` [#105](https://github.com/rust-lang/annotate-snippets-rs/pull/105) +- `Annotation`s can be created without labels + +### Features +- `footer` was expanded to allow annotating sources by accepting `Message` [#98](https://github.com/rust-lang/annotate-snippets-rs/pull/98) ## [0.10.2] - 2024-02-29 From d0e7997c6859a4ca690f81f019e158743dffd0f7 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Fri, 15 Mar 2024 15:18:16 -0600 Subject: [PATCH 171/455] chore: Release annotate-snippets version 0.11.0 --- CHANGELOG.md | 5 ++++- Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 268aaf53..ad3df890 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). <!-- next-header --> ## [Unreleased] - ReleaseDate +## [0.11.0] - 2024-03-15 + ### Breaking Changes - Switched from char spans to byte spans [#90](https://github.com/rust-lang/annotate-snippets-rs/pull/90/commits/b65b8cabcd34da9fed88490a7a1cd8085777706a) @@ -116,7 +118,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Update the syntax to Rust 2018 idioms. (#4) <!-- next-url --> -[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.2...HEAD +[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.0...HEAD +[0.11.0]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.2...0.11.0 [0.10.2]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.1...0.10.2 [0.10.1]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.0...0.10.1 [0.10.0]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.9.2...0.10.0 diff --git a/Cargo.lock b/Cargo.lock index 06297756..c18b289f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,7 +19,7 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "annotate-snippets" -version = "0.10.2" +version = "0.11.0" dependencies = [ "anstream", "anstyle", diff --git a/Cargo.toml b/Cargo.toml index 5f685e1f..17b66000 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "annotate-snippets" -version = "0.10.2" +version = "0.11.0" edition = "2021" rust-version = "1.73" # MSRV authors = ["Zibi Braniecki <gandalf@mozilla.com>"] From 3d5ead81cf3962997045915cd9b137086d35d1a9 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 21 Mar 2024 10:46:56 -0500 Subject: [PATCH 172/455] chore(ci): Configure standard lints --- Cargo.toml | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 ++ 2 files changed, 86 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index b8ecde11..e51c5ddd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,87 @@ include = [ "examples/**/*" ] +[workspace.lints.rust] +missing_docs = "warn" +rust_2018_idioms = "warn" +unreachable_pub = "warn" +unsafe-op-in-unsafe-fn = "warn" +unsafe_code = "warn" +unused-crate-dependencies = "warn" +unused-lifetimes = "warn" +unused-macro-rules = "warn" +unused-qualifications = "warn" + +[workspace.lints.clippy] +bool_assert_comparison = "allow" +branches_sharing_code = "allow" +checked_conversions = "warn" +collapsible_else_if = "allow" +create_dir = "warn" +dbg_macro = "warn" +debug_assert_with_mut_call = "warn" +doc_markdown = "warn" +empty_enum = "warn" +enum_glob_use = "warn" +exhaustive_enums = "warn" +exhaustive_structs = "warn" +exit = "warn" +expl_impl_clone_on_copy = "warn" +explicit_deref_methods = "warn" +explicit_into_iter_loop = "warn" +fallible_impl_from = "warn" +filter_map_next = "warn" +flat_map_option = "warn" +float_cmp_const = "warn" +fn_params_excessive_bools = "warn" +from_iter_instead_of_collect = "warn" +if_same_then_else = "allow" +implicit_clone = "warn" +imprecise_flops = "warn" +inconsistent_struct_constructor = "warn" +inefficient_to_string = "warn" +infinite_loop = "warn" +invalid_upcast_comparisons = "warn" +items_after_statements = "warn" +large_digit_groups = "warn" +large_stack_arrays = "warn" +large_types_passed_by_value = "warn" +let_and_return = "allow" # sometimes good to name what you are returning +linkedlist = "warn" +lossy_float_literal = "warn" +macro_use_imports = "warn" +match_wildcard_for_single_variants = "warn" +mem_forget = "warn" +mutex_integer = "warn" +needless_continue = "warn" +needless_for_each = "warn" +negative_feature_names = "warn" +path_buf_push_overwrite = "warn" +print_stderr = "warn" +print_stdout = "warn" +ptr_as_ptr = "warn" +rc_mutex = "warn" +redundant_feature_names = "warn" +ref_option_ref = "warn" +rest_pat_in_fully_bound_structs = "warn" +return_self_not_must_use = "warn" +same_functions_in_if_condition = "warn" +self_named_module_files = "warn" +semicolon_if_nothing_returned = "warn" +single_match_else = "warn" +str_to_string = "warn" +string_add = "warn" +string_add_assign = "warn" +string_lit_as_bytes = "warn" +string_to_string = "warn" +tests_outside_test_module = "warn" +todo = "warn" +trait_duplication_in_bounds = "warn" +unwrap_used = "warn" +verbose_file_reads = "warn" +wildcard_imports = "warn" +zero_sized_map_values = "warn" + [package] name = "PROJECT" version = "0.0.1" @@ -46,3 +127,6 @@ default = [] [dependencies] [dev-dependencies] + +[lints] +workspace = true diff --git a/src/lib.rs b/src/lib.rs index 45bf577c..8ce46b57 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,4 @@ +//! > DESCRIPTION + #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![allow(non_snake_case)] // TODO: Delete me From 54975a06d41eb142077aaf2732bfe563a26efa31 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 21 Mar 2024 11:51:15 -0500 Subject: [PATCH 173/455] test: Clarify test case name --- tests/fixtures/no-color/{one_past.svg => ann_removed_nl.svg} | 0 tests/fixtures/no-color/{one_past.toml => ann_removed_nl.toml} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tests/fixtures/no-color/{one_past.svg => ann_removed_nl.svg} (100%) rename tests/fixtures/no-color/{one_past.toml => ann_removed_nl.toml} (100%) diff --git a/tests/fixtures/no-color/one_past.svg b/tests/fixtures/no-color/ann_removed_nl.svg similarity index 100% rename from tests/fixtures/no-color/one_past.svg rename to tests/fixtures/no-color/ann_removed_nl.svg diff --git a/tests/fixtures/no-color/one_past.toml b/tests/fixtures/no-color/ann_removed_nl.toml similarity index 100% rename from tests/fixtures/no-color/one_past.toml rename to tests/fixtures/no-color/ann_removed_nl.toml From 01ca512f66fc38f9fd3ce6f4ad103e97fb91ffd2 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 21 Mar 2024 11:53:17 -0500 Subject: [PATCH 174/455] test: Show existing EOF annotation behavior --- tests/fixtures/no-color/ann_eof.svg | 33 ++++++++++++++++++++++++++++ tests/fixtures/no-color/ann_eof.toml | 12 ++++++++++ 2 files changed, 45 insertions(+) create mode 100644 tests/fixtures/no-color/ann_eof.svg create mode 100644 tests/fixtures/no-color/ann_eof.toml diff --git a/tests/fixtures/no-color/ann_eof.svg b/tests/fixtures/no-color/ann_eof.svg new file mode 100644 index 00000000..ba3b204d --- /dev/null +++ b/tests/fixtures/no-color/ann_eof.svg @@ -0,0 +1,33 @@ +<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg"> + <style> + .fg { fill: #AAAAAA } + .bg { background: #000000 } + .container { + padding: 0 10px; + line-height: 18px; + } + tspan { + font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + white-space: pre; + line-height: 18px; + } + </style> + + <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> + + <text xml:space="preserve" class="container fg"> + <tspan x="10px" y="28px"><tspan>error: expected `.`, `=`</tspan> +</tspan> + <tspan x="10px" y="46px"><tspan> --> Cargo.toml:1:5</tspan> +</tspan> + <tspan x="10px" y="64px"><tspan> |</tspan> +</tspan> + <tspan x="10px" y="82px"><tspan>1 | asdf</tspan> +</tspan> + <tspan x="10px" y="100px"><tspan> | </tspan> +</tspan> + <tspan x="10px" y="118px"><tspan> |</tspan> +</tspan> + </text> + +</svg> diff --git a/tests/fixtures/no-color/ann_eof.toml b/tests/fixtures/no-color/ann_eof.toml new file mode 100644 index 00000000..313d2204 --- /dev/null +++ b/tests/fixtures/no-color/ann_eof.toml @@ -0,0 +1,12 @@ +[message] +level = "Error" +title = "expected `.`, `=`" + +[[message.snippets]] +source = "asdf" +line_start = 1 +origin = "Cargo.toml" +[[message.snippets.annotations]] +label = "" +level = "Error" +range = [4, 4] From 0e820651c901ed2772a842d45d033f1c6ff1c5e9 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 21 Mar 2024 11:54:45 -0500 Subject: [PATCH 175/455] test: Show existing empty span behavior --- tests/fixtures/no-color/ann_insertion.svg | 33 ++++++++++++++++++++++ tests/fixtures/no-color/ann_insertion.toml | 12 ++++++++ 2 files changed, 45 insertions(+) create mode 100644 tests/fixtures/no-color/ann_insertion.svg create mode 100644 tests/fixtures/no-color/ann_insertion.toml diff --git a/tests/fixtures/no-color/ann_insertion.svg b/tests/fixtures/no-color/ann_insertion.svg new file mode 100644 index 00000000..ba3b204d --- /dev/null +++ b/tests/fixtures/no-color/ann_insertion.svg @@ -0,0 +1,33 @@ +<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg"> + <style> + .fg { fill: #AAAAAA } + .bg { background: #000000 } + .container { + padding: 0 10px; + line-height: 18px; + } + tspan { + font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + white-space: pre; + line-height: 18px; + } + </style> + + <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> + + <text xml:space="preserve" class="container fg"> + <tspan x="10px" y="28px"><tspan>error: expected `.`, `=`</tspan> +</tspan> + <tspan x="10px" y="46px"><tspan> --> Cargo.toml:1:5</tspan> +</tspan> + <tspan x="10px" y="64px"><tspan> |</tspan> +</tspan> + <tspan x="10px" y="82px"><tspan>1 | asdf</tspan> +</tspan> + <tspan x="10px" y="100px"><tspan> | </tspan> +</tspan> + <tspan x="10px" y="118px"><tspan> |</tspan> +</tspan> + </text> + +</svg> diff --git a/tests/fixtures/no-color/ann_insertion.toml b/tests/fixtures/no-color/ann_insertion.toml new file mode 100644 index 00000000..ffd2140b --- /dev/null +++ b/tests/fixtures/no-color/ann_insertion.toml @@ -0,0 +1,12 @@ +[message] +level = "Error" +title = "expected `.`, `=`" + +[[message.snippets]] +source = "asf" +line_start = 1 +origin = "Cargo.toml" +[[message.snippets.annotations]] +label = "'d' belongs here" +level = "Error" +range = [2, 2] From 7310fd4c45ed2d92f0ecabb155ba8e5a3bb86bb2 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 21 Mar 2024 11:58:03 -0500 Subject: [PATCH 176/455] fix: Allow empty spans --- src/renderer/display_list.rs | 14 ++++++++------ tests/fixtures/no-color/ann_eof.svg | 2 +- tests/fixtures/no-color/ann_insertion.svg | 6 +++--- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index 53caa38b..4d2836a3 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -1057,6 +1057,7 @@ fn format_body( Range { start, .. } if start > line_end_index => true, Range { start, end } if start >= line_start_index && end <= line_end_index + // Allow annotating eof or stripped eol || start == line_end_index && end - start <= 1 => { if let DisplayLine::Source { @@ -1068,14 +1069,15 @@ fn format_body( .chars() .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) .sum::<usize>(); - // This allows for annotations to be placed one past the - // last character - let safe_end = (end - line_start_index).saturating_sub(line_length); - let annotation_end_col = line[0..(end - line_start_index) - safe_end] + let mut annotation_end_col = line + [0..(end - line_start_index).min(line_length)] .chars() .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) - .sum::<usize>() - + safe_end; + .sum::<usize>(); + if annotation_start_col == annotation_end_col { + // At least highlight something + annotation_end_col += 1; + } span_left_margin = min(span_left_margin, annotation_start_col); span_right_margin = max(span_right_margin, annotation_end_col); diff --git a/tests/fixtures/no-color/ann_eof.svg b/tests/fixtures/no-color/ann_eof.svg index ba3b204d..c8900d03 100644 --- a/tests/fixtures/no-color/ann_eof.svg +++ b/tests/fixtures/no-color/ann_eof.svg @@ -24,7 +24,7 @@ </tspan> <tspan x="10px" y="82px"><tspan>1 | asdf</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> | </tspan> + <tspan x="10px" y="100px"><tspan> | ^</tspan> </tspan> <tspan x="10px" y="118px"><tspan> |</tspan> </tspan> diff --git a/tests/fixtures/no-color/ann_insertion.svg b/tests/fixtures/no-color/ann_insertion.svg index ba3b204d..b15b81b4 100644 --- a/tests/fixtures/no-color/ann_insertion.svg +++ b/tests/fixtures/no-color/ann_insertion.svg @@ -18,13 +18,13 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan>error: expected `.`, `=`</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> --> Cargo.toml:1:5</tspan> + <tspan x="10px" y="46px"><tspan> --> Cargo.toml:1:3</tspan> </tspan> <tspan x="10px" y="64px"><tspan> |</tspan> </tspan> - <tspan x="10px" y="82px"><tspan>1 | asdf</tspan> + <tspan x="10px" y="82px"><tspan>1 | asf</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> | </tspan> + <tspan x="10px" y="100px"><tspan> | ^ 'd' belongs here</tspan> </tspan> <tspan x="10px" y="118px"><tspan> |</tspan> </tspan> From ace6e07683db64f05a237d8c833e2320a951b5d4 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 21 Mar 2024 12:20:47 -0500 Subject: [PATCH 177/455] chore(ci): Don't update stable and MSRV together We might want to hold one or the other back --- .github/renovate.json5 | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 06c1d639..373fc0e5 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -59,10 +59,9 @@ schedule: [ '* * * * *', ], - groupName: 'rust-version', }, { - commitMessageTopic: 'STABLE', + commitMessageTopic: 'Rust Stable', matchManagers: [ 'custom.regex', ], @@ -73,7 +72,6 @@ schedule: [ '* * * * *', ], - groupName: 'rust-version', }, // Goals: // - Keep version reqs low, ignoring compatible normal/build dependencies From de0e330ba2295df8697f73b66feca2107b9da430 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 21 Mar 2024 12:22:05 -0500 Subject: [PATCH 178/455] test(render): Clarify a test name --- .../fixtures/no-color/{issue_52.svg => fold_bad_origin_line.svg} | 0 .../no-color/{issue_52.toml => fold_bad_origin_line.toml} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tests/fixtures/no-color/{issue_52.svg => fold_bad_origin_line.svg} (100%) rename tests/fixtures/no-color/{issue_52.toml => fold_bad_origin_line.toml} (100%) diff --git a/tests/fixtures/no-color/issue_52.svg b/tests/fixtures/no-color/fold_bad_origin_line.svg similarity index 100% rename from tests/fixtures/no-color/issue_52.svg rename to tests/fixtures/no-color/fold_bad_origin_line.svg diff --git a/tests/fixtures/no-color/issue_52.toml b/tests/fixtures/no-color/fold_bad_origin_line.toml similarity index 100% rename from tests/fixtures/no-color/issue_52.toml rename to tests/fixtures/no-color/fold_bad_origin_line.toml From 22e6c1c5f1363304324e03319c2270110b5ac6fb Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 21 Mar 2024 12:27:20 -0500 Subject: [PATCH 179/455] test(render): Clarify multiline cases As `fold` is special machinery, I made that test under that. The others, I couldn't tell what different cases they were covering, so I kept the names. I did name them with the `ann` prefix to highlight these are testing annotations. --- .../no-color/{multiline_annotation2.svg => ann_multiline.svg} | 0 .../no-color/{multiline_annotation2.toml => ann_multiline.toml} | 0 .../no-color/{multiline_annotation3.svg => ann_multiline2.svg} | 0 .../no-color/{multiline_annotation3.toml => ann_multiline2.toml} | 0 .../no-color/{multiline_annotation.svg => fold_ann_multiline.svg} | 0 .../{multiline_annotation.toml => fold_ann_multiline.toml} | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename tests/fixtures/no-color/{multiline_annotation2.svg => ann_multiline.svg} (100%) rename tests/fixtures/no-color/{multiline_annotation2.toml => ann_multiline.toml} (100%) rename tests/fixtures/no-color/{multiline_annotation3.svg => ann_multiline2.svg} (100%) rename tests/fixtures/no-color/{multiline_annotation3.toml => ann_multiline2.toml} (100%) rename tests/fixtures/no-color/{multiline_annotation.svg => fold_ann_multiline.svg} (100%) rename tests/fixtures/no-color/{multiline_annotation.toml => fold_ann_multiline.toml} (100%) diff --git a/tests/fixtures/no-color/multiline_annotation2.svg b/tests/fixtures/no-color/ann_multiline.svg similarity index 100% rename from tests/fixtures/no-color/multiline_annotation2.svg rename to tests/fixtures/no-color/ann_multiline.svg diff --git a/tests/fixtures/no-color/multiline_annotation2.toml b/tests/fixtures/no-color/ann_multiline.toml similarity index 100% rename from tests/fixtures/no-color/multiline_annotation2.toml rename to tests/fixtures/no-color/ann_multiline.toml diff --git a/tests/fixtures/no-color/multiline_annotation3.svg b/tests/fixtures/no-color/ann_multiline2.svg similarity index 100% rename from tests/fixtures/no-color/multiline_annotation3.svg rename to tests/fixtures/no-color/ann_multiline2.svg diff --git a/tests/fixtures/no-color/multiline_annotation3.toml b/tests/fixtures/no-color/ann_multiline2.toml similarity index 100% rename from tests/fixtures/no-color/multiline_annotation3.toml rename to tests/fixtures/no-color/ann_multiline2.toml diff --git a/tests/fixtures/no-color/multiline_annotation.svg b/tests/fixtures/no-color/fold_ann_multiline.svg similarity index 100% rename from tests/fixtures/no-color/multiline_annotation.svg rename to tests/fixtures/no-color/fold_ann_multiline.svg diff --git a/tests/fixtures/no-color/multiline_annotation.toml b/tests/fixtures/no-color/fold_ann_multiline.toml similarity index 100% rename from tests/fixtures/no-color/multiline_annotation.toml rename to tests/fixtures/no-color/fold_ann_multiline.toml From a741b6bd90662651084067afa936499196381d9a Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 21 Mar 2024 12:46:37 -0500 Subject: [PATCH 180/455] test(render): Show existing leading / trailing behavior --- tests/fixtures/no-color/fold_leading.svg | 45 ++++++++++++++++++++++ tests/fixtures/no-color/fold_leading.toml | 26 +++++++++++++ tests/fixtures/no-color/fold_trailing.svg | 33 ++++++++++++++++ tests/fixtures/no-color/fold_trailing.toml | 25 ++++++++++++ 4 files changed, 129 insertions(+) create mode 100644 tests/fixtures/no-color/fold_leading.svg create mode 100644 tests/fixtures/no-color/fold_leading.toml create mode 100644 tests/fixtures/no-color/fold_trailing.svg create mode 100644 tests/fixtures/no-color/fold_trailing.toml diff --git a/tests/fixtures/no-color/fold_leading.svg b/tests/fixtures/no-color/fold_leading.svg new file mode 100644 index 00000000..c85f9d7a --- /dev/null +++ b/tests/fixtures/no-color/fold_leading.svg @@ -0,0 +1,45 @@ +<svg width="740px" height="236px" xmlns="http://www.w3.org/2000/svg"> + <style> + .fg { fill: #AAAAAA } + .bg { background: #000000 } + .container { + padding: 0 10px; + line-height: 18px; + } + tspan { + font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + white-space: pre; + line-height: 18px; + } + </style> + + <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> + + <text xml:space="preserve" class="container fg"> + <tspan x="10px" y="28px"><tspan>error[E0308]: invalid type: integer `20`, expected a bool</tspan> +</tspan> + <tspan x="10px" y="46px"><tspan> --> Cargo.toml:11:13</tspan> +</tspan> + <tspan x="10px" y="64px"><tspan> |</tspan> +</tspan> + <tspan x="10px" y="82px"><tspan> 1 | [workspace]</tspan> +</tspan> + <tspan x="10px" y="100px"><tspan> 2 | </tspan> +</tspan> + <tspan x="10px" y="118px"><tspan> 3 | [package]</tspan> +</tspan> + <tspan x="10px" y="136px"><tspan> 4 | name = "hello"</tspan> +</tspan> + <tspan x="10px" y="154px"><tspan>...</tspan> +</tspan> + <tspan x="10px" y="172px"><tspan>10 | [lints]</tspan> +</tspan> + <tspan x="10px" y="190px"><tspan>11 | workspace = 20</tspan> +</tspan> + <tspan x="10px" y="208px"><tspan> | ^^</tspan> +</tspan> + <tspan x="10px" y="226px"><tspan> |</tspan> +</tspan> + </text> + +</svg> diff --git a/tests/fixtures/no-color/fold_leading.toml b/tests/fixtures/no-color/fold_leading.toml new file mode 100644 index 00000000..e3fc696a --- /dev/null +++ b/tests/fixtures/no-color/fold_leading.toml @@ -0,0 +1,26 @@ +[message] +level = "Error" +id = "E0308" +title = "invalid type: integer `20`, expected a bool" + +[[message.snippets]] +source = """ +[workspace] + +[package] +name = "hello" +version = "1.0.0" +license = "MIT" +rust-version = "1.70" +edition = "2021" + +[lints] +workspace = 20 +""" +line_start = 1 +origin = "Cargo.toml" +fold = true +[[message.snippets.annotations]] +label = "" +level = "Error" +range = [132, 134] diff --git a/tests/fixtures/no-color/fold_trailing.svg b/tests/fixtures/no-color/fold_trailing.svg new file mode 100644 index 00000000..15c98502 --- /dev/null +++ b/tests/fixtures/no-color/fold_trailing.svg @@ -0,0 +1,33 @@ +<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg"> + <style> + .fg { fill: #AAAAAA } + .bg { background: #000000 } + .container { + padding: 0 10px; + line-height: 18px; + } + tspan { + font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + white-space: pre; + line-height: 18px; + } + </style> + + <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> + + <text xml:space="preserve" class="container fg"> + <tspan x="10px" y="28px"><tspan>error[E0308]: invalid type: integer `20`, expected a lints table</tspan> +</tspan> + <tspan x="10px" y="46px"><tspan> --> Cargo.toml:1:9</tspan> +</tspan> + <tspan x="10px" y="64px"><tspan> |</tspan> +</tspan> + <tspan x="10px" y="82px"><tspan>1 | lints = 20</tspan> +</tspan> + <tspan x="10px" y="100px"><tspan> | ^^</tspan> +</tspan> + <tspan x="10px" y="118px"><tspan> |</tspan> +</tspan> + </text> + +</svg> diff --git a/tests/fixtures/no-color/fold_trailing.toml b/tests/fixtures/no-color/fold_trailing.toml new file mode 100644 index 00000000..8ee4c051 --- /dev/null +++ b/tests/fixtures/no-color/fold_trailing.toml @@ -0,0 +1,25 @@ +[message] +level = "Error" +id = "E0308" +title = "invalid type: integer `20`, expected a lints table" + +[[message.snippets]] +source = """ +lints = 20 + +[workspace] + +[package] +name = "hello" +version = "1.0.0" +license = "MIT" +rust-version = "1.70" +edition = "2021" +""" +line_start = 1 +origin = "Cargo.toml" +fold = true +[[message.snippets.annotations]] +label = "" +level = "Error" +range = [8, 10] From 894f734291bb499267d53ec34d8402fea7f18c84 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 21 Mar 2024 13:05:21 -0500 Subject: [PATCH 181/455] fix(render): On fold, strip prefix/suffix rather than fold As we don't show `...` on non-first lines without fold, it seems to make sense to do the same thing for fold. To show this, we likely would want to add more nuance than just `fold`. This has the benefit of offering a "magic mode" where the annotated lines get selected automatically. This was implemented by pre-processing things which should keep overhead low as other calculations down the line aren't done. --- src/renderer/display_list.rs | 55 +++++++++++++++++-- .../no-color/fold_bad_origin_line.svg | 10 ++-- tests/fixtures/no-color/fold_leading.svg | 20 ++----- tests/formatter.rs | 1 - 4 files changed, 57 insertions(+), 29 deletions(-) diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index 4d2836a3..fa8274c5 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -696,17 +696,19 @@ impl<'a> Iterator for CursorLines<'a> { } fn format_message( - snippet::Message { + message: snippet::Message<'_>, + term_width: usize, + anonymized_line_numbers: bool, + primary: bool, +) -> Vec<DisplaySet<'_>> { + let snippet::Message { level, id, title, footer, snippets, - }: snippet::Message<'_>, - term_width: usize, - anonymized_line_numbers: bool, - primary: bool, -) -> Vec<DisplaySet<'_>> { + } = message; + let mut sets = vec![]; let body = if !snippets.is_empty() || primary { vec![format_title(level, id, title)] @@ -715,6 +717,7 @@ fn format_message( }; for (idx, snippet) in snippets.into_iter().enumerate() { + let snippet = fold_prefix_suffix(snippet); sets.push(format_snippet( snippet, idx == 0, @@ -876,6 +879,46 @@ fn format_header<'a>( None } +fn fold_prefix_suffix(mut snippet: snippet::Snippet<'_>) -> snippet::Snippet<'_> { + if !snippet.fold { + return snippet; + } + + let ann_start = snippet + .annotations + .iter() + .map(|ann| ann.range.start) + .min() + .unwrap_or(0); + if let Some(before_new_start) = snippet.source[0..ann_start].rfind('\n') { + let new_start = before_new_start + 1; + + let line_offset = snippet.source[..new_start].lines().count(); + snippet.line_start += line_offset; + + snippet.source = &snippet.source[new_start..]; + + for ann in &mut snippet.annotations { + let range_start = ann.range.start - new_start; + let range_end = ann.range.end - new_start; + ann.range = range_start..range_end; + } + } + + let ann_end = snippet + .annotations + .iter() + .map(|ann| ann.range.end) + .max() + .unwrap_or(snippet.source.len()); + if let Some(end_offset) = snippet.source[ann_end..].find('\n') { + let new_end = ann_end + end_offset; + snippet.source = &snippet.source[..new_end]; + } + + snippet +} + fn fold_body(mut body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> { enum Line { Fold(usize), diff --git a/tests/fixtures/no-color/fold_bad_origin_line.svg b/tests/fixtures/no-color/fold_bad_origin_line.svg index 5ed22289..13a08344 100644 --- a/tests/fixtures/no-color/fold_bad_origin_line.svg +++ b/tests/fixtures/no-color/fold_bad_origin_line.svg @@ -1,4 +1,4 @@ -<svg width="740px" height="146px" xmlns="http://www.w3.org/2000/svg"> +<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -22,13 +22,11 @@ </tspan> <tspan x="10px" y="64px"><tspan> |</tspan> </tspan> - <tspan x="10px" y="82px"><tspan>...</tspan> + <tspan x="10px" y="82px"><tspan>3 | invalid syntax</tspan> </tspan> - <tspan x="10px" y="100px"><tspan>3 | invalid syntax</tspan> + <tspan x="10px" y="100px"><tspan> | -------------- error here</tspan> </tspan> - <tspan x="10px" y="118px"><tspan> | -------------- error here</tspan> -</tspan> - <tspan x="10px" y="136px"><tspan> |</tspan> + <tspan x="10px" y="118px"><tspan> |</tspan> </tspan> </text> diff --git a/tests/fixtures/no-color/fold_leading.svg b/tests/fixtures/no-color/fold_leading.svg index c85f9d7a..72887a28 100644 --- a/tests/fixtures/no-color/fold_leading.svg +++ b/tests/fixtures/no-color/fold_leading.svg @@ -1,4 +1,4 @@ -<svg width="740px" height="236px" xmlns="http://www.w3.org/2000/svg"> +<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -22,23 +22,11 @@ </tspan> <tspan x="10px" y="64px"><tspan> |</tspan> </tspan> - <tspan x="10px" y="82px"><tspan> 1 | [workspace]</tspan> + <tspan x="10px" y="82px"><tspan>11 | workspace = 20</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> 2 | </tspan> + <tspan x="10px" y="100px"><tspan> | ^^</tspan> </tspan> - <tspan x="10px" y="118px"><tspan> 3 | [package]</tspan> -</tspan> - <tspan x="10px" y="136px"><tspan> 4 | name = "hello"</tspan> -</tspan> - <tspan x="10px" y="154px"><tspan>...</tspan> -</tspan> - <tspan x="10px" y="172px"><tspan>10 | [lints]</tspan> -</tspan> - <tspan x="10px" y="190px"><tspan>11 | workspace = 20</tspan> -</tspan> - <tspan x="10px" y="208px"><tspan> | ^^</tspan> -</tspan> - <tspan x="10px" y="226px"><tspan> |</tspan> + <tspan x="10px" y="118px"><tspan> |</tspan> </tspan> </text> diff --git a/tests/formatter.rs b/tests/formatter.rs index 3b024896..ebe03ff6 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -14,7 +14,6 @@ fn test_i_29() { error: oops --> <current file>:2:8 | -1 | First line 2 | Second oops line | ^^^^ oops |"#]] From 6e55eccc4ab63394b3221c4c51fe09cfdc8a330f Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 21 Mar 2024 14:41:10 -0500 Subject: [PATCH 182/455] fix(render): Fold like rustc Before, we elided all small sets of unhighlighted lines and then partially elided large sets of unhighlighted lines (favoring the start). In contrast, rustc shows 2 lines of context, split between the start and end of the fold. If there are 3 unhighlighted lines, it just skips folding. This makes a lot more sense. See https://github.com/rust-lang/rust/blob/2627e9f3012a97d3136b3e11bf6bd0853c38a534/compiler/rustc_errors/src/emitter.rs#L1878C17-L1939C18 In contrast to rustc, I made the amount of context a variable to make the intent clearer and to open the door to it being configurable. --- examples/expected_type.svg | 14 +- src/renderer/display_list.rs | 130 ++++++------------ .../fixtures/no-color/fold_ann_multiline.svg | 18 +-- 3 files changed, 59 insertions(+), 103 deletions(-) diff --git a/examples/expected_type.svg b/examples/expected_type.svg index ae065047..a355dbcc 100644 --- a/examples/expected_type.svg +++ b/examples/expected_type.svg @@ -1,4 +1,4 @@ -<svg width="860px" height="200px" xmlns="http://www.w3.org/2000/svg"> +<svg width="860px" height="218px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -29,15 +29,17 @@ </tspan> <tspan x="10px" y="100px"><tspan class="fg-bright-blue bold"> |</tspan><tspan class="fg-bright-blue bold"> ----------------</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">info</tspan><tspan class="fg-bright-blue bold">: while parsing this struct</tspan> </tspan> - <tspan x="10px" y="118px"><tspan>...</tspan> + <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">27 |</tspan><tspan> label: "expected struct `annotate_snippets::snippet::Slice`, found reference"</tspan> </tspan> - <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">29 |</tspan><tspan> range: <22, 25>,</tspan> + <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">28 |</tspan><tspan> ,</tspan> </tspan> - <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold"> |</tspan><tspan class="fg-bright-red bold"> ^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan> + <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">29 |</tspan><tspan> range: <22, 25>,</tspan> </tspan> - <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold"> |</tspan> + <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold"> |</tspan><tspan class="fg-bright-red bold"> ^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan> </tspan> - <tspan x="10px" y="190px"> + <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold"> |</tspan> +</tspan> + <tspan x="10px" y="208px"> </tspan> </text> diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index fa8274c5..ead3db9a 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -919,105 +919,65 @@ fn fold_prefix_suffix(mut snippet: snippet::Snippet<'_>) -> snippet::Snippet<'_> snippet } -fn fold_body(mut body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> { - enum Line { - Fold(usize), - Source(usize), - } +fn fold_body(body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> { + const INNER_CONTEXT: usize = 1; + const INNER_UNFOLD_SIZE: usize = INNER_CONTEXT * 2 + 1; let mut lines = vec![]; - let mut no_annotation_lines_counter = 0; - for (idx, line) in body.iter().enumerate() { - match line { - DisplayLine::Source { - line: DisplaySourceLine::Content { .. }, - annotations, - .. - } => { + let mut unhighlighed_lines = vec![]; + for line in body { + match &line { + DisplayLine::Source { annotations, .. } => { if annotations.is_empty() { - no_annotation_lines_counter += 1; - continue; + unhighlighed_lines.push(line); } else { - let fold_start = idx - no_annotation_lines_counter; - if no_annotation_lines_counter >= 2 { - let fold_end = idx; - let pre_len = if no_annotation_lines_counter > 8 { - 4 - } else { - 0 - }; - let post_len = if no_annotation_lines_counter > 8 { - 2 - } else { - 1 - }; - for (i, _) in body - .iter() - .enumerate() - .take(fold_start + pre_len) - .skip(fold_start) - { - lines.push(Line::Source(i)); - } - lines.push(Line::Fold(idx)); - for (i, _) in body - .iter() - .enumerate() - .take(fold_end) - .skip(fold_end + 1 - post_len) - { - lines.push(Line::Source(i)); + if lines.is_empty() { + // Ignore leading unhighlighed lines + unhighlighed_lines.clear(); + } + match unhighlighed_lines.len() { + 0 => {} + n if n <= INNER_UNFOLD_SIZE => { + // Rather than render `...`, don't fold + lines.append(&mut unhighlighed_lines); } - } else { - for (i, _) in body.iter().enumerate().take(idx).skip(fold_start) { - lines.push(Line::Source(i)); + _ => { + lines.extend(unhighlighed_lines.drain(..INNER_CONTEXT)); + let inline_marks = lines + .last() + .and_then(|line| { + if let DisplayLine::Source { + ref inline_marks, .. + } = line + { + let mut inline_marks = inline_marks.clone(); + for mark in &mut inline_marks { + mark.mark_type = DisplayMarkType::AnnotationThrough; + } + Some(inline_marks) + } else { + None + } + }) + .unwrap_or_default(); + lines.push(DisplayLine::Fold { + inline_marks: inline_marks.clone(), + }); + unhighlighed_lines + .drain(..unhighlighed_lines.len().saturating_sub(INNER_CONTEXT)); + lines.append(&mut unhighlighed_lines); } } - no_annotation_lines_counter = 0; + lines.push(line); } } - DisplayLine::Source { .. } => { - no_annotation_lines_counter += 1; - continue; - } _ => { - no_annotation_lines_counter += 1; - } - } - lines.push(Line::Source(idx)); - } - - let mut new_body = vec![]; - let mut removed = 0; - for line in lines { - match line { - Line::Source(i) => { - new_body.push(body.remove(i - removed)); - removed += 1; - } - Line::Fold(i) => { - if let DisplayLine::Source { - line: DisplaySourceLine::Content { .. }, - ref inline_marks, - ref annotations, - .. - } = body.get(i - removed).unwrap() - { - if !annotations.is_empty() { - new_body.push(DisplayLine::Fold { - inline_marks: inline_marks.clone(), - }); - } else { - unreachable!() - } - } else { - unreachable!() - } + unhighlighed_lines.push(line); } } } - new_body + lines } fn format_body( diff --git a/tests/fixtures/no-color/fold_ann_multiline.svg b/tests/fixtures/no-color/fold_ann_multiline.svg index 5fa6e81a..0d2d67cd 100644 --- a/tests/fixtures/no-color/fold_ann_multiline.svg +++ b/tests/fixtures/no-color/fold_ann_multiline.svg @@ -1,4 +1,4 @@ -<svg width="869px" height="272px" xmlns="http://www.w3.org/2000/svg"> +<svg width="869px" height="218px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -28,21 +28,15 @@ </tspan> <tspan x="10px" y="118px"><tspan>52 | / for ann in annotations {</tspan> </tspan> - <tspan x="10px" y="136px"><tspan>53 | | match (ann.range.0, ann.range.1) {</tspan> + <tspan x="10px" y="136px"><tspan>... |</tspan> </tspan> - <tspan x="10px" y="154px"><tspan>54 | | (None, None) => continue,</tspan> + <tspan x="10px" y="154px"><tspan>71 | | }</tspan> </tspan> - <tspan x="10px" y="172px"><tspan>55 | | (Some(start), Some(end)) if start > end_index || end < start_index => continue,</tspan> + <tspan x="10px" y="172px"><tspan>72 | | }</tspan> </tspan> - <tspan x="10px" y="190px"><tspan>... |</tspan> + <tspan x="10px" y="190px"><tspan> | |_____^ expected enum `std::option::Option`, found ()</tspan> </tspan> - <tspan x="10px" y="208px"><tspan>71 | | }</tspan> -</tspan> - <tspan x="10px" y="226px"><tspan>72 | | }</tspan> -</tspan> - <tspan x="10px" y="244px"><tspan> | |_____^ expected enum `std::option::Option`, found ()</tspan> -</tspan> - <tspan x="10px" y="262px"><tspan> |</tspan> + <tspan x="10px" y="208px"><tspan> |</tspan> </tspan> </text> From a2c27b5b4b5c108c07c6e252afeb011f1d8b3e17 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 21 Mar 2024 15:36:16 -0500 Subject: [PATCH 183/455] docs: Update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad3df890..c91e63d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/). <!-- next-header --> ## [Unreleased] - ReleaseDate +### Fixes + +- Switch `fold` to use rustc's logic: always show first and last line of folded section and detect if its worth folding +- When `fold`ing the start of a `source`, don't show anything, like we do for the end of the `source` +- Render an underline for an empty span on `Annotation`s + ## [0.11.0] - 2024-03-15 ### Breaking Changes From 54a4d181680f17b3d1b3ad61e95e66cf41748563 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 21 Mar 2024 14:44:01 -0600 Subject: [PATCH 184/455] chore: Release annotate-snippets version 0.11.1 --- CHANGELOG.md | 5 ++++- Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c91e63d7..85f967e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). <!-- next-header --> ## [Unreleased] - ReleaseDate +## [0.11.1] - 2024-03-21 + ### Fixes - Switch `fold` to use rustc's logic: always show first and last line of folded section and detect if its worth folding @@ -124,7 +126,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Update the syntax to Rust 2018 idioms. (#4) <!-- next-url --> -[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.0...HEAD +[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.1...HEAD +[0.11.1]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.0...0.11.1 [0.11.0]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.2...0.11.0 [0.10.2]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.1...0.10.2 [0.10.1]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.0...0.10.1 diff --git a/Cargo.lock b/Cargo.lock index c18b289f..479ee8cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,7 +19,7 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "annotate-snippets" -version = "0.11.0" +version = "0.11.1" dependencies = [ "anstream", "anstyle", diff --git a/Cargo.toml b/Cargo.toml index 17b66000..a9a7c284 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "annotate-snippets" -version = "0.11.0" +version = "0.11.1" edition = "2021" rust-version = "1.73" # MSRV authors = ["Zibi Braniecki <gandalf@mozilla.com>"] From 8ec86ab9a22aa7333af26113d8b725333966635f Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 25 Mar 2024 12:33:58 -0500 Subject: [PATCH 185/455] chore: Normalize clippy lint names --- Cargo.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e51c5ddd..d03936f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,12 +20,12 @@ include = [ missing_docs = "warn" rust_2018_idioms = "warn" unreachable_pub = "warn" -unsafe-op-in-unsafe-fn = "warn" +unsafe_op_in_unsafe_fn = "warn" unsafe_code = "warn" -unused-crate-dependencies = "warn" -unused-lifetimes = "warn" -unused-macro-rules = "warn" -unused-qualifications = "warn" +unused_crate_dependencies = "warn" +unused_lifetimes = "warn" +unused_macro_rules = "warn" +unused_qualifications = "warn" [workspace.lints.clippy] bool_assert_comparison = "allow" From 8e647d9cd40a6891d524737d97d93a43a9e7b965 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 28 Mar 2024 09:35:59 -0500 Subject: [PATCH 186/455] chore: Encourage use of workspace.dependencies --- Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index d03936f4..e07a4738 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,8 @@ include = [ "examples/**/*" ] +[workspace.dependencies] + [workspace.lints.rust] missing_docs = "warn" rust_2018_idioms = "warn" From 126eb3d4dc4f59bcbee11d9d55545f01f75fb734 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 28 Mar 2024 09:48:35 -0500 Subject: [PATCH 187/455] chore: Encourage a single test binary --- Cargo.lock | 49 ++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 ++ tests/testsuite/delete_me.rs | 0 tests/testsuite/main.rs | 1 + 4 files changed, 52 insertions(+) create mode 100644 tests/testsuite/delete_me.rs create mode 100644 tests/testsuite/main.rs diff --git a/Cargo.lock b/Cargo.lock index 49c1f2dc..e50f2865 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5,3 +5,52 @@ version = 3 [[package]] name = "PROJECT" version = "0.0.1" +dependencies = [ + "automod", +] + +[[package]] +name = "automod" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edf3ee19dbc0a46d740f6f0926bde8c50f02bdbc7b536842da28f6ac56513a8b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/Cargo.toml b/Cargo.toml index e07a4738..d5e3b014 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ include = [ ] [workspace.dependencies] +automod = "1.0.14" [workspace.lints.rust] missing_docs = "warn" @@ -129,6 +130,7 @@ default = [] [dependencies] [dev-dependencies] +automod.workspace = true [lints] workspace = true diff --git a/tests/testsuite/delete_me.rs b/tests/testsuite/delete_me.rs new file mode 100644 index 00000000..e69de29b diff --git a/tests/testsuite/main.rs b/tests/testsuite/main.rs new file mode 100644 index 00000000..44413747 --- /dev/null +++ b/tests/testsuite/main.rs @@ -0,0 +1 @@ +automod::dir!("tests/testsuite"); From c8b190be3a7397d63ffb175f8387ef98e7896b5a Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 28 Mar 2024 09:50:22 -0500 Subject: [PATCH 188/455] chore(ci): Use latest SARIF Now that we run clippy on stable, we can do this --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 12d398cd..52ce7f22 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -128,9 +128,9 @@ jobs: components: clippy - uses: Swatinem/rust-cache@v2 - name: Install SARIF tools - run: cargo install clippy-sarif --version 0.3.4 --locked # Held back due to msrv + run: cargo install clippy-sarif --locked - name: Install SARIF tools - run: cargo install sarif-fmt --version 0.3.4 --locked # Held back due to msrv + run: cargo install sarif-fmt --locked - name: Check run: > cargo clippy --workspace --all-features --all-targets --message-format=json -- -D warnings --allow deprecated From 9b1b56620156971664aaf0f7a693bf3bc72ca0cb Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 28 Mar 2024 09:51:51 -0500 Subject: [PATCH 189/455] chore(ci): Fix all rust-version-specific checks to stable --- .github/workflows/ci.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 52ce7f22..42c6be7e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -91,7 +91,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: stable + toolchain: "1.76" # STABLE - uses: Swatinem/rust-cache@v2 - name: Check documentation env: @@ -106,9 +106,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - # Not MSRV because its harder to jump between versions and people are - # more likely to have stable - toolchain: stable + toolchain: "1.76" # STABLE components: rustfmt - uses: Swatinem/rust-cache@v2 - name: Check formatting From 92d486c4b03efa984a9d03aa7279a1febe84d816 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 28 Mar 2024 09:53:02 -0500 Subject: [PATCH 190/455] chore(ci): Speed up lockfile check --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 42c6be7e..7a455a4d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,7 +81,7 @@ jobs: toolchain: stable - uses: Swatinem/rust-cache@v2 - name: "Is lockfile updated?" - run: cargo fetch --locked + run: cargo update --workspace --locked docs: name: Docs runs-on: ubuntu-latest From 9258d9af7b87bc0394ef09be7e65bf6152d99f4b Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 28 Mar 2024 09:57:23 -0500 Subject: [PATCH 191/455] chore(ci): More exhaustively check features --- .github/workflows/ci.yml | 15 ++++----------- .github/workflows/rust-next.yml | 18 ++++++------------ 2 files changed, 10 insertions(+), 23 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7a455a4d..134c3179 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,14 +44,11 @@ jobs: with: toolchain: ${{ matrix.rust }} - uses: Swatinem/rust-cache@v2 + - uses: taiki-e/install-action@cargo-hack - name: Build run: cargo test --workspace --no-run - - name: Default features - run: cargo test --workspace - - name: All features - run: cargo test --workspace --all-features - - name: No-default features - run: cargo test --workspace --no-default-features + - name: Test + run: cargo hack test --feature-powerset --workspace msrv: name: "Check MSRV" runs-on: ubuntu-latest @@ -65,11 +62,7 @@ jobs: - uses: Swatinem/rust-cache@v2 - uses: taiki-e/install-action@cargo-hack - name: Default features - run: cargo hack check --locked --rust-version --ignore-private --workspace --all-targets - - name: All features - run: cargo hack check --locked --rust-version --ignore-private --workspace --all-targets --all-features - - name: No-default features - run: cargo hack check --locked --rust-version --ignore-private --workspace --all-targets --no-default-features + run: cargo hack check --feature-powerset --locked --rust-version --ignore-private --workspace --all-targets lockfile: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/rust-next.yml b/.github/workflows/rust-next.yml index 49e5d8c3..e673b652 100644 --- a/.github/workflows/rust-next.yml +++ b/.github/workflows/rust-next.yml @@ -36,14 +36,11 @@ jobs: with: toolchain: ${{ matrix.rust }} - uses: Swatinem/rust-cache@v2 + - uses: taiki-e/install-action@cargo-hack - name: Build run: cargo test --workspace --no-run - - name: Default features - run: cargo test --workspace - - name: All features - run: cargo test --workspace --all-features - - name: No-default features - run: cargo test --workspace --no-default-features + - name: Test + run: cargo hack test --feature-powerset --workspace latest: name: "Check latest dependencies" runs-on: ubuntu-latest @@ -55,13 +52,10 @@ jobs: with: toolchain: stable - uses: Swatinem/rust-cache@v2 + - uses: taiki-e/install-action@cargo-hack - name: Update dependencues run: cargo update - name: Build run: cargo test --workspace --no-run - - name: Default features - run: cargo test --workspace - - name: All features - run: cargo test --workspace --all-features - - name: No-default features - run: cargo test --workspace --no-default-features + - name: Test + run: cargo hack test --feature-powerset --workspace From 2714cca7c31a9c73716e88a93693c119c527d7f1 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 28 Mar 2024 09:58:31 -0500 Subject: [PATCH 192/455] chore(ci): Don't check for unused crates --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d5e3b014..4097fef0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,6 @@ rust_2018_idioms = "warn" unreachable_pub = "warn" unsafe_op_in_unsafe_fn = "warn" unsafe_code = "warn" -unused_crate_dependencies = "warn" unused_lifetimes = "warn" unused_macro_rules = "warn" unused_qualifications = "warn" From 314eef7f5fb7e415e8cd92887e5e878e9bfa929b Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 28 Mar 2024 10:38:57 -0500 Subject: [PATCH 193/455] chore: Dont check must_use See https://github.com/rust-lang/rust-clippy/issues/8339 --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 4097fef0..666e6e39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,7 +81,6 @@ rc_mutex = "warn" redundant_feature_names = "warn" ref_option_ref = "warn" rest_pat_in_fully_bound_structs = "warn" -return_self_not_must_use = "warn" same_functions_in_if_condition = "warn" self_named_module_files = "warn" semicolon_if_nothing_returned = "warn" From 6a9d2bf50fa78d8f277b77be836b48fad8c7c764 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 28 Mar 2024 12:45:57 -0500 Subject: [PATCH 194/455] chore: Don't warn on unsafe This works well when a package is a safe abstraction but to universally apply in a template to all members of a workspace doesn't make sense. --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 666e6e39..50bfea56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,6 @@ missing_docs = "warn" rust_2018_idioms = "warn" unreachable_pub = "warn" unsafe_op_in_unsafe_fn = "warn" -unsafe_code = "warn" unused_lifetimes = "warn" unused_macro_rules = "warn" unused_qualifications = "warn" From 8d4b1b6c8daf3c32828bf92725811cf433917081 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 28 Mar 2024 13:24:28 -0500 Subject: [PATCH 195/455] chore: Remove clippy::tests_outside_test_module See https://github.com/rust-lang/rust-clippy/issues/11024 --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 50bfea56..ed593eba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,7 +89,6 @@ string_add = "warn" string_add_assign = "warn" string_lit_as_bytes = "warn" string_to_string = "warn" -tests_outside_test_module = "warn" todo = "warn" trait_duplication_in_bounds = "warn" unwrap_used = "warn" From 99e034bbbbae7d60bb68d68c6d0db8338a97b030 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 28 Mar 2024 15:10:58 -0500 Subject: [PATCH 196/455] chore: Move print lints to lib.rs While there is a config for ignoring these in tests, it doesn't help with examples. --- Cargo.toml | 2 -- src/lib.rs | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ed593eba..faf1e63a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,8 +73,6 @@ needless_continue = "warn" needless_for_each = "warn" negative_feature_names = "warn" path_buf_push_overwrite = "warn" -print_stderr = "warn" -print_stdout = "warn" ptr_as_ptr = "warn" rc_mutex = "warn" redundant_feature_names = "warn" diff --git a/src/lib.rs b/src/lib.rs index 8ce46b57..2eabbd09 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,6 @@ //! > DESCRIPTION #![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![warn(clippy::print_stderr)] +#![warn(clippy::print_stdout)] #![allow(non_snake_case)] // TODO: Delete me From a516bda4adb0f367da527488697ea308fbe58b38 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 1 Apr 2024 08:58:36 -0500 Subject: [PATCH 197/455] chore: Drop workspace.dependencies Without automated checks, this will make it harder to track breaking changes. --- Cargo.toml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index faf1e63a..983e0905 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,9 +16,6 @@ include = [ "examples/**/*" ] -[workspace.dependencies] -automod = "1.0.14" - [workspace.lints.rust] missing_docs = "warn" rust_2018_idioms = "warn" @@ -124,7 +121,7 @@ default = [] [dependencies] [dev-dependencies] -automod.workspace = true +automod = "1.0.14" [lints] workspace = true From ebc70d00f9259146592b7987bfcb8a0cb6c16661 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 1 Apr 2024 09:11:24 -0500 Subject: [PATCH 198/455] chore: Only check missing_docs in lib This also fires in examples and other places. While docs in examples would be nice, it isn't universally applicable and `allow`s would undermine the examples. --- Cargo.toml | 1 - src/lib.rs | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 983e0905..715131bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,6 @@ include = [ ] [workspace.lints.rust] -missing_docs = "warn" rust_2018_idioms = "warn" unreachable_pub = "warn" unsafe_op_in_unsafe_fn = "warn" diff --git a/src/lib.rs b/src/lib.rs index 2eabbd09..39877b76 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ //! > DESCRIPTION #![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![warn(missing_docs)] #![warn(clippy::print_stderr)] #![warn(clippy::print_stdout)] #![allow(non_snake_case)] // TODO: Delete me From 3278d49444c33ece610de3fb5547bd885124dfe7 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 1 Apr 2024 10:35:16 -0500 Subject: [PATCH 199/455] chore: Allow print in tests --- .clippy.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/.clippy.toml b/.clippy.toml index 293c14f3..027eef41 100644 --- a/.clippy.toml +++ b/.clippy.toml @@ -1,4 +1,5 @@ warn-on-all-wildcard-imports = true +allow-print-in-tests = true allow-expect-in-tests = true allow-unwrap-in-tests = true allow-dbg-in-tests = true From d634de649f30d5a4deade46333606bc63897d05e Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 1 Apr 2024 11:36:58 -0500 Subject: [PATCH 200/455] chore(ci): Ensure CI job always runs --- .github/workflows/ci.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 134c3179..95b13b4f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,11 +23,13 @@ jobs: permissions: contents: none name: CI - needs: [test, msrv, docs, rustfmt, clippy] + needs: [test, msrv, lockfile, docs, rustfmt, clippy] runs-on: ubuntu-latest + if: "always()" steps: - - name: Done - run: exit 0 + - name: Failed + run: exit 1 + if: "contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped')" test: name: Test strategy: From 2570b58a0feaf355dede9080a9f4c98f8ba5580b Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 1 Apr 2024 12:31:49 -0500 Subject: [PATCH 201/455] chore(ci): Skip branch protections --- .github/settings.yml | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/.github/settings.yml b/.github/settings.yml index 7d5e4fce..08983ae0 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -42,14 +42,18 @@ labels: color: '#c2e0c6' description: "Help wanted!" -branches: - - name: main - protection: - required_pull_request_reviews: null - required_conversation_resolution: true - required_status_checks: - # Required. Require branches to be up to date before merging. - strict: false - contexts: ["CI", "Lint Commits", "Spell Check with Typos"] - enforce_admins: false - restrictions: null +# This serves more as documentation. +# Branch protection API was replaced by rulesets but settings isn't updated. +# See https://github.com/repository-settings/app/issues/825 +# +# branches: +# - name: main +# protection: +# required_pull_request_reviews: null +# required_conversation_resolution: true +# required_status_checks: +# # Required. Require branches to be up to date before merging. +# strict: false +# contexts: ["CI", "Lint Commits", "Spell Check with Typos"] +# enforce_admins: false +# restrictions: null From afd275590c5568e8f7ca60abc1f33b20e3679c03 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 1 Apr 2024 12:36:16 -0500 Subject: [PATCH 202/455] chore(ci): Don't block on Lint Commits --- .github/settings.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/settings.yml b/.github/settings.yml index 08983ae0..457eed62 100644 --- a/.github/settings.yml +++ b/.github/settings.yml @@ -54,6 +54,6 @@ labels: # required_status_checks: # # Required. Require branches to be up to date before merging. # strict: false -# contexts: ["CI", "Lint Commits", "Spell Check with Typos"] +# contexts: ["CI", "Spell Check with Typos"] # enforce_admins: false # restrictions: null From 14225df351a4510a6fad72e716b29173347aac84 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Tue, 16 Apr 2024 21:46:56 -0500 Subject: [PATCH 203/455] chore(ci): Auto-merge linter version updates --- .github/renovate.json5 | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 373fc0e5..62ca46b6 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -72,6 +72,7 @@ schedule: [ '* * * * *', ], + automerge: true, }, // Goals: // - Keep version reqs low, ignoring compatible normal/build dependencies From be30b1bba034344c1a7c526b2b1898a8767471c5 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Fri, 26 Apr 2024 09:20:18 -0500 Subject: [PATCH 204/455] chore(ci): Try again with not auto-updating MSRV The overhead for MSRV bumping is a lot lower and its annoying merging all of the PRs (and I don't want these auto-merged) --- .github/renovate.json5 | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 62ca46b6..c1844208 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -7,24 +7,6 @@ configMigration: true, dependencyDashboard: true, customManagers: [ - { - customType: 'regex', - fileMatch: [ - '^rust-toolchain\\.toml$', - 'Cargo.toml$', - 'clippy.toml$', - '\\.clippy.toml$', - '^\\.github/workflows/ci.yml$', - '^\\.github/workflows/rust-next.yml$', - ], - matchStrings: [ - 'MSRV.*?(?<currentValue>\\d+\\.\\d+(\\.\\d+)?)', - '(?<currentValue>\\d+\\.\\d+(\\.\\d+)?).*?MSRV', - ], - depNameTemplate: 'MSRV', - packageNameTemplate: 'rust-lang/rust', - datasourceTemplate: 'github-releases', - }, { customType: 'regex', fileMatch: [ @@ -45,21 +27,6 @@ }, ], packageRules: [ - { - commitMessageTopic: 'MSRV', - matchManagers: [ - 'custom.regex', - ], - matchPackageNames: [ - 'MSRV', - ], - minimumReleaseAge: '336 days', // 8 releases * 6 weeks per release * 7 days per week - internalChecksFilter: 'strict', - extractVersion: '^(?<version>\\d+\\.\\d+)', // Drop the patch version - schedule: [ - '* * * * *', - ], - }, { commitMessageTopic: 'Rust Stable', matchManagers: [ From a01f25da96e8bd3e216fbc19ac9883ab3bf969ce Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Fri, 26 Apr 2024 09:23:28 -0500 Subject: [PATCH 205/455] chore(ci): Reduce noisy lints Want to add this back in later but this is slowing down migration of my repos. --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 39877b76..2eabbd09 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,6 @@ //! > DESCRIPTION #![cfg_attr(docsrs, feature(doc_auto_cfg))] -#![warn(missing_docs)] #![warn(clippy::print_stderr)] #![warn(clippy::print_stdout)] #![allow(non_snake_case)] // TODO: Delete me From 82cf9a62b027c10c6fafdcaaee24c4e92d7da61d Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Fri, 26 Apr 2024 09:35:55 -0500 Subject: [PATCH 206/455] chore(ci): Reduce noisy lints --- Cargo.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 715131bb..898251ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,9 +35,6 @@ debug_assert_with_mut_call = "warn" doc_markdown = "warn" empty_enum = "warn" enum_glob_use = "warn" -exhaustive_enums = "warn" -exhaustive_structs = "warn" -exit = "warn" expl_impl_clone_on_copy = "warn" explicit_deref_methods = "warn" explicit_into_iter_loop = "warn" @@ -85,7 +82,6 @@ string_lit_as_bytes = "warn" string_to_string = "warn" todo = "warn" trait_duplication_in_bounds = "warn" -unwrap_used = "warn" verbose_file_reads = "warn" wildcard_imports = "warn" zero_sized_map_values = "warn" From 181a2cf5e673d0f6f42133a5b30ccafd86b0106d Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Fri, 26 Apr 2024 11:36:19 -0500 Subject: [PATCH 207/455] chore(ci): Allow prelude wildcard imports --- .clippy.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/.clippy.toml b/.clippy.toml index 027eef41..1d4c5dc6 100644 --- a/.clippy.toml +++ b/.clippy.toml @@ -1,4 +1,3 @@ -warn-on-all-wildcard-imports = true allow-print-in-tests = true allow-expect-in-tests = true allow-unwrap-in-tests = true From 51de731521efb05c5503e05c33036d8fa439bc5a Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Fri, 26 Apr 2024 15:59:46 -0500 Subject: [PATCH 208/455] chore(ci): Lint clippy::items_after_statements seems too strict --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 898251ed..5a905800 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,7 +51,6 @@ inconsistent_struct_constructor = "warn" inefficient_to_string = "warn" infinite_loop = "warn" invalid_upcast_comparisons = "warn" -items_after_statements = "warn" large_digit_groups = "warn" large_stack_arrays = "warn" large_types_passed_by_value = "warn" From d1840f0518b5ed6c4d2258e36de8f5fa0c166fbf Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Sat, 27 Apr 2024 11:38:55 -0600 Subject: [PATCH 209/455] feat: Add Debug to public types --- src/renderer/mod.rs | 2 +- src/snippet.rs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index ee63da9f..845d2931 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -24,7 +24,7 @@ use stylesheet::Stylesheet; pub const DEFAULT_TERM_WIDTH: usize = 140; /// A renderer for [`Message`]s -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Renderer { anonymized_line_numbers: bool, term_width: usize, diff --git a/src/snippet.rs b/src/snippet.rs index e7d4bef6..8e9a3a88 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -15,6 +15,7 @@ use std::ops::Range; /// Primary structure provided for formatting /// /// See [`Level::title`] to create a [`Message`] +#[derive(Debug)] pub struct Message<'a> { pub(crate) level: Level, pub(crate) id: Option<&'a str>, @@ -55,6 +56,7 @@ impl<'a> Message<'a> { /// /// One `Snippet` is meant to represent a single, continuous, /// slice of source code that you want to annotate. +#[derive(Debug)] pub struct Snippet<'a> { pub(crate) origin: Option<&'a str>, pub(crate) line_start: usize, From b816eb9d568aa2d08c8cf370c7609d0d3346d5e9 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Sat, 27 Apr 2024 11:41:16 -0600 Subject: [PATCH 210/455] chore: Warn when public types missing debug --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index a2e4231d..bfd2dc6d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ #![deny(rust_2018_idioms)] +#![warn(missing_debug_implementations)] //! A library for formatting of text or programming code snippets. //! From 12109c20deaf1021cda7ebef4690ffd9c40c169b Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Sat, 27 Apr 2024 11:53:41 -0600 Subject: [PATCH 211/455] chore: Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85f967e2..62e20e2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). <!-- next-header --> ## [Unreleased] - ReleaseDate +### Added + +- All public types now implement `Debug` [#119](https://github.com/rust-lang/annotate-snippets-rs/pull/119) + ## [0.11.1] - 2024-03-21 ### Fixes From c8d76d6d502841cf5014cb80c6b3581d1c68c23e Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Sat, 27 Apr 2024 11:55:28 -0600 Subject: [PATCH 212/455] chore: Release annotate-snippets version 0.11.2 --- CHANGELOG.md | 5 ++++- Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62e20e2c..d379628b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). <!-- next-header --> ## [Unreleased] - ReleaseDate +## [0.11.2] - 2024-04-27 + ### Added - All public types now implement `Debug` [#119](https://github.com/rust-lang/annotate-snippets-rs/pull/119) @@ -130,7 +132,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Update the syntax to Rust 2018 idioms. (#4) <!-- next-url --> -[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.1...HEAD +[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.2...HEAD +[0.11.2]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.1...0.11.2 [0.11.1]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.0...0.11.1 [0.11.0]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.2...0.11.0 [0.10.2]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.1...0.10.2 diff --git a/Cargo.lock b/Cargo.lock index 479ee8cf..db244d14 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,7 +19,7 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "annotate-snippets" -version = "0.11.1" +version = "0.11.2" dependencies = [ "anstream", "anstyle", diff --git a/Cargo.toml b/Cargo.toml index a9a7c284..5a5d40b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "annotate-snippets" -version = "0.11.1" +version = "0.11.2" edition = "2021" rust-version = "1.73" # MSRV authors = ["Zibi Braniecki <gandalf@mozilla.com>"] From a6f3a123f8b57be381b3f03520bd6ebf86da351e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 1 May 2024 02:10:48 +0000 Subject: [PATCH 213/455] chore(deps): update rust crate serde to 1.0.199 --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index db244d14..49e824ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -701,18 +701,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.197" +version = "1.0.199" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.199" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 5a5d40b2..d7ff69b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ anstream = "0.6.13" criterion = "0.5.1" difference = "2.0.0" glob = "0.3.1" -serde = { version = "1.0.197", features = ["derive"] } +serde = { version = "1.0.199", features = ["derive"] } snapbox = { version = "0.5.9", features = ["diff", "harness", "path", "term-svg", "cmd", "examples"] } toml = "0.5.11" From edf68fac18e2520df402eb96bc3681a59e74fe3f Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Fri, 24 May 2024 09:37:04 -0500 Subject: [PATCH 214/455] chore: Remove unused feature --- Cargo.lock | 67 +----------------------------------------------------- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 67 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 49e824ef..2ee2a33b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,12 +108,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.4.1" @@ -221,15 +215,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" -[[package]] -name = "content_inspector" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7bda66e858c683005a53a9a60c69a4aca7eeaa45d124526e389f7aec8e62f38" -dependencies = [ - "memchr", -] - [[package]] name = "criterion" version = "0.5.1" @@ -305,12 +290,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" -[[package]] -name = "dunce" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" - [[package]] name = "either" version = "1.9.0" @@ -348,24 +327,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "fastrand" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" - -[[package]] -name = "filetime" -version = "0.2.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "windows-sys 0.52.0", -] - [[package]] name = "glob" version = "0.3.1" @@ -621,15 +582,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "regex" version = "1.10.2" @@ -665,7 +617,7 @@ version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ - "bitflags 2.4.1", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -745,10 +697,7 @@ dependencies = [ "anstream", "anstyle", "anstyle-svg", - "content_inspector", - "dunce", "escargot", - "filetime", "ignore", "libc", "libtest-mimic", @@ -757,9 +706,7 @@ dependencies = [ "serde_json", "similar", "snapbox-macros", - "tempfile", "wait-timeout", - "walkdir", "windows-sys 0.52.0", ] @@ -789,18 +736,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "tempfile" -version = "3.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" -dependencies = [ - "cfg-if", - "fastrand", - "rustix", - "windows-sys 0.52.0", -] - [[package]] name = "termcolor" version = "1.4.1" diff --git a/Cargo.toml b/Cargo.toml index d7ff69b4..9b7d867a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ criterion = "0.5.1" difference = "2.0.0" glob = "0.3.1" serde = { version = "1.0.199", features = ["derive"] } -snapbox = { version = "0.5.9", features = ["diff", "harness", "path", "term-svg", "cmd", "examples"] } +snapbox = { version = "0.5.9", features = ["diff", "harness", "term-svg", "cmd", "examples"] } toml = "0.5.11" [[bench]] From 3d43314f355632f7718c73a9e7b5ab64afc7b6b9 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Fri, 24 May 2024 09:37:38 -0500 Subject: [PATCH 215/455] chore: Upgrade snapbox --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2ee2a33b..c0eb93dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -690,9 +690,9 @@ checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21" [[package]] name = "snapbox" -version = "0.5.9" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ac441e1ecf678f68423d47f376d53fabce1afba92c8f68e31508eb27df8562a" +checksum = "f37d101fcafc8e73748fd8a1b7048f5979f93d372fd17027d7724c1643bc379b" dependencies = [ "anstream", "anstyle", @@ -712,9 +712,9 @@ dependencies = [ [[package]] name = "snapbox-macros" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1c4b838b05d15ab22754068cb73500b2f3b07bf09d310e15b27f88160f1de40" +checksum = "b1f4c14672714436c09254801c934b203196a51182a5107fb76591c7cc56424d" dependencies = [ "anstream", ] diff --git a/Cargo.toml b/Cargo.toml index 9b7d867a..548157f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ criterion = "0.5.1" difference = "2.0.0" glob = "0.3.1" serde = { version = "1.0.199", features = ["derive"] } -snapbox = { version = "0.5.9", features = ["diff", "harness", "term-svg", "cmd", "examples"] } +snapbox = { version = "0.5.14", features = ["diff", "harness", "term-svg", "cmd", "examples"] } toml = "0.5.11" [[bench]] From 123a2228588e1c9ff5afe0239bb8920ce0b52887 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Fri, 24 May 2024 09:37:52 -0500 Subject: [PATCH 216/455] refactor: Resolve snapbox deprecations --- tests/examples.rs | 2 +- tests/fixtures/main.rs | 1 - tests/formatter.rs | 32 ++++++++++++++++---------------- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/tests/examples.rs b/tests/examples.rs index 6025f5e5..c866548a 100644 --- a/tests/examples.rs +++ b/tests/examples.rs @@ -33,5 +33,5 @@ fn assert_example(target: &str, expected: snapbox::Data) { .env("CLICOLOR_FORCE", "1") .assert() .success() - .stdout_eq(expected); + .stdout_eq_(expected.raw()); } diff --git a/tests/fixtures/main.rs b/tests/fixtures/main.rs index 841b3639..78fe9615 100644 --- a/tests/fixtures/main.rs +++ b/tests/fixtures/main.rs @@ -10,7 +10,6 @@ fn main() { #[cfg(not(windows))] snapbox::harness::Harness::new("tests/fixtures/", setup, test) .select(["*/*.toml"]) - .action_env("SNAPSHOTS") .test(); } diff --git a/tests/formatter.rs b/tests/formatter.rs index ebe03ff6..f8793c12 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -1,6 +1,6 @@ use annotate_snippets::{Level, Renderer, Snippet}; -use snapbox::{assert_eq, str}; +use snapbox::{assert_data_eq, str}; #[test] fn test_i_29() { @@ -20,7 +20,7 @@ error: oops .indent(false); let renderer = Renderer::plain(); - assert_eq(expected, renderer.render(snippets).to_string()); + assert_data_eq!(renderer.render(snippets).to_string(), expected); } #[test] @@ -41,7 +41,7 @@ error .indent(false); let renderer = Renderer::plain(); - assert_eq(expected, renderer.render(snippets).to_string()); + assert_data_eq!(renderer.render(snippets).to_string(), expected); } #[test] @@ -64,7 +64,7 @@ error .indent(false); let renderer = Renderer::plain(); - assert_eq(expected, renderer.render(snippets).to_string()); + assert_data_eq!(renderer.render(snippets).to_string(), expected); } #[test] @@ -88,7 +88,7 @@ error .indent(false); let renderer = Renderer::plain(); - assert_eq(expected, renderer.render(snippets).to_string()); + assert_data_eq!(renderer.render(snippets).to_string(), expected); } #[test] @@ -109,7 +109,7 @@ error .indent(false); let renderer = Renderer::plain(); - assert_eq(expected, renderer.render(snippets).to_string()); + assert_data_eq!(renderer.render(snippets).to_string(), expected); } #[test] @@ -118,7 +118,7 @@ fn test_format_title() { let expected = str![r#"error[E0001]: This is a title"#]; let renderer = Renderer::plain(); - assert_eq(expected, renderer.render(input).to_string()); + assert_data_eq!(renderer.render(input).to_string(), expected); } #[test] @@ -136,7 +136,7 @@ error |"#]] .indent(false); let renderer = Renderer::plain(); - assert_eq(expected, renderer.render(input).to_string()); + assert_data_eq!(renderer.render(input).to_string(), expected); } #[test] @@ -159,7 +159,7 @@ error |"#]] .indent(false); let renderer = Renderer::plain(); - assert_eq(expected, renderer.render(input).to_string()); + assert_data_eq!(renderer.render(input).to_string(), expected); } #[test] @@ -183,7 +183,7 @@ error |"#]] .indent(false); let renderer = Renderer::plain(); - assert_eq(expected, renderer.render(input).to_string()); + assert_data_eq!(renderer.render(input).to_string(), expected); } #[test] @@ -196,7 +196,7 @@ error = error: This __is__ a title"#]] .indent(false); let renderer = Renderer::plain(); - assert_eq(expected, renderer.render(input).to_string()); + assert_data_eq!(renderer.render(input).to_string(), expected); } #[test] @@ -227,7 +227,7 @@ error |"#]] .indent(false); let renderer = Renderer::plain(); - assert_eq(expected, renderer.render(input).to_string()); + assert_data_eq!(renderer.render(input).to_string(), expected); } #[test] @@ -246,7 +246,7 @@ error |"#]] .indent(false); let renderer = Renderer::plain(); - assert_eq(expected, renderer.render(input).to_string()); + assert_data_eq!(renderer.render(input).to_string(), expected); } #[test] @@ -267,7 +267,7 @@ error |"#]] .indent(false); let renderer = Renderer::plain(); - assert_eq(expected, renderer.render(input).to_string()); + assert_data_eq!(renderer.render(input).to_string(), expected); } #[test] @@ -282,7 +282,7 @@ error |"#]] .indent(false); let renderer = Renderer::plain(); - assert_eq(expected, renderer.render(input).to_string()); + assert_data_eq!(renderer.render(input).to_string(), expected); } #[test] @@ -301,5 +301,5 @@ LL | abc |"#]] .indent(false); let renderer = Renderer::plain().anonymized_line_numbers(true); - assert_eq(expected, renderer.render(input).to_string()); + assert_data_eq!(renderer.render(input).to_string(), expected); } From eb5e8580e1eeac82eacf042c42ce04942444fad1 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Fri, 24 May 2024 09:39:44 -0500 Subject: [PATCH 217/455] refactor: Migrate from snapbox::harness to tryfn --- Cargo.lock | 29 ++++++++++++++++++++++++++--- Cargo.toml | 3 ++- tests/fixtures/main.rs | 8 ++++---- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c0eb93dc..333030f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,8 +27,9 @@ dependencies = [ "difference", "glob", "serde", - "snapbox", + "snapbox 0.5.14", "toml", + "tryfn", "unicode-width", ] @@ -698,9 +699,7 @@ dependencies = [ "anstyle", "anstyle-svg", "escargot", - "ignore", "libc", - "libtest-mimic", "normalize-line-endings", "os_pipe", "serde_json", @@ -710,6 +709,19 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "snapbox" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28bef451bfd0a7a395fb3979e130422658926bb2e0d9a168e72aca4c9b5c5738" +dependencies = [ + "anstream", + "anstyle", + "normalize-line-endings", + "similar", + "snapbox-macros", +] + [[package]] name = "snapbox-macros" version = "0.3.9" @@ -773,6 +785,17 @@ dependencies = [ "serde", ] +[[package]] +name = "tryfn" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "493e1390312bb94363f762687be32a1bd01c3333dfad25a5a7fffab1edc64839" +dependencies = [ + "ignore", + "libtest-mimic", + "snapbox 0.6.0", +] + [[package]] name = "unicode-ident" version = "1.0.12" diff --git a/Cargo.toml b/Cargo.toml index 548157f3..b49d940b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,8 +33,9 @@ criterion = "0.5.1" difference = "2.0.0" glob = "0.3.1" serde = { version = "1.0.199", features = ["derive"] } -snapbox = { version = "0.5.14", features = ["diff", "harness", "term-svg", "cmd", "examples"] } +snapbox = { version = "0.5.14", features = ["diff", "term-svg", "cmd", "examples"] } toml = "0.5.11" +tryfn = "0.2.1" [[bench]] name = "simple" diff --git a/tests/fixtures/main.rs b/tests/fixtures/main.rs index 78fe9615..c0e351ce 100644 --- a/tests/fixtures/main.rs +++ b/tests/fixtures/main.rs @@ -8,15 +8,15 @@ use std::error::Error; fn main() { #[cfg(not(windows))] - snapbox::harness::Harness::new("tests/fixtures/", setup, test) + tryfn::Harness::new("tests/fixtures/", setup, test) .select(["*/*.toml"]) .test(); } -fn setup(input_path: std::path::PathBuf) -> snapbox::harness::Case { +fn setup(input_path: std::path::PathBuf) -> tryfn::Case { let name = input_path.file_name().unwrap().to_str().unwrap().to_owned(); - let expected = input_path.with_extension("svg"); - snapbox::harness::Case { + let expected = tryfn::Data::read_from(&input_path.with_extension("svg"), None); + tryfn::Case { name, fixture: input_path, expected, From 7624aca2fbc368ec6a91be9ed82f48193d25946b Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Fri, 24 May 2024 09:42:23 -0500 Subject: [PATCH 218/455] chore: Upgrade to snapbox 0.6 --- Cargo.lock | 21 ++++------------- Cargo.toml | 2 +- tests/formatter.rs | 56 +++++++++++++++++++++++----------------------- 3 files changed, 33 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 333030f3..074ab74e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,7 +27,7 @@ dependencies = [ "difference", "glob", "serde", - "snapbox 0.5.14", + "snapbox", "toml", "tryfn", "unicode-width", @@ -691,9 +691,9 @@ checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21" [[package]] name = "snapbox" -version = "0.5.14" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f37d101fcafc8e73748fd8a1b7048f5979f93d372fd17027d7724c1643bc379b" +checksum = "28bef451bfd0a7a395fb3979e130422658926bb2e0d9a168e72aca4c9b5c5738" dependencies = [ "anstream", "anstyle", @@ -709,19 +709,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "snapbox" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28bef451bfd0a7a395fb3979e130422658926bb2e0d9a168e72aca4c9b5c5738" -dependencies = [ - "anstream", - "anstyle", - "normalize-line-endings", - "similar", - "snapbox-macros", -] - [[package]] name = "snapbox-macros" version = "0.3.9" @@ -793,7 +780,7 @@ checksum = "493e1390312bb94363f762687be32a1bd01c3333dfad25a5a7fffab1edc64839" dependencies = [ "ignore", "libtest-mimic", - "snapbox 0.6.0", + "snapbox", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index b49d940b..72bf8952 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ criterion = "0.5.1" difference = "2.0.0" glob = "0.3.1" serde = { version = "1.0.199", features = ["derive"] } -snapbox = { version = "0.5.14", features = ["diff", "term-svg", "cmd", "examples"] } +snapbox = { version = "0.6.0", features = ["diff", "term-svg", "cmd", "examples"] } toml = "0.5.11" tryfn = "0.2.1" diff --git a/tests/formatter.rs b/tests/formatter.rs index f8793c12..5b746e12 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -16,8 +16,8 @@ error: oops | 2 | Second oops line | ^^^^ oops - |"#]] - .indent(false); + | +"#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(snippets).to_string(), expected); @@ -37,8 +37,8 @@ error | 1 | こんにちは、世界 | ^^^^ world - |"#]] - .indent(false); + | +"#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(snippets).to_string(), expected); @@ -60,8 +60,8 @@ error | _____^ 2 | | ございます | |______^ Good morning - |"#]] - .indent(false); + | +"#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(snippets).to_string(), expected); @@ -84,8 +84,8 @@ error | ^^^^^^ Sushi1 2 | 食べたい🍣 | ---- note: Sushi2 - |"#]] - .indent(false); + | +"#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(snippets).to_string(), expected); @@ -105,8 +105,8 @@ error | 1 | こんにちは、新しいWorld! | ^^^^^^^^^^^ New world - |"#]] - .indent(false); + | +"#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(snippets).to_string(), expected); @@ -133,8 +133,8 @@ error | 5402 | This is line 1 5403 | This is line 2 - |"#]] - .indent(false); + | +"#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); } @@ -156,8 +156,8 @@ error ::: file2.rs | 2 | This is slice 2 - |"#]] - .indent(false); + | +"#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); } @@ -180,8 +180,8 @@ error 5402 | This is line 1 5403 | This is line 2 | -- info: Test annotation - |"#]] - .indent(false); + | +"#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); } @@ -193,8 +193,8 @@ fn test_format_footer_title() { .footer(Level::Error.title("This __is__ a title")); let expected = str![[r#" error - = error: This __is__ a title"#]] - .indent(false); + = error: This __is__ a title +"#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); } @@ -224,8 +224,8 @@ error | 56 | This is an example 57 | of content lines - |"#]] - .indent(false); + | +"#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); } @@ -243,8 +243,8 @@ error | 1 | tests | ----- help: Example string - |"#]] - .indent(false); + | +"#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); } @@ -264,8 +264,8 @@ error 1 | tests | ----- help: Example string | ----- help: Second line - |"#]] - .indent(false); + | +"#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); } @@ -279,8 +279,8 @@ fn test_only_source() { error --> file.rs | - |"#]] - .indent(false); + | +"#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); } @@ -298,8 +298,8 @@ LL | This is an example LL | of content lines LL | LL | abc - |"#]] - .indent(false); + | +"#]]; let renderer = Renderer::plain().anonymized_line_numbers(true); assert_data_eq!(renderer.render(input).to_string(), expected); } From b8d0b9ef1247144c553fde60e4445d3b4d10d934 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Fri, 24 May 2024 09:42:47 -0500 Subject: [PATCH 219/455] refactor: Resolve deprecations --- tests/examples.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/examples.rs b/tests/examples.rs index c866548a..b6576629 100644 --- a/tests/examples.rs +++ b/tests/examples.rs @@ -33,5 +33,5 @@ fn assert_example(target: &str, expected: snapbox::Data) { .env("CLICOLOR_FORCE", "1") .assert() .success() - .stdout_eq_(expected.raw()); + .stdout_eq(expected.raw()); } From 78741e51bbbe6c83aaa98305623a8ffc6226493f Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 27 May 2024 21:45:38 -0500 Subject: [PATCH 220/455] chore: Remove lints that lead to bad code --- Cargo.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5a905800..80906914 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,7 +58,6 @@ let_and_return = "allow" # sometimes good to name what you are returning linkedlist = "warn" lossy_float_literal = "warn" macro_use_imports = "warn" -match_wildcard_for_single_variants = "warn" mem_forget = "warn" mutex_integer = "warn" needless_continue = "warn" @@ -73,7 +72,6 @@ rest_pat_in_fully_bound_structs = "warn" same_functions_in_if_condition = "warn" self_named_module_files = "warn" semicolon_if_nothing_returned = "warn" -single_match_else = "warn" str_to_string = "warn" string_add = "warn" string_add_assign = "warn" From 44916f6d2b8da4d778186083c31ff52a73187edf Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 29 May 2024 16:15:19 -0500 Subject: [PATCH 221/455] chore: Update deny config --- deny.toml | 176 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 136 insertions(+), 40 deletions(-) diff --git a/deny.toml b/deny.toml index 21fa937f..b6ecbe9c 100644 --- a/deny.toml +++ b/deny.toml @@ -4,32 +4,82 @@ # * allow - No warning or error will be produced, though in some cases a note # will be +# Root options + +# The graph table configures how the dependency graph is constructed and thus +# which crates the checks are performed against +[graph] +# If 1 or more target triples (and optionally, target_features) are specified, +# only the specified targets will be checked when running `cargo deny check`. +# This means, if a particular package is only ever used as a target specific +# dependency, such as, for example, the `nix` crate only being used via the +# `target_family = "unix"` configuration, that only having windows targets in +# this list would mean the nix crate, as well as any of its exclusive +# dependencies not shared by any other crates, would be ignored, as the target +# list here is effectively saying which targets you are building for. +targets = [ + # The triple can be any string, but only the target triples built in to + # rustc (as of 1.40) can be checked against actual config expressions + #"x86_64-unknown-linux-musl", + # You can also specify which target_features you promise are enabled for a + # particular target. target_features are currently not validated against + # the actual valid features supported by the target architecture. + #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, +] +# When creating the dependency graph used as the source of truth when checks are +# executed, this field can be used to prune crates from the graph, removing them +# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate +# is pruned from the graph, all of its dependencies will also be pruned unless +# they are connected to another crate in the graph that hasn't been pruned, +# so it should be used with care. The identifiers are [Package ID Specifications] +# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) +#exclude = [] +# If true, metadata will be collected with `--all-features`. Note that this can't +# be toggled off if true, if you want to conditionally enable `--all-features` it +# is recommended to pass `--all-features` on the cmd line instead +all-features = false +# If true, metadata will be collected with `--no-default-features`. The same +# caveat with `all-features` applies +no-default-features = false +# If set, these feature will be enabled when collecting metadata. If `--features` +# is specified on the cmd line they will take precedence over this option. +#features = [] + +# The output table provides options for how/if diagnostics are outputted +[output] +# When outputting inclusion graphs in diagnostics that include features, this +# option can be used to specify the depth at which feature edges will be added. +# This option is included since the graphs can be quite large and the addition +# of features from the crate(s) to all of the graph roots can be far too verbose. +# This option can be overridden via `--feature-depth` on the cmd line +feature-depth = 1 + # This section is considered when running `cargo deny check advisories` # More documentation for the advisories section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html [advisories] -# The lint level for security vulnerabilities -vulnerability = "deny" -# The lint level for unmaintained crates -unmaintained = "warn" -# The lint level for crates that have been yanked from their source registry -yanked = "warn" -# The lint level for crates with security notices. Note that as of -# 2019-12-17 there are no security notice advisories in -# https://github.com/rustsec/advisory-db -notice = "warn" +# The path where the advisory databases are cloned/fetched into +#db-path = "$CARGO_HOME/advisory-dbs" +# The url(s) of the advisory databases to use +#db-urls = ["https://github.com/rustsec/advisory-db"] # A list of advisory IDs to ignore. Note that ignored advisories will still # output a note when they are encountered. -# -# e.g. "RUSTSEC-0000-0000", ignore = [ + #"RUSTSEC-0000-0000", + #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, + #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish + #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" }, ] +# If this is true, then cargo deny will use the git executable to fetch advisory database. +# If this is false, then it uses a built-in git library. +# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. +# See Git Authentication for more information about setting up git authentication. +#git-fetch-with-cli = true # This section is considered when running `cargo deny check licenses` # More documentation for the licenses section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html [licenses] -unlicensed = "deny" # List of explicitly allowed licenses # See https://spdx.org/licenses/ for list of possible licenses # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. @@ -42,26 +92,8 @@ allow = [ "Unicode-DFS-2016", "CC0-1.0", "ISC", + "OpenSSL", ] -# List of explicitly disallowed licenses -# See https://spdx.org/licenses/ for list of possible licenses -# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. -deny = [ -] -# Lint level for licenses considered copyleft -copyleft = "deny" -# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses -# * both - The license will be approved if it is both OSI-approved *AND* FSF -# * either - The license will be approved if it is either OSI-approved *OR* FSF -# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF -# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved -# * neither - This predicate is ignored and the default lint level is used -allow-osi-fsf-free = "neither" -# Lint level used when no other predicates are matched -# 1. License isn't in the allow or deny lists -# 2. License isn't copyleft -# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither" -default = "deny" # The confidence threshold for detecting a license from license text. # The higher the value, the more closely the license text must be to the # canonical license text of a valid SPDX license file. @@ -72,7 +104,25 @@ confidence-threshold = 0.8 exceptions = [ # Each entry is the crate and version constraint, and its specific allow # list - #{ allow = ["Zlib"], name = "adler32", version = "*" }, + #{ allow = ["Zlib"], crate = "adler32" }, +] + +# Some crates don't have (easily) machine readable licensing information, +# adding a clarification entry for it allows you to manually specify the +# licensing information +[[licenses.clarify]] +# The package spec the clarification applies to +crate = "ring" +# The SPDX expression for the license requirements of the crate +expression = "MIT AND ISC AND OpenSSL" +# One or more files in the crate's source used as the "source of truth" for +# the license expression. If the contents match, the clarification will be used +# when running the license check, otherwise the clarification will be ignored +# and the crate will be checked normally, which may produce warnings or errors +# depending on the rest of your configuration +license-files = [ +# Each entry is a crate relative path, and the (opaque) hash of its contents +{ path = "LICENSE", hash = 0xbd0eed23 } ] [licenses.private] @@ -81,6 +131,12 @@ exceptions = [ # To see how to mark a crate as unpublished (to the official registry), # visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. ignore = true +# One or more private registries that you might publish crates to, if a crate +# is only published to private registries, and ignore is true, the crate will +# not have its license(s) checked +registries = [ + #"https://sekretz.com/registry +] # This section is considered when running `cargo deny check bans`. # More documentation about the 'bans' section can be found here: @@ -89,7 +145,7 @@ ignore = true # Lint level for when multiple versions of the same crate are detected multiple-versions = "warn" # Lint level for when a crate version requirement is `*` -wildcards = "warn" +wildcards = "allow" # The graph highlighting used when creating dotgraphs for crates # with multiple versions # * lowest-version - The path to the lowest versioned duplicate is highlighted @@ -106,17 +162,53 @@ workspace-default-features = "allow" external-default-features = "allow" # List of crates that are allowed. Use with care! allow = [ - #{ name = "ansi_term", version = "=0.11.0" }, + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" }, ] # List of crates to deny deny = [ - # Each entry the name of a crate and a version range. If version is - # not specified, all versions will be matched. - #{ name = "ansi_term", version = "=0.11.0" }, - # + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" }, # Wrapper crates can optionally be specified to allow the crate when it # is a direct dependency of the otherwise banned crate - #{ name = "ansi_term", version = "=0.11.0", wrappers = [] }, + #{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] }, +] + +# List of features to allow/deny +# Each entry the name of a crate and a version range. If version is +# not specified, all versions will be matched. +#[[bans.features]] +#crate = "reqwest" +# Features to not allow +#deny = ["json"] +# Features to allow +#allow = [ +# "rustls", +# "__rustls", +# "__tls", +# "hyper-rustls", +# "rustls", +# "rustls-pemfile", +# "rustls-tls-webpki-roots", +# "tokio-rustls", +# "webpki-roots", +#] +# If true, the allowed features must exactly match the enabled feature set. If +# this is set there is no point setting `deny` +#exact = true + +# Certain crates/versions that will be skipped when doing duplicate detection. +skip = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" }, +] +# Similarly to `skip` allows you to skip certain crates during duplicate +# detection. Unlike skip, it also includes the entire tree of transitive +# dependencies starting at the specified crate, up to a certain depth, which is +# by default infinite. +skip-tree = [ + #"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies + #{ crate = "ansi_term@0.11.0", depth = 20 }, ] # This section is considered when running `cargo deny check sources`. @@ -138,3 +230,7 @@ allow-git = [] [sources.allow-org] # 1 or more github.com organizations to allow git sources for github = [] +# 1 or more gitlab.com organizations to allow git sources for +gitlab = [] +# 1 or more bitbucket.org organizations to allow git sources for +bitbucket = [] From dce69df2e44d87443dfd6955078bd912f53bddc8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 1 Jun 2024 00:43:32 +0000 Subject: [PATCH 222/455] chore(deps): update compatible (dev) --- Cargo.lock | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 074ab74e..70efddcf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -35,15 +35,16 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] @@ -401,6 +402,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "itertools" version = "0.10.5" @@ -654,18 +661,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.199" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.199" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", @@ -691,9 +698,9 @@ checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21" [[package]] name = "snapbox" -version = "0.6.0" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28bef451bfd0a7a395fb3979e130422658926bb2e0d9a168e72aca4c9b5c5738" +checksum = "94204b12a4d3550420babdb4148c6639692e4e3e61060866929c5107f208aeb6" dependencies = [ "anstream", "anstyle", From ce6badcd188650dac4b3c97b69bde86a738917a0 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 3 Jun 2024 10:29:26 -0500 Subject: [PATCH 223/455] chore: Fix typo --- .github/workflows/rust-next.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rust-next.yml b/.github/workflows/rust-next.yml index e673b652..ab499633 100644 --- a/.github/workflows/rust-next.yml +++ b/.github/workflows/rust-next.yml @@ -53,7 +53,7 @@ jobs: toolchain: stable - uses: Swatinem/rust-cache@v2 - uses: taiki-e/install-action@cargo-hack - - name: Update dependencues + - name: Update dependencies run: cargo update - name: Build run: cargo test --workspace --no-run From 1353a953a527b7ebc0b0a3f267fc47f56359e886 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Tue, 4 Jun 2024 15:33:16 -0500 Subject: [PATCH 224/455] chore: Encourage use of repository --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 80906914..c9695d79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,6 +87,7 @@ zero_sized_map_values = "warn" name = "PROJECT" version = "0.0.1" description = "DESCRIPTION" +repository = "REPOSITORY" categories = [] keywords = [] license.workspace = true From 7039c66c7f0a42b84136a2f166ce6446edbb9ce0 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Tue, 4 Jun 2024 15:33:50 -0500 Subject: [PATCH 225/455] chore: Encourage cloneable repositories --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c9695d79..96cb2348 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ resolver = "2" [workspace.package] +repository = "REPOSITORY" license = "MIT OR Apache-2.0" edition = "2021" rust-version = "1.65.0" # MSRV @@ -87,9 +88,9 @@ zero_sized_map_values = "warn" name = "PROJECT" version = "0.0.1" description = "DESCRIPTION" -repository = "REPOSITORY" categories = [] keywords = [] +repository.workspace = true license.workspace = true edition.workspace = true rust-version.workspace = true From b5a6b53ecd4a377df293ea85fb695f1137573404 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 6 Jun 2024 13:59:28 -0500 Subject: [PATCH 226/455] chore: Update from epage/_rust template --- .clippy.toml | 4 + .github/renovate.json5 | 24 +++-- .github/workflows/audit.yml | 4 + .github/workflows/ci.yml | 50 +++++---- .github/workflows/committed.yml | 4 + .github/workflows/pre-commit.yml | 6 +- .github/workflows/rust-next.yml | 30 +++--- .github/workflows/spelling.yml | 4 + Cargo.toml | 3 + deny.toml | 176 ++++++++++++++++++++++++------- src/lib.rs | 8 +- tests/fixtures/main.rs | 2 +- 12 files changed, 222 insertions(+), 93 deletions(-) create mode 100644 .clippy.toml diff --git a/.clippy.toml b/.clippy.toml new file mode 100644 index 00000000..2e52a640 --- /dev/null +++ b/.clippy.toml @@ -0,0 +1,4 @@ +allow-print-in-tests = true +allow-expect-in-tests = true +allow-unwrap-in-tests = true +allow-dbg-in-tests = true diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 87676d4d..c1844208 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -3,6 +3,7 @@ 'before 5am on the first day of the month', ], semanticCommits: 'enabled', + commitMessageLowerCase: 'never', configMigration: true, dependencyDashboard: true, customManagers: [ @@ -17,30 +18,35 @@ '^\\.github/workflows/rust-next.yml$', ], matchStrings: [ - 'MSRV.*?(?<currentValue>\\d+\\.\\d+(\\.\\d+)?)', - '(?<currentValue>\\d+\\.\\d+(\\.\\d+)?).*?MSRV', + 'STABLE.*?(?<currentValue>\\d+\\.\\d+(\\.\\d+)?)', + '(?<currentValue>\\d+\\.\\d+(\\.\\d+)?).*?STABLE', ], - depNameTemplate: 'rust', + depNameTemplate: 'STABLE', packageNameTemplate: 'rust-lang/rust', datasourceTemplate: 'github-releases', }, ], packageRules: [ { - commitMessageTopic: 'MSRV', + commitMessageTopic: 'Rust Stable', matchManagers: [ 'custom.regex', ], matchPackageNames: [ - 'rust', + 'STABLE', ], - minimumReleaseAge: '84 days', - internalChecksFilter: 'strict', - extractVersion: '^(?<version>\\d+\\.\\d+)', + extractVersion: '^(?<version>\\d+\\.\\d+)', // Drop the patch version schedule: [ '* * * * *', ], + automerge: true, }, + // Goals: + // - Keep version reqs low, ignoring compatible normal/build dependencies + // - Take advantage of latest dev-dependencies + // - Rollup safe upgrades to reduce CI runner load + // - Help keep number of versions down by always using latest breaking change + // - Have lockfile and manifest in-sync { matchManagers: [ 'cargo', @@ -66,6 +72,7 @@ matchCurrentVersion: '>=1.0.0', matchUpdateTypes: [ 'minor', + 'patch', ], enabled: false, }, @@ -93,6 +100,7 @@ matchCurrentVersion: '>=1.0.0', matchUpdateTypes: [ 'minor', + 'patch', ], automerge: true, groupName: 'compatible (dev)', diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 442e6379..35b3da84 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -17,6 +17,10 @@ env: CARGO_TERM_COLOR: always CLICOLOR: 1 +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + jobs: security_audit: permissions: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f10a0db1..a6216528 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,21 +14,27 @@ env: CARGO_TERM_COLOR: always CLICOLOR: 1 +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + jobs: ci: permissions: contents: none name: CI - needs: [test, msrv, docs, rustfmt, clippy] + needs: [test, msrv, lockfile, docs, rustfmt, clippy] runs-on: ubuntu-latest + if: "always()" steps: - - name: Done - run: exit 0 + - name: Failed + run: exit 1 + if: "contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped')" test: name: Test strategy: matrix: - os: ["ubuntu-latest", "windows-latest", "macos-latest"] + os: ["ubuntu-latest", "windows-latest", "macos-14"] rust: ["stable"] continue-on-error: ${{ matrix.rust != 'stable' }} runs-on: ${{ matrix.os }} @@ -40,16 +46,13 @@ jobs: with: toolchain: ${{ matrix.rust }} - uses: Swatinem/rust-cache@v2 + - uses: taiki-e/install-action@cargo-hack - name: Build - run: cargo test --no-run --workspace --all-features - - name: Default features - run: cargo test --workspace - - name: All features - run: cargo test --workspace --all-features - - name: No-default features - run: cargo test --workspace --no-default-features + run: cargo test --workspace --no-run + - name: Test + run: cargo hack test --feature-powerset --workspace msrv: - name: "Check MSRV: 1.73" # MSRV + name: "Check MSRV" runs-on: ubuntu-latest steps: - name: Checkout repository @@ -57,14 +60,11 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.73" # MSRV + toolchain: stable - uses: Swatinem/rust-cache@v2 + - uses: taiki-e/install-action@cargo-hack - name: Default features - run: cargo check --workspace --all-targets - - name: All features - run: cargo check --workspace --all-targets --all-features - - name: No-default features - run: cargo check --workspace --all-targets --no-default-features + run: cargo hack check --feature-powerset --locked --rust-version --ignore-private --workspace --all-targets lockfile: runs-on: ubuntu-latest steps: @@ -76,7 +76,7 @@ jobs: toolchain: stable - uses: Swatinem/rust-cache@v2 - name: "Is lockfile updated?" - run: cargo fetch --locked + run: cargo update --workspace --locked docs: name: Docs runs-on: ubuntu-latest @@ -86,7 +86,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: stable + toolchain: "1.76" # STABLE - uses: Swatinem/rust-cache@v2 - name: Check documentation env: @@ -101,9 +101,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - # Not MSRV because its harder to jump between versions and people are - # more likely to have stable - toolchain: stable + toolchain: "1.76" # STABLE components: rustfmt - uses: Swatinem/rust-cache@v2 - name: Check formatting @@ -119,13 +117,13 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.73" # MSRV + toolchain: "1.76" # STABLE components: clippy - uses: Swatinem/rust-cache@v2 - name: Install SARIF tools - run: cargo install clippy-sarif --version 0.3.4 --locked # Held back due to msrv + run: cargo install clippy-sarif --locked - name: Install SARIF tools - run: cargo install sarif-fmt --version 0.3.4 --locked # Held back due to msrv + run: cargo install sarif-fmt --locked - name: Check run: > cargo clippy --workspace --all-features --all-targets --message-format=json -- -D warnings --allow deprecated diff --git a/.github/workflows/committed.yml b/.github/workflows/committed.yml index 04625584..e7a50fbb 100644 --- a/.github/workflows/committed.yml +++ b/.github/workflows/committed.yml @@ -11,6 +11,10 @@ env: CARGO_TERM_COLOR: always CLICOLOR: 1 +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + jobs: committed: name: Lint Commits diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 6eb6ca19..3d04055c 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -5,13 +5,17 @@ permissions: {} # none on: pull_request: push: - branches: [main] + branches: [master] env: RUST_BACKTRACE: 1 CARGO_TERM_COLOR: always CLICOLOR: 1 +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + jobs: pre-commit: permissions: diff --git a/.github/workflows/rust-next.yml b/.github/workflows/rust-next.yml index d8c2d257..ab499633 100644 --- a/.github/workflows/rust-next.yml +++ b/.github/workflows/rust-next.yml @@ -12,12 +12,16 @@ env: CARGO_TERM_COLOR: always CLICOLOR: 1 +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + jobs: test: name: Test strategy: matrix: - os: ["ubuntu-latest", "windows-latest", "macos-latest"] + os: ["ubuntu-latest", "windows-latest", "macos-latest", "macos-14"] rust: ["stable", "beta"] include: - os: ubuntu-latest @@ -32,12 +36,11 @@ jobs: with: toolchain: ${{ matrix.rust }} - uses: Swatinem/rust-cache@v2 - - name: Default features - run: cargo test --workspace - - name: All features - run: cargo test --workspace --all-features - - name: No-default features - run: cargo test --workspace --no-default-features + - uses: taiki-e/install-action@cargo-hack + - name: Build + run: cargo test --workspace --no-run + - name: Test + run: cargo hack test --feature-powerset --workspace latest: name: "Check latest dependencies" runs-on: ubuntu-latest @@ -49,11 +52,10 @@ jobs: with: toolchain: stable - uses: Swatinem/rust-cache@v2 - - name: Update dependencues + - uses: taiki-e/install-action@cargo-hack + - name: Update dependencies run: cargo update - - name: Default features - run: cargo test --workspace --all-targets - - name: All features - run: cargo test --workspace --all-targets --all-features - - name: No-default features - run: cargo test --workspace --all-targets --no-default-features + - name: Build + run: cargo test --workspace --no-run + - name: Test + run: cargo hack test --feature-powerset --workspace diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml index 12f75859..8e58d9ec 100644 --- a/.github/workflows/spelling.yml +++ b/.github/workflows/spelling.yml @@ -10,6 +10,10 @@ env: CARGO_TERM_COLOR: always CLICOLOR: 1 +concurrency: + group: "${{ github.workflow }}-${{ github.ref }}" + cancel-in-progress: true + jobs: spelling: name: Spell Check with Typos diff --git a/Cargo.toml b/Cargo.toml index 72bf8952..a317c711 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,3 +48,6 @@ harness = false [features] default = [] testing-colors = [] + +[lints.rust] +rust_2018_idioms = "warn" diff --git a/deny.toml b/deny.toml index 21fa937f..b6ecbe9c 100644 --- a/deny.toml +++ b/deny.toml @@ -4,32 +4,82 @@ # * allow - No warning or error will be produced, though in some cases a note # will be +# Root options + +# The graph table configures how the dependency graph is constructed and thus +# which crates the checks are performed against +[graph] +# If 1 or more target triples (and optionally, target_features) are specified, +# only the specified targets will be checked when running `cargo deny check`. +# This means, if a particular package is only ever used as a target specific +# dependency, such as, for example, the `nix` crate only being used via the +# `target_family = "unix"` configuration, that only having windows targets in +# this list would mean the nix crate, as well as any of its exclusive +# dependencies not shared by any other crates, would be ignored, as the target +# list here is effectively saying which targets you are building for. +targets = [ + # The triple can be any string, but only the target triples built in to + # rustc (as of 1.40) can be checked against actual config expressions + #"x86_64-unknown-linux-musl", + # You can also specify which target_features you promise are enabled for a + # particular target. target_features are currently not validated against + # the actual valid features supported by the target architecture. + #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, +] +# When creating the dependency graph used as the source of truth when checks are +# executed, this field can be used to prune crates from the graph, removing them +# from the view of cargo-deny. This is an extremely heavy hammer, as if a crate +# is pruned from the graph, all of its dependencies will also be pruned unless +# they are connected to another crate in the graph that hasn't been pruned, +# so it should be used with care. The identifiers are [Package ID Specifications] +# (https://doc.rust-lang.org/cargo/reference/pkgid-spec.html) +#exclude = [] +# If true, metadata will be collected with `--all-features`. Note that this can't +# be toggled off if true, if you want to conditionally enable `--all-features` it +# is recommended to pass `--all-features` on the cmd line instead +all-features = false +# If true, metadata will be collected with `--no-default-features`. The same +# caveat with `all-features` applies +no-default-features = false +# If set, these feature will be enabled when collecting metadata. If `--features` +# is specified on the cmd line they will take precedence over this option. +#features = [] + +# The output table provides options for how/if diagnostics are outputted +[output] +# When outputting inclusion graphs in diagnostics that include features, this +# option can be used to specify the depth at which feature edges will be added. +# This option is included since the graphs can be quite large and the addition +# of features from the crate(s) to all of the graph roots can be far too verbose. +# This option can be overridden via `--feature-depth` on the cmd line +feature-depth = 1 + # This section is considered when running `cargo deny check advisories` # More documentation for the advisories section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html [advisories] -# The lint level for security vulnerabilities -vulnerability = "deny" -# The lint level for unmaintained crates -unmaintained = "warn" -# The lint level for crates that have been yanked from their source registry -yanked = "warn" -# The lint level for crates with security notices. Note that as of -# 2019-12-17 there are no security notice advisories in -# https://github.com/rustsec/advisory-db -notice = "warn" +# The path where the advisory databases are cloned/fetched into +#db-path = "$CARGO_HOME/advisory-dbs" +# The url(s) of the advisory databases to use +#db-urls = ["https://github.com/rustsec/advisory-db"] # A list of advisory IDs to ignore. Note that ignored advisories will still # output a note when they are encountered. -# -# e.g. "RUSTSEC-0000-0000", ignore = [ + #"RUSTSEC-0000-0000", + #{ id = "RUSTSEC-0000-0000", reason = "you can specify a reason the advisory is ignored" }, + #"a-crate-that-is-yanked@0.1.1", # you can also ignore yanked crate versions if you wish + #{ crate = "a-crate-that-is-yanked@0.1.1", reason = "you can specify why you are ignoring the yanked crate" }, ] +# If this is true, then cargo deny will use the git executable to fetch advisory database. +# If this is false, then it uses a built-in git library. +# Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. +# See Git Authentication for more information about setting up git authentication. +#git-fetch-with-cli = true # This section is considered when running `cargo deny check licenses` # More documentation for the licenses section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html [licenses] -unlicensed = "deny" # List of explicitly allowed licenses # See https://spdx.org/licenses/ for list of possible licenses # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. @@ -42,26 +92,8 @@ allow = [ "Unicode-DFS-2016", "CC0-1.0", "ISC", + "OpenSSL", ] -# List of explicitly disallowed licenses -# See https://spdx.org/licenses/ for list of possible licenses -# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. -deny = [ -] -# Lint level for licenses considered copyleft -copyleft = "deny" -# Blanket approval or denial for OSI-approved or FSF Free/Libre licenses -# * both - The license will be approved if it is both OSI-approved *AND* FSF -# * either - The license will be approved if it is either OSI-approved *OR* FSF -# * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF -# * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved -# * neither - This predicate is ignored and the default lint level is used -allow-osi-fsf-free = "neither" -# Lint level used when no other predicates are matched -# 1. License isn't in the allow or deny lists -# 2. License isn't copyleft -# 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither" -default = "deny" # The confidence threshold for detecting a license from license text. # The higher the value, the more closely the license text must be to the # canonical license text of a valid SPDX license file. @@ -72,7 +104,25 @@ confidence-threshold = 0.8 exceptions = [ # Each entry is the crate and version constraint, and its specific allow # list - #{ allow = ["Zlib"], name = "adler32", version = "*" }, + #{ allow = ["Zlib"], crate = "adler32" }, +] + +# Some crates don't have (easily) machine readable licensing information, +# adding a clarification entry for it allows you to manually specify the +# licensing information +[[licenses.clarify]] +# The package spec the clarification applies to +crate = "ring" +# The SPDX expression for the license requirements of the crate +expression = "MIT AND ISC AND OpenSSL" +# One or more files in the crate's source used as the "source of truth" for +# the license expression. If the contents match, the clarification will be used +# when running the license check, otherwise the clarification will be ignored +# and the crate will be checked normally, which may produce warnings or errors +# depending on the rest of your configuration +license-files = [ +# Each entry is a crate relative path, and the (opaque) hash of its contents +{ path = "LICENSE", hash = 0xbd0eed23 } ] [licenses.private] @@ -81,6 +131,12 @@ exceptions = [ # To see how to mark a crate as unpublished (to the official registry), # visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. ignore = true +# One or more private registries that you might publish crates to, if a crate +# is only published to private registries, and ignore is true, the crate will +# not have its license(s) checked +registries = [ + #"https://sekretz.com/registry +] # This section is considered when running `cargo deny check bans`. # More documentation about the 'bans' section can be found here: @@ -89,7 +145,7 @@ ignore = true # Lint level for when multiple versions of the same crate are detected multiple-versions = "warn" # Lint level for when a crate version requirement is `*` -wildcards = "warn" +wildcards = "allow" # The graph highlighting used when creating dotgraphs for crates # with multiple versions # * lowest-version - The path to the lowest versioned duplicate is highlighted @@ -106,17 +162,53 @@ workspace-default-features = "allow" external-default-features = "allow" # List of crates that are allowed. Use with care! allow = [ - #{ name = "ansi_term", version = "=0.11.0" }, + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is allowed" }, ] # List of crates to deny deny = [ - # Each entry the name of a crate and a version range. If version is - # not specified, all versions will be matched. - #{ name = "ansi_term", version = "=0.11.0" }, - # + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason it is banned" }, # Wrapper crates can optionally be specified to allow the crate when it # is a direct dependency of the otherwise banned crate - #{ name = "ansi_term", version = "=0.11.0", wrappers = [] }, + #{ crate = "ansi_term@0.11.0", wrappers = ["this-crate-directly-depends-on-ansi_term"] }, +] + +# List of features to allow/deny +# Each entry the name of a crate and a version range. If version is +# not specified, all versions will be matched. +#[[bans.features]] +#crate = "reqwest" +# Features to not allow +#deny = ["json"] +# Features to allow +#allow = [ +# "rustls", +# "__rustls", +# "__tls", +# "hyper-rustls", +# "rustls", +# "rustls-pemfile", +# "rustls-tls-webpki-roots", +# "tokio-rustls", +# "webpki-roots", +#] +# If true, the allowed features must exactly match the enabled feature set. If +# this is set there is no point setting `deny` +#exact = true + +# Certain crates/versions that will be skipped when doing duplicate detection. +skip = [ + #"ansi_term@0.11.0", + #{ crate = "ansi_term@0.11.0", reason = "you can specify a reason why it can't be updated/removed" }, +] +# Similarly to `skip` allows you to skip certain crates during duplicate +# detection. Unlike skip, it also includes the entire tree of transitive +# dependencies starting at the specified crate, up to a certain depth, which is +# by default infinite. +skip-tree = [ + #"ansi_term@0.11.0", # will be skipped along with _all_ of its direct and transitive dependencies + #{ crate = "ansi_term@0.11.0", depth = 20 }, ] # This section is considered when running `cargo deny check sources`. @@ -138,3 +230,7 @@ allow-git = [] [sources.allow-org] # 1 or more github.com organizations to allow git sources for github = [] +# 1 or more gitlab.com organizations to allow git sources for +gitlab = [] +# 1 or more bitbucket.org organizations to allow git sources for +bitbucket = [] diff --git a/src/lib.rs b/src/lib.rs index bfd2dc6d..ed9b3f84 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,3 @@ -#![deny(rust_2018_idioms)] -#![warn(missing_debug_implementations)] - //! A library for formatting of text or programming code snippets. //! //! It's primary purpose is to build an ASCII-graphical representation of the snippet @@ -40,6 +37,11 @@ //! cargo add annotate-snippets --dev --feature testing-colors //! ``` +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![warn(clippy::print_stderr)] +#![warn(clippy::print_stdout)] +#![warn(missing_debug_implementations)] + pub mod renderer; mod snippet; diff --git a/tests/fixtures/main.rs b/tests/fixtures/main.rs index c0e351ce..319e03fd 100644 --- a/tests/fixtures/main.rs +++ b/tests/fixtures/main.rs @@ -26,7 +26,7 @@ fn setup(input_path: std::path::PathBuf) -> tryfn::Case { fn test(input_path: &std::path::Path) -> Result<Data, Box<dyn Error>> { let src = std::fs::read_to_string(input_path)?; let (renderer, message): (Renderer, Message<'_>) = - toml::from_str(&src).map(|a: Fixture| (a.renderer.into(), a.message.into()))?; + toml::from_str(&src).map(|a: Fixture<'_>| (a.renderer.into(), a.message.into()))?; let actual = renderer.render(message).to_string(); Ok(Data::from(actual).coerce_to(DataFormat::TermSvg)) } From 9c43a845b0a537f01b6c039626b56b9de91982b9 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 6 Jun 2024 14:00:12 -0500 Subject: [PATCH 227/455] chore: Update lints from epage/_rust template --- Cargo.toml | 66 ++++++++++++++++++++++++++++++++++- benches/simple.rs | 2 +- src/renderer/display_list.rs | 64 ++++++++++++++++----------------- src/renderer/margin.rs | 4 +-- tests/fixtures/deserialize.rs | 14 ++++---- tests/fixtures/main.rs | 2 +- 6 files changed, 108 insertions(+), 44 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a317c711..5022f464 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,4 +50,68 @@ default = [] testing-colors = [] [lints.rust] -rust_2018_idioms = "warn" +rust_2018_idioms = { level = "warn", priority = -1 } +unreachable_pub = "warn" +unsafe_op_in_unsafe_fn = "warn" +unused_lifetimes = "warn" +unused_macro_rules = "warn" +unused_qualifications = "warn" + +[lints.clippy] +bool_assert_comparison = "allow" +branches_sharing_code = "allow" +checked_conversions = "warn" +collapsible_else_if = "allow" +create_dir = "warn" +dbg_macro = "warn" +debug_assert_with_mut_call = "warn" +doc_markdown = "warn" +empty_enum = "warn" +enum_glob_use = "warn" +expl_impl_clone_on_copy = "warn" +explicit_deref_methods = "warn" +explicit_into_iter_loop = "warn" +fallible_impl_from = "warn" +filter_map_next = "warn" +flat_map_option = "warn" +float_cmp_const = "warn" +fn_params_excessive_bools = "warn" +from_iter_instead_of_collect = "warn" +if_same_then_else = "allow" +implicit_clone = "warn" +imprecise_flops = "warn" +inconsistent_struct_constructor = "warn" +inefficient_to_string = "warn" +infinite_loop = "warn" +invalid_upcast_comparisons = "warn" +large_digit_groups = "warn" +large_stack_arrays = "warn" +large_types_passed_by_value = "warn" +let_and_return = "allow" # sometimes good to name what you are returning +linkedlist = "warn" +lossy_float_literal = "warn" +macro_use_imports = "warn" +mem_forget = "warn" +mutex_integer = "warn" +needless_continue = "warn" +needless_for_each = "warn" +negative_feature_names = "warn" +path_buf_push_overwrite = "warn" +ptr_as_ptr = "warn" +rc_mutex = "warn" +redundant_feature_names = "warn" +ref_option_ref = "warn" +rest_pat_in_fully_bound_structs = "warn" +same_functions_in_if_condition = "warn" +self_named_module_files = "warn" +semicolon_if_nothing_returned = "warn" +str_to_string = "warn" +string_add = "warn" +string_add_assign = "warn" +string_lit_as_bytes = "warn" +string_to_string = "warn" +todo = "warn" +trait_duplication_in_bounds = "warn" +verbose_file_reads = "warn" +wildcard_imports = "warn" +zero_sized_map_values = "warn" diff --git a/benches/simple.rs b/benches/simple.rs index fccb70be..723793ea 100644 --- a/benches/simple.rs +++ b/benches/simple.rs @@ -50,7 +50,7 @@ fn create_snippet(renderer: Renderer) { pub fn criterion_benchmark(c: &mut Criterion) { c.bench_function("format", |b| { - b.iter(|| black_box(create_snippet(Renderer::plain()))) + b.iter(|| black_box(create_snippet(Renderer::plain()))); }); } diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index ead3db9a..d94a660b 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -1,4 +1,4 @@ -//! display_list module stores the output model for the snippet. +//! `display_list` module stores the output model for the snippet. //! //! `DisplayList` is a central structure in the crate, which contains //! the structured list of lines to be displayed. @@ -48,9 +48,9 @@ const WARNING_TXT: &str = "warning"; /// List of lines to be displayed. pub(crate) struct DisplayList<'a> { - pub body: Vec<DisplaySet<'a>>, - pub stylesheet: &'a Stylesheet, - pub anonymized_line_numbers: bool, + pub(crate) body: Vec<DisplaySet<'a>>, + pub(crate) stylesheet: &'a Stylesheet, + pub(crate) anonymized_line_numbers: bool, } impl<'a> PartialEq for DisplayList<'a> { @@ -147,8 +147,8 @@ impl<'a> DisplayList<'a> { #[derive(Debug, PartialEq)] pub(crate) struct DisplaySet<'a> { - pub display_lines: Vec<DisplayLine<'a>>, - pub margin: Margin, + pub(crate) display_lines: Vec<DisplayLine<'a>>, + pub(crate) margin: Margin, } impl<'a> DisplaySet<'a> { @@ -493,15 +493,15 @@ impl<'a> DisplaySet<'a> { /// Inline annotation which can be used in either Raw or Source line. #[derive(Debug, PartialEq)] -pub struct Annotation<'a> { - pub annotation_type: DisplayAnnotationType, - pub id: Option<&'a str>, - pub label: Vec<DisplayTextFragment<'a>>, +pub(crate) struct Annotation<'a> { + pub(crate) annotation_type: DisplayAnnotationType, + pub(crate) id: Option<&'a str>, + pub(crate) label: Vec<DisplayTextFragment<'a>>, } /// A single line used in `DisplayList`. #[derive(Debug, PartialEq)] -pub enum DisplayLine<'a> { +pub(crate) enum DisplayLine<'a> { /// A line with `lineno` portion of the slice. Source { lineno: Option<usize>, @@ -519,7 +519,7 @@ pub enum DisplayLine<'a> { /// A source line. #[derive(Debug, PartialEq)] -pub enum DisplaySourceLine<'a> { +pub(crate) enum DisplaySourceLine<'a> { /// A line with the content of the Snippet. Content { text: &'a str, @@ -530,17 +530,17 @@ pub enum DisplaySourceLine<'a> { } #[derive(Debug, PartialEq)] -pub struct DisplaySourceAnnotation<'a> { - pub annotation: Annotation<'a>, - pub range: (usize, usize), - pub annotation_type: DisplayAnnotationType, - pub annotation_part: DisplayAnnotationPart, +pub(crate) struct DisplaySourceAnnotation<'a> { + pub(crate) annotation: Annotation<'a>, + pub(crate) range: (usize, usize), + pub(crate) annotation_type: DisplayAnnotationType, + pub(crate) annotation_part: DisplayAnnotationPart, } /// Raw line - a line which does not have the `lineno` part and is not considered /// a part of the snippet. #[derive(Debug, PartialEq)] -pub enum DisplayRawLine<'a> { +pub(crate) enum DisplayRawLine<'a> { /// A line which provides information about the location of the given /// slice in the project structure. Origin { @@ -566,23 +566,23 @@ pub enum DisplayRawLine<'a> { /// An inline text fragment which any label is composed of. #[derive(Debug, PartialEq)] -pub struct DisplayTextFragment<'a> { - pub content: &'a str, - pub style: DisplayTextStyle, +pub(crate) struct DisplayTextFragment<'a> { + pub(crate) content: &'a str, + pub(crate) style: DisplayTextStyle, } /// A style for the `DisplayTextFragment` which can be visually formatted. /// /// This information may be used to emphasis parts of the label. #[derive(Debug, Clone, Copy, PartialEq)] -pub enum DisplayTextStyle { +pub(crate) enum DisplayTextStyle { Regular, Emphasis, } /// An indicator of what part of the annotation a given `Annotation` is. #[derive(Debug, Clone, PartialEq)] -pub enum DisplayAnnotationPart { +pub(crate) enum DisplayAnnotationPart { /// A standalone, single-line annotation. Standalone, /// A continuation of a multi-line label of an annotation. @@ -595,14 +595,14 @@ pub enum DisplayAnnotationPart { /// A visual mark used in `inline_marks` field of the `DisplaySourceLine`. #[derive(Debug, Clone, PartialEq)] -pub struct DisplayMark { - pub mark_type: DisplayMarkType, - pub annotation_type: DisplayAnnotationType, +pub(crate) struct DisplayMark { + pub(crate) mark_type: DisplayMarkType, + pub(crate) annotation_type: DisplayAnnotationType, } /// A type of the `DisplayMark`. #[derive(Debug, Clone, PartialEq)] -pub enum DisplayMarkType { +pub(crate) enum DisplayMarkType { /// A mark indicating a multiline annotation going through the current line. AnnotationThrough, /// A mark indicating a multiline annotation starting on the given line. @@ -617,7 +617,7 @@ pub enum DisplayMarkType { /// * An underline for `Error` may be `^^^` while for `Warning` it could be `---`. /// * `ColorStylesheet` may use different colors for different annotations. #[derive(Debug, Clone, PartialEq)] -pub enum DisplayAnnotationType { +pub(crate) enum DisplayAnnotationType { None, Error, Warning, @@ -642,7 +642,7 @@ impl From<snippet::Level> for DisplayAnnotationType { /// for multi-slice cases. // TODO: private #[derive(Debug, Clone, PartialEq)] -pub enum DisplayHeaderType { +pub(crate) enum DisplayHeaderType { /// Initial header is the first header in the snippet. Initial, @@ -728,9 +728,9 @@ fn format_message( } if let Some(first) = sets.first_mut() { - body.into_iter().for_each(|line| { + for line in body { first.display_lines.insert(0, line); - }); + } } else { sets.push(DisplaySet { display_lines: body, @@ -1335,7 +1335,7 @@ const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[ ]; fn normalize_whitespace(str: &str) -> String { - let mut s = str.to_string(); + let mut s = str.to_owned(); for (c, replacement) in OUTPUT_REPLACEMENTS { s = s.replace(*c, replacement); } diff --git a/src/renderer/margin.rs b/src/renderer/margin.rs index 3f1b28b0..c4844166 100644 --- a/src/renderer/margin.rs +++ b/src/renderer/margin.rs @@ -5,7 +5,7 @@ const LONG_WHITESPACE: usize = 20; const LONG_WHITESPACE_PADDING: usize = 4; #[derive(Clone, Copy, Debug, PartialEq)] -pub struct Margin { +pub(crate) struct Margin { /// The available whitespace in the left that can be consumed when centering. whitespace_left: usize, /// The column of the beginning of left-most span. @@ -24,7 +24,7 @@ pub struct Margin { } impl Margin { - pub fn new( + pub(crate) fn new( whitespace_left: usize, span_left: usize, span_right: usize, diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs index 2d1452b6..3ddef798 100644 --- a/tests/fixtures/deserialize.rs +++ b/tests/fixtures/deserialize.rs @@ -5,11 +5,11 @@ use annotate_snippets::renderer::DEFAULT_TERM_WIDTH; use annotate_snippets::{Annotation, Level, Message, Renderer, Snippet}; #[derive(Deserialize)] -pub struct Fixture<'a> { +pub(crate) struct Fixture<'a> { #[serde(default)] - pub renderer: RendererDef, + pub(crate) renderer: RendererDef, #[serde(borrow)] - pub message: MessageDef<'a>, + pub(crate) message: MessageDef<'a>, } #[derive(Deserialize)] @@ -88,7 +88,7 @@ impl<'a> From<SnippetDef<'a>> for Snippet<'a> { } = val; let mut snippet = Snippet::source(source).line_start(line_start).fold(fold); if let Some(origin) = origin { - snippet = snippet.origin(origin) + snippet = snippet.origin(origin); } snippet = snippet.annotations(annotations); snippet @@ -127,11 +127,11 @@ impl<'a> From<AnnotationDef<'a>> for Annotation<'a> { } #[derive(Serialize, Deserialize)] -pub struct LabelDef<'a> { +pub(crate) struct LabelDef<'a> { #[serde(with = "LevelDef")] - pub level: Level, + pub(crate) level: Level, #[serde(borrow)] - pub label: &'a str, + pub(crate) label: &'a str, } #[allow(dead_code)] diff --git a/tests/fixtures/main.rs b/tests/fixtures/main.rs index 319e03fd..81d0246c 100644 --- a/tests/fixtures/main.rs +++ b/tests/fixtures/main.rs @@ -15,7 +15,7 @@ fn main() { fn setup(input_path: std::path::PathBuf) -> tryfn::Case { let name = input_path.file_name().unwrap().to_str().unwrap().to_owned(); - let expected = tryfn::Data::read_from(&input_path.with_extension("svg"), None); + let expected = Data::read_from(&input_path.with_extension("svg"), None); tryfn::Case { name, fixture: input_path, From 4ded7b25a0b0b6ac6bfafe8b5de1832f6f1bcbca Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 6 Jun 2024 14:32:46 -0500 Subject: [PATCH 228/455] chore: Drop MSRV to 1.65 --- Cargo.lock | 450 +++++++++++++++++++++++++---------------------------- Cargo.toml | 2 +- 2 files changed, 217 insertions(+), 235 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 70efddcf..1e3073f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -21,7 +21,7 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" name = "annotate-snippets" version = "0.11.2" dependencies = [ - "anstream", + "anstream 0.6.14", "anstyle", "criterion", "difference", @@ -33,6 +33,21 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "anstream" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon 1.0.2", + "colorchoice", + "is-terminal", + "utf8parse", +] + [[package]] name = "anstream" version = "0.6.14" @@ -42,7 +57,7 @@ dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", - "anstyle-wincon", + "anstyle-wincon 3.0.3", "colorchoice", "is_terminal_polyfill", "utf8parse", @@ -50,44 +65,44 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-lossy" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a0444767dbd4aea9355cb47a370eb184dbfe918875e127eff52cb9d1638181" +checksum = "6fcff6599f06e21b0165c85052ccd6e67dc388ddd1c516a9dc5f55dc8cacf004" dependencies = [ "anstyle", ] [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-svg" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6ddad447b448d6d5db36b31cbd3ff27c7af071619501998eeceab01968287a" +checksum = "bbbf0bf947d663010f0b4132f28ca08da9151f3b9035fa7578a38de521c1d1aa" dependencies = [ - "anstream", + "anstream 0.6.14", "anstyle", "anstyle-lossy", "html-escape", @@ -96,31 +111,35 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "windows-sys 0.48.0", ] [[package]] -name = "autocfg" -version = "1.1.0" +name = "anstyle-wincon" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] [[package]] -name = "bitflags" -version = "2.4.1" +name = "autocfg" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "bstr" -version = "1.8.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" dependencies = [ "memchr", "serde", @@ -146,9 +165,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "ciborium" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ "ciborium-io", "ciborium-ll", @@ -157,15 +176,15 @@ dependencies = [ [[package]] name = "ciborium-io" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] name = "ciborium-ll" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" dependencies = [ "ciborium-io", "half", @@ -173,21 +192,22 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.7" +version = "4.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b" +checksum = "fb690e81c7840c0d7aade59f242ea3b41b9bc27bcd5997890e7702ae4b32e487" dependencies = [ "clap_builder", "clap_derive", + "once_cell", ] [[package]] name = "clap_builder" -version = "4.4.7" +version = "4.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663" +checksum = "5ed2e96bc16d8d740f6f48d663eddf4b8a0983e79210fd55479b7bcd0a69860e" dependencies = [ - "anstream", + "anstream 0.3.2", "anstyle", "clap_lex", "strsim", @@ -195,9 +215,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.7" +version = "4.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" dependencies = [ "heck", "proc-macro2", @@ -207,15 +227,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "criterion" @@ -255,36 +275,34 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.15" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", - "memoffset", - "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" -dependencies = [ - "cfg-if", -] +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "difference" @@ -294,19 +312,9 @@ checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" [[package]] name = "either" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" - -[[package]] -name = "errno" -version = "0.3.8" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" [[package]] name = "escape8259" @@ -319,9 +327,9 @@ dependencies = [ [[package]] name = "escargot" -version = "0.5.10" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f474c6844cbd04e783d0f25757583db4f491770ca618bedf2fb01815fc79939" +checksum = "650eb5f6eeda986377996e9ed570cbc20cc16d30440696f82f129c863e4e3e83" dependencies = [ "log", "once_cell", @@ -350,9 +358,12 @@ dependencies = [ [[package]] name = "half" -version = "1.8.2" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" +dependencies = [ + "crunchy", +] [[package]] name = "heck" @@ -362,9 +373,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "html-escape" @@ -377,36 +388,40 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.22" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" +checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" dependencies = [ - "crossbeam-deque", "globset", + "lazy_static", "log", "memchr", - "regex-automata", + "regex", "same-file", + "thread_local", "walkdir", "winapi-util", ] [[package]] name = "is-terminal" -version = "0.4.9" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" dependencies = [ "hermit-abi", - "rustix", - "windows-sys 0.48.0", + "libc", + "windows-sys 0.52.0", ] [[package]] name = "is_terminal_polyfill" -version = "1.70.0" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +checksum = "b52b2de84ed0341893ce61ca1af04fa54eea0a764ecc38c6855cc5db84dc1927" +dependencies = [ + "is-terminal", +] [[package]] name = "itertools" @@ -419,30 +434,36 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.65" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" -version = "0.2.153" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libtest-mimic" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f0f4c6f44ecfd52e8b443f2ad18f2b996540135771561283c2352ce56a1c70b" +checksum = "cc0bda45ed5b3a2904262c1bb91e526127aa70e7ef3758aba2ef93cf896b9b58" dependencies = [ "clap", "escape8259", @@ -450,32 +471,17 @@ dependencies = [ "threadpool", ] -[[package]] -name = "linux-raw-sys" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" - [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" - -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "normalize-line-endings" @@ -485,9 +491,9 @@ checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -504,9 +510,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "oorandom" @@ -516,9 +522,9 @@ checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "os_pipe" -version = "1.1.5" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57119c3b893986491ec9aa85056780d3a0f3cf4da7cc09dd3650dbd6c6738fb9" +checksum = "29d73ba8daf8fac13b0501d1abeddcfe21ba7401ada61a819144b6c2a4f32209" dependencies = [ "libc", "windows-sys 0.52.0", @@ -526,9 +532,9 @@ dependencies = [ [[package]] name = "plotters" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" dependencies = [ "num-traits", "plotters-backend", @@ -539,42 +545,42 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" +checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7" [[package]] name = "plotters-svg" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705" dependencies = [ "plotters-backend", ] [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] [[package]] name = "rayon" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -582,9 +588,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -592,9 +598,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.2" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", @@ -604,9 +610,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", @@ -615,34 +621,21 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" - -[[package]] -name = "rustix" -version = "0.38.31" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.52.0", -] +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" @@ -653,12 +646,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - [[package]] name = "serde" version = "1.0.203" @@ -681,9 +668,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", @@ -692,9 +679,9 @@ dependencies = [ [[package]] name = "similar" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21" +checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" [[package]] name = "snapbox" @@ -702,7 +689,7 @@ version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94204b12a4d3550420babdb4148c6639692e4e3e61060866929c5107f208aeb6" dependencies = [ - "anstream", + "anstream 0.6.14", "anstyle", "anstyle-svg", "escargot", @@ -722,7 +709,7 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1f4c14672714436c09254801c934b203196a51182a5107fb76591c7cc56424d" dependencies = [ - "anstream", + "anstream 0.6.14", ] [[package]] @@ -733,9 +720,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "2.0.48" +version = "2.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" dependencies = [ "proc-macro2", "quote", @@ -751,6 +738,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "threadpool" version = "1.8.1" @@ -798,9 +795,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "utf8-width" @@ -825,9 +822,9 @@ dependencies = [ [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -835,9 +832,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -845,9 +842,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", @@ -860,9 +857,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -870,9 +867,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", @@ -883,51 +880,29 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.88" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "web-sys" -version = "0.3.65" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys 0.52.0", ] -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-sys" version = "0.48.0" @@ -943,7 +918,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -963,17 +938,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -984,9 +960,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -996,9 +972,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -1008,9 +984,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -1020,9 +1002,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -1032,9 +1014,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -1044,9 +1026,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -1056,6 +1038,6 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" diff --git a/Cargo.toml b/Cargo.toml index 5022f464..12fadf62 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "annotate-snippets" version = "0.11.2" edition = "2021" -rust-version = "1.73" # MSRV +rust-version = "1.65" # MSRV authors = ["Zibi Braniecki <gandalf@mozilla.com>"] description = "Library for building code annotations" license = "Apache-2.0/MIT" From 8908d92ea2198e3b18be16c3a5ef9953a8d29e15 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 6 Jun 2024 19:33:34 +0000 Subject: [PATCH 229/455] chore(deps): Update Rust Stable to v1.78 --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a6216528..b643aba8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,7 +86,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.76" # STABLE + toolchain: "1.78" # STABLE - uses: Swatinem/rust-cache@v2 - name: Check documentation env: @@ -101,7 +101,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.76" # STABLE + toolchain: "1.78" # STABLE components: rustfmt - uses: Swatinem/rust-cache@v2 - name: Check formatting @@ -117,7 +117,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.76" # STABLE + toolchain: "1.78" # STABLE components: clippy - uses: Swatinem/rust-cache@v2 - name: Install SARIF tools From 3c15fc804e7d98f6f3eb9fb9891b5b797d0f0f98 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 6 Jun 2024 14:54:09 -0500 Subject: [PATCH 230/455] docs: Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d379628b..51665a95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). <!-- next-header --> ## [Unreleased] - ReleaseDate +### Fixes + +- Dropped MSRV to 1.65 + ## [0.11.2] - 2024-04-27 ### Added From 92275d29d8e6cb123e7e92f9a3d89c5a332d52cc Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 6 Jun 2024 14:04:54 -0600 Subject: [PATCH 231/455] chore: Release annotate-snippets version 0.11.3 --- CHANGELOG.md | 5 ++++- Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51665a95..40586d5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). <!-- next-header --> ## [Unreleased] - ReleaseDate +## [0.11.3] - 2024-06-06 + ### Fixes - Dropped MSRV to 1.65 @@ -136,7 +138,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Update the syntax to Rust 2018 idioms. (#4) <!-- next-url --> -[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.2...HEAD +[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.3...HEAD +[0.11.3]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.2...0.11.3 [0.11.2]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.1...0.11.2 [0.11.1]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.0...0.11.1 [0.11.0]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.10.2...0.11.0 diff --git a/Cargo.lock b/Cargo.lock index 1e3073f4..a38d9da0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,7 +19,7 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "annotate-snippets" -version = "0.11.2" +version = "0.11.3" dependencies = [ "anstream 0.6.14", "anstyle", diff --git a/Cargo.toml b/Cargo.toml index 12fadf62..1b6bd100 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "annotate-snippets" -version = "0.11.2" +version = "0.11.3" edition = "2021" rust-version = "1.65" # MSRV authors = ["Zibi Braniecki <gandalf@mozilla.com>"] From ca313bfb3d8895f8602724f782ad0012ddf26200 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 13 Jun 2024 14:20:48 +0000 Subject: [PATCH 232/455] chore(deps): Update Rust Stable to v1.79 --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b643aba8..e1d62e47 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,7 +86,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.78" # STABLE + toolchain: "1.79" # STABLE - uses: Swatinem/rust-cache@v2 - name: Check documentation env: @@ -101,7 +101,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.78" # STABLE + toolchain: "1.79" # STABLE components: rustfmt - uses: Swatinem/rust-cache@v2 - name: Check formatting @@ -117,7 +117,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.78" # STABLE + toolchain: "1.79" # STABLE components: clippy - uses: Swatinem/rust-cache@v2 - name: Install SARIF tools From 004e6f9388aeb2755d9903d98f83d13ba7a9d172 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Fri, 14 Jun 2024 12:34:37 -0600 Subject: [PATCH 233/455] test: Cleanup ann_multiline2 source --- tests/fixtures/no-color/ann_multiline2.svg | 4 ++-- tests/fixtures/no-color/ann_multiline2.toml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/fixtures/no-color/ann_multiline2.svg b/tests/fixtures/no-color/ann_multiline2.svg index 18a9bf6d..de5c6f02 100644 --- a/tests/fixtures/no-color/ann_multiline2.svg +++ b/tests/fixtures/no-color/ann_multiline2.svg @@ -22,11 +22,11 @@ </tspan> <tspan x="10px" y="64px"><tspan> |</tspan> </tspan> - <tspan x="10px" y="82px"><tspan>26 | This is an exampl</tspan> + <tspan x="10px" y="82px"><tspan>26 | This is an example</tspan> </tspan> <tspan x="10px" y="100px"><tspan> | ____________^</tspan> </tspan> - <tspan x="10px" y="118px"><tspan>27 | | e of an edge case of an annotation overflowing</tspan> + <tspan x="10px" y="118px"><tspan>27 | | of an edge case of an annotation overflowing</tspan> </tspan> <tspan x="10px" y="136px"><tspan> | |_^ this should not be on separate lines</tspan> </tspan> diff --git a/tests/fixtures/no-color/ann_multiline2.toml b/tests/fixtures/no-color/ann_multiline2.toml index dd853324..afb3aa9c 100644 --- a/tests/fixtures/no-color/ann_multiline2.toml +++ b/tests/fixtures/no-color/ann_multiline2.toml @@ -5,8 +5,8 @@ title = "spacing error found" [[message.snippets]] source = """ -This is an exampl -e of an edge case of an annotation overflowing +This is an example +of an edge case of an annotation overflowing to exactly one character on next line. """ line_start = 26 @@ -15,4 +15,4 @@ fold = false [[message.snippets.annotations]] label = "this should not be on separate lines" level = "Error" -range = [11, 18] +range = [11, 19] From c68600d669efa3c9a371067c770d711598b3422e Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Fri, 14 Jun 2024 16:46:53 -0600 Subject: [PATCH 234/455] fix: Improve annotating line endings --- src/renderer/display_list.rs | 87 +++- tests/fixtures/no-color/ann_multiline2.svg | 14 +- .../fixtures/no-color/fold_ann_multiline.svg | 14 +- tests/formatter.rs | 429 ++++++++++++++++++ 4 files changed, 511 insertions(+), 33 deletions(-) diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index d94a660b..bc46f7f5 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -524,6 +524,7 @@ pub(crate) enum DisplaySourceLine<'a> { Content { text: &'a str, range: (usize, usize), // meta information for annotation placement. + end_line: EndLine, }, /// An empty source line. Empty, @@ -658,7 +659,8 @@ impl<'a> CursorLines<'a> { } } -enum EndLine { +#[derive(Copy, Clone, Debug, PartialEq)] +pub(crate) enum EndLine { Eof = 0, Crlf = 1, Lf = 2, @@ -847,13 +849,20 @@ fn format_header<'a>( for item in body { if let DisplayLine::Source { - line: DisplaySourceLine::Content { text, range }, + line: + DisplaySourceLine::Content { + text, + range, + end_line, + }, lineno, .. } = item { - if main_range >= range.0 && main_range <= range.1 { - let char_column = text[0..(main_range - range.0)].chars().count(); + if main_range >= range.0 && main_range <= range.1 + *end_line as usize { + let char_column = text[0..(main_range - range.0).min(text.len())] + .chars() + .count(); col = char_column + 1; line_offset = lineno.unwrap_or(1); break; @@ -927,8 +936,18 @@ fn fold_body(body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> { let mut unhighlighed_lines = vec![]; for line in body { match &line { - DisplayLine::Source { annotations, .. } => { - if annotations.is_empty() { + DisplayLine::Source { + annotations, + inline_marks, + .. + } => { + if annotations.is_empty() + // A multiline start mark (`/`) needs be treated as an + // annotation or the line could get folded. + && inline_marks + .iter() + .all(|m| m.mark_type != DisplayMarkType::AnnotationStart) + { unhighlighed_lines.push(line); } else { if lines.is_empty() { @@ -1016,12 +1035,14 @@ fn format_body( for (idx, (line, end_line)) in CursorLines::new(snippet.source).enumerate() { let line_length: usize = line.len(); let line_range = (current_index, current_index + line_length); + let end_line_size = end_line as usize; body.push(DisplayLine::Source { lineno: Some(current_line), inline_marks: vec![], line: DisplaySourceLine::Content { text: line, range: line_range, + end_line, }, annotations: vec![], }); @@ -1045,7 +1066,7 @@ fn format_body( let line_start_index = line_range.0; let line_end_index = line_range.1; current_line += 1; - current_index += line_length + end_line as usize; + current_index += line_length + end_line_size; // It would be nice to use filter_drain here once it's stable. annotations.retain(|annotation| { @@ -1057,18 +1078,24 @@ fn format_body( }; let label_right = annotation.label.map_or(0, |label| label.len() + 1); match annotation.range { - Range { start, .. } if start > line_end_index => true, + // This handles if the annotation is on the next line. We add + // the `end_line_size` to account for annotating the line end. + Range { start, .. } if start > line_end_index + end_line_size => true, + // This handles the case where an annotation is contained + // within the current line including any line-end characters. Range { start, end } - if start >= line_start_index && end <= line_end_index - // Allow annotating eof or stripped eol - || start == line_end_index && end - start <= 1 => + if start >= line_start_index + // We add at least one to `line_end_index` to allow + // highlighting the end of a file + && end <= line_end_index + max(end_line_size, 1) => { if let DisplayLine::Source { ref mut annotations, .. } = body[body_idx] { - let annotation_start_col = line[0..(start - line_start_index)] + let annotation_start_col = line + [0..(start - line_start_index).min(line_length)] .chars() .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) .sum::<usize>(); @@ -1101,11 +1128,16 @@ fn format_body( } false } + // This handles the case where a multiline annotation starts + // somewhere on the current line, including any line-end chars Range { start, end } if start >= line_start_index - && start <= line_end_index + // The annotation can start on a line ending + && start <= line_end_index + end_line_size.saturating_sub(1) && end > line_end_index => { + // Special case for multiline annotations that start at the + // beginning of a line, which requires a special mark (`/`) if start - line_start_index == 0 { if let DisplayLine::Source { ref mut inline_marks, @@ -1122,7 +1154,8 @@ fn format_body( .. } = body[body_idx] { - let annotation_start_col = line[0..(start - line_start_index)] + let annotation_start_col = line + [0..(start - line_start_index).min(line_length)] .chars() .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) .sum::<usize>(); @@ -1147,7 +1180,11 @@ fn format_body( } true } - Range { start, end } if start < line_start_index && end > line_end_index => { + // This handles the case where a multiline annotation starts + // somewhere before this line and ends after it as well + Range { start, end } + if start < line_start_index && end > line_end_index + max(end_line_size, 1) => + { if let DisplayLine::Source { ref mut inline_marks, .. @@ -1160,10 +1197,14 @@ fn format_body( } true } + // This handles the case where a multiline annotation ends + // somewhere on the current line, including any line-end chars Range { start, end } if start < line_start_index && end >= line_start_index - && end <= line_end_index => + // We add at least one to `line_end_index` to allow + // highlighting the end of a file + && end <= line_end_index + max(end_line_size, 1) => { if let DisplayLine::Source { ref mut inline_marks, @@ -1175,13 +1216,21 @@ fn format_body( mark_type: DisplayMarkType::AnnotationThrough, annotation_type: DisplayAnnotationType::from(annotation.level), }); - let end_mark = line[0..(end - line_start_index)] + let end_mark = line[0..(end - line_start_index).min(line_length)] .chars() .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) .sum::<usize>() .saturating_sub(1); - - let end_plus_one = end_mark + 1; + // If the annotation ends on a line-end character, we + // need to annotate one past the end of the line + let (end_mark, end_plus_one) = if end > line_end_index + // Special case for highlighting the end of a file + || (end == line_end_index + 1 && end_line_size == 0) + { + (end_mark + 1, end_mark + 2) + } else { + (end_mark, end_mark + 1) + }; span_left_margin = min(span_left_margin, end_mark); span_right_margin = max(span_right_margin, end_plus_one); diff --git a/tests/fixtures/no-color/ann_multiline2.svg b/tests/fixtures/no-color/ann_multiline2.svg index de5c6f02..49c2c4b7 100644 --- a/tests/fixtures/no-color/ann_multiline2.svg +++ b/tests/fixtures/no-color/ann_multiline2.svg @@ -1,4 +1,4 @@ -<svg width="740px" height="182px" xmlns="http://www.w3.org/2000/svg"> +<svg width="740px" height="164px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -22,17 +22,15 @@ </tspan> <tspan x="10px" y="64px"><tspan> |</tspan> </tspan> - <tspan x="10px" y="82px"><tspan>26 | This is an example</tspan> + <tspan x="10px" y="82px"><tspan>26 | This is an example</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> | ____________^</tspan> + <tspan x="10px" y="100px"><tspan> | ^^^^^^^ this should not be on separate lines</tspan> </tspan> - <tspan x="10px" y="118px"><tspan>27 | | of an edge case of an annotation overflowing</tspan> + <tspan x="10px" y="118px"><tspan>27 | of an edge case of an annotation overflowing</tspan> </tspan> - <tspan x="10px" y="136px"><tspan> | |_^ this should not be on separate lines</tspan> + <tspan x="10px" y="136px"><tspan>28 | to exactly one character on next line.</tspan> </tspan> - <tspan x="10px" y="154px"><tspan>28 | to exactly one character on next line.</tspan> -</tspan> - <tspan x="10px" y="172px"><tspan> |</tspan> + <tspan x="10px" y="154px"><tspan> |</tspan> </tspan> </text> diff --git a/tests/fixtures/no-color/fold_ann_multiline.svg b/tests/fixtures/no-color/fold_ann_multiline.svg index 0d2d67cd..f82fe25d 100644 --- a/tests/fixtures/no-color/fold_ann_multiline.svg +++ b/tests/fixtures/no-color/fold_ann_multiline.svg @@ -1,4 +1,4 @@ -<svg width="869px" height="218px" xmlns="http://www.w3.org/2000/svg"> +<svg width="869px" height="236px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -28,15 +28,17 @@ </tspan> <tspan x="10px" y="118px"><tspan>52 | / for ann in annotations {</tspan> </tspan> - <tspan x="10px" y="136px"><tspan>... |</tspan> + <tspan x="10px" y="136px"><tspan>53 | | match (ann.range.0, ann.range.1) {</tspan> </tspan> - <tspan x="10px" y="154px"><tspan>71 | | }</tspan> + <tspan x="10px" y="154px"><tspan>... |</tspan> </tspan> - <tspan x="10px" y="172px"><tspan>72 | | }</tspan> + <tspan x="10px" y="172px"><tspan>71 | | }</tspan> </tspan> - <tspan x="10px" y="190px"><tspan> | |_____^ expected enum `std::option::Option`, found ()</tspan> + <tspan x="10px" y="190px"><tspan>72 | | }</tspan> </tspan> - <tspan x="10px" y="208px"><tspan> |</tspan> + <tspan x="10px" y="208px"><tspan> | |_____^ expected enum `std::option::Option`, found ()</tspan> +</tspan> + <tspan x="10px" y="226px"><tspan> |</tspan> </tspan> </text> diff --git a/tests/formatter.rs b/tests/formatter.rs index 5b746e12..fa3927c4 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -303,3 +303,432 @@ LL | abc let renderer = Renderer::plain().anonymized_line_numbers(true); assert_data_eq!(renderer.render(input).to_string(), expected); } + +#[test] +fn issue_130() { + let input = Level::Error.title("dummy").snippet( + Snippet::source("foo\nbar\nbaz") + .origin("file/path") + .line_start(3) + .fold(true) + .annotation(Level::Error.span(4..11)), // bar\nbaz + ); + + let expected = str![[r#" +error: dummy + --> file/path:4:1 + | +4 | / bar +5 | | baz + | |___^ + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn unterminated_string_multiline() { + let source = "\ +a\" +// ... +"; + let input = Level::Error.title("").snippet( + Snippet::source(source) + .origin("file/path") + .line_start(3) + .fold(true) + .annotation(Level::Error.span(0..10)), // 1..10 works + ); + let expected = str![[r#" +error + --> file/path:3:1 + | +3 | / a" +4 | | // ... + | |_______^ + | +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(false); + assert_data_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn char_and_nl_annotate_char() { + let source = "a\r\nb"; + let input = Level::Error.title("").snippet( + Snippet::source(source) + .origin("file/path") + .line_start(3) + .annotation(Level::Error.span(0..2)), // a\r + ); + let expected = str![[r#" +error + --> file/path:3:1 + | +3 | a + | ^ +4 | b + |"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(false); + assert_data_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn char_eol_annotate_char() { + let source = "a\r\nb"; + let input = Level::Error.title("").snippet( + Snippet::source(source) + .origin("file/path") + .line_start(3) + .annotation(Level::Error.span(0..3)), // a\r\n + ); + let expected = str![[r#" +error + --> file/path:3:1 + | +3 | a + | ^ +4 | b + |"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(false); + assert_data_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn char_eol_annotate_char_double_width() { + let snippets = Level::Error.title("").snippet( + Snippet::source("こん\r\nにちは\r\n世界") + .origin("<current file>") + .annotation(Level::Error.span(3..8)), // ん\r\n + ); + + let expected = str![[r#" +error + --> <current file>:1:2 + | +1 | こん + | ^^ +2 | にちは +3 | 世界 + | +"#]]; + + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(snippets).to_string(), expected); +} + +#[test] +fn annotate_eol() { + let source = "a\r\nb"; + let input = Level::Error.title("").snippet( + Snippet::source(source) + .origin("file/path") + .line_start(3) + .annotation(Level::Error.span(1..2)), // \r + ); + let expected = str![[r#" +error + --> file/path:3:2 + | +3 | a + | ^ +4 | b + |"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(false); + assert_data_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn annotate_eol2() { + let source = "a\r\nb"; + let input = Level::Error.title("").snippet( + Snippet::source(source) + .origin("file/path") + .line_start(3) + .annotation(Level::Error.span(1..3)), // \r\n + ); + let expected = str![[r#" +error + --> file/path:3:2 + | +3 | a + | ^ +4 | b + |"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(false); + assert_data_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn annotate_eol3() { + let source = "a\r\nb"; + let input = Level::Error.title("").snippet( + Snippet::source(source) + .origin("file/path") + .line_start(3) + .annotation(Level::Error.span(2..3)), // \n + ); + let expected = str![[r#" +error + --> file/path:3:2 + | +3 | a + | ^ +4 | b + |"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(false); + assert_data_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn annotate_eol4() { + let source = "a\r\nb"; + let input = Level::Error.title("").snippet( + Snippet::source(source) + .origin("file/path") + .line_start(3) + .annotation(Level::Error.span(2..2)), // \n + ); + let expected = str![[r#" +error + --> file/path:3:2 + | +3 | a + | ^ +4 | b + |"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(false); + assert_data_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn annotate_eol_double_width() { + let snippets = Level::Error.title("").snippet( + Snippet::source("こん\r\nにちは\r\n世界") + .origin("<current file>") + .annotation(Level::Error.span(7..8)), // \n + ); + + let expected = str![[r#" +error + --> <current file>:1:3 + | +1 | こん + | ^ +2 | にちは +3 | 世界 + | +"#]]; + + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(snippets).to_string(), expected); +} + +#[test] +fn multiline_eol_start() { + let source = "a\r\nb"; + let input = Level::Error.title("").snippet( + Snippet::source(source) + .origin("file/path") + .line_start(3) + .annotation(Level::Error.span(1..4)), // \r\nb + ); + let expected = str![[r#" +error + --> file/path:3:2 + | +3 | a + | __^ +4 | | b + | |_^ + |"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(false); + assert_data_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn multiline_eol_start2() { + let source = "a\r\nb"; + let input = Level::Error.title("").snippet( + Snippet::source(source) + .origin("file/path") + .line_start(3) + .annotation(Level::Error.span(2..4)), // \nb + ); + let expected = str![[r#" +error + --> file/path:3:2 + | +3 | a + | __^ +4 | | b + | |_^ + |"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(false); + assert_data_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn multiline_eol_start3() { + let source = "a\nb"; + let input = Level::Error.title("").snippet( + Snippet::source(source) + .origin("file/path") + .line_start(3) + .annotation(Level::Error.span(1..3)), // \nb + ); + let expected = str![[r#" +error + --> file/path:3:2 + | +3 | a + | __^ +4 | | b + | |_^ + |"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(false); + assert_data_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn multiline_eol_start_double_width() { + let snippets = Level::Error.title("").snippet( + Snippet::source("こん\r\nにちは\r\n世界") + .origin("<current file>") + .annotation(Level::Error.span(7..11)), // \r\nに + ); + + let expected = str![[r#" +error + --> <current file>:1:3 + | +1 | こん + | _____^ +2 | | にちは + | |__^ +3 | 世界 + | +"#]]; + + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(snippets).to_string(), expected); +} + +#[test] +fn multiline_eol_start_eol_end() { + let source = "a\nb\nc"; + let input = Level::Error.title("").snippet( + Snippet::source(source) + .origin("file/path") + .line_start(3) + .annotation(Level::Error.span(1..4)), // \nb\n + ); + let expected = str![[r#" +error + --> file/path:3:2 + | +3 | a + | __^ +4 | | b + | |__^ +5 | c + | +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(false); + assert_data_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn multiline_eol_start_eol_end2() { + let source = "a\r\nb\r\nc"; + let input = Level::Error.title("").snippet( + Snippet::source(source) + .origin("file/path") + .line_start(3) + .annotation(Level::Error.span(2..5)), // \nb\r + ); + let expected = str![[r#" +error + --> file/path:3:2 + | +3 | a + | __^ +4 | | b + | |__^ +5 | c + | +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(false); + assert_data_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn multiline_eol_start_eol_end3() { + let source = "a\r\nb\r\nc"; + let input = Level::Error.title("").snippet( + Snippet::source(source) + .origin("file/path") + .line_start(3) + .annotation(Level::Error.span(2..6)), // \nb\r\n + ); + let expected = str![[r#" +error + --> file/path:3:2 + | +3 | a + | __^ +4 | | b + | |__^ +5 | c + | +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(false); + assert_data_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn multiline_eol_start_eof_end() { + let source = "a\r\nb"; + let input = Level::Error.title("").snippet( + Snippet::source(source) + .origin("file/path") + .line_start(3) + .annotation(Level::Error.span(1..5)), // \r\nb(EOF) + ); + let expected = str![[r#" +error + --> file/path:3:2 + | +3 | a + | __^ +4 | | b + | |__^ + | +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(false); + assert_data_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn multiline_eol_start_eof_end_double_width() { + let source = "ん\r\nに"; + let input = Level::Error.title("").snippet( + Snippet::source(source) + .origin("file/path") + .line_start(3) + .annotation(Level::Error.span(3..9)), // \r\nに(EOF) + ); + let expected = str![[r#" +error + --> file/path:3:2 + | +3 | ん + | ___^ +4 | | に + | |___^ + | +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(false); + assert_data_eq!(renderer.render(input).to_string(), expected); +} From b3b10c98824ff07963ed58c79a795c5b641db15e Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Fri, 14 Jun 2024 18:28:43 -0600 Subject: [PATCH 235/455] docs: Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40586d5a..4c38f1fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). <!-- next-header --> ## [Unreleased] - ReleaseDate +### Fixes + +- Annotations for `\r\n` are now correctly handled [#131](https://github.com/rust-lang/annotate-snippets-rs/pull/131) + ## [0.11.3] - 2024-06-06 ### Fixes From fb498f918087557f48dd34b81f3bf4081fe6e961 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Fri, 14 Jun 2024 18:30:02 -0600 Subject: [PATCH 236/455] chore: Release annotate-snippets version 0.11.4 --- CHANGELOG.md | 5 ++++- Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c38f1fd..1c7d2d22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). <!-- next-header --> ## [Unreleased] - ReleaseDate +## [0.11.4] - 2024-06-15 + ### Fixes - Annotations for `\r\n` are now correctly handled [#131](https://github.com/rust-lang/annotate-snippets-rs/pull/131) @@ -142,7 +144,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Update the syntax to Rust 2018 idioms. (#4) <!-- next-url --> -[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.3...HEAD +[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.4...HEAD +[0.11.4]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.3...0.11.4 [0.11.3]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.2...0.11.3 [0.11.2]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.1...0.11.2 [0.11.1]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.0...0.11.1 diff --git a/Cargo.lock b/Cargo.lock index a38d9da0..40a064e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,7 +19,7 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "annotate-snippets" -version = "0.11.3" +version = "0.11.4" dependencies = [ "anstream 0.6.14", "anstyle", diff --git a/Cargo.toml b/Cargo.toml index 1b6bd100..62ab3871 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "annotate-snippets" -version = "0.11.3" +version = "0.11.4" edition = "2021" rust-version = "1.65" # MSRV authors = ["Zibi Braniecki <gandalf@mozilla.com>"] From 52bf734fc084c6a0aa5eb0a44b2d47c88c0868e4 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 19 Jun 2024 11:38:04 -0600 Subject: [PATCH 237/455] test: Add tests for multiple annotations per line --- tests/formatter.rs | 165 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/tests/formatter.rs b/tests/formatter.rs index fa3927c4..d0ac3692 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -732,3 +732,168 @@ error let renderer = Renderer::plain().anonymized_line_numbers(false); assert_data_eq!(renderer.render(input).to_string(), expected); } + +#[test] +fn two_single_line_same_line() { + let source = r#"bar = { version = "0.1.0", optional = true }"#; + let input = Level::Error.title("unused optional dependency").snippet( + Snippet::source(source) + .origin("Cargo.toml") + .line_start(4) + .annotation( + Level::Error + .span(0..3) + .label("I need this to be really long so I can test overlaps"), + ) + .annotation( + Level::Info + .span(27..42) + .label("This should also be long but not too long"), + ), + ); + let expected = str![[r#" +error: unused optional dependency + --> Cargo.toml:4:1 + | +4 | bar = { version = "0.1.0", optional = true } + | ^^^ I need this to be really long so I can test overlaps + | --------------- info: This should also be long but not too long + | +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(false); + assert_data_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn multi_and_single() { + let source = r#"bar = { version = "0.1.0", optional = true } +this is another line +so is this +bar = { version = "0.1.0", optional = true } +"#; + let input = Level::Error.title("unused optional dependency").snippet( + Snippet::source(source) + .line_start(4) + .annotation( + Level::Error + .span(41..119) + .label("I need this to be really long so I can test overlaps"), + ) + .annotation( + Level::Info + .span(27..42) + .label("This should also be long but not too long"), + ), + ); + let expected = str![[r#" +error: unused optional dependency + | +4 | bar = { version = "0.1.0", optional = true } + | __________________________________________^ + | --------------- info: This should also be long but not too long +5 | | this is another line +6 | | so is this +7 | | bar = { version = "0.1.0", optional = true } + | |__________________________________________^ I need this to be really long so I can test overlaps + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn two_multi_and_single() { + let source = r#"bar = { version = "0.1.0", optional = true } +this is another line +so is this +bar = { version = "0.1.0", optional = true } +"#; + let input = Level::Error.title("unused optional dependency").snippet( + Snippet::source(source) + .line_start(4) + .annotation( + Level::Error + .span(41..119) + .label("I need this to be really long so I can test overlaps"), + ) + .annotation( + Level::Error + .span(8..102) + .label("I need this to be really long so I can test overlaps"), + ) + .annotation( + Level::Info + .span(27..42) + .label("This should also be long but not too long"), + ), + ); + let expected = str![[r#" +error: unused optional dependency + | +4 | bar = { version = "0.1.0", optional = true } + | __________________________________________^ + | _________^ + | --------------- info: This should also be long but not too long +5 | || this is another line +6 | || so is this +7 | || bar = { version = "0.1.0", optional = true } + | ||__________________________________________^ I need this to be really long so I can test overlaps + | ||_________________________^ I need this to be really long so I can test overlaps + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn three_multi_and_single() { + let source = r#"bar = { version = "0.1.0", optional = true } +this is another line +so is this +bar = { version = "0.1.0", optional = true } +this is another line +"#; + let input = Level::Error.title("unused optional dependency").snippet( + Snippet::source(source) + .line_start(4) + .annotation( + Level::Error + .span(41..119) + .label("I need this to be really long so I can test overlaps"), + ) + .annotation( + Level::Error + .span(8..102) + .label("I need this to be really long so I can test overlaps"), + ) + .annotation( + Level::Error + .span(48..126) + .label("I need this to be really long so I can test overlaps"), + ) + .annotation( + Level::Info + .span(27..42) + .label("This should also be long but not too long"), + ), + ); + let expected = str![[r#" +error: unused optional dependency + | +4 | bar = { version = "0.1.0", optional = true } + | __________________________________________^ + | _________^ + | --------------- info: This should also be long but not too long +5 | || this is another line + | ||____^ +6 | ||| so is this +7 | ||| bar = { version = "0.1.0", optional = true } + | |||__________________________________________^ I need this to be really long so I can test overlaps + | |||_________________________^ I need this to be really long so I can test overlaps +8 | | this is another line + | |____^ I need this to be really long so I can test overlaps + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input).to_string(), expected); +} From d1d3a628e5eab73c5524d919820d85e2969e80ac Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 19 Jun 2024 11:39:56 -0600 Subject: [PATCH 238/455] test: Add some of Rust's parser tests --- tests/rustc_tests.rs | 778 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 778 insertions(+) create mode 100644 tests/rustc_tests.rs diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs new file mode 100644 index 00000000..f87b2068 --- /dev/null +++ b/tests/rustc_tests.rs @@ -0,0 +1,778 @@ +//! These tests have been adapted from [Rust's parser tests][parser-tests]. +//! +//! [parser-tests]: https://github.com/rust-lang/rust/blob/894f7a4ba6554d3797404bbf550d9919df060b97/compiler/rustc_parse/src/parser/tests.rs + +use annotate_snippets::{Level, Renderer, Snippet}; + +use snapbox::{assert_data_eq, str}; + +#[test] +fn ends_on_col0() { + let source = r#" +fn foo() { +} +"#; + let input = Level::Error.title("foo").snippet( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation(Level::Error.span(10..13).label("test")), + ); + + let expected = str![[r#" +error: foo + --> test.rs:2:10 + | +2 | fn foo() { + | __________^ +3 | | } + | |_^ test + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input).to_string(), expected); +} +#[test] +fn ends_on_col2() { + let source = r#" +fn foo() { + + + } +"#; + let input = Level::Error.title("foo").snippet( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation(Level::Error.span(10..17).label("test")), + ); + + let expected = str![[r#" +error: foo + --> test.rs:2:10 + | +2 | fn foo() { + | __________^ +3 | | +4 | | +5 | | } + | |___^ test + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input).to_string(), expected); +} +#[test] +fn non_nested() { + let source = r#" +fn foo() { + X0 Y0 + X1 Y1 + X2 Y2 +} +"#; + let input = Level::Error.title("foo").snippet( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation(Level::Error.span(14..32).label("`X` is a good letter")) + .annotation( + Level::Warning + .span(17..35) + .label("`Y` is a good letter too"), + ), + ); + + let expected = str![[r#" +error: foo + --> test.rs:3:3 + | +3 | X0 Y0 + | ___^ + | ______- +4 | || X1 Y1 +5 | || X2 Y2 + | ||____^ `X` is a good letter + | ||_______- `Y` is a good letter too + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input).to_string(), expected); +} +#[test] +fn nested() { + let source = r#" +fn foo() { + X0 Y0 + Y1 X1 +} +"#; + let input = Level::Error.title("foo").snippet( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation(Level::Error.span(14..27).label("`X` is a good letter")) + .annotation( + Level::Warning + .span(17..24) + .label("`Y` is a good letter too"), + ), + ); + + let expected = str![[r#" +error: foo + --> test.rs:3:3 + | +3 | X0 Y0 + | ___^ + | ______- +4 | || Y1 X1 + | ||_______^ `X` is a good letter + | ||____- `Y` is a good letter too + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input).to_string(), expected); +} +#[test] +fn different_overlap() { + let source = r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#; + let input = Level::Error.title("foo").snippet( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation(Level::Error.span(17..38).label("`X` is a good letter")) + .annotation( + Level::Warning + .span(31..49) + .label("`Y` is a good letter too"), + ), + ); + + let expected = str![[r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ______^ +4 | | X1 Y1 Z1 + | |_________- +5 | || X2 Y2 Z2 + | ||____^ `X` is a good letter +6 | | X3 Y3 Z3 + | |____- `Y` is a good letter too + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input).to_string(), expected); +} +#[test] +fn triple_overlap() { + let source = r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 +} +"#; + let input = Level::Error.title("foo").snippet( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation(Level::Error.span(14..38).label("`X` is a good letter")) + .annotation( + Level::Warning + .span(17..41) + .label("`Y` is a good letter too"), + ) + .annotation(Level::Warning.span(20..44).label("`Z` label")), + ); + + let expected = str![[r#" +error: foo + --> test.rs:3:3 + | +3 | X0 Y0 Z0 + | ___^ + | ______- + | _________- +4 | ||| X1 Y1 Z1 +5 | ||| X2 Y2 Z2 + | |||____^ `X` is a good letter + | |||_______- `Y` is a good letter too + | |||__________- `Z` label + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input).to_string(), expected); +} +#[test] +fn triple_exact_overlap() { + let source = r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 +} +"#; + let input = Level::Error.title("foo").snippet( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation(Level::Error.span(14..38).label("`X` is a good letter")) + .annotation( + Level::Warning + .span(14..38) + .label("`Y` is a good letter too"), + ) + .annotation(Level::Warning.span(14..38).label("`Z` label")), + ); + + let expected = str![[r#" +error: foo + --> test.rs:3:3 + | +3 | X0 Y0 Z0 + | ___^ + | ___- + | ___- +4 | ||| X1 Y1 Z1 +5 | ||| X2 Y2 Z2 + | |||____^ `X` is a good letter + | |||____- `Y` is a good letter too + | |||____- `Z` label + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input).to_string(), expected); +} +#[test] +fn minimum_depth() { + let source = r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#; + let input = Level::Error.title("foo").snippet( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation(Level::Error.span(17..27).label("`X` is a good letter")) + .annotation( + Level::Warning + .span(28..44) + .label("`Y` is a good letter too"), + ) + .annotation(Level::Warning.span(36..52).label("`Z`")), + ); + + let expected = str![[r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ______^ +4 | | X1 Y1 Z1 + | |____^ `X` is a good letter + | |______- +5 | | X2 Y2 Z2 + | |__________- `Y` is a good letter too + | |___- +6 | | X3 Y3 Z3 + | |_______- `Z` + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input).to_string(), expected); +} +#[test] +fn non_overlapping() { + let source = r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#; + let input = Level::Error.title("foo").snippet( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation(Level::Error.span(14..27).label("`X` is a good letter")) + .annotation( + Level::Warning + .span(39..55) + .label("`Y` is a good letter too"), + ), + ); + + let expected = str![[r#" +error: foo + --> test.rs:3:3 + | +3 | X0 Y0 Z0 + | ___^ +4 | | X1 Y1 Z1 + | |____^ `X` is a good letter +5 | X2 Y2 Z2 + | ______- +6 | | X3 Y3 Z3 + | |__________- `Y` is a good letter too + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input).to_string(), expected); +} +#[test] +fn overlapping_start_and_end() { + let source = r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#; + let input = Level::Error.title("foo").snippet( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation(Level::Error.span(17..27).label("`X` is a good letter")) + .annotation( + Level::Warning + .span(31..55) + .label("`Y` is a good letter too"), + ), + ); + + let expected = str![[r#" +error: foo + --> test.rs:3:6 + | +3 | X0 Y0 Z0 + | ______^ +4 | | X1 Y1 Z1 + | |____^ `X` is a good letter + | |_________- +5 | | X2 Y2 Z2 +6 | | X3 Y3 Z3 + | |__________- `Y` is a good letter too + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input).to_string(), expected); +} +#[test] +fn multiple_labels_primary_without_message() { + let source = r#" +fn foo() { + a { b { c } d } +} +"#; + let input = Level::Error.title("foo").snippet( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation(Level::Error.span(18..25).label("")) + .annotation(Level::Warning.span(14..27).label("`a` is a good letter")) + .annotation(Level::Warning.span(22..23).label("")), + ); + + let expected = str![[r#" +error: foo + --> test.rs:3:7 + | +3 | a { b { c } d } + | ^^^^^^^ + | ------------- `a` is a good letter + | - + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input).to_string(), expected); +} +#[test] +fn multiple_labels_secondary_without_message() { + let source = r#" +fn foo() { + a { b { c } d } +} +"#; + let input = Level::Error.title("foo").snippet( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation(Level::Error.span(14..27).label("`a` is a good letter")) + .annotation(Level::Warning.span(18..25).label("")), + ); + + let expected = str![[r#" +error: foo + --> test.rs:3:3 + | +3 | a { b { c } d } + | ^^^^^^^^^^^^^ `a` is a good letter + | ------- + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input).to_string(), expected); +} +#[test] +fn multiple_labels_primary_without_message_2() { + let source = r#" +fn foo() { + a { b { c } d } +} +"#; + let input = Level::Error.title("foo").snippet( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation(Level::Error.span(18..25).label("`b` is a good letter")) + .annotation(Level::Warning.span(14..27).label("")) + .annotation(Level::Warning.span(22..23).label("")), + ); + + let expected = str![[r#" +error: foo + --> test.rs:3:7 + | +3 | a { b { c } d } + | ^^^^^^^ `b` is a good letter + | ------------- + | - + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input).to_string(), expected); +} +#[test] +fn multiple_labels_secondary_without_message_2() { + let source = r#" +fn foo() { + a { b { c } d } +} +"#; + let input = Level::Error.title("foo").snippet( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation(Level::Error.span(14..27).label("")) + .annotation(Level::Warning.span(18..25).label("`b` is a good letter")), + ); + + let expected = str![[r#" +error: foo + --> test.rs:3:3 + | +3 | a { b { c } d } + | ^^^^^^^^^^^^^ + | ------- `b` is a good letter + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input).to_string(), expected); +} +#[test] +fn multiple_labels_secondary_without_message_3() { + let source = r#" +fn foo() { + a bc d +} +"#; + let input = Level::Error.title("foo").snippet( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation(Level::Error.span(14..18).label("`a` is a good letter")) + .annotation(Level::Warning.span(18..22).label("")), + ); + + let expected = str![[r#" +error: foo + --> test.rs:3:3 + | +3 | a bc d + | ^^^^ `a` is a good letter + | ---- + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input).to_string(), expected); +} +#[test] +fn multiple_labels_without_message() { + let source = r#" +fn foo() { + a { b { c } d } +} +"#; + let input = Level::Error.title("foo").snippet( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation(Level::Error.span(14..27).label("")) + .annotation(Level::Warning.span(18..25).label("")), + ); + + let expected = str![[r#" +error: foo + --> test.rs:3:3 + | +3 | a { b { c } d } + | ^^^^^^^^^^^^^ + | ------- + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input).to_string(), expected); +} +#[test] +fn multiple_labels_without_message_2() { + let source = r#" +fn foo() { + a { b { c } d } +} +"#; + let input = Level::Error.title("foo").snippet( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation(Level::Error.span(18..25).label("")) + .annotation(Level::Warning.span(14..27).label("")) + .annotation(Level::Warning.span(22..23).label("")), + ); + + let expected = str![[r#" +error: foo + --> test.rs:3:7 + | +3 | a { b { c } d } + | ^^^^^^^ + | ------------- + | - + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input).to_string(), expected); +} +#[test] +fn multiple_labels_with_message() { + let source = r#" +fn foo() { + a { b { c } d } +} +"#; + let input = Level::Error.title("foo").snippet( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation(Level::Error.span(14..27).label("`a` is a good letter")) + .annotation(Level::Warning.span(18..25).label("`b` is a good letter")), + ); + + let expected = str![[r#" +error: foo + --> test.rs:3:3 + | +3 | a { b { c } d } + | ^^^^^^^^^^^^^ `a` is a good letter + | ------- `b` is a good letter + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input).to_string(), expected); +} +#[test] +fn ingle_label_with_message() { + let source = r#" +fn foo() { + a { b { c } d } +} +"#; + let input = Level::Error.title("foo").snippet( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation(Level::Error.span(14..27).label("`a` is a good letter")), + ); + + let expected = str![[r#" +error: foo + --> test.rs:3:3 + | +3 | a { b { c } d } + | ^^^^^^^^^^^^^ `a` is a good letter + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input).to_string(), expected); +} +#[test] +fn single_label_without_message() { + let source = r#" +fn foo() { + a { b { c } d } +} +"#; + let input = Level::Error.title("foo").snippet( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation(Level::Error.span(14..27).label("")), + ); + + let expected = str![[r#" +error: foo + --> test.rs:3:3 + | +3 | a { b { c } d } + | ^^^^^^^^^^^^^ + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input).to_string(), expected); +} +#[test] +fn long_snippet() { + let source = r#" +fn foo() { + X0 Y0 Z0 + X1 Y1 Z1 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 + X2 Y2 Z2 + X3 Y3 Z3 +} +"#; + let input = Level::Error.title("foo").snippet( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation(Level::Error.span(17..27).label("`X` is a good letter")) + .annotation( + Level::Warning + .span(31..76) + .label("`Y` is a good letter too"), + ), + ); + + let expected = str![[r#" +error: foo + --> test.rs:3:6 + | + 3 | X0 Y0 Z0 + | ______^ + 4 | | X1 Y1 Z1 + | |____^ `X` is a good letter + | |_________- + 5 | | 1 +... | +15 | | X2 Y2 Z2 +16 | | X3 Y3 Z3 + | |__________- `Y` is a good letter too + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input).to_string(), expected); +} +#[test] +fn long_snippet_multiple_spans() { + let source = r#" +fn foo() { + X0 Y0 Z0 +1 +2 +3 + X1 Y1 Z1 +4 +5 +6 + X2 Y2 Z2 +7 +8 +9 +10 + X3 Y3 Z3 +} +"#; + let input = Level::Error.title("foo").snippet( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation(Level::Error.span(17..73).label("`Y` is a good letter")) + .annotation( + Level::Warning + .span(37..56) + .label("`Z` is a good letter too"), + ), + ); + + let expected = str![[r#" +error: foo + --> test.rs:3:6 + | + 3 | X0 Y0 Z0 + | ______^ + 4 | | 1 + 5 | | 2 + 6 | | 3 + 7 | | X1 Y1 Z1 + | |_________- + 8 | || 4 + 9 | || 5 +10 | || 6 +11 | || X2 Y2 Z2 + | ||__________- `Z` is a good letter too +12 | | 7 +... | +15 | | 10 +16 | | X3 Y3 Z3 + | |_______^ `Y` is a good letter + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input).to_string(), expected); +} From 9ec2939584d9d4ad6c601020d5c68ca08bfceb66 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 19 Jun 2024 21:02:23 -0600 Subject: [PATCH 239/455] fix: Properly handle multiple annotations on one line --- examples/expected_type.svg | 8 +- examples/footer.svg | 8 +- examples/format.svg | 12 +- examples/multislice.svg | 8 +- src/renderer/display_list.rs | 768 ++++++++++++------ src/renderer/mod.rs | 1 + src/renderer/styled_buffer.rs | 97 +++ tests/fixtures/no-color/simple.svg | 2 +- tests/fixtures/no-color/strip_line_non_ws.svg | 10 +- tests/formatter.rs | 46 +- tests/rustc_tests.rs | 177 ++-- 11 files changed, 750 insertions(+), 387 deletions(-) create mode 100644 src/renderer/styled_buffer.rs diff --git a/examples/expected_type.svg b/examples/expected_type.svg index a355dbcc..ed19ef38 100644 --- a/examples/expected_type.svg +++ b/examples/expected_type.svg @@ -23,11 +23,11 @@ </tspan> <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> examples/footer.rs:29:25</tspan> </tspan> - <tspan x="10px" y="64px"><tspan class="fg-bright-blue bold"> |</tspan> + <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">26 |</tspan><tspan> annotations: vec![SourceAnnotation {</tspan> </tspan> - <tspan x="10px" y="100px"><tspan class="fg-bright-blue bold"> |</tspan><tspan class="fg-bright-blue bold"> ----------------</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">info</tspan><tspan class="fg-bright-blue bold">: while parsing this struct</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">----------------</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">info: while parsing this struct</tspan> </tspan> <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">27 |</tspan><tspan> label: "expected struct `annotate_snippets::snippet::Slice`, found reference"</tspan> </tspan> @@ -35,9 +35,9 @@ </tspan> <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">29 |</tspan><tspan> range: <22, 25>,</tspan> </tspan> - <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold"> |</tspan><tspan class="fg-bright-red bold"> ^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan> + <tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan> </tspan> - <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold"> |</tspan> + <tspan x="10px" y="190px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> <tspan x="10px" y="208px"> </tspan> diff --git a/examples/footer.svg b/examples/footer.svg index 34f81c8a..76e7d776 100644 --- a/examples/footer.svg +++ b/examples/footer.svg @@ -24,15 +24,15 @@ </tspan> <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> src/multislice.rs:13:22</tspan> </tspan> - <tspan x="10px" y="64px"><tspan class="fg-bright-blue bold"> |</tspan> + <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">13 |</tspan><tspan> slices: vec!["A",</tspan> </tspan> - <tspan x="10px" y="100px"><tspan class="fg-bright-blue bold"> |</tspan><tspan class="fg-bright-red bold"> ^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan> </tspan> - <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold"> |</tspan> + <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">=</tspan><tspan> </tspan><tspan class="fg-bright-green bold">note</tspan><tspan>: expected type: `snippet::Annotation`</tspan> + <tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">= </tspan><tspan class="fg-bright-green bold">note</tspan><tspan>: expected type: `snippet::Annotation`</tspan> </tspan> <tspan x="10px" y="154px"><tspan> found type: `__&__snippet::Annotation`</tspan> </tspan> diff --git a/examples/format.svg b/examples/format.svg index dd6c1c07..a427c944 100644 --- a/examples/format.svg +++ b/examples/format.svg @@ -24,15 +24,15 @@ </tspan> <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> src/format.rs:51:6</tspan> </tspan> - <tspan x="10px" y="64px"><tspan class="fg-bright-blue bold"> |</tspan> + <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">51 |</tspan><tspan> ) -> Option<String> {</tspan> </tspan> - <tspan x="10px" y="100px"><tspan class="fg-bright-blue bold"> |</tspan><tspan> </tspan><tspan class="fg-yellow bold"> --------------</tspan><tspan> </tspan><tspan class="fg-yellow bold">expected `Option<String>` because of return type</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">--------------</tspan><tspan> </tspan><tspan class="fg-yellow bold">expected `Option<String>` because of return type</tspan> </tspan> <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">52 |</tspan><tspan> for ann in annotations {</tspan> </tspan> - <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold"> |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">_____^</tspan> + <tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">_____^</tspan> </tspan> <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">53 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> match (ann.range.0, ann.range.1) {</tspan> </tspan> @@ -52,7 +52,7 @@ </tspan> <tspan x="10px" y="298px"><tspan class="fg-bright-blue bold">61 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> };</tspan> </tspan> - <tspan x="10px" y="316px"><tspan class="fg-bright-blue bold">62 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> </tspan> + <tspan x="10px" y="316px"><tspan class="fg-bright-blue bold">62 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan> </tspan> <tspan x="10px" y="334px"><tspan class="fg-bright-blue bold">63 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> return Some(format!(</tspan> </tspan> @@ -74,9 +74,9 @@ </tspan> <tspan x="10px" y="496px"><tspan class="fg-bright-blue bold">72 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan> </tspan> - <tspan x="10px" y="514px"><tspan class="fg-bright-blue bold"> |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan class="fg-bright-red bold">____^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected enum `std::option::Option`</tspan> + <tspan x="10px" y="514px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|____^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected enum `std::option::Option`</tspan> </tspan> - <tspan x="10px" y="532px"><tspan class="fg-bright-blue bold"> |</tspan> + <tspan x="10px" y="532px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> <tspan x="10px" y="550px"> </tspan> diff --git a/examples/multislice.svg b/examples/multislice.svg index 2ab959a3..216a3592 100644 --- a/examples/multislice.svg +++ b/examples/multislice.svg @@ -23,19 +23,19 @@ </tspan> <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> src/format.rs</tspan> </tspan> - <tspan x="10px" y="64px"><tspan class="fg-bright-blue bold"> |</tspan> + <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold"> 51 |</tspan><tspan> Foo</tspan> </tspan> - <tspan x="10px" y="100px"><tspan class="fg-bright-blue bold"> |</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">:::</tspan><tspan> src/display.rs</tspan> </tspan> - <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold"> |</tspan> + <tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">129 |</tspan><tspan> Faa</tspan> </tspan> - <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold"> |</tspan> + <tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> <tspan x="10px" y="190px"> </tspan> diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index bc46f7f5..2c8dbf9c 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -32,11 +32,13 @@ //! //! The above snippet has been built out of the following structure: use crate::snippet; -use std::cmp::{max, min}; -use std::fmt::{Display, Write}; +use std::cmp::{max, min, Reverse}; +use std::collections::HashMap; +use std::fmt::Display; use std::ops::Range; use std::{cmp, fmt}; +use crate::renderer::styled_buffer::StyledBuffer; use crate::renderer::{stylesheet::Stylesheet, Margin, Style, DEFAULT_TERM_WIDTH}; const ANONYMIZED_LINE_NUM: &str = "LL"; @@ -83,19 +85,31 @@ impl<'a> Display for DisplayList<'a> { } else { ((lineno_width as f64).log10().floor() as usize) + 1 }; - let inline_marks_width = self.body.iter().fold(0, |max, set| { - set.display_lines.iter().fold(max, |max, line| match line { - DisplayLine::Source { inline_marks, .. } => cmp::max(inline_marks.len(), max), - _ => max, + + let multiline_depth = self.body.iter().fold(0, |max, set| { + set.display_lines.iter().fold(max, |max2, line| match line { + DisplayLine::Source { annotations, .. } => cmp::max( + annotations.iter().fold(max2, |max3, line| { + cmp::max( + match line.annotation_part { + DisplayAnnotationPart::Standalone => 0, + DisplayAnnotationPart::LabelContinuation => 0, + DisplayAnnotationPart::MultilineStart(depth) => depth + 1, + DisplayAnnotationPart::MultilineEnd(depth) => depth + 1, + }, + max3, + ) + }), + max, + ), + _ => max2, }) }); - - let mut count_offset = 0; + let mut buffer = StyledBuffer::new(); for set in self.body.iter() { - self.format_set(set, lineno_width, inline_marks_width, count_offset, f)?; - count_offset += set.display_lines.len(); + self.format_set(set, lineno_width, multiline_depth, &mut buffer)?; } - Ok(()) + write!(f, "{}", buffer.render(self.stylesheet)?) } } @@ -119,27 +133,18 @@ impl<'a> DisplayList<'a> { &self, set: &DisplaySet<'_>, lineno_width: usize, - inline_marks_width: usize, - count_offset: usize, - f: &mut fmt::Formatter<'_>, + multiline_depth: usize, + buffer: &mut StyledBuffer, ) -> fmt::Result { - let body_len = self - .body - .iter() - .map(|set| set.display_lines.len()) - .sum::<usize>(); - for (i, line) in set.display_lines.iter().enumerate() { + for line in &set.display_lines { set.format_line( line, lineno_width, - inline_marks_width, + multiline_depth, self.stylesheet, self.anonymized_line_numbers, - f, + buffer, )?; - if i + count_offset + 1 < body_len { - f.write_char('\n')?; - } } Ok(()) } @@ -154,36 +159,27 @@ pub(crate) struct DisplaySet<'a> { impl<'a> DisplaySet<'a> { fn format_label( &self, + line_offset: usize, label: &[DisplayTextFragment<'_>], stylesheet: &Stylesheet, - f: &mut fmt::Formatter<'_>, + buffer: &mut StyledBuffer, ) -> fmt::Result { - let emphasis_style = stylesheet.emphasis(); - for fragment in label { - match fragment.style { - DisplayTextStyle::Regular => fragment.content.fmt(f)?, - DisplayTextStyle::Emphasis => { - write!( - f, - "{}{}{}", - emphasis_style.render(), - fragment.content, - emphasis_style.render_reset() - )?; - } - } + let style = match fragment.style { + DisplayTextStyle::Regular => stylesheet.none(), + DisplayTextStyle::Emphasis => stylesheet.emphasis(), + }; + buffer.append(line_offset, fragment.content, *style); } Ok(()) } - fn format_annotation( &self, + line_offset: usize, annotation: &Annotation<'_>, continuation: bool, - in_source: bool, stylesheet: &Stylesheet, - f: &mut fmt::Formatter<'_>, + buffer: &mut StyledBuffer, ) -> fmt::Result { let color = get_annotation_style(&annotation.annotation_type, stylesheet); let formatted_len = if let Some(id) = &annotation.id { @@ -193,31 +189,27 @@ impl<'a> DisplaySet<'a> { }; if continuation { - format_repeat_char(' ', formatted_len + 2, f)?; - return self.format_label(&annotation.label, stylesheet, f); + for _ in 0..formatted_len + 2 { + buffer.append(line_offset, " ", Style::new()); + } + return self.format_label(line_offset, &annotation.label, stylesheet, buffer); } if formatted_len == 0 { - self.format_label(&annotation.label, stylesheet, f) + self.format_label(line_offset, &annotation.label, stylesheet, buffer) } else { - write!(f, "{}", color.render())?; - format_annotation_type(&annotation.annotation_type, f)?; - if let Some(id) = &annotation.id { - f.write_char('[')?; - f.write_str(id)?; - f.write_char(']')?; - } - write!(f, "{}", color.render_reset())?; + let id = match &annotation.id { + Some(id) => format!("[{}]", id), + None => String::new(), + }; + buffer.append( + line_offset, + &format!("{}{}", annotation_type_str(&annotation.annotation_type), id), + *color, + ); if !is_annotation_empty(annotation) { - if in_source { - write!(f, "{}", color.render())?; - f.write_str(": ")?; - self.format_label(&annotation.label, stylesheet, f)?; - write!(f, "{}", color.render_reset())?; - } else { - f.write_str(": ")?; - self.format_label(&annotation.label, stylesheet, f)?; - } + buffer.append(line_offset, ": ", stylesheet.none); + self.format_label(line_offset, &annotation.label, stylesheet, buffer)?; } Ok(()) } @@ -226,10 +218,11 @@ impl<'a> DisplaySet<'a> { #[inline] fn format_raw_line( &self, + line_offset: usize, line: &DisplayRawLine<'_>, lineno_width: usize, stylesheet: &Stylesheet, - f: &mut fmt::Formatter<'_>, + buffer: &mut StyledBuffer, ) -> fmt::Result { match line { DisplayRawLine::Origin { @@ -242,34 +235,15 @@ impl<'a> DisplaySet<'a> { DisplayHeaderType::Continuation => ":::", }; let lineno_color = stylesheet.line_no(); - + buffer.puts(line_offset, lineno_width, header_sigil, *lineno_color); + buffer.puts(line_offset, lineno_width + 4, path, stylesheet.none); if let Some((col, row)) = pos { - format_repeat_char(' ', lineno_width, f)?; - write!( - f, - "{}{}{}", - lineno_color.render(), - header_sigil, - lineno_color.render_reset() - )?; - f.write_char(' ')?; - path.fmt(f)?; - f.write_char(':')?; - col.fmt(f)?; - f.write_char(':')?; - row.fmt(f) - } else { - format_repeat_char(' ', lineno_width, f)?; - write!( - f, - "{}{}{}", - lineno_color.render(), - header_sigil, - lineno_color.render_reset() - )?; - f.write_char(' ')?; - path.fmt(f) + buffer.append(line_offset, ":", stylesheet.none); + buffer.append(line_offset, col.to_string().as_str(), stylesheet.none); + buffer.append(line_offset, ":", stylesheet.none); + buffer.append(line_offset, row.to_string().as_str(), stylesheet.none); } + Ok(()) } DisplayRawLine::Annotation { annotation, @@ -278,35 +252,35 @@ impl<'a> DisplaySet<'a> { } => { if *source_aligned { if *continuation { - format_repeat_char(' ', lineno_width + 3, f)?; + for _ in 0..lineno_width + 3 { + buffer.append(line_offset, " ", stylesheet.none); + } } else { let lineno_color = stylesheet.line_no(); - format_repeat_char(' ', lineno_width, f)?; - f.write_char(' ')?; - write!( - f, - "{}={}", - lineno_color.render(), - lineno_color.render_reset() - )?; - f.write_char(' ')?; + for _ in 0..lineno_width + 1 { + buffer.append(line_offset, " ", stylesheet.none); + } + buffer.append(line_offset, "=", *lineno_color); + buffer.append(line_offset, " ", *lineno_color); } } - self.format_annotation(annotation, *continuation, false, stylesheet, f) + self.format_annotation(line_offset, annotation, *continuation, stylesheet, buffer) } } } + // Adapted from https://github.com/rust-lang/rust/blob/894f7a4ba6554d3797404bbf550d9919df060b97/compiler/rustc_errors/src/emitter.rs#L706-L1155 #[inline] fn format_line( &self, dl: &DisplayLine<'_>, lineno_width: usize, - inline_marks_width: usize, + multiline_depth: usize, stylesheet: &Stylesheet, anonymized_line_numbers: bool, - f: &mut fmt::Formatter<'_>, + buffer: &mut StyledBuffer, ) -> fmt::Result { + let line_offset = buffer.num_lines(); match dl { DisplayLine::Source { lineno, @@ -316,36 +290,45 @@ impl<'a> DisplaySet<'a> { } => { let lineno_color = stylesheet.line_no(); if anonymized_line_numbers && lineno.is_some() { - write!(f, "{}", lineno_color.render())?; - f.write_str(ANONYMIZED_LINE_NUM)?; - f.write_str(" |")?; - write!(f, "{}", lineno_color.render_reset())?; + let num = format!("{:>width$} |", ANONYMIZED_LINE_NUM, width = lineno_width); + buffer.puts(line_offset, 0, &num, *lineno_color); } else { - write!(f, "{}", lineno_color.render())?; match lineno { - Some(n) => write!(f, "{:>width$}", n, width = lineno_width), - None => format_repeat_char(' ', lineno_width, f), - }?; - f.write_str(" |")?; - write!(f, "{}", lineno_color.render_reset())?; + Some(n) => { + let num = format!("{:>width$} |", n, width = lineno_width); + buffer.puts(line_offset, 0, &num, *lineno_color); + } + None => { + buffer.putc(line_offset, lineno_width + 1, '|', *lineno_color); + } + }; } - if let DisplaySourceLine::Content { text, .. } = line { - if !inline_marks.is_empty() || 0 < inline_marks_width { - f.write_char(' ')?; - self.format_inline_marks(inline_marks, inline_marks_width, stylesheet, f)?; + // The width of the line number, a space, pipe, and a space + // `123 | ` is `lineno_width + 3`. + let width_offset = lineno_width + 3; + let code_offset = if multiline_depth == 0 { + width_offset + } else { + width_offset + multiline_depth + 1 + }; + + // Add any inline marks to the code line + if !inline_marks.is_empty() || 0 < multiline_depth { + format_inline_marks( + line_offset, + inline_marks, + lineno_width, + stylesheet, + buffer, + )?; } - f.write_char(' ')?; let text = normalize_whitespace(text); let line_len = text.as_bytes().len(); - let mut left = self.margin.left(line_len); + let left = self.margin.left(line_len); let right = self.margin.right(line_len); - if self.margin.was_cut_left() { - "...".fmt(f)?; - left += 3; - } // On long lines, we strip the source line, accounting for unicode. let mut taken = 0; let code: String = text @@ -364,135 +347,341 @@ impl<'a> DisplaySet<'a> { true }) .collect(); - + buffer.puts(line_offset, code_offset, &code, Style::new()); + if self.margin.was_cut_left() { + // We have stripped some code/whitespace from the beginning, make it clear. + buffer.puts(line_offset, code_offset, "...", *lineno_color); + } if self.margin.was_cut_right(line_len) { - code[..taken.saturating_sub(3)].fmt(f)?; - "...".fmt(f)?; - } else { - code.fmt(f)?; + buffer.puts(line_offset, code_offset + taken - 3, "...", *lineno_color); } - let mut left: usize = text + let left: usize = text .chars() .take(left) .map(|ch| unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1)) .sum(); - if self.margin.was_cut_left() { - left = left.saturating_sub(3); + let mut annotations = annotations.clone(); + annotations.sort_by_key(|a| Reverse(a.range.0)); + + let mut annotations_positions = vec![]; + let mut line_len = 0; + let mut p = 0; + for (i, annotation) in annotations.iter().enumerate() { + for (j, next) in annotations.iter().enumerate() { + // This label overlaps with another one and both take space ( + // they have text and are not multiline lines). + if overlaps(next, annotation, 0) + && annotation.has_label() + && j > i + && p == 0 + // We're currently on the first line, move the label one line down + { + // If we're overlapping with an un-labelled annotation with the same span + // we can just merge them in the output + if next.range.0 == annotation.range.0 + && next.range.1 == annotation.range.1 + && !next.has_label() + { + continue; + } + + // This annotation needs a new line in the output. + p += 1; + break; + } + } + annotations_positions.push((p, annotation)); + for (j, next) in annotations.iter().enumerate() { + if j > i { + let l = next + .annotation + .label + .iter() + .map(|label| label.content) + .collect::<Vec<_>>() + .join("") + .len() + + 2; + // Do not allow two labels to be in the same line if they + // overlap including padding, to avoid situations like: + // + // fn foo(x: u32) { + // -------^------ + // | | + // fn_spanx_span + // + // Both labels must have some text, otherwise they are not + // overlapping. Do not add a new line if this annotation or + // the next are vertical line placeholders. If either this + // or the next annotation is multiline start/end, move it + // to a new line so as not to overlap the horizontal lines. + if (overlaps(next, annotation, l) + && annotation.has_label() + && next.has_label()) + || (annotation.takes_space() && next.has_label()) + || (annotation.has_label() && next.takes_space()) + || (annotation.takes_space() && next.takes_space()) + || (overlaps(next, annotation, l) + && next.range.1 <= annotation.range.1 + && next.has_label() + && p == 0) + // Avoid #42595. + { + // This annotation needs a new line in the output. + p += 1; + break; + } + } + } + line_len = max(line_len, p); } - for annotation in annotations { - // Each annotation should be on its own line - f.write_char('\n')?; - // Add the line number and the line number delimiter - write!(f, "{}", stylesheet.line_no.render())?; - format_repeat_char(' ', lineno_width, f)?; - f.write_str(" |")?; - write!(f, "{}", stylesheet.line_no.render_reset())?; - - if !inline_marks.is_empty() || 0 < inline_marks_width { - f.write_char(' ')?; - self.format_inline_marks( - inline_marks, - inline_marks_width, - stylesheet, - f, - )?; + if line_len != 0 { + line_len += 1; + } + + // Draw the column separator for any extra lines that were + // created + // + // After this we will have: + // + // 2 | fn foo() { + // | + // | + // | + // 3 | + // 4 | } + // | + if !annotations_positions.is_empty() { + for pos in 0..=line_len { + buffer.putc( + line_offset + pos + 1, + lineno_width + 1, + '|', + stylesheet.line_no, + ); + } + } + + // Write the horizontal lines for multiline annotations + // (only the first and last lines need this). + // + // After this we will have: + // + // 2 | fn foo() { + // | __________ + // | + // | + // 3 | + // 4 | } + // | _ + for &(pos, annotation) in &annotations_positions { + let style = get_annotation_style(&annotation.annotation_type, stylesheet); + let pos = pos + 1; + match annotation.annotation_part { + DisplayAnnotationPart::MultilineStart(depth) + | DisplayAnnotationPart::MultilineEnd(depth) => { + for col in width_offset + depth + ..(code_offset + annotation.range.0).saturating_sub(left) + { + buffer.putc(line_offset + pos, col + 1, '_', *style); + } + } + _ => {} + } + } + + // Write the vertical lines for labels that are on a different line as the underline. + // + // After this we will have: + // + // 2 | fn foo() { + // | __________ + // | | | + // | | + // 3 | | + // 4 | | } + // | |_ + for &(pos, annotation) in &annotations_positions { + let style = get_annotation_style(&annotation.annotation_type, stylesheet); + let pos = pos + 1; + if pos > 1 && (annotation.has_label() || annotation.takes_space()) { + for p in line_offset + 2..=line_offset + pos { + buffer.putc( + p, + (code_offset + annotation.range.0).saturating_sub(left), + '|', + *style, + ); + } + } + match annotation.annotation_part { + DisplayAnnotationPart::MultilineStart(depth) => { + for p in line_offset + pos + 1..line_offset + line_len + 2 { + buffer.putc(p, width_offset + depth, '|', *style); + } + } + DisplayAnnotationPart::MultilineEnd(depth) => { + for p in line_offset..=line_offset + pos { + buffer.putc(p, width_offset + depth, '|', *style); + } + } + _ => {} + } + } + + // Add in any inline marks for any extra lines that have + // been created. Output should look like above. + for inline_mark in inline_marks { + if let DisplayMarkType::AnnotationThrough(depth) = inline_mark.mark_type { + let style = + get_annotation_style(&inline_mark.annotation_type, stylesheet); + if annotations_positions.is_empty() { + buffer.putc(line_offset, width_offset + depth, '|', *style); + } else { + for p in line_offset..=line_offset + line_len + 1 { + buffer.putc(p, width_offset + depth, '|', *style); + } + } + } + } + + // Write the labels on the annotations that actually have a label. + // + // After this we will have: + // + // 2 | fn foo() { + // | __________ + // | | + // | something about `foo` + // 3 | + // 4 | } + // | _ test + for &(pos, annotation) in &annotations_positions { + if !is_annotation_empty(&annotation.annotation) { + let style = + get_annotation_style(&annotation.annotation_type, stylesheet); + let mut formatted_len = if let Some(id) = &annotation.annotation.id { + 2 + id.len() + + annotation_type_len(&annotation.annotation.annotation_type) + } else { + annotation_type_len(&annotation.annotation.annotation_type) + }; + let (pos, col) = if pos == 0 { + (pos + 1, (annotation.range.1 + 1).saturating_sub(left)) + } else { + (pos + 2, annotation.range.0.saturating_sub(left)) + }; + if annotation.annotation_part + == DisplayAnnotationPart::LabelContinuation + { + formatted_len = 0; + } else if formatted_len != 0 { + formatted_len += 2; + let id = match &annotation.annotation.id { + Some(id) => format!("[{}]", id), + None => String::new(), + }; + buffer.puts( + line_offset + pos, + col + code_offset, + &format!( + "{}{}: ", + annotation_type_str(&annotation.annotation_type), + id + ), + *style, + ); + } else { + formatted_len = 0; + } + let mut before = 0; + for fragment in &annotation.annotation.label { + let inner_col = before + formatted_len + col + code_offset; + buffer.puts(line_offset + pos, inner_col, fragment.content, *style); + before += fragment.content.len(); + } + } + } + + // Sort from biggest span to smallest span so that smaller spans are + // represented in the output: + // + // x | fn foo() + // | ^^^---^^ + // | | | + // | | something about `foo` + // | something about `fn foo()` + annotations_positions.sort_by_key(|(_, ann)| { + // Decreasing order. When annotations share the same length, prefer `Primary`. + Reverse(ann.len()) + }); + + // Write the underlines. + // + // After this we will have: + // + // 2 | fn foo() { + // | ____-_____^ + // | | + // | something about `foo` + // 3 | + // 4 | } + // | _^ test + for &(_, annotation) in &annotations_positions { + let mark = match annotation.annotation_type { + DisplayAnnotationType::Error => '^', + DisplayAnnotationType::Warning => '-', + DisplayAnnotationType::Info => '-', + DisplayAnnotationType::Note => '-', + DisplayAnnotationType::Help => '-', + DisplayAnnotationType::None => ' ', + }; + let style = get_annotation_style(&annotation.annotation_type, stylesheet); + for p in annotation.range.0..annotation.range.1 { + buffer.putc( + line_offset + 1, + (code_offset + p).saturating_sub(left), + mark, + *style, + ); } - self.format_source_annotation(annotation, left, stylesheet, f)?; } } else if !inline_marks.is_empty() { - f.write_char(' ')?; - self.format_inline_marks(inline_marks, inline_marks_width, stylesheet, f)?; + format_inline_marks( + line_offset, + inline_marks, + lineno_width, + stylesheet, + buffer, + )?; } Ok(()) } DisplayLine::Fold { inline_marks } => { - f.write_str("...")?; - if !inline_marks.is_empty() || 0 < inline_marks_width { - format_repeat_char(' ', lineno_width, f)?; - self.format_inline_marks(inline_marks, inline_marks_width, stylesheet, f)?; + buffer.puts(line_offset, 0, "...", *stylesheet.line_no()); + if !inline_marks.is_empty() || 0 < multiline_depth { + format_inline_marks( + line_offset, + inline_marks, + lineno_width, + stylesheet, + buffer, + )?; } Ok(()) } - DisplayLine::Raw(line) => self.format_raw_line(line, lineno_width, stylesheet, f), - } - } - - fn format_inline_marks( - &self, - inline_marks: &[DisplayMark], - inline_marks_width: usize, - stylesheet: &Stylesheet, - f: &mut fmt::Formatter<'_>, - ) -> fmt::Result { - format_repeat_char(' ', inline_marks_width - inline_marks.len(), f)?; - for mark in inline_marks { - let annotation_style = get_annotation_style(&mark.annotation_type, stylesheet); - write!(f, "{}", annotation_style.render())?; - f.write_char(match mark.mark_type { - DisplayMarkType::AnnotationThrough => '|', - DisplayMarkType::AnnotationStart => '/', - })?; - write!(f, "{}", annotation_style.render_reset())?; - } - Ok(()) - } - - fn format_source_annotation( - &self, - annotation: &DisplaySourceAnnotation<'_>, - left: usize, - stylesheet: &Stylesheet, - f: &mut fmt::Formatter<'_>, - ) -> fmt::Result { - let indent_char = match annotation.annotation_part { - DisplayAnnotationPart::Standalone => ' ', - DisplayAnnotationPart::LabelContinuation => ' ', - DisplayAnnotationPart::MultilineStart => '_', - DisplayAnnotationPart::MultilineEnd => '_', - }; - let mark = match annotation.annotation_type { - DisplayAnnotationType::Error => '^', - DisplayAnnotationType::Warning => '-', - DisplayAnnotationType::Info => '-', - DisplayAnnotationType::Note => '-', - DisplayAnnotationType::Help => '-', - DisplayAnnotationType::None => ' ', - }; - let color = get_annotation_style(&annotation.annotation_type, stylesheet); - let range = ( - annotation.range.0.saturating_sub(left), - annotation.range.1.saturating_sub(left), - ); - let indent_length = match annotation.annotation_part { - DisplayAnnotationPart::LabelContinuation => range.1, - _ => range.0, - }; - write!(f, "{}", color.render())?; - format_repeat_char(indent_char, indent_length + 1, f)?; - format_repeat_char(mark, range.1 - indent_length, f)?; - write!(f, "{}", color.render_reset())?; - - if !is_annotation_empty(&annotation.annotation) { - f.write_char(' ')?; - write!(f, "{}", color.render())?; - self.format_annotation( - &annotation.annotation, - annotation.annotation_part == DisplayAnnotationPart::LabelContinuation, - true, - stylesheet, - f, - )?; - write!(f, "{}", color.render_reset())?; + DisplayLine::Raw(line) => { + self.format_raw_line(line_offset, line, lineno_width, stylesheet, buffer) + } } - Ok(()) } } /// Inline annotation which can be used in either Raw or Source line. -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub(crate) struct Annotation<'a> { pub(crate) annotation_type: DisplayAnnotationType, pub(crate) id: Option<&'a str>, @@ -530,7 +719,7 @@ pub(crate) enum DisplaySourceLine<'a> { Empty, } -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub(crate) struct DisplaySourceAnnotation<'a> { pub(crate) annotation: Annotation<'a>, pub(crate) range: (usize, usize), @@ -538,6 +727,34 @@ pub(crate) struct DisplaySourceAnnotation<'a> { pub(crate) annotation_part: DisplayAnnotationPart, } +impl<'a> DisplaySourceAnnotation<'a> { + fn has_label(&self) -> bool { + !self + .annotation + .label + .iter() + .all(|label| label.content.is_empty()) + } + + // Length of this annotation as displayed in the stderr output + fn len(&self) -> usize { + // Account for usize underflows + if self.range.1 > self.range.0 { + self.range.1 - self.range.0 + } else { + self.range.0 - self.range.1 + } + } + + fn takes_space(&self) -> bool { + // Multiline annotations always have to keep vertical space. + matches!( + self.annotation_part, + DisplayAnnotationPart::MultilineStart(_) | DisplayAnnotationPart::MultilineEnd(_) + ) + } +} + /// Raw line - a line which does not have the `lineno` part and is not considered /// a part of the snippet. #[derive(Debug, PartialEq)] @@ -566,7 +783,7 @@ pub(crate) enum DisplayRawLine<'a> { } /// An inline text fragment which any label is composed of. -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub(crate) struct DisplayTextFragment<'a> { pub(crate) content: &'a str, pub(crate) style: DisplayTextStyle, @@ -589,9 +806,9 @@ pub(crate) enum DisplayAnnotationPart { /// A continuation of a multi-line label of an annotation. LabelContinuation, /// A line starting a multiline annotation. - MultilineStart, + MultilineStart(usize), /// A line ending a multiline annotation. - MultilineEnd, + MultilineEnd(usize), } /// A visual mark used in `inline_marks` field of the `DisplaySourceLine`. @@ -605,7 +822,7 @@ pub(crate) struct DisplayMark { #[derive(Debug, Clone, PartialEq)] pub(crate) enum DisplayMarkType { /// A mark indicating a multiline annotation going through the current line. - AnnotationThrough, + AnnotationThrough(usize), /// A mark indicating a multiline annotation starting on the given line. AnnotationStart, } @@ -969,10 +1186,7 @@ fn fold_body(body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> { ref inline_marks, .. } = line { - let mut inline_marks = inline_marks.clone(); - for mark in &mut inline_marks { - mark.mark_type = DisplayMarkType::AnnotationThrough; - } + let inline_marks = inline_marks.clone(); Some(inline_marks) } else { None @@ -1031,7 +1245,12 @@ fn format_body( let mut label_right_margin = 0; let mut max_line_len = 0; + let mut depth_map: HashMap<usize, usize> = HashMap::new(); + let mut current_depth = 0; let mut annotations = snippet.annotations; + annotations.sort_by_key(|a| a.range.start); + let mut annotations = annotations.into_iter().enumerate().collect::<Vec<_>>(); + for (idx, (line, end_line)) in CursorLines::new(snippet.source).enumerate() { let line_length: usize = line.len(); let line_range = (current_index, current_index + line_length); @@ -1069,7 +1288,7 @@ fn format_body( current_index += line_length + end_line_size; // It would be nice to use filter_drain here once it's stable. - annotations.retain(|annotation| { + annotations.retain(|(key, annotation)| { let body_idx = idx; let annotation_type = match annotation.level { snippet::Level::Error => DisplayAnnotationType::None, @@ -1175,8 +1394,10 @@ fn format_body( }, range, annotation_type: DisplayAnnotationType::from(annotation.level), - annotation_part: DisplayAnnotationPart::MultilineStart, + annotation_part: DisplayAnnotationPart::MultilineStart(current_depth), }); + depth_map.insert(*key, current_depth); + current_depth += 1; } true } @@ -1190,8 +1411,9 @@ fn format_body( .. } = body[body_idx] { + let depth = depth_map.get(key).cloned().unwrap_or_default(); inline_marks.push(DisplayMark { - mark_type: DisplayMarkType::AnnotationThrough, + mark_type: DisplayMarkType::AnnotationThrough(depth), annotation_type: DisplayAnnotationType::from(annotation.level), }); } @@ -1207,15 +1429,10 @@ fn format_body( && end <= line_end_index + max(end_line_size, 1) => { if let DisplayLine::Source { - ref mut inline_marks, ref mut annotations, .. } = body[body_idx] { - inline_marks.push(DisplayMark { - mark_type: DisplayMarkType::AnnotationThrough, - annotation_type: DisplayAnnotationType::from(annotation.level), - }); let end_mark = line[0..(end - line_start_index).min(line_length)] .chars() .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) @@ -1237,6 +1454,7 @@ fn format_body( label_right_margin = max(label_right_margin, end_plus_one + label_right); let range = (end_mark, end_plus_one); + let depth = depth_map.remove(key).unwrap_or(0); annotations.push(DisplaySourceAnnotation { annotation: Annotation { annotation_type, @@ -1245,7 +1463,7 @@ fn format_body( }, range, annotation_type: DisplayAnnotationType::from(annotation.level), - annotation_part: DisplayAnnotationPart::MultilineEnd, + annotation_part: DisplayAnnotationPart::MultilineEnd(depth), }); } false @@ -1253,6 +1471,12 @@ fn format_body( _ => true, } }); + // Reset the depth counter, but only after we've processed all + // annotations for a given line. + let max = depth_map.len(); + if current_depth > max { + current_depth = max; + } } if snippet.fold { @@ -1313,25 +1537,15 @@ fn format_body( } } -fn format_repeat_char(c: char, n: usize, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for _ in 0..n { - f.write_char(c)?; - } - Ok(()) -} - #[inline] -fn format_annotation_type( - annotation_type: &DisplayAnnotationType, - f: &mut fmt::Formatter<'_>, -) -> fmt::Result { +fn annotation_type_str(annotation_type: &DisplayAnnotationType) -> &'static str { match annotation_type { - DisplayAnnotationType::Error => f.write_str(ERROR_TXT), - DisplayAnnotationType::Help => f.write_str(HELP_TXT), - DisplayAnnotationType::Info => f.write_str(INFO_TXT), - DisplayAnnotationType::Note => f.write_str(NOTE_TXT), - DisplayAnnotationType::Warning => f.write_str(WARNING_TXT), - DisplayAnnotationType::None => Ok(()), + DisplayAnnotationType::Error => ERROR_TXT, + DisplayAnnotationType::Help => HELP_TXT, + DisplayAnnotationType::Info => INFO_TXT, + DisplayAnnotationType::Note => NOTE_TXT, + DisplayAnnotationType::Warning => WARNING_TXT, + DisplayAnnotationType::None => "", } } @@ -1390,3 +1604,33 @@ fn normalize_whitespace(str: &str) -> String { } s } + +fn overlaps( + a1: &DisplaySourceAnnotation<'_>, + a2: &DisplaySourceAnnotation<'_>, + padding: usize, +) -> bool { + (a2.range.0..a2.range.1).contains(&a1.range.0) + || (a1.range.0..a1.range.1 + padding).contains(&a2.range.0) +} + +fn format_inline_marks( + line: usize, + inline_marks: &[DisplayMark], + lineno_width: usize, + stylesheet: &Stylesheet, + buf: &mut StyledBuffer, +) -> fmt::Result { + for mark in inline_marks.iter() { + let annotation_style = get_annotation_style(&mark.annotation_type, stylesheet); + match mark.mark_type { + DisplayMarkType::AnnotationThrough(depth) => { + buf.putc(line, 3 + lineno_width + depth, '|', *annotation_style); + } + DisplayMarkType::AnnotationStart => { + buf.putc(line, 3 + lineno_width, '/', *annotation_style); + } + }; + } + Ok(()) +} diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 845d2931..b9edcc6c 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -12,6 +12,7 @@ mod display_list; mod margin; +mod styled_buffer; pub(crate) mod stylesheet; use crate::snippet::Message; diff --git a/src/renderer/styled_buffer.rs b/src/renderer/styled_buffer.rs new file mode 100644 index 00000000..ec834e1b --- /dev/null +++ b/src/renderer/styled_buffer.rs @@ -0,0 +1,97 @@ +//! Adapted from [styled_buffer] +//! +//! [styled_buffer]: https://github.com/rust-lang/rust/blob/894f7a4ba6554d3797404bbf550d9919df060b97/compiler/rustc_errors/src/styled_buffer.rs + +use crate::renderer::stylesheet::Stylesheet; +use anstyle::Style; +use std::fmt; +use std::fmt::Write; + +#[derive(Debug)] +pub(crate) struct StyledBuffer { + lines: Vec<Vec<StyledChar>>, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub(crate) struct StyledChar { + ch: char, + style: Style, +} + +impl StyledChar { + pub(crate) const SPACE: Self = StyledChar::new(' ', Style::new()); + + pub(crate) const fn new(ch: char, style: Style) -> StyledChar { + StyledChar { ch, style } + } +} + +impl StyledBuffer { + pub(crate) fn new() -> StyledBuffer { + StyledBuffer { lines: vec![] } + } + + fn ensure_lines(&mut self, line: usize) { + if line >= self.lines.len() { + self.lines.resize(line + 1, Vec::new()); + } + } + + pub(crate) fn render(&self, stylesheet: &Stylesheet) -> Result<String, fmt::Error> { + let mut str = String::new(); + for (i, line) in self.lines.iter().enumerate() { + let mut current_style = stylesheet.none; + for ch in line { + if ch.style != current_style { + if !line.is_empty() { + write!(str, "{}", current_style.render_reset())?; + } + current_style = ch.style; + write!(str, "{}", current_style.render())?; + } + write!(str, "{}", ch.ch)?; + } + write!(str, "{}", current_style.render_reset())?; + if i != self.lines.len() - 1 { + writeln!(str)?; + } + } + Ok(str) + } + + /// Sets `chr` with `style` for given `line`, `col`. + /// If `line` does not exist in our buffer, adds empty lines up to the given + /// and fills the last line with unstyled whitespace. + pub(crate) fn putc(&mut self, line: usize, col: usize, chr: char, style: Style) { + self.ensure_lines(line); + if col >= self.lines[line].len() { + self.lines[line].resize(col + 1, StyledChar::SPACE); + } + self.lines[line][col] = StyledChar::new(chr, style); + } + + /// Sets `string` with `style` for given `line`, starting from `col`. + /// If `line` does not exist in our buffer, adds empty lines up to the given + /// and fills the last line with unstyled whitespace. + pub(crate) fn puts(&mut self, line: usize, col: usize, string: &str, style: Style) { + let mut n = col; + for c in string.chars() { + self.putc(line, n, c, style); + n += 1; + } + } + /// For given `line` inserts `string` with `style` after old content of that line, + /// adding lines if needed + pub(crate) fn append(&mut self, line: usize, string: &str, style: Style) { + if line >= self.lines.len() { + self.puts(line, 0, string, style); + } else { + let col = self.lines[line].len(); + self.puts(line, col, string, style); + } + } + + pub(crate) fn num_lines(&self) -> usize { + self.lines.len() + } +} diff --git a/tests/fixtures/no-color/simple.svg b/tests/fixtures/no-color/simple.svg index 51a3a65a..ae7b03cf 100644 --- a/tests/fixtures/no-color/simple.svg +++ b/tests/fixtures/no-color/simple.svg @@ -26,7 +26,7 @@ </tspan> <tspan x="10px" y="100px"><tspan> | - expected one of `.`, `;`, `?`, or an operator here</tspan> </tspan> - <tspan x="10px" y="118px"><tspan>170 | </tspan> + <tspan x="10px" y="118px"><tspan>170 |</tspan> </tspan> <tspan x="10px" y="136px"><tspan>171 | for line in &self.body {</tspan> </tspan> diff --git a/tests/fixtures/no-color/strip_line_non_ws.svg b/tests/fixtures/no-color/strip_line_non_ws.svg index 2be38901..f1977dc5 100644 --- a/tests/fixtures/no-color/strip_line_non_ws.svg +++ b/tests/fixtures/no-color/strip_line_non_ws.svg @@ -1,4 +1,4 @@ -<svg width="1196px" height="146px" xmlns="http://www.w3.org/2000/svg"> +<svg width="1196px" height="164px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -24,11 +24,13 @@ </tspan> <tspan x="10px" y="82px"><tspan>LL | ... = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () ...</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> | ^^ expected `()`, found integer</tspan> + <tspan x="10px" y="100px"><tspan> | ^^ ^^ expected `()`, found integer</tspan> </tspan> - <tspan x="10px" y="118px"><tspan> | ^^ expected due to this</tspan> + <tspan x="10px" y="118px"><tspan> | |</tspan> </tspan> - <tspan x="10px" y="136px"><tspan> |</tspan> + <tspan x="10px" y="136px"><tspan> | expected due to this</tspan> +</tspan> + <tspan x="10px" y="154px"><tspan> |</tspan> </tspan> </text> diff --git a/tests/formatter.rs b/tests/formatter.rs index d0ac3692..7f914de9 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -262,8 +262,10 @@ fn test_source_annotation_standalone_multiline() { error | 1 | tests - | ----- help: Example string - | ----- help: Second line + | ----- + | | + | help: Example string + | help: Second line | "#]]; let renderer = Renderer::plain(); @@ -296,7 +298,7 @@ error | LL | This is an example LL | of content lines -LL | +LL | LL | abc | "#]]; @@ -756,8 +758,9 @@ error: unused optional dependency --> Cargo.toml:4:1 | 4 | bar = { version = "0.1.0", optional = true } - | ^^^ I need this to be really long so I can test overlaps - | --------------- info: This should also be long but not too long + | ^^^ --------------- info: This should also be long but not too long + | | + | I need this to be really long so I can test overlaps | "#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); @@ -789,8 +792,9 @@ bar = { version = "0.1.0", optional = true } error: unused optional dependency | 4 | bar = { version = "0.1.0", optional = true } - | __________________________________________^ - | --------------- info: This should also be long but not too long + | ____________________________--------------^ + | | | + | | info: This should also be long but not too long 5 | | this is another line 6 | | so is this 7 | | bar = { version = "0.1.0", optional = true } @@ -831,14 +835,16 @@ bar = { version = "0.1.0", optional = true } error: unused optional dependency | 4 | bar = { version = "0.1.0", optional = true } - | __________________________________________^ - | _________^ - | --------------- info: This should also be long but not too long + | _________^__________________--------------^ + | | | | + | |_________| info: This should also be long but not too long + | || 5 | || this is another line 6 | || so is this 7 | || bar = { version = "0.1.0", optional = true } - | ||__________________________________________^ I need this to be really long so I can test overlaps - | ||_________________________^ I need this to be really long so I can test overlaps + | ||_________________________^________________^ I need this to be really long so I can test overlaps + | |__________________________| + | I need this to be really long so I can test overlaps | "#]]; let renderer = Renderer::plain(); @@ -881,15 +887,17 @@ this is another line error: unused optional dependency | 4 | bar = { version = "0.1.0", optional = true } - | __________________________________________^ - | _________^ - | --------------- info: This should also be long but not too long -5 | || this is another line - | ||____^ + | __________^__________________--------------^ + | | | | + | |__________| info: This should also be long but not too long + | || +5 | || this is another line + | || ____^ 6 | ||| so is this 7 | ||| bar = { version = "0.1.0", optional = true } - | |||__________________________________________^ I need this to be really long so I can test overlaps - | |||_________________________^ I need this to be really long so I can test overlaps + | |||_________________________^________________^ I need this to be really long so I can test overlaps + | |_|_________________________| + | | I need this to be really long so I can test overlaps 8 | | this is another line | |____^ I need this to be really long so I can test overlaps | diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index f87b2068..ed2c7ad2 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -55,8 +55,8 @@ error: foo | 2 | fn foo() { | __________^ -3 | | -4 | | +3 | | +4 | | 5 | | } | |___^ test | @@ -91,12 +91,14 @@ error: foo --> test.rs:3:3 | 3 | X0 Y0 - | ___^ - | ______- + | ___^__- + | |___| + | || 4 | || X1 Y1 5 | || X2 Y2 - | ||____^ `X` is a good letter - | ||_______- `Y` is a good letter too + | ||____^__- `Y` is a good letter too + | |_____| + | `X` is a good letter | "#]]; let renderer = Renderer::plain(); @@ -128,11 +130,13 @@ error: foo --> test.rs:3:3 | 3 | X0 Y0 - | ___^ - | ______- + | ___^__- + | |___| + | || 4 | || Y1 X1 - | ||_______^ `X` is a good letter - | ||____- `Y` is a good letter too + | ||____-__^ `X` is a good letter + | |____| + | `Y` is a good letter too | "#]]; let renderer = Renderer::plain(); @@ -166,9 +170,9 @@ error: foo --> test.rs:3:6 | 3 | X0 Y0 Z0 - | ______^ -4 | | X1 Y1 Z1 - | |_________- + | _______^ +4 | | X1 Y1 Z1 + | | _________- 5 | || X2 Y2 Z2 | ||____^ `X` is a good letter 6 | | X3 Y3 Z3 @@ -206,14 +210,16 @@ error: foo --> test.rs:3:3 | 3 | X0 Y0 Z0 - | ___^ - | ______- - | _________- + | ___^__-__- + | |___|__| + | ||___| + | ||| 4 | ||| X1 Y1 Z1 5 | ||| X2 Y2 Z2 - | |||____^ `X` is a good letter - | |||_______- `Y` is a good letter too - | |||__________- `Z` label + | |||____^__-__- `Z` label + | ||_____|__| + | |______| `Y` is a good letter too + | `X` is a good letter | "#]]; let renderer = Renderer::plain(); @@ -247,14 +253,17 @@ error: foo --> test.rs:3:3 | 3 | X0 Y0 Z0 - | ___^ - | ___- - | ___- + | _____- + | | ____| + | || ___| + | ||| 4 | ||| X1 Y1 Z1 5 | ||| X2 Y2 Z2 - | |||____^ `X` is a good letter - | |||____- `Y` is a good letter too - | |||____- `Z` label + | ||| - + | |||____| + | ||____`X` is a good letter + | |____`Y` is a good letter too + | `Z` label | "#]]; let renderer = Renderer::plain(); @@ -288,16 +297,18 @@ fn foo() { error: foo --> test.rs:3:6 | -3 | X0 Y0 Z0 - | ______^ -4 | | X1 Y1 Z1 - | |____^ `X` is a good letter - | |______- -5 | | X2 Y2 Z2 - | |__________- `Y` is a good letter too - | |___- -6 | | X3 Y3 Z3 - | |_______- `Z` +3 | X0 Y0 Z0 + | _______^ +4 | | X1 Y1 Z1 + | | ____^_- + | ||____| + | | `X` is a good letter +5 | | X2 Y2 Z2 + | |___-______- `Y` is a good letter too + | ___| + | | +6 | | X3 Y3 Z3 + | |_______- `Z` | "#]]; let renderer = Renderer::plain(); @@ -370,14 +381,15 @@ fn foo() { error: foo --> test.rs:3:6 | -3 | X0 Y0 Z0 - | ______^ -4 | | X1 Y1 Z1 - | |____^ `X` is a good letter - | |_________- -5 | | X2 Y2 Z2 -6 | | X3 Y3 Z3 - | |__________- `Y` is a good letter too +3 | X0 Y0 Z0 + | _______^ +4 | | X1 Y1 Z1 + | | ____^____- + | ||____| + | | `X` is a good letter +5 | | X2 Y2 Z2 +6 | | X3 Y3 Z3 + | |__________- `Y` is a good letter too | "#]]; let renderer = Renderer::plain(); @@ -405,9 +417,7 @@ error: foo --> test.rs:3:7 | 3 | a { b { c } d } - | ^^^^^^^ - | ------------- `a` is a good letter - | - + | ----^^^^-^^-- `a` is a good letter | "#]]; let renderer = Renderer::plain(); @@ -434,8 +444,7 @@ error: foo --> test.rs:3:3 | 3 | a { b { c } d } - | ^^^^^^^^^^^^^ `a` is a good letter - | ------- + | ^^^^-------^^ `a` is a good letter | "#]]; let renderer = Renderer::plain(); @@ -463,9 +472,9 @@ error: foo --> test.rs:3:7 | 3 | a { b { c } d } - | ^^^^^^^ `b` is a good letter - | ------------- - | - + | ----^^^^-^^-- + | | + | `b` is a good letter | "#]]; let renderer = Renderer::plain(); @@ -492,8 +501,9 @@ error: foo --> test.rs:3:3 | 3 | a { b { c } d } - | ^^^^^^^^^^^^^ - | ------- `b` is a good letter + | ^^^^-------^^ + | | + | `b` is a good letter | "#]]; let renderer = Renderer::plain(); @@ -520,8 +530,9 @@ error: foo --> test.rs:3:3 | 3 | a bc d - | ^^^^ `a` is a good letter - | ---- + | ^^^^---- + | | + | `a` is a good letter | "#]]; let renderer = Renderer::plain(); @@ -548,8 +559,7 @@ error: foo --> test.rs:3:3 | 3 | a { b { c } d } - | ^^^^^^^^^^^^^ - | ------- + | ^^^^-------^^ | "#]]; let renderer = Renderer::plain(); @@ -577,9 +587,7 @@ error: foo --> test.rs:3:7 | 3 | a { b { c } d } - | ^^^^^^^ - | ------------- - | - + | ----^^^^-^^-- | "#]]; let renderer = Renderer::plain(); @@ -606,8 +614,10 @@ error: foo --> test.rs:3:3 | 3 | a { b { c } d } - | ^^^^^^^^^^^^^ `a` is a good letter - | ------- `b` is a good letter + | ^^^^-------^^ + | | | + | | `b` is a good letter + | `a` is a good letter | "#]]; let renderer = Renderer::plain(); @@ -702,16 +712,17 @@ fn foo() { error: foo --> test.rs:3:6 | - 3 | X0 Y0 Z0 - | ______^ - 4 | | X1 Y1 Z1 - | |____^ `X` is a good letter - | |_________- - 5 | | 1 -... | -15 | | X2 Y2 Z2 -16 | | X3 Y3 Z3 - | |__________- `Y` is a good letter too + 3 | X0 Y0 Z0 + | _______^ + 4 | | X1 Y1 Z1 + | | ____^____- + | ||____| + | | `X` is a good letter + 5 | | 1 +... | +15 | | X2 Y2 Z2 +16 | | X3 Y3 Z3 + | |__________- `Y` is a good letter too | "#]]; let renderer = Renderer::plain(); @@ -755,22 +766,22 @@ error: foo --> test.rs:3:6 | 3 | X0 Y0 Z0 - | ______^ - 4 | | 1 - 5 | | 2 - 6 | | 3 - 7 | | X1 Y1 Z1 - | |_________- + | _______^ + 4 | | 1 + 5 | | 2 + 6 | | 3 + 7 | | X1 Y1 Z1 + | | _________- 8 | || 4 9 | || 5 10 | || 6 11 | || X2 Y2 Z2 | ||__________- `Z` is a good letter too -12 | | 7 -... | -15 | | 10 -16 | | X3 Y3 Z3 - | |_______^ `Y` is a good letter +12 | | 7 +... | +15 | | 10 +16 | | X3 Y3 Z3 + | |________^ `Y` is a good letter | "#]]; let renderer = Renderer::plain(); From 0175871363182516e0b69fbe95d7a997f58564a3 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 19 Jun 2024 21:13:36 -0600 Subject: [PATCH 240/455] feat: Merge multiline annotations with matching spans --- src/renderer/display_list.rs | 52 ++++++++++++++++++++++++++++++++++++ tests/rustc_tests.rs | 23 ++++++++-------- 2 files changed, 63 insertions(+), 12 deletions(-) diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index 2c8dbf9c..8d0c1f08 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -1248,6 +1248,58 @@ fn format_body( let mut depth_map: HashMap<usize, usize> = HashMap::new(); let mut current_depth = 0; let mut annotations = snippet.annotations; + let ranges = annotations + .iter() + .map(|a| a.range.clone()) + .collect::<Vec<_>>(); + // We want to merge multiline annotations that have the same range into one + // multiline annotation to save space. This is done by making any duplicate + // multiline annotations into a single-line annotation pointing at the end + // of the range. + // + // 3 | X0 Y0 Z0 + // | _____^ + // | | ____| + // | || ___| + // | ||| + // 4 | ||| X1 Y1 Z1 + // 5 | ||| X2 Y2 Z2 + // | ||| ^ + // | |||____| + // | ||____`X` is a good letter + // | |____`Y` is a good letter too + // | `Z` label + // Should be + // error: foo + // --> test.rs:3:3 + // | + // 3 | / X0 Y0 Z0 + // 4 | | X1 Y1 Z1 + // 5 | | X2 Y2 Z2 + // | | ^ + // | |____| + // | `X` is a good letter + // | `Y` is a good letter too + // | `Z` label + // | + ranges.iter().enumerate().for_each(|(r_idx, range)| { + annotations + .iter_mut() + .enumerate() + .skip(r_idx + 1) + .for_each(|(ann_idx, ann)| { + // Skip if the annotation's index matches the range index + if ann_idx != r_idx + // We only want to merge multiline annotations + && snippet.source[ann.range.clone()].lines().count() > 1 + // We only want to merge annotations that have the same range + && ann.range.start == range.start + && ann.range.end == range.end + { + ann.range.start = ann.range.end.saturating_sub(1); + } + }); + }); annotations.sort_by_key(|a| a.range.start); let mut annotations = annotations.into_iter().enumerate().collect::<Vec<_>>(); diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index ed2c7ad2..8db9b80f 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -248,22 +248,21 @@ fn foo() { .annotation(Level::Warning.span(14..38).label("`Z` label")), ); + // This should have a `^` but we currently don't support the idea of a + // "primary" annotation, which would solve this let expected = str![[r#" error: foo --> test.rs:3:3 | -3 | X0 Y0 Z0 - | _____- - | | ____| - | || ___| - | ||| -4 | ||| X1 Y1 Z1 -5 | ||| X2 Y2 Z2 - | ||| - - | |||____| - | ||____`X` is a good letter - | |____`Y` is a good letter too - | `Z` label +3 | X0 Y0 Z0 + | ___^ +4 | | X1 Y1 Z1 +5 | | X2 Y2 Z2 + | | - + | |____| + | `X` is a good letter + | `Y` is a good letter too + | `Z` label | "#]]; let renderer = Renderer::plain(); From 66bbd827167c037a2105719edbae4e8fd50f86a9 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 19 Jun 2024 21:21:54 -0600 Subject: [PATCH 241/455] feat: Match Rust's multiline start special case --- examples/format.svg | 52 +++++++++++----------- src/renderer/display_list.rs | 86 +++++++++++++++++++----------------- tests/rustc_tests.rs | 6 +-- 3 files changed, 73 insertions(+), 71 deletions(-) diff --git a/examples/format.svg b/examples/format.svg index a427c944..ac196c01 100644 --- a/examples/format.svg +++ b/examples/format.svg @@ -1,4 +1,4 @@ -<svg width="740px" height="560px" xmlns="http://www.w3.org/2000/svg"> +<svg width="740px" height="542px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -30,55 +30,53 @@ </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">--------------</tspan><tspan> </tspan><tspan class="fg-yellow bold">expected `Option<String>` because of return type</tspan> </tspan> - <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">52 |</tspan><tspan> for ann in annotations {</tspan> + <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">52 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">/</tspan><tspan> for ann in annotations {</tspan> </tspan> - <tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">_____^</tspan> + <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">53 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> match (ann.range.0, ann.range.1) {</tspan> </tspan> - <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">53 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> match (ann.range.0, ann.range.1) {</tspan> + <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">54 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> (None, None) => continue,</tspan> </tspan> - <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">54 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> (None, None) => continue,</tspan> + <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">55 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> (Some(start), Some(end)) if start > end_index => continue,</tspan> </tspan> - <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">55 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> (Some(start), Some(end)) if start > end_index => continue,</tspan> + <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">56 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> (Some(start), Some(end)) if start >= start_index => {</tspan> </tspan> - <tspan x="10px" y="208px"><tspan class="fg-bright-blue bold">56 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> (Some(start), Some(end)) if start >= start_index => {</tspan> + <tspan x="10px" y="208px"><tspan class="fg-bright-blue bold">57 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> let label = if let Some(ref label) = ann.label {</tspan> </tspan> - <tspan x="10px" y="226px"><tspan class="fg-bright-blue bold">57 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> let label = if let Some(ref label) = ann.label {</tspan> + <tspan x="10px" y="226px"><tspan class="fg-bright-blue bold">58 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> format!(" {}", label)</tspan> </tspan> - <tspan x="10px" y="244px"><tspan class="fg-bright-blue bold">58 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> format!(" {}", label)</tspan> + <tspan x="10px" y="244px"><tspan class="fg-bright-blue bold">59 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> } else {</tspan> </tspan> - <tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">59 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> } else {</tspan> + <tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">60 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> String::from("")</tspan> </tspan> - <tspan x="10px" y="280px"><tspan class="fg-bright-blue bold">60 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> String::from("")</tspan> + <tspan x="10px" y="280px"><tspan class="fg-bright-blue bold">61 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> };</tspan> </tspan> - <tspan x="10px" y="298px"><tspan class="fg-bright-blue bold">61 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> };</tspan> + <tspan x="10px" y="298px"><tspan class="fg-bright-blue bold">62 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan> </tspan> - <tspan x="10px" y="316px"><tspan class="fg-bright-blue bold">62 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan> + <tspan x="10px" y="316px"><tspan class="fg-bright-blue bold">63 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> return Some(format!(</tspan> </tspan> - <tspan x="10px" y="334px"><tspan class="fg-bright-blue bold">63 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> return Some(format!(</tspan> + <tspan x="10px" y="334px"><tspan class="fg-bright-blue bold">64 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> "{}{}{}",</tspan> </tspan> - <tspan x="10px" y="352px"><tspan class="fg-bright-blue bold">64 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> "{}{}{}",</tspan> + <tspan x="10px" y="352px"><tspan class="fg-bright-blue bold">65 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> " ".repeat(start - start_index),</tspan> </tspan> - <tspan x="10px" y="370px"><tspan class="fg-bright-blue bold">65 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> " ".repeat(start - start_index),</tspan> + <tspan x="10px" y="370px"><tspan class="fg-bright-blue bold">66 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> "^".repeat(end - start),</tspan> </tspan> - <tspan x="10px" y="388px"><tspan class="fg-bright-blue bold">66 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> "^".repeat(end - start),</tspan> + <tspan x="10px" y="388px"><tspan class="fg-bright-blue bold">67 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> label</tspan> </tspan> - <tspan x="10px" y="406px"><tspan class="fg-bright-blue bold">67 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> label</tspan> + <tspan x="10px" y="406px"><tspan class="fg-bright-blue bold">68 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> ));</tspan> </tspan> - <tspan x="10px" y="424px"><tspan class="fg-bright-blue bold">68 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> ));</tspan> + <tspan x="10px" y="424px"><tspan class="fg-bright-blue bold">69 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan> </tspan> - <tspan x="10px" y="442px"><tspan class="fg-bright-blue bold">69 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan> + <tspan x="10px" y="442px"><tspan class="fg-bright-blue bold">70 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> _ => continue,</tspan> </tspan> - <tspan x="10px" y="460px"><tspan class="fg-bright-blue bold">70 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> _ => continue,</tspan> + <tspan x="10px" y="460px"><tspan class="fg-bright-blue bold">71 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan> </tspan> - <tspan x="10px" y="478px"><tspan class="fg-bright-blue bold">71 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan> + <tspan x="10px" y="478px"><tspan class="fg-bright-blue bold">72 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan> </tspan> - <tspan x="10px" y="496px"><tspan class="fg-bright-blue bold">72 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan> + <tspan x="10px" y="496px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|____^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected enum `std::option::Option`</tspan> </tspan> - <tspan x="10px" y="514px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|____^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected enum `std::option::Option`</tspan> + <tspan x="10px" y="514px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="532px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> -</tspan> - <tspan x="10px" y="550px"> + <tspan x="10px" y="532px"> </tspan> </text> diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index 8d0c1f08..955b4b2f 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -442,6 +442,42 @@ impl<'a> DisplaySet<'a> { line_len += 1; } + // This is a special case where we have a multiline + // annotation that is at the start of the line disregarding + // any leading whitespace, and no other multiline + // annotations overlap it. In this case, we want to draw + // + // 2 | fn foo() { + // | _^ + // 3 | | + // 4 | | } + // | |_^ test + // + // we simplify the output to: + // + // 2 | / fn foo() { + // 3 | | + // 4 | | } + // | |_^ test + if multiline_depth == 1 + && annotations_positions.len() == 1 + && annotations_positions + .first() + .map_or(false, |(_, annotation)| { + matches!( + annotation.annotation_part, + DisplayAnnotationPart::MultilineStart(_) + ) && text + .chars() + .take(annotation.range.0) + .all(|c| c.is_whitespace()) + }) + { + let (_, ann) = annotations_positions.remove(0); + let style = get_annotation_style(&ann.annotation_type, stylesheet); + buffer.putc(line_offset, 3 + lineno_width, '/', *style); + } + // Draw the column separator for any extra lines that were // created // @@ -535,15 +571,13 @@ impl<'a> DisplaySet<'a> { // Add in any inline marks for any extra lines that have // been created. Output should look like above. for inline_mark in inline_marks { - if let DisplayMarkType::AnnotationThrough(depth) = inline_mark.mark_type { - let style = - get_annotation_style(&inline_mark.annotation_type, stylesheet); - if annotations_positions.is_empty() { - buffer.putc(line_offset, width_offset + depth, '|', *style); - } else { - for p in line_offset..=line_offset + line_len + 1 { - buffer.putc(p, width_offset + depth, '|', *style); - } + let DisplayMarkType::AnnotationThrough(depth) = inline_mark.mark_type; + let style = get_annotation_style(&inline_mark.annotation_type, stylesheet); + if annotations_positions.is_empty() { + buffer.putc(line_offset, width_offset + depth, '|', *style); + } else { + for p in line_offset..=line_offset + line_len + 1 { + buffer.putc(p, width_offset + depth, '|', *style); } } } @@ -823,8 +857,6 @@ pub(crate) struct DisplayMark { pub(crate) enum DisplayMarkType { /// A mark indicating a multiline annotation going through the current line. AnnotationThrough(usize), - /// A mark indicating a multiline annotation starting on the given line. - AnnotationStart, } /// A type of the `Annotation` which may impact the sigils, style or text displayed. @@ -1153,18 +1185,8 @@ fn fold_body(body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> { let mut unhighlighed_lines = vec![]; for line in body { match &line { - DisplayLine::Source { - annotations, - inline_marks, - .. - } => { - if annotations.is_empty() - // A multiline start mark (`/`) needs be treated as an - // annotation or the line could get folded. - && inline_marks - .iter() - .all(|m| m.mark_type != DisplayMarkType::AnnotationStart) - { + DisplayLine::Source { annotations, .. } => { + if annotations.is_empty() { unhighlighed_lines.push(line); } else { if lines.is_empty() { @@ -1407,20 +1429,7 @@ fn format_body( && start <= line_end_index + end_line_size.saturating_sub(1) && end > line_end_index => { - // Special case for multiline annotations that start at the - // beginning of a line, which requires a special mark (`/`) - if start - line_start_index == 0 { - if let DisplayLine::Source { - ref mut inline_marks, - .. - } = body[body_idx] - { - inline_marks.push(DisplayMark { - mark_type: DisplayMarkType::AnnotationStart, - annotation_type: DisplayAnnotationType::from(annotation.level), - }); - } - } else if let DisplayLine::Source { + if let DisplayLine::Source { ref mut annotations, .. } = body[body_idx] @@ -1679,9 +1688,6 @@ fn format_inline_marks( DisplayMarkType::AnnotationThrough(depth) => { buf.putc(line, 3 + lineno_width + depth, '|', *annotation_style); } - DisplayMarkType::AnnotationStart => { - buf.putc(line, 3 + lineno_width, '/', *annotation_style); - } }; } Ok(()) diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index 8db9b80f..620ca454 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -254,8 +254,7 @@ fn foo() { error: foo --> test.rs:3:3 | -3 | X0 Y0 Z0 - | ___^ +3 | / X0 Y0 Z0 4 | | X1 Y1 Z1 5 | | X2 Y2 Z2 | | - @@ -340,8 +339,7 @@ fn foo() { error: foo --> test.rs:3:3 | -3 | X0 Y0 Z0 - | ___^ +3 | / X0 Y0 Z0 4 | | X1 Y1 Z1 | |____^ `X` is a good letter 5 | X2 Y2 Z2 From 2a274e149f7f6f7f80f08486bd34c4fc7b8d63c8 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Fri, 21 Jun 2024 14:21:41 -0400 Subject: [PATCH 242/455] chore(ci): Auto-update Mac now that latest uses m1 --- .github/workflows/ci.yml | 2 +- .github/workflows/rust-next.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 95b13b4f..6e064993 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: name: Test strategy: matrix: - os: ["ubuntu-latest", "windows-latest", "macos-14"] + os: ["ubuntu-latest", "windows-latest", "macos-latest"] rust: ["stable"] continue-on-error: ${{ matrix.rust != 'stable' }} runs-on: ${{ matrix.os }} diff --git a/.github/workflows/rust-next.yml b/.github/workflows/rust-next.yml index ab499633..e98386c4 100644 --- a/.github/workflows/rust-next.yml +++ b/.github/workflows/rust-next.yml @@ -21,7 +21,7 @@ jobs: name: Test strategy: matrix: - os: ["ubuntu-latest", "windows-latest", "macos-latest", "macos-14"] + os: ["ubuntu-latest", "windows-latest", "macos-latest"] rust: ["stable", "beta"] include: - os: ubuntu-latest From b25bd3e02f2256f34daad1efca730ec581c139cf Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Mon, 24 Jun 2024 11:05:33 -0600 Subject: [PATCH 243/455] feat: Match Rust's overlapping multiline starts --- src/renderer/display_list.rs | 34 ++++++++++++++++++++++++++++++++-- tests/rustc_tests.rs | 17 +++++++---------- 2 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index 955b4b2f..eea49718 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -269,7 +269,7 @@ impl<'a> DisplaySet<'a> { } } - // Adapted from https://github.com/rust-lang/rust/blob/894f7a4ba6554d3797404bbf550d9919df060b97/compiler/rustc_errors/src/emitter.rs#L706-L1155 + // Adapted from https://github.com/rust-lang/rust/blob/d371d17496f2ce3a56da76aa083f4ef157572c20/compiler/rustc_errors/src/emitter.rs#L706-L1211 #[inline] fn format_line( &self, @@ -366,7 +366,7 @@ impl<'a> DisplaySet<'a> { annotations.sort_by_key(|a| Reverse(a.range.0)); let mut annotations_positions = vec![]; - let mut line_len = 0; + let mut line_len: usize = 0; let mut p = 0; for (i, annotation) in annotations.iter().enumerate() { for (j, next) in annotations.iter().enumerate() { @@ -442,6 +442,36 @@ impl<'a> DisplaySet<'a> { line_len += 1; } + if annotations_positions.iter().all(|(_, ann)| { + matches!( + ann.annotation_part, + DisplayAnnotationPart::MultilineStart(_) + ) + }) { + if let Some(max_pos) = + annotations_positions.iter().map(|(pos, _)| *pos).max() + { + // Special case the following, so that we minimize overlapping multiline spans. + // + // 3 │ X0 Y0 Z0 + // │ ┏━━━━━┛ │ │ < We are writing these lines + // │ ┃┌───────┘ │ < by reverting the "depth" of + // │ ┃│┌─────────┘ < their multilne spans. + // 4 │ ┃││ X1 Y1 Z1 + // 5 │ ┃││ X2 Y2 Z2 + // │ ┃│└────╿──│──┘ `Z` label + // │ ┃└─────│──┤ + // │ ┗━━━━━━┥ `Y` is a good letter too + // ╰╴ `X` is a good letter + for (pos, _) in &mut annotations_positions { + *pos = max_pos - *pos; + } + // We know then that we don't need an additional line for the span label, saving us + // one line of vertical space. + line_len = line_len.saturating_sub(1); + } + } + // This is a special case where we have a multiline // annotation that is at the start of the line disregarding // any leading whitespace, and no other multiline diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index 620ca454..54b73211 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -91,9 +91,8 @@ error: foo --> test.rs:3:3 | 3 | X0 Y0 - | ___^__- - | |___| - | || + | ____^ - + | | ______| 4 | || X1 Y1 5 | || X2 Y2 | ||____^__- `Y` is a good letter too @@ -130,9 +129,8 @@ error: foo --> test.rs:3:3 | 3 | X0 Y0 - | ___^__- - | |___| - | || + | ____^ - + | | ______| 4 | || Y1 X1 | ||____-__^ `X` is a good letter | |____| @@ -210,10 +208,9 @@ error: foo --> test.rs:3:3 | 3 | X0 Y0 Z0 - | ___^__-__- - | |___|__| - | ||___| - | ||| + | _____^ - - + | | _______| | + | || _________| 4 | ||| X1 Y1 Z1 5 | ||| X2 Y2 Z2 | |||____^__-__- `Z` label From 10e6e40bc668d4912d560b8ca871a5a312cd431d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 00:49:35 +0000 Subject: [PATCH 244/455] chore(deps): Update Rust crate snapbox to v0.6.10 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 40a064e1..d47f14d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -685,9 +685,9 @@ checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" [[package]] name = "snapbox" -version = "0.6.7" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94204b12a4d3550420babdb4148c6639692e4e3e61060866929c5107f208aeb6" +checksum = "40e14d10e4c2b4331ac24c33baa5a03e1fbca81c045b285b53b2a612d28569fb" dependencies = [ "anstream 0.6.14", "anstyle", From 0547ff2d0f135d541faef3735143b40c174b4c3a Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 4 Jul 2024 12:54:40 -0400 Subject: [PATCH 245/455] docs(contrib): Clarify our policies --- CONTRIBUTING.md | 45 ++++++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e9d70793..1a6dd1cd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,27 +29,42 @@ to re-work some of it and the discouragement that goes along with that. ### Process -Before posting a PR, we request that the commit history get cleaned up. -However, we recommend avoiding this during the review to make it easier to -check how feedback was handled. Once the PR is ready, we'll ask you to clean up -the commit history from the review. Once you let us know this is done, we can -move forward with merging! If you are uncomfortable with these parts of git, -let us know and we can help. - -For commit messages, we use [Conventional](https://www.conventionalcommits.org) -style. If you already wrote your commits and don't feel comfortable changing -them, don't worry and go ahead and create your PR. We'll work with you on the -best route forward. You can check your branch locally with -[`committed`](https://github.com/crate-ci/committed). - As a heads up, we'll be running your PR through the following gauntlet: - warnings turned to compile errors - `cargo test` - `rustfmt` - `clippy` - `rustdoc` -- [`committed`](https://github.com/crate-ci/committed) -- [`typos`](https://github.com/crate-ci/typos) +- [`committed`](https://github.com/crate-ci/committed) as we use [Conventional](https://www.conventionalcommits.org) commit styl +- [`typos`](https://github.com/crate-ci/typos) to check spelling + +Not everything can be checked automatically though. + +We request that the commit history get cleaned up. +We ask that commits are atomic, meaning they are complete and have a single responsibility. +PRs shoukd tell a cohesive story, with test and refactor commits that keep the +fix or feature commits simple and clear. + +Specifically, we would encouage +- File renames be isolated into their own commit +- Add tests in a commit before their feature or fix, showing the current behavior. + The diff for the feature/fix commit will then show how the behavior changed, + making it clearer to reviewrs and the community and showing people that the + test is verifying the expected state. + - e.g. [clap#5520](https://github.com/clap-rs/clap/pull/5520) + +Note that we are talking about ideals. +We understand having a clean history requires more advanced git skills; +feel free to ask us for help! +We might even suggest where it would work to be lax. +We also understand that editing some early commits may cause a lot of churn +with merge conflicts which can make it not worth editing all of the history. + +For code organization, we recommend +- Grouping `impl` blocks next to their type (or trait) +- Grouping private items after the `pub` item that uses them. + - The intent is to help people quickly find the "relevant" details, allowing them to "dig deeper" as needed. Or put another way, the `pub` items serve as a table-of-contents. + - The exact order is fuzzy; do what makes sense ## Releasing From eb4e999f1b679936ce1d11aa68b923066aff2ab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jalil=20David=20Salam=C3=A9=20Messina?= <60845989+jalil-salame@users.noreply.github.com> Date: Thu, 4 Jul 2024 19:06:12 +0200 Subject: [PATCH 246/455] Fix typos in CONTRIBUTING.md I found this through [mastodon][1] and found the typos jarring. [1]: https://hachyderm.io/@epage/112729287446906823 --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1a6dd1cd..87d9134e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,14 +35,14 @@ As a heads up, we'll be running your PR through the following gauntlet: - `rustfmt` - `clippy` - `rustdoc` -- [`committed`](https://github.com/crate-ci/committed) as we use [Conventional](https://www.conventionalcommits.org) commit styl +- [`committed`](https://github.com/crate-ci/committed) as we use [Conventional](https://www.conventionalcommits.org) commit style - [`typos`](https://github.com/crate-ci/typos) to check spelling Not everything can be checked automatically though. -We request that the commit history get cleaned up. +We request that the commit history gets cleaned up. We ask that commits are atomic, meaning they are complete and have a single responsibility. -PRs shoukd tell a cohesive story, with test and refactor commits that keep the +PRs should tell a cohesive story, with test and refactor commits that keep the fix or feature commits simple and clear. Specifically, we would encouage From bdb06a11df6cf4d8b06848520f18609ab07c7b5e Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Tue, 9 Jul 2024 11:06:45 -0500 Subject: [PATCH 247/455] chore(ci): Verify version requirements --- .github/workflows/ci.yml | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e064993..d49017e7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: permissions: contents: none name: CI - needs: [test, msrv, lockfile, docs, rustfmt, clippy] + needs: [test, msrv, lockfile, docs, rustfmt, clippy, minimal-versions] runs-on: ubuntu-latest if: "always()" steps: @@ -65,6 +65,24 @@ jobs: - uses: taiki-e/install-action@cargo-hack - name: Default features run: cargo hack check --feature-powerset --locked --rust-version --ignore-private --workspace --all-targets + minimal-versions: + name: Minimal versions + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Install stable Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: stable + - name: Install nightly Rust + uses: dtolnay/rust-toolchain@stable + with: + toolchain: nightly + - name: Downgrade dependencies to minimal versions + run: cargo +nightly generate-lockfile -Z minimal-versions + - name: Compile with minimal versions + run: cargo +stable check --workspace --all-features --locked lockfile: runs-on: ubuntu-latest steps: From 87d9ae55c792a4f37b3f989250c1a3512df2926e Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 25 Jul 2024 15:48:09 -0500 Subject: [PATCH 248/455] chore: Fix clippy::lint_groups_priority for 1.80 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 96cb2348..90d89f6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ include = [ ] [workspace.lints.rust] -rust_2018_idioms = "warn" +rust_2018_idioms = { level = "warn", priority = -1 } unreachable_pub = "warn" unsafe_op_in_unsafe_fn = "warn" unused_lifetimes = "warn" From c87ce7f7737563ec8d0c9e44ae8ae0bf984062e8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 25 Jul 2024 21:11:48 +0000 Subject: [PATCH 249/455] chore(deps): Update Rust Stable to v1.80 --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e1d62e47..89809f55 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,7 +86,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.79" # STABLE + toolchain: "1.80" # STABLE - uses: Swatinem/rust-cache@v2 - name: Check documentation env: @@ -101,7 +101,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.79" # STABLE + toolchain: "1.80" # STABLE components: rustfmt - uses: Swatinem/rust-cache@v2 - name: Check formatting @@ -117,7 +117,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.79" # STABLE + toolchain: "1.80" # STABLE components: clippy - uses: Swatinem/rust-cache@v2 - name: Install SARIF tools From 553258af51034bc84dc9f951201e7b8f2285b57e Mon Sep 17 00:00:00 2001 From: Josh Triplett <josh@joshtriplett.org> Date: Thu, 25 Jul 2024 15:35:58 -0700 Subject: [PATCH 250/455] Have clippy warn about uninlined format arguments This makes clippy warn about `format!("{}", var)`, with a machine-applicable fix converting to `format!("{var}")`. --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 90d89f6e..97c7ed79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -80,6 +80,7 @@ string_lit_as_bytes = "warn" string_to_string = "warn" todo = "warn" trait_duplication_in_bounds = "warn" +uninlined_format_args = "warn" verbose_file_reads = "warn" wildcard_imports = "warn" zero_sized_map_values = "warn" From 43a10aa8619972e446213a1c8a6649893b082f4b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 1 Aug 2024 00:38:57 +0000 Subject: [PATCH 251/455] chore(deps): Update compatible (dev) --- Cargo.lock | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d47f14d5..a686b028 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,7 +21,7 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" name = "annotate-snippets" version = "0.11.4" dependencies = [ - "anstream 0.6.14", + "anstream 0.6.15", "anstyle", "criterion", "difference", @@ -50,9 +50,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.14" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", @@ -102,7 +102,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbf0bf947d663010f0b4132f28ca08da9151f3b9035fa7578a38de521c1d1aa" dependencies = [ - "anstream 0.6.14", + "anstream 0.6.15", "anstyle", "anstyle-lossy", "html-escape", @@ -648,18 +648,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", @@ -685,11 +685,11 @@ checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" [[package]] name = "snapbox" -version = "0.6.10" +version = "0.6.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40e14d10e4c2b4331ac24c33baa5a03e1fbca81c045b285b53b2a612d28569fb" +checksum = "027c936207f85d10d015e21faf5c676c7e08c453ed371adf55c0874c443ca77a" dependencies = [ - "anstream 0.6.14", + "anstream 0.6.15", "anstyle", "anstyle-svg", "escargot", @@ -705,11 +705,11 @@ dependencies = [ [[package]] name = "snapbox-macros" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f4c14672714436c09254801c934b203196a51182a5107fb76591c7cc56424d" +checksum = "16569f53ca23a41bb6f62e0a5084aa1661f4814a67fa33696a79073e03a664af" dependencies = [ - "anstream 0.6.14", + "anstream 0.6.15", ] [[package]] @@ -778,9 +778,9 @@ dependencies = [ [[package]] name = "tryfn" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "493e1390312bb94363f762687be32a1bd01c3333dfad25a5a7fffab1edc64839" +checksum = "5fe242ee9e646acec9ab73a5c540e8543ed1b107f0ce42be831e0775d423c396" dependencies = [ "ignore", "libtest-mimic", From c5443c45979b52a7ef3790c9604681a171ee76f8 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 21 Aug 2024 15:01:07 -0600 Subject: [PATCH 252/455] test: Move all fixture tests to color --- tests/fixtures/{no-color => color}/ann_eof.svg | 0 tests/fixtures/{no-color => color}/ann_eof.toml | 0 tests/fixtures/{no-color => color}/ann_insertion.svg | 0 tests/fixtures/{no-color => color}/ann_insertion.toml | 0 tests/fixtures/{no-color => color}/ann_multiline.svg | 0 tests/fixtures/{no-color => color}/ann_multiline.toml | 0 tests/fixtures/{no-color => color}/ann_multiline2.svg | 0 tests/fixtures/{no-color => color}/ann_multiline2.toml | 0 tests/fixtures/{no-color => color}/ann_removed_nl.svg | 0 tests/fixtures/{no-color => color}/ann_removed_nl.toml | 0 .../fixtures/{no-color => color}/ensure-emoji-highlight-width.svg | 0 .../{no-color => color}/ensure-emoji-highlight-width.toml | 0 tests/fixtures/{no-color => color}/fold_ann_multiline.svg | 0 tests/fixtures/{no-color => color}/fold_ann_multiline.toml | 0 tests/fixtures/{no-color => color}/fold_bad_origin_line.svg | 0 tests/fixtures/{no-color => color}/fold_bad_origin_line.toml | 0 tests/fixtures/{no-color => color}/fold_leading.svg | 0 tests/fixtures/{no-color => color}/fold_leading.toml | 0 tests/fixtures/{no-color => color}/fold_trailing.svg | 0 tests/fixtures/{no-color => color}/fold_trailing.toml | 0 tests/fixtures/{no-color => color}/issue_9.svg | 0 tests/fixtures/{no-color => color}/issue_9.toml | 0 tests/fixtures/{no-color => color}/multiple_annotations.svg | 0 tests/fixtures/{no-color => color}/multiple_annotations.toml | 0 tests/fixtures/{no-color => color}/simple.svg | 0 tests/fixtures/{no-color => color}/simple.toml | 0 tests/fixtures/{no-color => color}/strip_line.svg | 0 tests/fixtures/{no-color => color}/strip_line.toml | 0 tests/fixtures/{no-color => color}/strip_line_char.svg | 0 tests/fixtures/{no-color => color}/strip_line_char.toml | 0 tests/fixtures/{no-color => color}/strip_line_non_ws.svg | 0 tests/fixtures/{no-color => color}/strip_line_non_ws.toml | 0 32 files changed, 0 insertions(+), 0 deletions(-) rename tests/fixtures/{no-color => color}/ann_eof.svg (100%) rename tests/fixtures/{no-color => color}/ann_eof.toml (100%) rename tests/fixtures/{no-color => color}/ann_insertion.svg (100%) rename tests/fixtures/{no-color => color}/ann_insertion.toml (100%) rename tests/fixtures/{no-color => color}/ann_multiline.svg (100%) rename tests/fixtures/{no-color => color}/ann_multiline.toml (100%) rename tests/fixtures/{no-color => color}/ann_multiline2.svg (100%) rename tests/fixtures/{no-color => color}/ann_multiline2.toml (100%) rename tests/fixtures/{no-color => color}/ann_removed_nl.svg (100%) rename tests/fixtures/{no-color => color}/ann_removed_nl.toml (100%) rename tests/fixtures/{no-color => color}/ensure-emoji-highlight-width.svg (100%) rename tests/fixtures/{no-color => color}/ensure-emoji-highlight-width.toml (100%) rename tests/fixtures/{no-color => color}/fold_ann_multiline.svg (100%) rename tests/fixtures/{no-color => color}/fold_ann_multiline.toml (100%) rename tests/fixtures/{no-color => color}/fold_bad_origin_line.svg (100%) rename tests/fixtures/{no-color => color}/fold_bad_origin_line.toml (100%) rename tests/fixtures/{no-color => color}/fold_leading.svg (100%) rename tests/fixtures/{no-color => color}/fold_leading.toml (100%) rename tests/fixtures/{no-color => color}/fold_trailing.svg (100%) rename tests/fixtures/{no-color => color}/fold_trailing.toml (100%) rename tests/fixtures/{no-color => color}/issue_9.svg (100%) rename tests/fixtures/{no-color => color}/issue_9.toml (100%) rename tests/fixtures/{no-color => color}/multiple_annotations.svg (100%) rename tests/fixtures/{no-color => color}/multiple_annotations.toml (100%) rename tests/fixtures/{no-color => color}/simple.svg (100%) rename tests/fixtures/{no-color => color}/simple.toml (100%) rename tests/fixtures/{no-color => color}/strip_line.svg (100%) rename tests/fixtures/{no-color => color}/strip_line.toml (100%) rename tests/fixtures/{no-color => color}/strip_line_char.svg (100%) rename tests/fixtures/{no-color => color}/strip_line_char.toml (100%) rename tests/fixtures/{no-color => color}/strip_line_non_ws.svg (100%) rename tests/fixtures/{no-color => color}/strip_line_non_ws.toml (100%) diff --git a/tests/fixtures/no-color/ann_eof.svg b/tests/fixtures/color/ann_eof.svg similarity index 100% rename from tests/fixtures/no-color/ann_eof.svg rename to tests/fixtures/color/ann_eof.svg diff --git a/tests/fixtures/no-color/ann_eof.toml b/tests/fixtures/color/ann_eof.toml similarity index 100% rename from tests/fixtures/no-color/ann_eof.toml rename to tests/fixtures/color/ann_eof.toml diff --git a/tests/fixtures/no-color/ann_insertion.svg b/tests/fixtures/color/ann_insertion.svg similarity index 100% rename from tests/fixtures/no-color/ann_insertion.svg rename to tests/fixtures/color/ann_insertion.svg diff --git a/tests/fixtures/no-color/ann_insertion.toml b/tests/fixtures/color/ann_insertion.toml similarity index 100% rename from tests/fixtures/no-color/ann_insertion.toml rename to tests/fixtures/color/ann_insertion.toml diff --git a/tests/fixtures/no-color/ann_multiline.svg b/tests/fixtures/color/ann_multiline.svg similarity index 100% rename from tests/fixtures/no-color/ann_multiline.svg rename to tests/fixtures/color/ann_multiline.svg diff --git a/tests/fixtures/no-color/ann_multiline.toml b/tests/fixtures/color/ann_multiline.toml similarity index 100% rename from tests/fixtures/no-color/ann_multiline.toml rename to tests/fixtures/color/ann_multiline.toml diff --git a/tests/fixtures/no-color/ann_multiline2.svg b/tests/fixtures/color/ann_multiline2.svg similarity index 100% rename from tests/fixtures/no-color/ann_multiline2.svg rename to tests/fixtures/color/ann_multiline2.svg diff --git a/tests/fixtures/no-color/ann_multiline2.toml b/tests/fixtures/color/ann_multiline2.toml similarity index 100% rename from tests/fixtures/no-color/ann_multiline2.toml rename to tests/fixtures/color/ann_multiline2.toml diff --git a/tests/fixtures/no-color/ann_removed_nl.svg b/tests/fixtures/color/ann_removed_nl.svg similarity index 100% rename from tests/fixtures/no-color/ann_removed_nl.svg rename to tests/fixtures/color/ann_removed_nl.svg diff --git a/tests/fixtures/no-color/ann_removed_nl.toml b/tests/fixtures/color/ann_removed_nl.toml similarity index 100% rename from tests/fixtures/no-color/ann_removed_nl.toml rename to tests/fixtures/color/ann_removed_nl.toml diff --git a/tests/fixtures/no-color/ensure-emoji-highlight-width.svg b/tests/fixtures/color/ensure-emoji-highlight-width.svg similarity index 100% rename from tests/fixtures/no-color/ensure-emoji-highlight-width.svg rename to tests/fixtures/color/ensure-emoji-highlight-width.svg diff --git a/tests/fixtures/no-color/ensure-emoji-highlight-width.toml b/tests/fixtures/color/ensure-emoji-highlight-width.toml similarity index 100% rename from tests/fixtures/no-color/ensure-emoji-highlight-width.toml rename to tests/fixtures/color/ensure-emoji-highlight-width.toml diff --git a/tests/fixtures/no-color/fold_ann_multiline.svg b/tests/fixtures/color/fold_ann_multiline.svg similarity index 100% rename from tests/fixtures/no-color/fold_ann_multiline.svg rename to tests/fixtures/color/fold_ann_multiline.svg diff --git a/tests/fixtures/no-color/fold_ann_multiline.toml b/tests/fixtures/color/fold_ann_multiline.toml similarity index 100% rename from tests/fixtures/no-color/fold_ann_multiline.toml rename to tests/fixtures/color/fold_ann_multiline.toml diff --git a/tests/fixtures/no-color/fold_bad_origin_line.svg b/tests/fixtures/color/fold_bad_origin_line.svg similarity index 100% rename from tests/fixtures/no-color/fold_bad_origin_line.svg rename to tests/fixtures/color/fold_bad_origin_line.svg diff --git a/tests/fixtures/no-color/fold_bad_origin_line.toml b/tests/fixtures/color/fold_bad_origin_line.toml similarity index 100% rename from tests/fixtures/no-color/fold_bad_origin_line.toml rename to tests/fixtures/color/fold_bad_origin_line.toml diff --git a/tests/fixtures/no-color/fold_leading.svg b/tests/fixtures/color/fold_leading.svg similarity index 100% rename from tests/fixtures/no-color/fold_leading.svg rename to tests/fixtures/color/fold_leading.svg diff --git a/tests/fixtures/no-color/fold_leading.toml b/tests/fixtures/color/fold_leading.toml similarity index 100% rename from tests/fixtures/no-color/fold_leading.toml rename to tests/fixtures/color/fold_leading.toml diff --git a/tests/fixtures/no-color/fold_trailing.svg b/tests/fixtures/color/fold_trailing.svg similarity index 100% rename from tests/fixtures/no-color/fold_trailing.svg rename to tests/fixtures/color/fold_trailing.svg diff --git a/tests/fixtures/no-color/fold_trailing.toml b/tests/fixtures/color/fold_trailing.toml similarity index 100% rename from tests/fixtures/no-color/fold_trailing.toml rename to tests/fixtures/color/fold_trailing.toml diff --git a/tests/fixtures/no-color/issue_9.svg b/tests/fixtures/color/issue_9.svg similarity index 100% rename from tests/fixtures/no-color/issue_9.svg rename to tests/fixtures/color/issue_9.svg diff --git a/tests/fixtures/no-color/issue_9.toml b/tests/fixtures/color/issue_9.toml similarity index 100% rename from tests/fixtures/no-color/issue_9.toml rename to tests/fixtures/color/issue_9.toml diff --git a/tests/fixtures/no-color/multiple_annotations.svg b/tests/fixtures/color/multiple_annotations.svg similarity index 100% rename from tests/fixtures/no-color/multiple_annotations.svg rename to tests/fixtures/color/multiple_annotations.svg diff --git a/tests/fixtures/no-color/multiple_annotations.toml b/tests/fixtures/color/multiple_annotations.toml similarity index 100% rename from tests/fixtures/no-color/multiple_annotations.toml rename to tests/fixtures/color/multiple_annotations.toml diff --git a/tests/fixtures/no-color/simple.svg b/tests/fixtures/color/simple.svg similarity index 100% rename from tests/fixtures/no-color/simple.svg rename to tests/fixtures/color/simple.svg diff --git a/tests/fixtures/no-color/simple.toml b/tests/fixtures/color/simple.toml similarity index 100% rename from tests/fixtures/no-color/simple.toml rename to tests/fixtures/color/simple.toml diff --git a/tests/fixtures/no-color/strip_line.svg b/tests/fixtures/color/strip_line.svg similarity index 100% rename from tests/fixtures/no-color/strip_line.svg rename to tests/fixtures/color/strip_line.svg diff --git a/tests/fixtures/no-color/strip_line.toml b/tests/fixtures/color/strip_line.toml similarity index 100% rename from tests/fixtures/no-color/strip_line.toml rename to tests/fixtures/color/strip_line.toml diff --git a/tests/fixtures/no-color/strip_line_char.svg b/tests/fixtures/color/strip_line_char.svg similarity index 100% rename from tests/fixtures/no-color/strip_line_char.svg rename to tests/fixtures/color/strip_line_char.svg diff --git a/tests/fixtures/no-color/strip_line_char.toml b/tests/fixtures/color/strip_line_char.toml similarity index 100% rename from tests/fixtures/no-color/strip_line_char.toml rename to tests/fixtures/color/strip_line_char.toml diff --git a/tests/fixtures/no-color/strip_line_non_ws.svg b/tests/fixtures/color/strip_line_non_ws.svg similarity index 100% rename from tests/fixtures/no-color/strip_line_non_ws.svg rename to tests/fixtures/color/strip_line_non_ws.svg diff --git a/tests/fixtures/no-color/strip_line_non_ws.toml b/tests/fixtures/color/strip_line_non_ws.toml similarity index 100% rename from tests/fixtures/no-color/strip_line_non_ws.toml rename to tests/fixtures/color/strip_line_non_ws.toml From a059969c14e8c10fa1799a9f8e073699cd438ade Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 21 Aug 2024 15:02:49 -0600 Subject: [PATCH 253/455] test: Make all color tests have color --- tests/fixtures/color/ann_eof.svg | 15 +++++---- tests/fixtures/color/ann_eof.toml | 3 ++ tests/fixtures/color/ann_insertion.svg | 15 +++++---- tests/fixtures/color/ann_insertion.toml | 3 ++ tests/fixtures/color/ann_multiline.svg | 21 +++++++----- tests/fixtures/color/ann_multiline.toml | 3 ++ tests/fixtures/color/ann_multiline2.svg | 19 ++++++----- tests/fixtures/color/ann_multiline2.toml | 3 ++ tests/fixtures/color/ann_removed_nl.svg | 15 +++++---- tests/fixtures/color/ann_removed_nl.toml | 3 ++ .../color/ensure-emoji-highlight-width.svg | 15 +++++---- .../color/ensure-emoji-highlight-width.toml | 3 ++ tests/fixtures/color/fold_ann_multiline.svg | 28 +++++++++------- tests/fixtures/color/fold_ann_multiline.toml | 3 ++ tests/fixtures/color/fold_bad_origin_line.svg | 16 +++++---- .../fixtures/color/fold_bad_origin_line.toml | 3 ++ tests/fixtures/color/fold_leading.svg | 15 +++++---- tests/fixtures/color/fold_leading.toml | 3 ++ tests/fixtures/color/fold_trailing.svg | 15 +++++---- tests/fixtures/color/fold_trailing.toml | 3 ++ tests/fixtures/color/issue_9.svg | 28 +++++++++------- tests/fixtures/color/issue_9.toml | 3 ++ tests/fixtures/color/multiple_annotations.svg | 33 ++++++++++--------- .../fixtures/color/multiple_annotations.toml | 3 ++ tests/fixtures/color/simple.svg | 22 ++++++++----- tests/fixtures/color/simple.toml | 3 ++ tests/fixtures/color/strip_line.svg | 15 +++++---- tests/fixtures/color/strip_line.toml | 2 +- tests/fixtures/color/strip_line_char.svg | 15 +++++---- tests/fixtures/color/strip_line_char.toml | 2 +- tests/fixtures/color/strip_line_non_ws.svg | 19 ++++++----- tests/fixtures/color/strip_line_non_ws.toml | 1 + tests/fixtures/deserialize.rs | 11 ++++++- 33 files changed, 231 insertions(+), 130 deletions(-) diff --git a/tests/fixtures/color/ann_eof.svg b/tests/fixtures/color/ann_eof.svg index c8900d03..bb12aecf 100644 --- a/tests/fixtures/color/ann_eof.svg +++ b/tests/fixtures/color/ann_eof.svg @@ -2,10 +2,13 @@ <style> .fg { fill: #AAAAAA } .bg { background: #000000 } + .fg-bright-blue { fill: #5555FF } + .fg-bright-red { fill: #FF5555 } .container { padding: 0 10px; line-height: 18px; } + .bold { font-weight: bold; } tspan { font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; white-space: pre; @@ -16,17 +19,17 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan>error: expected `.`, `=`</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">expected `.`, `=`</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> --> Cargo.toml:1:5</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> Cargo.toml:1:5</tspan> </tspan> - <tspan x="10px" y="64px"><tspan> |</tspan> + <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan>1 | asdf</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1 |</tspan><tspan> asdf</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> | ^</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan> </tspan> - <tspan x="10px" y="118px"><tspan> |</tspan> + <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> </text> diff --git a/tests/fixtures/color/ann_eof.toml b/tests/fixtures/color/ann_eof.toml index 313d2204..cee5f0fb 100644 --- a/tests/fixtures/color/ann_eof.toml +++ b/tests/fixtures/color/ann_eof.toml @@ -10,3 +10,6 @@ origin = "Cargo.toml" label = "" level = "Error" range = [4, 4] + +[renderer] +color = true diff --git a/tests/fixtures/color/ann_insertion.svg b/tests/fixtures/color/ann_insertion.svg index b15b81b4..1f4b6a24 100644 --- a/tests/fixtures/color/ann_insertion.svg +++ b/tests/fixtures/color/ann_insertion.svg @@ -2,10 +2,13 @@ <style> .fg { fill: #AAAAAA } .bg { background: #000000 } + .fg-bright-blue { fill: #5555FF } + .fg-bright-red { fill: #FF5555 } .container { padding: 0 10px; line-height: 18px; } + .bold { font-weight: bold; } tspan { font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; white-space: pre; @@ -16,17 +19,17 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan>error: expected `.`, `=`</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">expected `.`, `=`</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> --> Cargo.toml:1:3</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> Cargo.toml:1:3</tspan> </tspan> - <tspan x="10px" y="64px"><tspan> |</tspan> + <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan>1 | asf</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1 |</tspan><tspan> asf</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> | ^ 'd' belongs here</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">'d' belongs here</tspan> </tspan> - <tspan x="10px" y="118px"><tspan> |</tspan> + <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> </text> diff --git a/tests/fixtures/color/ann_insertion.toml b/tests/fixtures/color/ann_insertion.toml index ffd2140b..bf7411ef 100644 --- a/tests/fixtures/color/ann_insertion.toml +++ b/tests/fixtures/color/ann_insertion.toml @@ -10,3 +10,6 @@ origin = "Cargo.toml" label = "'d' belongs here" level = "Error" range = [2, 2] + +[renderer] +color = true diff --git a/tests/fixtures/color/ann_multiline.svg b/tests/fixtures/color/ann_multiline.svg index f4b4433b..91306914 100644 --- a/tests/fixtures/color/ann_multiline.svg +++ b/tests/fixtures/color/ann_multiline.svg @@ -2,10 +2,13 @@ <style> .fg { fill: #AAAAAA } .bg { background: #000000 } + .fg-bright-blue { fill: #5555FF } + .fg-bright-red { fill: #FF5555 } .container { padding: 0 10px; line-height: 18px; } + .bold { font-weight: bold; } tspan { font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; white-space: pre; @@ -16,23 +19,23 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan>error[E0027]: pattern does not mention fields `lineno`, `content`</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0027]</tspan><tspan>: </tspan><tspan class="bold">pattern does not mention fields `lineno`, `content`</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> --> src/display_list.rs:139:32</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> src/display_list.rs:139:32</tspan> </tspan> - <tspan x="10px" y="64px"><tspan> |</tspan> + <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan>139 | if let DisplayLine::Source {</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">139 |</tspan><tspan> if let DisplayLine::Source {</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> | ________________________________^</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">________________________________^</tspan> </tspan> - <tspan x="10px" y="118px"><tspan>140 | | ref mut inline_marks,</tspan> + <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">140 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> ref mut inline_marks,</tspan> </tspan> - <tspan x="10px" y="136px"><tspan>141 | | } = body[body_idx]</tspan> + <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">141 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> } = body[body_idx]</tspan> </tspan> - <tspan x="10px" y="154px"><tspan> | |_________________________^ missing fields `lineno`, `content`</tspan> + <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|_________________________^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">missing fields `lineno`, `content`</tspan> </tspan> - <tspan x="10px" y="172px"><tspan> |</tspan> + <tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> </text> diff --git a/tests/fixtures/color/ann_multiline.toml b/tests/fixtures/color/ann_multiline.toml index 671b5344..9d8c30f9 100644 --- a/tests/fixtures/color/ann_multiline.toml +++ b/tests/fixtures/color/ann_multiline.toml @@ -16,3 +16,6 @@ fold = false label = "missing fields `lineno`, `content`" level = "Error" range = [31, 128] + +[renderer] +color = true diff --git a/tests/fixtures/color/ann_multiline2.svg b/tests/fixtures/color/ann_multiline2.svg index 49c2c4b7..97948a47 100644 --- a/tests/fixtures/color/ann_multiline2.svg +++ b/tests/fixtures/color/ann_multiline2.svg @@ -2,10 +2,13 @@ <style> .fg { fill: #AAAAAA } .bg { background: #000000 } + .fg-bright-blue { fill: #5555FF } + .fg-bright-red { fill: #FF5555 } .container { padding: 0 10px; line-height: 18px; } + .bold { font-weight: bold; } tspan { font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; white-space: pre; @@ -16,21 +19,21 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan>error[E####]: spacing error found</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E####]</tspan><tspan>: </tspan><tspan class="bold">spacing error found</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> --> foo.txt:26:12</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> foo.txt:26:12</tspan> </tspan> - <tspan x="10px" y="64px"><tspan> |</tspan> + <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan>26 | This is an example</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">26 |</tspan><tspan> This is an example</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> | ^^^^^^^ this should not be on separate lines</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">this should not be on separate lines</tspan> </tspan> - <tspan x="10px" y="118px"><tspan>27 | of an edge case of an annotation overflowing</tspan> + <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">27 |</tspan><tspan> of an edge case of an annotation overflowing</tspan> </tspan> - <tspan x="10px" y="136px"><tspan>28 | to exactly one character on next line.</tspan> + <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">28 |</tspan><tspan> to exactly one character on next line.</tspan> </tspan> - <tspan x="10px" y="154px"><tspan> |</tspan> + <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> </text> diff --git a/tests/fixtures/color/ann_multiline2.toml b/tests/fixtures/color/ann_multiline2.toml index afb3aa9c..259d94b4 100644 --- a/tests/fixtures/color/ann_multiline2.toml +++ b/tests/fixtures/color/ann_multiline2.toml @@ -16,3 +16,6 @@ fold = false label = "this should not be on separate lines" level = "Error" range = [11, 19] + +[renderer] +color = true diff --git a/tests/fixtures/color/ann_removed_nl.svg b/tests/fixtures/color/ann_removed_nl.svg index c8900d03..bb12aecf 100644 --- a/tests/fixtures/color/ann_removed_nl.svg +++ b/tests/fixtures/color/ann_removed_nl.svg @@ -2,10 +2,13 @@ <style> .fg { fill: #AAAAAA } .bg { background: #000000 } + .fg-bright-blue { fill: #5555FF } + .fg-bright-red { fill: #FF5555 } .container { padding: 0 10px; line-height: 18px; } + .bold { font-weight: bold; } tspan { font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; white-space: pre; @@ -16,17 +19,17 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan>error: expected `.`, `=`</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">expected `.`, `=`</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> --> Cargo.toml:1:5</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> Cargo.toml:1:5</tspan> </tspan> - <tspan x="10px" y="64px"><tspan> |</tspan> + <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan>1 | asdf</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1 |</tspan><tspan> asdf</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> | ^</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan> </tspan> - <tspan x="10px" y="118px"><tspan> |</tspan> + <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> </text> diff --git a/tests/fixtures/color/ann_removed_nl.toml b/tests/fixtures/color/ann_removed_nl.toml index b681c293..36f74ef6 100644 --- a/tests/fixtures/color/ann_removed_nl.toml +++ b/tests/fixtures/color/ann_removed_nl.toml @@ -10,3 +10,6 @@ origin = "Cargo.toml" label = "" level = "Error" range = [4, 5] + +[renderer] +color = true diff --git a/tests/fixtures/color/ensure-emoji-highlight-width.svg b/tests/fixtures/color/ensure-emoji-highlight-width.svg index 0840805e..e5646e61 100644 --- a/tests/fixtures/color/ensure-emoji-highlight-width.svg +++ b/tests/fixtures/color/ensure-emoji-highlight-width.svg @@ -2,10 +2,13 @@ <style> .fg { fill: #AAAAAA } .bg { background: #000000 } + .fg-bright-blue { fill: #5555FF } + .fg-bright-red { fill: #FF5555 } .container { padding: 0 10px; line-height: 18px; } + .bold { font-weight: bold; } tspan { font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; white-space: pre; @@ -16,17 +19,17 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan>error: invalid character ` ` in package name: `haha this isn't a valid name 🐛`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">invalid character ` ` in package name: `haha this isn't a valid name 🐛`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> --> <file>:7:1</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> <file>:7:1</tspan> </tspan> - <tspan x="10px" y="64px"><tspan> |</tspan> + <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan>7 | "haha this isn't a valid name 🐛" = { package = "libc", version = "0.1" }</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">7 |</tspan><tspan> "haha this isn't a valid name 🐛" = { package = "libc", version = "0.1" }</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</tspan> </tspan> - <tspan x="10px" y="118px"><tspan> |</tspan> + <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> </text> diff --git a/tests/fixtures/color/ensure-emoji-highlight-width.toml b/tests/fixtures/color/ensure-emoji-highlight-width.toml index 7af05ff1..52168b48 100644 --- a/tests/fixtures/color/ensure-emoji-highlight-width.toml +++ b/tests/fixtures/color/ensure-emoji-highlight-width.toml @@ -13,3 +13,6 @@ origin = "<file>" label = "" level = "Error" range = [0, 35] + +[renderer] +color = true diff --git a/tests/fixtures/color/fold_ann_multiline.svg b/tests/fixtures/color/fold_ann_multiline.svg index f82fe25d..6a89c4f7 100644 --- a/tests/fixtures/color/fold_ann_multiline.svg +++ b/tests/fixtures/color/fold_ann_multiline.svg @@ -2,10 +2,14 @@ <style> .fg { fill: #AAAAAA } .bg { background: #000000 } + .fg-bright-blue { fill: #5555FF } + .fg-bright-red { fill: #FF5555 } + .fg-yellow { fill: #AA5500 } .container { padding: 0 10px; line-height: 18px; } + .bold { font-weight: bold; } tspan { font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; white-space: pre; @@ -16,29 +20,29 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan>error[E0308]: mismatched types</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> --> src/format.rs:51:6</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> src/format.rs:51:6</tspan> </tspan> - <tspan x="10px" y="64px"><tspan> |</tspan> + <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan>51 | ) -> Option<String> {</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">51 |</tspan><tspan> ) -> Option<String> {</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> | -------------- expected `std::option::Option<std::string::String>` because of return type</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">--------------</tspan><tspan> </tspan><tspan class="fg-yellow bold">expected `std::option::Option<std::string::String>` because of return type</tspan> </tspan> - <tspan x="10px" y="118px"><tspan>52 | / for ann in annotations {</tspan> + <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">52 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">/</tspan><tspan> for ann in annotations {</tspan> </tspan> - <tspan x="10px" y="136px"><tspan>53 | | match (ann.range.0, ann.range.1) {</tspan> + <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">53 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> match (ann.range.0, ann.range.1) {</tspan> </tspan> - <tspan x="10px" y="154px"><tspan>... |</tspan> + <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">...</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan> </tspan> - <tspan x="10px" y="172px"><tspan>71 | | }</tspan> + <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">71 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan> </tspan> - <tspan x="10px" y="190px"><tspan>72 | | }</tspan> + <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">72 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan> </tspan> - <tspan x="10px" y="208px"><tspan> | |_____^ expected enum `std::option::Option`, found ()</tspan> + <tspan x="10px" y="208px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|_____^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected enum `std::option::Option`, found ()</tspan> </tspan> - <tspan x="10px" y="226px"><tspan> |</tspan> + <tspan x="10px" y="226px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> </text> diff --git a/tests/fixtures/color/fold_ann_multiline.toml b/tests/fixtures/color/fold_ann_multiline.toml index 09fc7d44..80edfb55 100644 --- a/tests/fixtures/color/fold_ann_multiline.toml +++ b/tests/fixtures/color/fold_ann_multiline.toml @@ -39,3 +39,6 @@ range = [5, 19] label = "expected enum `std::option::Option`, found ()" level = "Error" range = [22, 766] + +[renderer] +color = true diff --git a/tests/fixtures/color/fold_bad_origin_line.svg b/tests/fixtures/color/fold_bad_origin_line.svg index 13a08344..23f1b646 100644 --- a/tests/fixtures/color/fold_bad_origin_line.svg +++ b/tests/fixtures/color/fold_bad_origin_line.svg @@ -2,10 +2,14 @@ <style> .fg { fill: #AAAAAA } .bg { background: #000000 } + .fg-bright-blue { fill: #5555FF } + .fg-bright-red { fill: #FF5555 } + .fg-yellow { fill: #AA5500 } .container { padding: 0 10px; line-height: 18px; } + .bold { font-weight: bold; } tspan { font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; white-space: pre; @@ -16,17 +20,17 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan>error</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> --> path/to/error.rs:3:1</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> path/to/error.rs:3:1</tspan> </tspan> - <tspan x="10px" y="64px"><tspan> |</tspan> + <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan>3 | invalid syntax</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">3 |</tspan><tspan> invalid syntax</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> | -------------- error here</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">--------------</tspan><tspan> </tspan><tspan class="fg-yellow bold">error here</tspan> </tspan> - <tspan x="10px" y="118px"><tspan> |</tspan> + <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> </text> diff --git a/tests/fixtures/color/fold_bad_origin_line.toml b/tests/fixtures/color/fold_bad_origin_line.toml index 1e81a713..3e40137a 100644 --- a/tests/fixtures/color/fold_bad_origin_line.toml +++ b/tests/fixtures/color/fold_bad_origin_line.toml @@ -15,3 +15,6 @@ fold = true label = "error here" level = "Warning" range = [2,16] + +[renderer] +color = true diff --git a/tests/fixtures/color/fold_leading.svg b/tests/fixtures/color/fold_leading.svg index 72887a28..e69965e9 100644 --- a/tests/fixtures/color/fold_leading.svg +++ b/tests/fixtures/color/fold_leading.svg @@ -2,10 +2,13 @@ <style> .fg { fill: #AAAAAA } .bg { background: #000000 } + .fg-bright-blue { fill: #5555FF } + .fg-bright-red { fill: #FF5555 } .container { padding: 0 10px; line-height: 18px; } + .bold { font-weight: bold; } tspan { font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; white-space: pre; @@ -16,17 +19,17 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan>error[E0308]: invalid type: integer `20`, expected a bool</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">invalid type: integer `20`, expected a bool</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> --> Cargo.toml:11:13</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> Cargo.toml:11:13</tspan> </tspan> - <tspan x="10px" y="64px"><tspan> |</tspan> + <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan>11 | workspace = 20</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">11 |</tspan><tspan> workspace = 20</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> | ^^</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^</tspan> </tspan> - <tspan x="10px" y="118px"><tspan> |</tspan> + <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> </text> diff --git a/tests/fixtures/color/fold_leading.toml b/tests/fixtures/color/fold_leading.toml index e3fc696a..90c6c8c4 100644 --- a/tests/fixtures/color/fold_leading.toml +++ b/tests/fixtures/color/fold_leading.toml @@ -24,3 +24,6 @@ fold = true label = "" level = "Error" range = [132, 134] + +[renderer] +color = true diff --git a/tests/fixtures/color/fold_trailing.svg b/tests/fixtures/color/fold_trailing.svg index 15c98502..41bf7c79 100644 --- a/tests/fixtures/color/fold_trailing.svg +++ b/tests/fixtures/color/fold_trailing.svg @@ -2,10 +2,13 @@ <style> .fg { fill: #AAAAAA } .bg { background: #000000 } + .fg-bright-blue { fill: #5555FF } + .fg-bright-red { fill: #FF5555 } .container { padding: 0 10px; line-height: 18px; } + .bold { font-weight: bold; } tspan { font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; white-space: pre; @@ -16,17 +19,17 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan>error[E0308]: invalid type: integer `20`, expected a lints table</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">invalid type: integer `20`, expected a lints table</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> --> Cargo.toml:1:9</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> Cargo.toml:1:9</tspan> </tspan> - <tspan x="10px" y="64px"><tspan> |</tspan> + <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan>1 | lints = 20</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1 |</tspan><tspan> lints = 20</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> | ^^</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^</tspan> </tspan> - <tspan x="10px" y="118px"><tspan> |</tspan> + <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> </text> diff --git a/tests/fixtures/color/fold_trailing.toml b/tests/fixtures/color/fold_trailing.toml index 8ee4c051..10b2240c 100644 --- a/tests/fixtures/color/fold_trailing.toml +++ b/tests/fixtures/color/fold_trailing.toml @@ -23,3 +23,6 @@ fold = true label = "" level = "Error" range = [8, 10] + +[renderer] +color = true diff --git a/tests/fixtures/color/issue_9.svg b/tests/fixtures/color/issue_9.svg index af22d82d..80b891e0 100644 --- a/tests/fixtures/color/issue_9.svg +++ b/tests/fixtures/color/issue_9.svg @@ -2,10 +2,14 @@ <style> .fg { fill: #AAAAAA } .bg { background: #000000 } + .fg-bright-blue { fill: #5555FF } + .fg-bright-red { fill: #FF5555 } + .fg-yellow { fill: #AA5500 } .container { padding: 0 10px; line-height: 18px; } + .bold { font-weight: bold; } tspan { font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; white-space: pre; @@ -16,29 +20,29 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan>error: expected one of `.`, `;`, `?`, or an operator, found `for`</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">expected one of `.`, `;`, `?`, or an operator, found `for`</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> --> /code/rust/src/test/ui/annotate-snippet/suggestion.rs:4:5</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> /code/rust/src/test/ui/annotate-snippet/suggestion.rs:4:5</tspan> </tspan> - <tspan x="10px" y="64px"><tspan> |</tspan> + <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan>4 | let x = vec![1];</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">4 |</tspan><tspan> let x = vec![1];</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> | - move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait</tspan> </tspan> - <tspan x="10px" y="118px"><tspan> |</tspan> + <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="136px"><tspan>7 | let y = x;</tspan> + <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">7 |</tspan><tspan> let y = x;</tspan> </tspan> - <tspan x="10px" y="154px"><tspan> | - value moved here</tspan> + <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">value moved here</tspan> </tspan> - <tspan x="10px" y="172px"><tspan> |</tspan> + <tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="190px"><tspan>9 | x;</tspan> + <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">9 |</tspan><tspan> x;</tspan> </tspan> - <tspan x="10px" y="208px"><tspan> | ^ value used here after move</tspan> + <tspan x="10px" y="208px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">value used here after move</tspan> </tspan> - <tspan x="10px" y="226px"><tspan> |</tspan> + <tspan x="10px" y="226px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> </text> diff --git a/tests/fixtures/color/issue_9.toml b/tests/fixtures/color/issue_9.toml index 1f35243c..9de17534 100644 --- a/tests/fixtures/color/issue_9.toml +++ b/tests/fixtures/color/issue_9.toml @@ -26,3 +26,6 @@ line_start = 9 label = "value used here after move" level = "Error" range = [0, 1] + +[renderer] +color = true diff --git a/tests/fixtures/color/multiple_annotations.svg b/tests/fixtures/color/multiple_annotations.svg index 18bca93e..84f47495 100644 --- a/tests/fixtures/color/multiple_annotations.svg +++ b/tests/fixtures/color/multiple_annotations.svg @@ -2,10 +2,13 @@ <style> .fg { fill: #AAAAAA } .bg { background: #000000 } + .fg-bright-blue { fill: #5555FF } + .fg-bright-red { fill: #FF5555 } .container { padding: 0 10px; line-height: 18px; } + .bold { font-weight: bold; } tspan { font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; white-space: pre; @@ -16,35 +19,35 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan>error</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> |</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="64px"><tspan> 96 | fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>) {</tspan> + <tspan x="10px" y="64px"><tspan class="fg-bright-blue bold"> 96 |</tspan><tspan> fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>) {</tspan> </tspan> - <tspan x="10px" y="82px"><tspan> 97 | if let Some(annotation) = main_annotation {</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold"> 97 |</tspan><tspan> if let Some(annotation) = main_annotation {</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> | ^^^^^^^^^^ Variable defined here</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">Variable defined here</tspan> </tspan> - <tspan x="10px" y="118px"><tspan> 98 | result.push(format_title_line(</tspan> + <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold"> 98 |</tspan><tspan> result.push(format_title_line(</tspan> </tspan> - <tspan x="10px" y="136px"><tspan> 99 | &annotation.annotation_type,</tspan> + <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold"> 99 |</tspan><tspan> &annotation.annotation_type,</tspan> </tspan> - <tspan x="10px" y="154px"><tspan> | ^^^^^^^^^^ Referenced here</tspan> + <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">Referenced here</tspan> </tspan> - <tspan x="10px" y="172px"><tspan>100 | None,</tspan> + <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">100 |</tspan><tspan> None,</tspan> </tspan> - <tspan x="10px" y="190px"><tspan>101 | &annotation.label,</tspan> + <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">101 |</tspan><tspan> &annotation.label,</tspan> </tspan> - <tspan x="10px" y="208px"><tspan> | ^^^^^^^^^^ Referenced again here</tspan> + <tspan x="10px" y="208px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">Referenced again here</tspan> </tspan> - <tspan x="10px" y="226px"><tspan>102 | ));</tspan> + <tspan x="10px" y="226px"><tspan class="fg-bright-blue bold">102 |</tspan><tspan> ));</tspan> </tspan> - <tspan x="10px" y="244px"><tspan>103 | }</tspan> + <tspan x="10px" y="244px"><tspan class="fg-bright-blue bold">103 |</tspan><tspan> }</tspan> </tspan> - <tspan x="10px" y="262px"><tspan>104 | }</tspan> + <tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">104 |</tspan><tspan> }</tspan> </tspan> - <tspan x="10px" y="280px"><tspan> |</tspan> + <tspan x="10px" y="280px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> </text> diff --git a/tests/fixtures/color/multiple_annotations.toml b/tests/fixtures/color/multiple_annotations.toml index 842b137e..824c5305 100644 --- a/tests/fixtures/color/multiple_annotations.toml +++ b/tests/fixtures/color/multiple_annotations.toml @@ -27,3 +27,6 @@ range = [184, 194] label = "Referenced again here" level = "Error" range = [243, 253] + +[renderer] +color = true diff --git a/tests/fixtures/color/simple.svg b/tests/fixtures/color/simple.svg index ae7b03cf..7b92d238 100644 --- a/tests/fixtures/color/simple.svg +++ b/tests/fixtures/color/simple.svg @@ -2,10 +2,14 @@ <style> .fg { fill: #AAAAAA } .bg { background: #000000 } + .fg-bright-blue { fill: #5555FF } + .fg-bright-red { fill: #FF5555 } + .fg-yellow { fill: #AA5500 } .container { padding: 0 10px; line-height: 18px; } + .bold { font-weight: bold; } tspan { font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; white-space: pre; @@ -16,23 +20,23 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan>error: expected one of `.`, `;`, `?`, or an operator, found `for`</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">expected one of `.`, `;`, `?`, or an operator, found `for`</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> --> src/format_color.rs:171:9</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> src/format_color.rs:171:9</tspan> </tspan> - <tspan x="10px" y="64px"><tspan> |</tspan> + <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan>169 | })</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">169 |</tspan><tspan> })</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> | - expected one of `.`, `;`, `?`, or an operator here</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">expected one of `.`, `;`, `?`, or an operator here</tspan> </tspan> - <tspan x="10px" y="118px"><tspan>170 |</tspan> + <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">170 |</tspan> </tspan> - <tspan x="10px" y="136px"><tspan>171 | for line in &self.body {</tspan> + <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">171 |</tspan><tspan> for line in &self.body {</tspan> </tspan> - <tspan x="10px" y="154px"><tspan> | ^^^ unexpected token</tspan> + <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">unexpected token</tspan> </tspan> - <tspan x="10px" y="172px"><tspan> |</tspan> + <tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> </text> diff --git a/tests/fixtures/color/simple.toml b/tests/fixtures/color/simple.toml index 76b5bac6..2e6969f7 100644 --- a/tests/fixtures/color/simple.toml +++ b/tests/fixtures/color/simple.toml @@ -17,3 +17,6 @@ range = [20, 23] label = "expected one of `.`, `;`, `?`, or an operator here" level = "Warning" range = [10, 11] + +[renderer] +color = true diff --git a/tests/fixtures/color/strip_line.svg b/tests/fixtures/color/strip_line.svg index b1fd8a6d..9da24fe3 100644 --- a/tests/fixtures/color/strip_line.svg +++ b/tests/fixtures/color/strip_line.svg @@ -2,10 +2,13 @@ <style> .fg { fill: #AAAAAA } .bg { background: #000000 } + .fg-bright-blue { fill: #5555FF } + .fg-bright-red { fill: #FF5555 } .container { padding: 0 10px; line-height: 18px; } + .bold { font-weight: bold; } tspan { font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; white-space: pre; @@ -16,17 +19,17 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan>error[E0308]: mismatched types</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> --> $DIR/whitespace-trimming.rs:4:193</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> $DIR/whitespace-trimming.rs:4:193</tspan> </tspan> - <tspan x="10px" y="64px"><tspan> |</tspan> + <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan>LL | ... let _: () = 42;</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">LL |</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">...</tspan><tspan> let _: () = 42;</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> | ^^ expected (), found integer</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected (), found integer</tspan> </tspan> - <tspan x="10px" y="118px"><tspan> |</tspan> + <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> </text> diff --git a/tests/fixtures/color/strip_line.toml b/tests/fixtures/color/strip_line.toml index 459cbe1c..546c96a0 100644 --- a/tests/fixtures/color/strip_line.toml +++ b/tests/fixtures/color/strip_line.toml @@ -14,5 +14,5 @@ level = "Error" range = [192, 194] [renderer] -color = false +color = true anonymized_line_numbers = true diff --git a/tests/fixtures/color/strip_line_char.svg b/tests/fixtures/color/strip_line_char.svg index 15296a14..cbafc789 100644 --- a/tests/fixtures/color/strip_line_char.svg +++ b/tests/fixtures/color/strip_line_char.svg @@ -2,10 +2,13 @@ <style> .fg { fill: #AAAAAA } .bg { background: #000000 } + .fg-bright-blue { fill: #5555FF } + .fg-bright-red { fill: #FF5555 } .container { padding: 0 10px; line-height: 18px; } + .bold { font-weight: bold; } tspan { font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; white-space: pre; @@ -16,17 +19,17 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan>error[E0308]: mismatched types</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> --> $DIR/whitespace-trimming.rs:4:193</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> $DIR/whitespace-trimming.rs:4:193</tspan> </tspan> - <tspan x="10px" y="64px"><tspan> |</tspan> + <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan>LL | ... let _: () = 42ñ</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">LL |</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">...</tspan><tspan> let _: () = 42ñ</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> | ^^ expected (), found integer</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected (), found integer</tspan> </tspan> - <tspan x="10px" y="118px"><tspan> |</tspan> + <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> </text> diff --git a/tests/fixtures/color/strip_line_char.toml b/tests/fixtures/color/strip_line_char.toml index dedefd5a..863abb3f 100644 --- a/tests/fixtures/color/strip_line_char.toml +++ b/tests/fixtures/color/strip_line_char.toml @@ -14,5 +14,5 @@ level = "Error" range = [192, 194] [renderer] -color = false +color = true anonymized_line_numbers = true diff --git a/tests/fixtures/color/strip_line_non_ws.svg b/tests/fixtures/color/strip_line_non_ws.svg index f1977dc5..e4f8a856 100644 --- a/tests/fixtures/color/strip_line_non_ws.svg +++ b/tests/fixtures/color/strip_line_non_ws.svg @@ -2,10 +2,13 @@ <style> .fg { fill: #AAAAAA } .bg { background: #000000 } + .fg-bright-blue { fill: #5555FF } + .fg-bright-red { fill: #FF5555 } .container { padding: 0 10px; line-height: 18px; } + .bold { font-weight: bold; } tspan { font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; white-space: pre; @@ -16,21 +19,21 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan>error[E0308]: mismatched types</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> --> $DIR/non-whitespace-trimming.rs:4:242</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> $DIR/non-whitespace-trimming.rs:4:242</tspan> </tspan> - <tspan x="10px" y="64px"><tspan> |</tspan> + <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan>LL | ... = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () ...</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">LL |</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">...</tspan><tspan> = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () </tspan><tspan class="fg-bright-blue bold">...</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> | ^^ ^^ expected `()`, found integer</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected `()`, found integer</tspan> </tspan> - <tspan x="10px" y="118px"><tspan> | |</tspan> + <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan> </tspan> - <tspan x="10px" y="136px"><tspan> | expected due to this</tspan> + <tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected due to this</tspan> </tspan> - <tspan x="10px" y="154px"><tspan> |</tspan> + <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> </text> diff --git a/tests/fixtures/color/strip_line_non_ws.toml b/tests/fixtures/color/strip_line_non_ws.toml index 06ecad85..c6573ff4 100644 --- a/tests/fixtures/color/strip_line_non_ws.toml +++ b/tests/fixtures/color/strip_line_non_ws.toml @@ -23,3 +23,4 @@ range = [236, 238] [renderer] anonymized_line_numbers = true +color = true diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs index 3ddef798..a38a88e7 100644 --- a/tests/fixtures/deserialize.rs +++ b/tests/fixtures/deserialize.rs @@ -151,6 +151,8 @@ pub struct RendererDef { anonymized_line_numbers: bool, #[serde(default)] term_width: Option<usize>, + #[serde(default)] + color: bool, } impl From<RendererDef> for Renderer { @@ -158,8 +160,15 @@ impl From<RendererDef> for Renderer { let RendererDef { anonymized_line_numbers, term_width, + color, } = val; - Renderer::plain() + + let renderer = if color { + Renderer::styled() + } else { + Renderer::plain() + }; + renderer .anonymized_line_numbers(anonymized_line_numbers) .term_width(term_width.unwrap_or(DEFAULT_TERM_WIDTH)) } From a687aff3a742903c60037f19284c884d4de0bca8 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 21 Aug 2024 15:23:30 -0600 Subject: [PATCH 254/455] test: Use consistent colors when testing --- Cargo.lock | 1 + Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index a686b028..226775f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,6 +21,7 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" name = "annotate-snippets" version = "0.11.4" dependencies = [ + "annotate-snippets", "anstream 0.6.15", "anstyle", "criterion", diff --git a/Cargo.toml b/Cargo.toml index 62ab3871..d3236865 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ anstyle = "1.0.4" unicode-width = "0.1.11" [dev-dependencies] +annotate-snippets = { path = ".", features = ["testing-colors"] } anstream = "0.6.13" criterion = "0.5.1" difference = "2.0.0" From 40d22784b139254d08750d624cff386d37382a20 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 21 Aug 2024 15:36:10 -0600 Subject: [PATCH 255/455] test: Add module to fixture test name in output --- tests/fixtures/main.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/fixtures/main.rs b/tests/fixtures/main.rs index 81d0246c..07c69d81 100644 --- a/tests/fixtures/main.rs +++ b/tests/fixtures/main.rs @@ -14,7 +14,15 @@ fn main() { } fn setup(input_path: std::path::PathBuf) -> tryfn::Case { - let name = input_path.file_name().unwrap().to_str().unwrap().to_owned(); + let parent = input_path + .parent() + .unwrap() + .file_name() + .unwrap() + .to_str() + .unwrap(); + let file_name = input_path.file_name().unwrap().to_str().unwrap(); + let name = format!("{}/{}", parent, file_name); let expected = Data::read_from(&input_path.with_extension("svg"), None); tryfn::Case { name, From 7a28f01acfe8eb95f4e54127e1b66aa31cf2aeed Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Fri, 23 Aug 2024 19:02:02 -0500 Subject: [PATCH 256/455] docs(contrib): Fix tpo --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 87d9134e..b0318b82 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -45,7 +45,7 @@ We ask that commits are atomic, meaning they are complete and have a single resp PRs should tell a cohesive story, with test and refactor commits that keep the fix or feature commits simple and clear. -Specifically, we would encouage +Specifically, we would encourage - File renames be isolated into their own commit - Add tests in a commit before their feature or fix, showing the current behavior. The diff for the feature/fix commit will then show how the behavior changed, From 37cf1085bc6aa53e18a37f0aa97be51afa6e7f14 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 1 Sep 2024 01:02:47 +0000 Subject: [PATCH 257/455] chore(deps): Update EmbarkStudios/cargo-deny-action action to v2 --- .github/workflows/audit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 07c70eeb..a94be159 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -47,7 +47,7 @@ jobs: - bans licenses sources steps: - uses: actions/checkout@v4 - - uses: EmbarkStudios/cargo-deny-action@v1 + - uses: EmbarkStudios/cargo-deny-action@v2 with: command: check ${{ matrix.checks }} rust-version: stable From 55eccd917e2eadb92d12c7181a7081e4935fbb8f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 1 Sep 2024 02:08:46 +0000 Subject: [PATCH 258/455] chore(deps): Update EmbarkStudios/cargo-deny-action action to v2 --- .github/workflows/audit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index 35b3da84..2da233df 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -47,7 +47,7 @@ jobs: - bans licenses sources steps: - uses: actions/checkout@v4 - - uses: EmbarkStudios/cargo-deny-action@v1 + - uses: EmbarkStudios/cargo-deny-action@v2 with: command: check ${{ matrix.checks }} rust-version: stable From e5f47698285d84e9e00fc942e1350d670f1ff2cf Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 9 Sep 2024 10:09:09 -0500 Subject: [PATCH 259/455] chore(ci): Exclude dev-dependencies from MSRV It would be nice to run tests but divan is getting in the way --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b643aba8..f07c7141 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,7 +64,7 @@ jobs: - uses: Swatinem/rust-cache@v2 - uses: taiki-e/install-action@cargo-hack - name: Default features - run: cargo hack check --feature-powerset --locked --rust-version --ignore-private --workspace --all-targets + run: cargo hack check --feature-powerset --locked --rust-version --ignore-private --workspace --lib --bins lockfile: runs-on: ubuntu-latest steps: From 4b7a702a9cec57633af564fbf04f88dcc0ee6592 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 9 Sep 2024 09:38:27 -0500 Subject: [PATCH 260/455] bench: Switch to divan --- Cargo.lock | 353 +++++++++++----------------------------------- Cargo.toml | 2 +- benches/simple.rs | 22 +-- 3 files changed, 88 insertions(+), 289 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1e3073f4..ffaa43e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,20 +11,14 @@ dependencies = [ "memchr", ] -[[package]] -name = "anes" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" - [[package]] name = "annotate-snippets" version = "0.11.2" dependencies = [ "anstream 0.6.14", "anstyle", - "criterion", "difference", + "divan", "glob", "serde", "snapbox", @@ -130,10 +124,10 @@ dependencies = [ ] [[package]] -name = "autocfg" -version = "1.3.0" +name = "bitflags" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bstr" @@ -145,51 +139,12 @@ dependencies = [ "serde", ] -[[package]] -name = "bumpalo" -version = "3.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" - -[[package]] -name = "cast" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" - [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "ciborium" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" -dependencies = [ - "ciborium-io", - "ciborium-ll", - "serde", -] - -[[package]] -name = "ciborium-io" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" - -[[package]] -name = "ciborium-ll" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" -dependencies = [ - "ciborium-io", - "half", -] - [[package]] name = "clap" version = "4.3.24" @@ -211,6 +166,7 @@ dependencies = [ "anstyle", "clap_lex", "strsim", + "terminal_size", ] [[package]] @@ -238,83 +194,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] -name = "criterion" -version = "0.5.1" +name = "condtype" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" -dependencies = [ - "anes", - "cast", - "ciborium", - "clap", - "criterion-plot", - "is-terminal", - "itertools", - "num-traits", - "once_cell", - "oorandom", - "plotters", - "rayon", - "regex", - "serde", - "serde_derive", - "serde_json", - "tinytemplate", - "walkdir", -] +checksum = "baf0a07a401f374238ab8e2f11a104d2851bf9ce711ec69804834de8af45c7af" [[package]] -name = "criterion-plot" -version = "0.5.0" +name = "difference" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" -dependencies = [ - "cast", - "itertools", -] +checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" [[package]] -name = "crossbeam-deque" -version = "0.8.5" +name = "divan" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "a0d567df2c9c2870a43f3f2bd65aaeb18dbce1c18f217c3e564b4fbaeb3ee56c" dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", + "cfg-if", + "clap", + "condtype", + "divan-macros", + "libc", + "regex-lite", ] [[package]] -name = "crossbeam-epoch" -version = "0.9.18" +name = "divan-macros" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +checksum = "27540baf49be0d484d8f0130d7d8da3011c32a44d4fc873368154f1510e574a2" dependencies = [ - "crossbeam-utils", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "crossbeam-utils" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "difference" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" - -[[package]] -name = "either" -version = "1.12.0" +name = "errno" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] [[package]] name = "escape8259" @@ -356,15 +280,6 @@ dependencies = [ "regex-syntax", ] -[[package]] -name = "half" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" -dependencies = [ - "crunchy", -] - [[package]] name = "heck" version = "0.4.1" @@ -403,6 +318,17 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "is-terminal" version = "0.4.12" @@ -423,30 +349,12 @@ dependencies = [ "is-terminal", ] -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" -[[package]] -name = "js-sys" -version = "0.3.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" -dependencies = [ - "wasm-bindgen", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -471,6 +379,12 @@ dependencies = [ "threadpool", ] +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + [[package]] name = "log" version = "0.4.21" @@ -489,15 +403,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - [[package]] name = "num_cpus" version = "1.16.0" @@ -514,12 +419,6 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" -[[package]] -name = "oorandom" -version = "11.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" - [[package]] name = "os_pipe" version = "1.2.0" @@ -530,34 +429,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "plotters" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" -dependencies = [ - "num-traits", - "plotters-backend", - "plotters-svg", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "plotters-backend" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7" - -[[package]] -name = "plotters-svg" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705" -dependencies = [ - "plotters-backend", -] - [[package]] name = "proc-macro2" version = "1.0.85" @@ -576,26 +447,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - [[package]] name = "regex" version = "1.10.4" @@ -619,12 +470,32 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + [[package]] name = "regex-syntax" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +[[package]] +name = "rustix" +version = "0.37.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] + [[package]] name = "rustversion" version = "1.0.17" @@ -738,6 +609,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "terminal_size" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237" +dependencies = [ + "rustix", + "windows-sys 0.48.0", +] + [[package]] name = "thread_local" version = "1.1.8" @@ -757,16 +638,6 @@ dependencies = [ "num_cpus", ] -[[package]] -name = "tinytemplate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" -dependencies = [ - "serde", - "serde_json", -] - [[package]] name = "toml" version = "0.5.11" @@ -830,70 +701,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "wasm-bindgen" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" - -[[package]] -name = "web-sys" -version = "0.3.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "winapi-util" version = "0.1.8" diff --git a/Cargo.toml b/Cargo.toml index 12fadf62..c29de958 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,8 +29,8 @@ unicode-width = "0.1.11" [dev-dependencies] anstream = "0.6.13" -criterion = "0.5.1" difference = "2.0.0" +divan = "0.1.14" glob = "0.3.1" serde = { version = "1.0.199", features = ["derive"] } snapbox = { version = "0.6.0", features = ["diff", "term-svg", "cmd", "examples"] } diff --git a/benches/simple.rs b/benches/simple.rs index 723793ea..d5926c2d 100644 --- a/benches/simple.rs +++ b/benches/simple.rs @@ -1,12 +1,7 @@ -#![allow(clippy::unit_arg)] -#[macro_use] -extern crate criterion; - -use criterion::{black_box, Criterion}; - use annotate_snippets::{Level, Renderer, Snippet}; -fn create_snippet(renderer: Renderer) { +#[divan::bench] +fn create_and_render() -> String { let source = r#") -> Option<String> { for ann in annotations { match (ann.range.0, ann.range.1) { @@ -45,14 +40,11 @@ fn create_snippet(renderer: Renderer) { ), ); - let _result = renderer.render(message).to_string(); + let renderer = Renderer::plain(); + let rendered = renderer.render(message).to_string(); + rendered } -pub fn criterion_benchmark(c: &mut Criterion) { - c.bench_function("format", |b| { - b.iter(|| black_box(create_snippet(Renderer::plain()))); - }); +fn main() { + divan::main(); } - -criterion_group!(benches, criterion_benchmark); -criterion_main!(benches); From da8cc5f2f535788afbbe07e6f7821d9ac91cc394 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 9 Sep 2024 09:42:49 -0500 Subject: [PATCH 261/455] bench: Generalize the binary name --- Cargo.toml | 2 +- benches/{simple.rs => bench.rs} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename benches/{simple.rs => bench.rs} (97%) diff --git a/Cargo.toml b/Cargo.toml index c29de958..2b72bd9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ toml = "0.5.11" tryfn = "0.2.1" [[bench]] -name = "simple" +name = "bench" harness = false [[test]] diff --git a/benches/simple.rs b/benches/bench.rs similarity index 97% rename from benches/simple.rs rename to benches/bench.rs index d5926c2d..eab7626c 100644 --- a/benches/simple.rs +++ b/benches/bench.rs @@ -1,7 +1,7 @@ use annotate_snippets::{Level, Renderer, Snippet}; #[divan::bench] -fn create_and_render() -> String { +fn simple() -> String { let source = r#") -> Option<String> { for ann in annotations { match (ann.range.0, ann.range.1) { From a4cca360fc493abf0ec183ef7203d3bd73ef0ab1 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 9 Sep 2024 09:56:16 -0500 Subject: [PATCH 262/455] bench: Check fold's performance --- benches/bench.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/benches/bench.rs b/benches/bench.rs index eab7626c..9747954b 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -45,6 +45,45 @@ fn simple() -> String { rendered } +#[divan::bench(args=[0, 1, 10, 100, 1_000, 10_000, 100_000])] +fn fold(bencher: divan::Bencher<'_, '_>, context: usize) { + bencher + .with_inputs(|| { + let line = "012345678901234567890123456789"; + let mut input = String::new(); + for _ in 1..=context { + input.push_str(line); + input.push('\n'); + } + let span_start = input.len() + line.len(); + let span = span_start..span_start; + + input.push_str(line); + input.push('\n'); + for _ in 1..=context { + input.push_str(line); + input.push('\n'); + } + (input, span) + }) + .bench_values(|(input, span)| { + let message = Level::Error.title("mismatched types").id("E0308").snippet( + Snippet::source(&input) + .fold(true) + .origin("src/format.rs") + .annotation( + Level::Warning + .span(span) + .label("expected `Option<String>` because of return type"), + ), + ); + + let renderer = Renderer::plain(); + let rendered = renderer.render(message).to_string(); + rendered + }); +} + fn main() { divan::main(); } From e8ce092df71abfbc5feab41b8458261e82d94ac2 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 9 Sep 2024 10:03:56 -0500 Subject: [PATCH 263/455] perf: Offer 'simd' feature for faster folding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ```console $ cargo bench && cargo bench -F simd Compiling annotate-snippets v0.11.2 (/home/epage/src/personal/annotate-snippets-rs) Finished `bench` profile [optimized] target(s) in 0.99s Running unittests src/lib.rs (target/release/deps/annotate_snippets-b51bb37991a7f496) running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Running benches/bench.rs (target/release/deps/bench-468ba612503afee1) Timer precision: 18 ns bench fastest │ slowest │ median │ mean │ samples │ iters ├─ fold │ │ │ │ │ │ ├─ 0 1.911 µs │ 19.44 µs │ 1.943 µs │ 2.146 µs │ 100 │ 100 │ ├─ 1 1.916 µs │ 3.158 µs │ 1.973 µs │ 1.982 µs │ 100 │ 100 │ ├─ 10 2.121 µs │ 6.05 µs │ 2.225 µs │ 2.281 µs │ 100 │ 100 │ ├─ 100 3.706 µs │ 7.007 µs │ 3.83 µs │ 3.876 µs │ 100 │ 100 │ ├─ 1000 19.42 µs │ 25.61 µs │ 19.48 µs │ 19.64 µs │ 100 │ 100 │ ├─ 10000 111.2 µs │ 204.2 µs │ 127 µs │ 133.6 µs │ 100 │ 100 │ ╰─ 100000 1.094 ms │ 1.747 ms │ 1.137 ms │ 1.158 ms │ 100 │ 100 ╰─ simple 10.14 µs │ 40.27 µs │ 10.5 µs │ 11.01 µs │ 100 │ 100 Compiling annotate-snippets v0.11.2 (/home/epage/src/personal/annotate-snippets-rs) Finished `bench` profile [optimized] target(s) in 0.99s Running unittests src/lib.rs (target/release/deps/annotate_snippets-9d4024ac94675e6a) running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s Running benches/bench.rs (target/release/deps/bench-d5470149969acbb8) Timer precision: 13 ns bench fastest │ slowest │ median │ mean │ samples │ iters ├─ fold │ │ │ │ │ │ ├─ 0 1.164 µs │ 13.91 µs │ 1.208 µs │ 1.408 µs │ 100 │ 100 │ ├─ 1 1.188 µs │ 4.289 µs │ 1.234 µs │ 1.277 µs │ 100 │ 100 │ ├─ 10 1.259 µs │ 3.822 µs │ 1.319 µs │ 1.419 µs │ 100 │ 100 │ ├─ 100 1.312 µs │ 2.732 µs │ 1.412 µs │ 1.519 µs │ 100 │ 100 │ ├─ 1000 1.917 µs │ 5.52 µs │ 2 µs │ 2.085 µs │ 100 │ 100 │ ├─ 10000 7.195 µs │ 29.55 µs │ 7.325 µs │ 7.638 µs │ 100 │ 100 │ ╰─ 100000 59.08 µs │ 403 µs │ 61.1 µs │ 65.52 µs │ 100 │ 100 ╰─ simple 9.92 µs │ 19.09 µs │ 10.33 µs │ 10.91 µs │ 100 │ 100 ``` --- Cargo.lock | 5 +++-- Cargo.toml | 2 ++ src/renderer/display_list.rs | 13 ++++++++++++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ffaa43e1..02b88124 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,6 +20,7 @@ dependencies = [ "difference", "divan", "glob", + "memchr", "serde", "snapbox", "toml", @@ -393,9 +394,9 @@ checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "normalize-line-endings" diff --git a/Cargo.toml b/Cargo.toml index 2b72bd9f..d59fa9f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ maintenance = { status = "actively-developed" } [dependencies] anstyle = "1.0.4" +memchr = { version = "2.7.4", optional = true } unicode-width = "0.1.11" [dev-dependencies] @@ -47,6 +48,7 @@ harness = false [features] default = [] +simd = ["memchr"] testing-colors = [] [lints.rust] diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index d94a660b..b5fef266 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -893,7 +893,7 @@ fn fold_prefix_suffix(mut snippet: snippet::Snippet<'_>) -> snippet::Snippet<'_> if let Some(before_new_start) = snippet.source[0..ann_start].rfind('\n') { let new_start = before_new_start + 1; - let line_offset = snippet.source[..new_start].lines().count(); + let line_offset = newline_count(&snippet.source[..new_start]); snippet.line_start += line_offset; snippet.source = &snippet.source[new_start..]; @@ -919,6 +919,17 @@ fn fold_prefix_suffix(mut snippet: snippet::Snippet<'_>) -> snippet::Snippet<'_> snippet } +fn newline_count(body: &str) -> usize { + #[cfg(feature = "simd")] + { + memchr::memchr_iter(b'\n', body.as_bytes()).count() + } + #[cfg(not(feature = "simd"))] + { + body.lines().count() + } +} + fn fold_body(body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> { const INNER_CONTEXT: usize = 1; const INNER_UNFOLD_SIZE: usize = INNER_CONTEXT * 2 + 1; From 6e193aa09aed80118df4e1317b8eed057bad6f0b Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 26 Sep 2024 20:59:12 -0500 Subject: [PATCH 264/455] chore: Ensure pre-commit gets non-system Python This is needed with the ubuntu-24.04 images so that `setup-python` will install a version of Python that the pre-commit action can install into. See pre-commit/action#210 for more of an analysis of this. --- .github/workflows/pre-commit.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 1b000abf..7b55a3d9 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -24,4 +24,6 @@ jobs: steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 + with: + python-version: '3.x' - uses: pre-commit/action@v3.0.1 From 67ea82154dac3ebaae1394a9cd4bc4e14f759307 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Fri, 27 Sep 2024 10:27:59 -0500 Subject: [PATCH 265/455] style: Use inline format args --- src/renderer/display_list.rs | 13 +++++-------- tests/fixtures/main.rs | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index fedf2689..1823e61a 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -198,7 +198,7 @@ impl<'a> DisplaySet<'a> { self.format_label(line_offset, &annotation.label, stylesheet, buffer) } else { let id = match &annotation.id { - Some(id) => format!("[{}]", id), + Some(id) => format!("[{id}]"), None => String::new(), }; buffer.append( @@ -290,12 +290,12 @@ impl<'a> DisplaySet<'a> { } => { let lineno_color = stylesheet.line_no(); if anonymized_line_numbers && lineno.is_some() { - let num = format!("{:>width$} |", ANONYMIZED_LINE_NUM, width = lineno_width); + let num = format!("{ANONYMIZED_LINE_NUM:>lineno_width$} |"); buffer.puts(line_offset, 0, &num, *lineno_color); } else { match lineno { Some(n) => { - let num = format!("{:>width$} |", n, width = lineno_width); + let num = format!("{n:>lineno_width$} |"); buffer.puts(line_offset, 0, &num, *lineno_color); } None => { @@ -645,7 +645,7 @@ impl<'a> DisplaySet<'a> { } else if formatted_len != 0 { formatted_len += 2; let id = match &annotation.annotation.id { - Some(id) => format!("[{}]", id), + Some(id) => format!("[{id}]"), None => String::new(), }; buffer.puts( @@ -1292,10 +1292,7 @@ fn format_body( None } }) { - panic!( - "SourceAnnotation range `{:?}` is beyond the end of buffer `{}`", - bigger, source_len - ) + panic!("SourceAnnotation range `{bigger:?}` is beyond the end of buffer `{source_len}`") } let mut body = vec![]; diff --git a/tests/fixtures/main.rs b/tests/fixtures/main.rs index 07c69d81..bf37e73d 100644 --- a/tests/fixtures/main.rs +++ b/tests/fixtures/main.rs @@ -22,7 +22,7 @@ fn setup(input_path: std::path::PathBuf) -> tryfn::Case { .to_str() .unwrap(); let file_name = input_path.file_name().unwrap().to_str().unwrap(); - let name = format!("{}/{}", parent, file_name); + let name = format!("{parent}/{file_name}"); let expected = Data::read_from(&input_path.with_extension("svg"), None); tryfn::Case { name, From 71039b9430af149e71189b62423b3c508beeb4c7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 01:02:07 +0000 Subject: [PATCH 266/455] chore(deps): Update compatible (dev) --- Cargo.lock | 75 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 42 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 244fffdf..74768255 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -521,18 +521,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.204" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.204" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", @@ -558,9 +558,9 @@ checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" [[package]] name = "snapbox" -version = "0.6.16" +version = "0.6.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "027c936207f85d10d015e21faf5c676c7e08c453ed371adf55c0874c443ca77a" +checksum = "840b73eb3148bc3cbc10ebe00ec9bc6d96033e658d022c4adcbf3f35596fd64a" dependencies = [ "anstream 0.6.15", "anstyle", @@ -573,7 +573,7 @@ dependencies = [ "similar", "snapbox-macros", "wait-timeout", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -727,7 +727,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -747,18 +756,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -769,9 +778,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -781,9 +790,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -793,15 +802,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -811,9 +820,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -823,9 +832,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -835,9 +844,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -847,6 +856,6 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" From 1fe2522b7b84554032dc8801e3a164b6caf59383 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 04:05:29 +0000 Subject: [PATCH 267/455] chore(deps): Update dependency STABLE to v1.81.0 --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 87baaab1..0888a57d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,7 +86,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.80" # STABLE + toolchain: "1.81.0" # STABLE - uses: Swatinem/rust-cache@v2 - name: Check documentation env: @@ -101,7 +101,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.80" # STABLE + toolchain: "1.81.0" # STABLE components: rustfmt - uses: Swatinem/rust-cache@v2 - name: Check formatting @@ -117,7 +117,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.80" # STABLE + toolchain: "1.81.0" # STABLE components: clippy - uses: Swatinem/rust-cache@v2 - name: Install SARIF tools From 218d7ff4ad0540fd4152d88940e9ff8819783295 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 10 Oct 2024 16:19:10 -0500 Subject: [PATCH 268/455] chore(ci): Fix CI --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2964a8fe..1e1e88a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,7 +64,7 @@ jobs: - uses: Swatinem/rust-cache@v2 - uses: taiki-e/install-action@cargo-hack - name: Default features - run: cargo hack check --feature-powerset --locked --rust-version --ignore-private --workspace --all-targets + run: cargo hack check --feature-powerset --locked --rust-version --ignore-private --workspace --lib --bins minimal-versions: name: Minimal versions runs-on: ubuntu-latest @@ -170,7 +170,7 @@ jobs: - name: Install cargo-tarpaulin run: cargo install cargo-tarpaulin - name: Gather coverage - run: cargo tarpaulin --output-dir coverage --out lcov + run: cargo tarpaulin --output-dir coverage --out lcov --timeout 120 - name: Publish to Coveralls uses: coverallsapp/github-action@master with: From 95657b3536062f9177f72b45158e7c7523b6a553 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 10 Oct 2024 15:43:44 -0600 Subject: [PATCH 269/455] chore: Bump MSRV to 1.66 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 879ce002..870129c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ resolver = "2" repository = "https://github.com/rust-lang/annotate-snippets-rs" license = "MIT OR Apache-2.0" edition = "2021" -rust-version = "1.65.0" # MSRV +rust-version = "1.66.0" # MSRV include = [ "build.rs", "src/**/*", From 928226ce921ee1048bba131218d8cecf5bbe1833 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 10 Oct 2024 15:44:58 -0600 Subject: [PATCH 270/455] chore: Update unicode-width --- Cargo.lock | 10 ++++++++-- Cargo.toml | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 74768255..8218ad69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,7 +26,7 @@ dependencies = [ "snapbox", "toml", "tryfn", - "unicode-width", + "unicode-width 0.2.0", ] [[package]] @@ -102,7 +102,7 @@ dependencies = [ "anstyle", "anstyle-lossy", "html-escape", - "unicode-width", + "unicode-width 0.1.13", ] [[package]] @@ -672,6 +672,12 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + [[package]] name = "utf8-width" version = "0.1.7" diff --git a/Cargo.toml b/Cargo.toml index 870129c4..5b77b77a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -117,7 +117,7 @@ maintenance = { status = "actively-developed" } [dependencies] anstyle = "1.0.4" memchr = { version = "2.7.4", optional = true } -unicode-width = "0.1.11" +unicode-width = "0.2.0" [dev-dependencies] annotate-snippets = { path = ".", features = ["testing-colors"] } From c08abcc0bcb2a1c39072de299dd7aa0c4bf2a4dc Mon Sep 17 00:00:00 2001 From: Karel Peeters <karel.peeters.leuven@gmail.com> Date: Sun, 13 Oct 2024 23:44:29 +0200 Subject: [PATCH 271/455] fix: Fix double mistake in EndLine EndLine::Crlf and EndLine::Lf had wrong lengths, and all usages of them were also flipped. Both issues happened to cancel out. This commit fixes both, and so this should be a no-op. --- src/renderer/display_list.rs | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index 1823e61a..e2f0d74f 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -940,9 +940,20 @@ impl<'a> CursorLines<'a> { #[derive(Copy, Clone, Debug, PartialEq)] pub(crate) enum EndLine { - Eof = 0, - Crlf = 1, - Lf = 2, + Eof, + Lf, + Crlf, +} + +impl EndLine { + /// The number of characters this line ending occupies in bytes. + pub(crate) fn len(self) -> usize { + match self { + EndLine::Eof => 0, + EndLine::Lf => 1, + EndLine::Crlf => 2, + } + } } impl<'a> Iterator for CursorLines<'a> { @@ -957,12 +968,12 @@ impl<'a> Iterator for CursorLines<'a> { .map(|x| { let ret = if 0 < x { if self.0.as_bytes()[x - 1] == b'\r' { - (&self.0[..x - 1], EndLine::Lf) + (&self.0[..x - 1], EndLine::Crlf) } else { - (&self.0[..x], EndLine::Crlf) + (&self.0[..x], EndLine::Lf) } } else { - ("", EndLine::Crlf) + ("", EndLine::Lf) }; self.0 = &self.0[x + 1..]; ret @@ -1138,7 +1149,7 @@ fn format_header<'a>( .. } = item { - if main_range >= range.0 && main_range <= range.1 + *end_line as usize { + if main_range >= range.0 && main_range <= range.1 + end_line.len() { let char_column = text[0..(main_range - range.0).min(text.len())] .chars() .count(); @@ -1366,7 +1377,7 @@ fn format_body( for (idx, (line, end_line)) in CursorLines::new(snippet.source).enumerate() { let line_length: usize = line.len(); let line_range = (current_index, current_index + line_length); - let end_line_size = end_line as usize; + let end_line_size = end_line.len(); body.push(DisplayLine::Source { lineno: Some(current_line), inline_marks: vec![], From ee114bc6ec0fdbaaf25b82b6a48720e401b935ab Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 17 Oct 2024 16:08:49 -0600 Subject: [PATCH 272/455] test: Don't borrow when deserializing fixtures --- tests/fixtures/deserialize.rs | 99 ++++++++++------------------------- tests/fixtures/main.rs | 6 ++- 2 files changed, 31 insertions(+), 74 deletions(-) diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs index a38a88e7..4dbf341e 100644 --- a/tests/fixtures/deserialize.rs +++ b/tests/fixtures/deserialize.rs @@ -1,36 +1,30 @@ -use serde::{Deserialize, Deserializer, Serialize}; +use serde::Deserialize; use std::ops::Range; use annotate_snippets::renderer::DEFAULT_TERM_WIDTH; use annotate_snippets::{Annotation, Level, Message, Renderer, Snippet}; #[derive(Deserialize)] -pub(crate) struct Fixture<'a> { +pub(crate) struct Fixture { #[serde(default)] pub(crate) renderer: RendererDef, - #[serde(borrow)] - pub(crate) message: MessageDef<'a>, + pub(crate) message: MessageDef, } #[derive(Deserialize)] -pub struct MessageDef<'a> { +pub struct MessageDef { #[serde(with = "LevelDef")] pub level: Level, - #[serde(borrow)] - pub title: &'a str, + pub title: String, #[serde(default)] - #[serde(borrow)] - pub id: Option<&'a str>, + pub id: Option<String>, #[serde(default)] - #[serde(borrow)] - pub footer: Vec<MessageDef<'a>>, - #[serde(deserialize_with = "deserialize_snippets")] - #[serde(borrow)] - pub snippets: Vec<Snippet<'a>>, + pub footer: Vec<MessageDef>, + pub snippets: Vec<SnippetDef>, } -impl<'a> From<MessageDef<'a>> for Message<'a> { - fn from(val: MessageDef<'a>) -> Self { +impl<'a> From<&'a MessageDef> for Message<'a> { + fn from(val: &'a MessageDef) -> Self { let MessageDef { level, title, @@ -42,43 +36,24 @@ impl<'a> From<MessageDef<'a>> for Message<'a> { if let Some(id) = id { message = message.id(id); } - message = message.snippets(snippets); - message = message.footers(footer.into_iter().map(Into::into)); + message = message.snippets(snippets.iter().map(Snippet::from)); + message = message.footers(footer.iter().map(Into::into)); message } } -fn deserialize_snippets<'de, D>(deserializer: D) -> Result<Vec<Snippet<'de>>, D::Error> -where - D: Deserializer<'de>, -{ - #[derive(Deserialize)] - struct Wrapper<'a>( - #[serde(with = "SnippetDef")] - #[serde(borrow)] - SnippetDef<'a>, - ); - - let v = Vec::deserialize(deserializer)?; - Ok(v.into_iter().map(|Wrapper(a)| a.into()).collect()) -} - #[derive(Deserialize)] -pub struct SnippetDef<'a> { - #[serde(borrow)] - pub source: &'a str, +pub struct SnippetDef { + pub source: String, pub line_start: usize, - #[serde(borrow)] - pub origin: Option<&'a str>, - #[serde(deserialize_with = "deserialize_annotations")] - #[serde(borrow)] - pub annotations: Vec<Annotation<'a>>, + pub origin: Option<String>, + pub annotations: Vec<AnnotationDef>, #[serde(default)] pub fold: bool, } -impl<'a> From<SnippetDef<'a>> for Snippet<'a> { - fn from(val: SnippetDef<'a>) -> Self { +impl<'a> From<&'a SnippetDef> for Snippet<'a> { + fn from(val: &'a SnippetDef) -> Self { let SnippetDef { source, line_start, @@ -86,56 +61,36 @@ impl<'a> From<SnippetDef<'a>> for Snippet<'a> { annotations, fold, } = val; - let mut snippet = Snippet::source(source).line_start(line_start).fold(fold); + let mut snippet = Snippet::source(source).line_start(*line_start).fold(*fold); if let Some(origin) = origin { snippet = snippet.origin(origin); } - snippet = snippet.annotations(annotations); + snippet = snippet.annotations(annotations.iter().map(Into::into)); snippet } } -fn deserialize_annotations<'de, D>(deserializer: D) -> Result<Vec<Annotation<'de>>, D::Error> -where - D: Deserializer<'de>, -{ - #[derive(Deserialize)] - struct Wrapper<'a>(#[serde(borrow)] AnnotationDef<'a>); - - let v = Vec::deserialize(deserializer)?; - Ok(v.into_iter().map(|Wrapper(a)| a.into()).collect()) -} - -#[derive(Serialize, Deserialize)] -pub struct AnnotationDef<'a> { +#[derive(Deserialize)] +pub struct AnnotationDef { pub range: Range<usize>, - #[serde(borrow)] - pub label: &'a str, + pub label: String, #[serde(with = "LevelDef")] pub level: Level, } -impl<'a> From<AnnotationDef<'a>> for Annotation<'a> { - fn from(val: AnnotationDef<'a>) -> Self { +impl<'a> From<&'a AnnotationDef> for Annotation<'a> { + fn from(val: &'a AnnotationDef) -> Self { let AnnotationDef { range, label, level, } = val; - level.span(range).label(label) + level.span(range.start..range.end).label(label) } } -#[derive(Serialize, Deserialize)] -pub(crate) struct LabelDef<'a> { - #[serde(with = "LevelDef")] - pub(crate) level: Level, - #[serde(borrow)] - pub(crate) label: &'a str, -} - #[allow(dead_code)] -#[derive(Serialize, Deserialize)] +#[derive(Deserialize)] #[serde(remote = "Level")] enum LevelDef { Error, diff --git a/tests/fixtures/main.rs b/tests/fixtures/main.rs index bf37e73d..5ff1105c 100644 --- a/tests/fixtures/main.rs +++ b/tests/fixtures/main.rs @@ -33,8 +33,10 @@ fn setup(input_path: std::path::PathBuf) -> tryfn::Case { fn test(input_path: &std::path::Path) -> Result<Data, Box<dyn Error>> { let src = std::fs::read_to_string(input_path)?; - let (renderer, message): (Renderer, Message<'_>) = - toml::from_str(&src).map(|a: Fixture<'_>| (a.renderer.into(), a.message.into()))?; + let fixture: Fixture = toml::from_str(&src)?; + let renderer: Renderer = fixture.renderer.into(); + let message: Message<'_> = (&fixture.message).into(); + let actual = renderer.render(message).to_string(); Ok(Data::from(actual).coerce_to(DataFormat::TermSvg)) } From 58425bfc95cb3dc53f7295bdfe97d1276f4f52d7 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 17 Oct 2024 16:12:19 -0600 Subject: [PATCH 273/455] chore(deps): Update Rust crate toml to 0.8.0 --- Cargo.lock | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 2 +- 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8218ad69..bc7c6504 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -232,6 +232,12 @@ dependencies = [ "syn", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.9" @@ -282,6 +288,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + [[package]] name = "heck" version = "0.4.1" @@ -320,6 +332,16 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "indexmap" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "io-lifetimes" version = "1.0.11" @@ -550,6 +572,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "similar" version = "2.5.0" @@ -642,11 +673,36 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.11" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", ] [[package]] @@ -865,3 +921,12 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml index 5b77b77a..d66ef092 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -127,7 +127,7 @@ divan = "0.1.14" glob = "0.3.1" serde = { version = "1.0.199", features = ["derive"] } snapbox = { version = "0.6.0", features = ["diff", "term-svg", "cmd", "examples"] } -toml = "0.5.11" +toml = "0.8.0" tryfn = "0.2.1" [[bench]] From 15e503f616b8c17834d7dbcd86169c828780a9d8 Mon Sep 17 00:00:00 2001 From: Karel Peeters <karel.peeters.leuven@gmail.com> Date: Mon, 14 Oct 2024 00:38:13 +0200 Subject: [PATCH 274/455] test: Add origin location test cases --- tests/formatter.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tests/formatter.rs b/tests/formatter.rs index 7f914de9..ab2eb076 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -905,3 +905,53 @@ error: unused optional dependency let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); } + +#[test] +fn origin_correct_start_line() { + let source = "aaa\nbbb\nccc\nddd\n"; + let input = Level::Error.title("title").snippet( + Snippet::source(source) + .origin("origin.txt") + .fold(false) + .annotation(Level::Error.span(8..8 + 3).label("annotation")), + ); + + let expected = str![[r#" +error: title + --> origin.txt:2:4 + | +1 | aaa +2 | bbb +3 | ccc + | ^^^ annotation +4 | ddd + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn origin_correct_mid_line() { + let source = "aaa\nbbb\nccc\nddd\n"; + let input = Level::Error.title("title").snippet( + Snippet::source(source) + .origin("origin.txt") + .fold(false) + .annotation(Level::Error.span(8 + 1..8 + 3).label("annotation")), + ); + + let expected = str![[r#" +error: title + --> origin.txt:3:2 + | +1 | aaa +2 | bbb +3 | ccc + | ^^ annotation +4 | ddd + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input).to_string(), expected); +} From 695e4284411f9f8b16a60cb193ef1ad678ee0b43 Mon Sep 17 00:00:00 2001 From: Karel Peeters <karel.peeters.leuven@gmail.com> Date: Mon, 14 Oct 2024 00:39:16 +0200 Subject: [PATCH 275/455] fix: Fix bug in origin location computation --- src/renderer/display_list.rs | 2 +- tests/formatter.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index e2f0d74f..8884b0db 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -1149,7 +1149,7 @@ fn format_header<'a>( .. } = item { - if main_range >= range.0 && main_range <= range.1 + end_line.len() { + if main_range >= range.0 && main_range < range.1 + max(*end_line as usize, 1) { let char_column = text[0..(main_range - range.0).min(text.len())] .chars() .count(); diff --git a/tests/formatter.rs b/tests/formatter.rs index ab2eb076..6faab76d 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -918,7 +918,7 @@ fn origin_correct_start_line() { let expected = str![[r#" error: title - --> origin.txt:2:4 + --> origin.txt:3:1 | 1 | aaa 2 | bbb From 8db276fc5ba20df6c0c2f8f0073cbcb2781f1648 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 21 Oct 2024 10:35:12 +0800 Subject: [PATCH 276/455] docs: Cross-reference source code view --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 97c7ed79..0e9372da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,7 +99,7 @@ include.workspace = true [package.metadata.docs.rs] all-features = true -rustdoc-args = ["--cfg", "docsrs"] +rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"] [package.metadata.release] pre-release-replacements = [ From e121dd6ef9e11dfa818a813bbaffb12a16cd174e Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 21 Oct 2024 15:39:54 +0800 Subject: [PATCH 277/455] chore(ci): Fix STABLE updates See rust-lang/cargo#14704 --- .github/renovate.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index c1844208..7ab13b9f 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -32,7 +32,7 @@ matchManagers: [ 'custom.regex', ], - matchPackageNames: [ + matchDepNames: [ 'STABLE', ], extractVersion: '^(?<version>\\d+\\.\\d+)', // Drop the patch version From 64e1a8ddca879700b8a8139d28cab60f6305e35b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 1 Nov 2024 00:38:11 +0000 Subject: [PATCH 278/455] chore(deps): Update compatible (dev) --- Cargo.lock | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bc7c6504..61af3f7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,7 +16,7 @@ name = "annotate-snippets" version = "0.11.4" dependencies = [ "annotate-snippets", - "anstream 0.6.15", + "anstream 0.6.17", "anstyle", "difference", "divan", @@ -46,14 +46,14 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", - "anstyle-wincon 3.0.3", + "anstyle-wincon 3.0.6", "colorchoice", "is_terminal_polyfill", "utf8parse", @@ -98,7 +98,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbf0bf947d663010f0b4132f28ca08da9151f3b9035fa7578a38de521c1d1aa" dependencies = [ - "anstream 0.6.15", + "anstream 0.6.17", "anstyle", "anstyle-lossy", "html-escape", @@ -117,12 +117,12 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.3" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -259,9 +259,9 @@ dependencies = [ [[package]] name = "escargot" -version = "0.5.11" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eb5f6eeda986377996e9ed570cbc20cc16d30440696f82f129c863e4e3e83" +checksum = "05a3ac187a16b5382fef8c69fd1bad123c67b7cf3932240a2d43dcdd32cded88" dependencies = [ "log", "once_cell", @@ -543,18 +543,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.210" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", @@ -589,11 +589,11 @@ checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" [[package]] name = "snapbox" -version = "0.6.17" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "840b73eb3148bc3cbc10ebe00ec9bc6d96033e658d022c4adcbf3f35596fd64a" +checksum = "881f1849454828a68363dd288b7a0a071e55e2a4356d2c38b567db18a9be0d9f" dependencies = [ - "anstream 0.6.15", + "anstream 0.6.17", "anstyle", "anstyle-svg", "escargot", @@ -613,7 +613,7 @@ version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16569f53ca23a41bb6f62e0a5084aa1661f4814a67fa33696a79073e03a664af" dependencies = [ - "anstream 0.6.15", + "anstream 0.6.17", ] [[package]] @@ -624,9 +624,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "2.0.66" +version = "2.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "e89275301d38033efb81a6e60e3497e734dfcc62571f2854bf4b16690398824c" dependencies = [ "proc-macro2", "quote", From 3a79f3a9ee9c9a1cde43331d0fc8305ad71bf5b4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 1 Nov 2024 03:24:47 +0000 Subject: [PATCH 279/455] chore(deps): Update dependency STABLE to v1.82.0 --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8867b993..cc7fa012 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -104,7 +104,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.81.0" # STABLE + toolchain: "1.82.0" # STABLE - uses: Swatinem/rust-cache@v2 - name: Check documentation env: @@ -119,7 +119,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.81.0" # STABLE + toolchain: "1.82.0" # STABLE components: rustfmt - uses: Swatinem/rust-cache@v2 - name: Check formatting @@ -135,7 +135,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.81.0" # STABLE + toolchain: "1.82.0" # STABLE components: clippy - uses: Swatinem/rust-cache@v2 - name: Install SARIF tools From afa23ae258a1a6ec9f2268cd0b57c20a88dea3be Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Fri, 1 Nov 2024 15:36:16 -0500 Subject: [PATCH 280/455] style: Ignore large Err variants --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 0e9372da..7e38ce35 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,6 +70,7 @@ rc_mutex = "warn" redundant_feature_names = "warn" ref_option_ref = "warn" rest_pat_in_fully_bound_structs = "warn" +result_large_err = "allow" same_functions_in_if_condition = "warn" self_named_module_files = "warn" semicolon_if_nothing_returned = "warn" From 006f98fb3a3e4d4a3054c9fc0ea33906a3e42d44 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 18 Nov 2024 13:48:49 -0600 Subject: [PATCH 281/455] chore(ci): Report deprecations in the review --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d49017e7..9fb9591c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -144,7 +144,7 @@ jobs: run: cargo install sarif-fmt --locked - name: Check run: > - cargo clippy --workspace --all-features --all-targets --message-format=json -- -D warnings --allow deprecated + cargo clippy --workspace --all-features --all-targets --message-format=json | clippy-sarif | tee clippy-results.sarif | sarif-fmt From 810013723912ca4faf8fbc87fc2ed25b2a531516 Mon Sep 17 00:00:00 2001 From: futreall <86553580+futreall@users.noreply.github.com> Date: Fri, 29 Nov 2024 16:16:58 +0200 Subject: [PATCH 282/455] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b0318b82..e9fca37f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,7 +49,7 @@ Specifically, we would encourage - File renames be isolated into their own commit - Add tests in a commit before their feature or fix, showing the current behavior. The diff for the feature/fix commit will then show how the behavior changed, - making it clearer to reviewrs and the community and showing people that the + making it clearer to reviewers and the community and showing people that the test is verifying the expected state. - e.g. [clap#5520](https://github.com/clap-rs/clap/pull/5520) From 9625bc071db30ce53ef960099ac16c868619a3a9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 1 Dec 2024 00:32:15 +0000 Subject: [PATCH 283/455] chore(deps): Update compatible (dev) (#162) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 61af3f7d..524efa4f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16,7 +16,7 @@ name = "annotate-snippets" version = "0.11.4" dependencies = [ "annotate-snippets", - "anstream 0.6.17", + "anstream 0.6.18", "anstyle", "difference", "divan", @@ -46,9 +46,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.17" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -98,7 +98,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbf0bf947d663010f0b4132f28ca08da9151f3b9035fa7578a38de521c1d1aa" dependencies = [ - "anstream 0.6.17", + "anstream 0.6.18", "anstyle", "anstyle-lossy", "html-escape", @@ -209,9 +209,9 @@ checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" [[package]] name = "divan" -version = "0.1.14" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d567df2c9c2870a43f3f2bd65aaeb18dbce1c18f217c3e564b4fbaeb3ee56c" +checksum = "ccc40f214f0d9e897cfc72e2edfa5c225d3252f758c537f11ac0a80371c073a6" dependencies = [ "cfg-if", "clap", @@ -223,9 +223,9 @@ dependencies = [ [[package]] name = "divan-macros" -version = "0.1.14" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27540baf49be0d484d8f0130d7d8da3011c32a44d4fc873368154f1510e574a2" +checksum = "7bdb5411188f7f878a17964798c1264b6b0a9f915bd39b20bf99193c923e1b4e" dependencies = [ "proc-macro2", "quote", @@ -543,18 +543,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", @@ -589,11 +589,11 @@ checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" [[package]] name = "snapbox" -version = "0.6.19" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "881f1849454828a68363dd288b7a0a071e55e2a4356d2c38b567db18a9be0d9f" +checksum = "1373ce406dfad473059bbc31d807715642182bbc952a811952b58d1c9e41dcfa" dependencies = [ - "anstream 0.6.17", + "anstream 0.6.18", "anstyle", "anstyle-svg", "escargot", @@ -613,7 +613,7 @@ version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16569f53ca23a41bb6f62e0a5084aa1661f4814a67fa33696a79073e03a664af" dependencies = [ - "anstream 0.6.17", + "anstream 0.6.18", ] [[package]] From 7bd4180a4218c46925ca2f9e30a28a10655e65cd Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 2 Dec 2024 11:04:06 -0600 Subject: [PATCH 284/455] style: Make clippy happy --- src/renderer/display_list.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index 8884b0db..c2cb82bc 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -55,13 +55,13 @@ pub(crate) struct DisplayList<'a> { pub(crate) anonymized_line_numbers: bool, } -impl<'a> PartialEq for DisplayList<'a> { +impl PartialEq for DisplayList<'_> { fn eq(&self, other: &Self) -> bool { self.body == other.body && self.anonymized_line_numbers == other.anonymized_line_numbers } } -impl<'a> fmt::Debug for DisplayList<'a> { +impl fmt::Debug for DisplayList<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("DisplayList") .field("body", &self.body) @@ -70,7 +70,7 @@ impl<'a> fmt::Debug for DisplayList<'a> { } } -impl<'a> Display for DisplayList<'a> { +impl Display for DisplayList<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let lineno_width = self.body.iter().fold(0, |max, set| { set.display_lines.iter().fold(max, |max, line| match line { @@ -156,7 +156,7 @@ pub(crate) struct DisplaySet<'a> { pub(crate) margin: Margin, } -impl<'a> DisplaySet<'a> { +impl DisplaySet<'_> { fn format_label( &self, line_offset: usize, @@ -791,7 +791,7 @@ pub(crate) struct DisplaySourceAnnotation<'a> { pub(crate) annotation_part: DisplayAnnotationPart, } -impl<'a> DisplaySourceAnnotation<'a> { +impl DisplaySourceAnnotation<'_> { fn has_label(&self) -> bool { !self .annotation @@ -932,7 +932,7 @@ pub(crate) enum DisplayHeaderType { struct CursorLines<'a>(&'a str); -impl<'a> CursorLines<'a> { +impl CursorLines<'_> { fn new(src: &str) -> CursorLines<'_> { CursorLines(src) } From 949ac19b7864a600a2c88d7468b01c9156b300bc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 17:10:06 +0000 Subject: [PATCH 285/455] chore(deps): Update Rust Stable to v1.83 --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 82108a79..14218fee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -104,7 +104,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.82.0" # STABLE + toolchain: "1.83" # STABLE - uses: Swatinem/rust-cache@v2 - name: Check documentation env: @@ -119,7 +119,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.82.0" # STABLE + toolchain: "1.83" # STABLE components: rustfmt - uses: Swatinem/rust-cache@v2 - name: Check formatting @@ -135,7 +135,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.82.0" # STABLE + toolchain: "1.83" # STABLE components: clippy - uses: Swatinem/rust-cache@v2 - name: Install SARIF tools From b2aecee7155c151f9b443db45957bf5075d5085d Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 2 Dec 2024 12:48:07 -0600 Subject: [PATCH 286/455] docs(contrib): Fix language --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e9fca37f..ee501ec8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,18 +7,18 @@ appreciate any level you're willing to do. Need some new functionality to help? You can let us know by opening an [issue][new issue]. It's helpful to look through [all issues][all issues] in -case its already being talked about. +case it's already being talked about. ## Bug Reports Please let us know about what problems you run into, whether in behavior or ergonomics of API. You can do this by opening an [issue][new issue]. It's -helpful to look through [all issues][all issues] in case its already being +helpful to look through [all issues][all issues] in case it's already being talked about. ## Pull Requests -Looking for an idea? Check our [issues][issues]. If it's look more open ended, +Looking for an idea? Check our [issues][issues]. If the issue looks open ended, it is probably best to post on the issue how you are thinking of resolving the issue so you can get feedback early in the process. We want you to be successful and it can be discouraging to find out a lot of re-work is needed. From 15ede43536f52b96d69152522bf57156662960a3 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Tue, 3 Dec 2024 12:54:52 -0600 Subject: [PATCH 287/455] docs(readme): Add a hyphen --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9e6c4e79..ace2bb24 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 -license, shall be dual licensed as above, without any additional terms or +license, shall be dual-licensed as above, without any additional terms or conditions. [Crates.io]: https://crates.io/crates/PROJECT From 30118b02feac986ec73566577796ff27a96a7596 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Mon, 9 Dec 2024 11:12:21 -0700 Subject: [PATCH 288/455] chore: Make master the allowed release branch --- release.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release.toml b/release.toml index 160b061b..f74b710a 100644 --- a/release.toml +++ b/release.toml @@ -1,2 +1,2 @@ dependent-version = "fix" -allow-branch = ["main"] +allow-branch = ["master"] From 286cce0b3ffe0caabeed6dd29dc48704654cf413 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Mon, 9 Dec 2024 11:19:01 -0700 Subject: [PATCH 289/455] docs: Update changelog --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c7d2d22..8e42b169 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](http://semver.org/). <!-- next-header --> ## [Unreleased] - ReleaseDate +### Added + +- `rustc`'s multiline annotation special case [#133](https://github.com/rust-lang/annotate-snippets-rs/pull/133) + - This special case happens when: + - The start of a multiline annotation is at the start of the line disregarding any leading whitespace + - No other multiline annotations overlap it +- `simd` feature for faster folding [#146](https://github.com/rust-lang/annotate-snippets-rs/pull/146) + +### Changed + +- Multiline annotations with matching spans get merged [#133](https://github.com/rust-lang/annotate-snippets-rs/pull/133) +- Multiple annotations on one line are no longer rendered on separate lines [#133](https://github.com/rust-lang/annotate-snippets-rs/pull/133) + +### Fixed + +- Overlapping multiline annotations are now correctly rendered [#133](https://github.com/rust-lang/annotate-snippets-rs/pull/133) +- Origin position is now correctly calculated when an annotation starts at the beginning of the line [#154](https://github.com/rust-lang/annotate-snippets-rs/pull/154) + ## [0.11.4] - 2024-06-15 ### Fixes From 72dd8c7b9210bace1be990e3e3018fab46fd8291 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Mon, 9 Dec 2024 11:20:24 -0700 Subject: [PATCH 290/455] chore: Release annotate-snippets version 0.11.5 --- CHANGELOG.md | 5 ++++- Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e42b169..ed078cb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). <!-- next-header --> ## [Unreleased] - ReleaseDate +## [0.11.5] - 2024-12-09 + ### Added - `rustc`'s multiline annotation special case [#133](https://github.com/rust-lang/annotate-snippets-rs/pull/133) @@ -162,7 +164,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Update the syntax to Rust 2018 idioms. (#4) <!-- next-url --> -[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.4...HEAD +[Unreleased]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.5...HEAD +[0.11.5]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.4...0.11.5 [0.11.4]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.3...0.11.4 [0.11.3]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.2...0.11.3 [0.11.2]: https://github.com/rust-lang/annotate-snippets-rs/compare/0.11.1...0.11.2 diff --git a/Cargo.lock b/Cargo.lock index 524efa4f..b2561b28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,7 +13,7 @@ dependencies = [ [[package]] name = "annotate-snippets" -version = "0.11.4" +version = "0.11.5" dependencies = [ "annotate-snippets", "anstream 0.6.18", diff --git a/Cargo.toml b/Cargo.toml index 0b03d944..f30c64f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,7 +88,7 @@ zero_sized_map_values = "warn" [package] name = "annotate-snippets" -version = "0.11.4" +version = "0.11.5" description = "Library for building code annotations" categories = [] keywords = ["code", "analysis", "ascii", "errors", "debug"] From 2f01ad6725cc0dfe275b8120bab58ff07d2bffe2 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 16 Dec 2024 14:07:22 -0600 Subject: [PATCH 291/455] chore(ci): Allow 2-clause BSD --- deny.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/deny.toml b/deny.toml index b6ecbe9c..ee5ae89b 100644 --- a/deny.toml +++ b/deny.toml @@ -87,6 +87,7 @@ allow = [ "MIT", "MIT-0", "Apache-2.0", + "BSD-2-Clause", "BSD-3-Clause", "MPL-2.0", "Unicode-DFS-2016", From e783559994c5e56d4d3327443dd70abb0f95ef68 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 18 Dec 2024 08:44:02 -0600 Subject: [PATCH 292/455] docs(contrib): Try to remove ambiguity about commits/PRs --- CONTRIBUTING.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ee501ec8..8048d243 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,15 +41,18 @@ As a heads up, we'll be running your PR through the following gauntlet: Not everything can be checked automatically though. We request that the commit history gets cleaned up. + We ask that commits are atomic, meaning they are complete and have a single responsibility. -PRs should tell a cohesive story, with test and refactor commits that keep the +A complete commit should build, pass tests, update documentation and tests, and not have dead code. + +PRs should tell a cohesive story, with refactor and test commits that keep the fix or feature commits simple and clear. Specifically, we would encourage - File renames be isolated into their own commit -- Add tests in a commit before their feature or fix, showing the current behavior. +- Add tests in a commit before their feature or fix, showing the current behavior (i.e. they should pass). The diff for the feature/fix commit will then show how the behavior changed, - making it clearer to reviewers and the community and showing people that the + making the commit's intent clearer to reviewers and the community, and showing people that the test is verifying the expected state. - e.g. [clap#5520](https://github.com/clap-rs/clap/pull/5520) From 7132bf31ce6fc004911ceed9caab1c02dda23b24 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 1 Jan 2025 02:28:36 +0000 Subject: [PATCH 293/455] chore(deps): Update compatible (dev) (#168) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b2561b28..acadd7b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -209,9 +209,9 @@ checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" [[package]] name = "divan" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccc40f214f0d9e897cfc72e2edfa5c225d3252f758c537f11ac0a80371c073a6" +checksum = "e0583193020b29b03682d8d33bb53a5b0f50df6daacece12ca99b904cfdcb8c4" dependencies = [ "cfg-if", "clap", @@ -223,9 +223,9 @@ dependencies = [ [[package]] name = "divan-macros" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bdb5411188f7f878a17964798c1264b6b0a9f915bd39b20bf99193c923e1b4e" +checksum = "8dc51d98e636f5e3b0759a39257458b22619cac7e96d932da6eeb052891bb67c" dependencies = [ "proc-macro2", "quote", @@ -271,9 +271,9 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "globset" @@ -543,18 +543,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.215" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", @@ -589,9 +589,9 @@ checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" [[package]] name = "snapbox" -version = "0.6.20" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1373ce406dfad473059bbc31d807715642182bbc952a811952b58d1c9e41dcfa" +checksum = "96dcfc4581e3355d70ac2ee14cfdf81dce3d85c85f1ed9e2c1d3013f53b3436b" dependencies = [ "anstream 0.6.18", "anstyle", From 32dc46465e7b76295a3dc29c3e89b3e3f9f936aa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 9 Jan 2025 18:23:37 +0000 Subject: [PATCH 294/455] chore(deps): Update Rust Stable to v1.84 (#173) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 14218fee..2073635e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -104,7 +104,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.83" # STABLE + toolchain: "1.84" # STABLE - uses: Swatinem/rust-cache@v2 - name: Check documentation env: @@ -119,7 +119,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.83" # STABLE + toolchain: "1.84" # STABLE components: rustfmt - uses: Swatinem/rust-cache@v2 - name: Check formatting @@ -135,7 +135,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.83" # STABLE + toolchain: "1.84" # STABLE components: clippy - uses: Swatinem/rust-cache@v2 - name: Install SARIF tools From d159ec5290740054b9b344f995d453d37307e7ff Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Fri, 24 Jan 2025 11:54:12 -0600 Subject: [PATCH 295/455] test: Verify readme --- src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 2eabbd09..f66c1100 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,3 +4,7 @@ #![warn(clippy::print_stderr)] #![warn(clippy::print_stdout)] #![allow(non_snake_case)] // TODO: Delete me + +#[doc = include_str!("../README.md")] +#[cfg(doctest)] +pub struct ReadmeDoctests; From fa2f56ad2a19018eba2f18af494e8b4fc6ec1cf7 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 12 Feb 2025 09:34:02 -0600 Subject: [PATCH 296/455] fix: Remove trailing pipe --- examples/expected_type.svg | 6 +-- examples/footer.svg | 10 ++-- examples/format.svg | 6 +-- examples/multislice.svg | 14 ++---- src/renderer/display_list.rs | 20 -------- tests/fixtures/color/ann_eof.svg | 4 +- tests/fixtures/color/ann_insertion.svg | 4 +- tests/fixtures/color/ann_multiline.svg | 4 +- tests/fixtures/color/ann_multiline2.svg | 4 +- tests/fixtures/color/ann_removed_nl.svg | 4 +- .../color/ensure-emoji-highlight-width.svg | 4 +- tests/fixtures/color/fold_ann_multiline.svg | 4 +- tests/fixtures/color/fold_bad_origin_line.svg | 4 +- tests/fixtures/color/fold_leading.svg | 4 +- tests/fixtures/color/fold_trailing.svg | 4 +- tests/fixtures/color/issue_9.svg | 16 ++----- tests/fixtures/color/multiple_annotations.svg | 4 +- tests/fixtures/color/simple.svg | 4 +- tests/fixtures/color/strip_line.svg | 4 +- tests/fixtures/color/strip_line_char.svg | 4 +- tests/fixtures/color/strip_line_non_ws.svg | 4 +- tests/formatter.rs | 48 ++++--------------- tests/rustc_tests.rs | 22 --------- 23 files changed, 42 insertions(+), 160 deletions(-) diff --git a/examples/expected_type.svg b/examples/expected_type.svg index ed19ef38..d5de44fc 100644 --- a/examples/expected_type.svg +++ b/examples/expected_type.svg @@ -1,4 +1,4 @@ -<svg width="860px" height="218px" xmlns="http://www.w3.org/2000/svg"> +<svg width="860px" height="200px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -37,9 +37,7 @@ </tspan> <tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan> </tspan> - <tspan x="10px" y="190px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> -</tspan> - <tspan x="10px" y="208px"> + <tspan x="10px" y="190px"> </tspan> </text> diff --git a/examples/footer.svg b/examples/footer.svg index 76e7d776..ab9e4dfd 100644 --- a/examples/footer.svg +++ b/examples/footer.svg @@ -1,4 +1,4 @@ -<svg width="844px" height="182px" xmlns="http://www.w3.org/2000/svg"> +<svg width="844px" height="164px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -30,13 +30,11 @@ </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan> </tspan> - <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> + <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">= </tspan><tspan class="fg-bright-green bold">note</tspan><tspan>: expected type: `snippet::Annotation`</tspan> </tspan> - <tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">= </tspan><tspan class="fg-bright-green bold">note</tspan><tspan>: expected type: `snippet::Annotation`</tspan> + <tspan x="10px" y="136px"><tspan> found type: `__&__snippet::Annotation`</tspan> </tspan> - <tspan x="10px" y="154px"><tspan> found type: `__&__snippet::Annotation`</tspan> -</tspan> - <tspan x="10px" y="172px"> + <tspan x="10px" y="154px"> </tspan> </text> diff --git a/examples/format.svg b/examples/format.svg index ac196c01..0e054572 100644 --- a/examples/format.svg +++ b/examples/format.svg @@ -1,4 +1,4 @@ -<svg width="740px" height="542px" xmlns="http://www.w3.org/2000/svg"> +<svg width="740px" height="524px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -74,9 +74,7 @@ </tspan> <tspan x="10px" y="496px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|____^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected enum `std::option::Option`</tspan> </tspan> - <tspan x="10px" y="514px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> -</tspan> - <tspan x="10px" y="532px"> + <tspan x="10px" y="514px"> </tspan> </text> diff --git a/examples/multislice.svg b/examples/multislice.svg index 216a3592..92ff9dfe 100644 --- a/examples/multislice.svg +++ b/examples/multislice.svg @@ -1,4 +1,4 @@ -<svg width="740px" height="200px" xmlns="http://www.w3.org/2000/svg"> +<svg width="740px" height="164px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -27,17 +27,13 @@ </tspan> <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold"> 51 |</tspan><tspan> Foo</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">:::</tspan><tspan> src/display.rs</tspan> </tspan> - <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">:::</tspan><tspan> src/display.rs</tspan> + <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> + <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">129 |</tspan><tspan> Faa</tspan> </tspan> - <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">129 |</tspan><tspan> Faa</tspan> -</tspan> - <tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> -</tspan> - <tspan x="10px" y="190px"> + <tspan x="10px" y="154px"> </tspan> </text> diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index c2cb82bc..460f985b 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -20,7 +20,6 @@ //! 152 | | return "test"; //! 153 | | } //! | |___^ error: expected `String`, for `&str`. -//! | //! ``` //! //! The first two lines of the example above are `Raw` lines, while the rest @@ -1013,7 +1012,6 @@ fn format_message( sets.push(format_snippet( snippet, idx == 0, - !footer.is_empty(), term_width, anonymized_line_numbers, )); @@ -1092,7 +1090,6 @@ fn format_label( fn format_snippet( snippet: snippet::Snippet<'_>, is_first: bool, - has_footer: bool, term_width: usize, anonymized_line_numbers: bool, ) -> DisplaySet<'_> { @@ -1102,7 +1099,6 @@ fn format_snippet( let mut body = format_body( snippet, need_empty_header, - has_footer, term_width, anonymized_line_numbers, ); @@ -1290,7 +1286,6 @@ fn fold_body(body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> { fn format_body( snippet: snippet::Snippet<'_>, need_empty_header: bool, - has_footer: bool, term_width: usize, anonymized_line_numbers: bool, ) -> DisplaySet<'_> { @@ -1605,21 +1600,6 @@ fn format_body( ); } - if has_footer { - body.push(DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - annotations: vec![], - }); - } else if let Some(DisplayLine::Source { .. }) = body.last() { - body.push(DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - annotations: vec![], - }); - } let max_line_num_len = if anonymized_line_numbers { ANONYMIZED_LINE_NUM.len() } else { diff --git a/tests/fixtures/color/ann_eof.svg b/tests/fixtures/color/ann_eof.svg index bb12aecf..b0fb8b6c 100644 --- a/tests/fixtures/color/ann_eof.svg +++ b/tests/fixtures/color/ann_eof.svg @@ -1,4 +1,4 @@ -<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg"> +<svg width="740px" height="110px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -28,8 +28,6 @@ <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1 |</tspan><tspan> asdf</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan> -</tspan> - <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> </text> diff --git a/tests/fixtures/color/ann_insertion.svg b/tests/fixtures/color/ann_insertion.svg index 1f4b6a24..35d65a05 100644 --- a/tests/fixtures/color/ann_insertion.svg +++ b/tests/fixtures/color/ann_insertion.svg @@ -1,4 +1,4 @@ -<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg"> +<svg width="740px" height="110px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -28,8 +28,6 @@ <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1 |</tspan><tspan> asf</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">'d' belongs here</tspan> -</tspan> - <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> </text> diff --git a/tests/fixtures/color/ann_multiline.svg b/tests/fixtures/color/ann_multiline.svg index 91306914..949eddcb 100644 --- a/tests/fixtures/color/ann_multiline.svg +++ b/tests/fixtures/color/ann_multiline.svg @@ -1,4 +1,4 @@ -<svg width="740px" height="182px" xmlns="http://www.w3.org/2000/svg"> +<svg width="740px" height="164px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -34,8 +34,6 @@ <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">141 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> } = body[body_idx]</tspan> </tspan> <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|_________________________^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">missing fields `lineno`, `content`</tspan> -</tspan> - <tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> </text> diff --git a/tests/fixtures/color/ann_multiline2.svg b/tests/fixtures/color/ann_multiline2.svg index 97948a47..064826a3 100644 --- a/tests/fixtures/color/ann_multiline2.svg +++ b/tests/fixtures/color/ann_multiline2.svg @@ -1,4 +1,4 @@ -<svg width="740px" height="164px" xmlns="http://www.w3.org/2000/svg"> +<svg width="740px" height="146px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -32,8 +32,6 @@ <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">27 |</tspan><tspan> of an edge case of an annotation overflowing</tspan> </tspan> <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">28 |</tspan><tspan> to exactly one character on next line.</tspan> -</tspan> - <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> </text> diff --git a/tests/fixtures/color/ann_removed_nl.svg b/tests/fixtures/color/ann_removed_nl.svg index bb12aecf..b0fb8b6c 100644 --- a/tests/fixtures/color/ann_removed_nl.svg +++ b/tests/fixtures/color/ann_removed_nl.svg @@ -1,4 +1,4 @@ -<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg"> +<svg width="740px" height="110px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -28,8 +28,6 @@ <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1 |</tspan><tspan> asdf</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan> -</tspan> - <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> </text> diff --git a/tests/fixtures/color/ensure-emoji-highlight-width.svg b/tests/fixtures/color/ensure-emoji-highlight-width.svg index e5646e61..077bca20 100644 --- a/tests/fixtures/color/ensure-emoji-highlight-width.svg +++ b/tests/fixtures/color/ensure-emoji-highlight-width.svg @@ -1,4 +1,4 @@ -<svg width="1356px" height="128px" xmlns="http://www.w3.org/2000/svg"> +<svg width="1356px" height="110px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -28,8 +28,6 @@ <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">7 |</tspan><tspan> "haha this isn't a valid name 🐛" = { package = "libc", version = "0.1" }</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</tspan> -</tspan> - <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> </text> diff --git a/tests/fixtures/color/fold_ann_multiline.svg b/tests/fixtures/color/fold_ann_multiline.svg index 6a89c4f7..39323c5f 100644 --- a/tests/fixtures/color/fold_ann_multiline.svg +++ b/tests/fixtures/color/fold_ann_multiline.svg @@ -1,4 +1,4 @@ -<svg width="869px" height="236px" xmlns="http://www.w3.org/2000/svg"> +<svg width="869px" height="218px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -41,8 +41,6 @@ <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">72 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan> </tspan> <tspan x="10px" y="208px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|_____^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected enum `std::option::Option`, found ()</tspan> -</tspan> - <tspan x="10px" y="226px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> </text> diff --git a/tests/fixtures/color/fold_bad_origin_line.svg b/tests/fixtures/color/fold_bad_origin_line.svg index 23f1b646..c7fb916f 100644 --- a/tests/fixtures/color/fold_bad_origin_line.svg +++ b/tests/fixtures/color/fold_bad_origin_line.svg @@ -1,4 +1,4 @@ -<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg"> +<svg width="740px" height="110px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -29,8 +29,6 @@ <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">3 |</tspan><tspan> invalid syntax</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">--------------</tspan><tspan> </tspan><tspan class="fg-yellow bold">error here</tspan> -</tspan> - <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> </text> diff --git a/tests/fixtures/color/fold_leading.svg b/tests/fixtures/color/fold_leading.svg index e69965e9..0ff7d158 100644 --- a/tests/fixtures/color/fold_leading.svg +++ b/tests/fixtures/color/fold_leading.svg @@ -1,4 +1,4 @@ -<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg"> +<svg width="740px" height="110px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -28,8 +28,6 @@ <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">11 |</tspan><tspan> workspace = 20</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^</tspan> -</tspan> - <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> </text> diff --git a/tests/fixtures/color/fold_trailing.svg b/tests/fixtures/color/fold_trailing.svg index 41bf7c79..ca9de402 100644 --- a/tests/fixtures/color/fold_trailing.svg +++ b/tests/fixtures/color/fold_trailing.svg @@ -1,4 +1,4 @@ -<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg"> +<svg width="740px" height="110px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -28,8 +28,6 @@ <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1 |</tspan><tspan> lints = 20</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^</tspan> -</tspan> - <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> </text> diff --git a/tests/fixtures/color/issue_9.svg b/tests/fixtures/color/issue_9.svg index 80b891e0..05e421e1 100644 --- a/tests/fixtures/color/issue_9.svg +++ b/tests/fixtures/color/issue_9.svg @@ -1,4 +1,4 @@ -<svg width="911px" height="236px" xmlns="http://www.w3.org/2000/svg"> +<svg width="911px" height="182px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -30,19 +30,13 @@ </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait</tspan> </tspan> - <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> + <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">7 |</tspan><tspan> let y = x;</tspan> </tspan> - <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">7 |</tspan><tspan> let y = x;</tspan> + <tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">value moved here</tspan> </tspan> - <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">value moved here</tspan> + <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">9 |</tspan><tspan> x;</tspan> </tspan> - <tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> -</tspan> - <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">9 |</tspan><tspan> x;</tspan> -</tspan> - <tspan x="10px" y="208px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">value used here after move</tspan> -</tspan> - <tspan x="10px" y="226px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> + <tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">value used here after move</tspan> </tspan> </text> diff --git a/tests/fixtures/color/multiple_annotations.svg b/tests/fixtures/color/multiple_annotations.svg index 84f47495..fc6fe68e 100644 --- a/tests/fixtures/color/multiple_annotations.svg +++ b/tests/fixtures/color/multiple_annotations.svg @@ -1,4 +1,4 @@ -<svg width="768px" height="290px" xmlns="http://www.w3.org/2000/svg"> +<svg width="768px" height="272px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -46,8 +46,6 @@ <tspan x="10px" y="244px"><tspan class="fg-bright-blue bold">103 |</tspan><tspan> }</tspan> </tspan> <tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">104 |</tspan><tspan> }</tspan> -</tspan> - <tspan x="10px" y="280px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> </text> diff --git a/tests/fixtures/color/simple.svg b/tests/fixtures/color/simple.svg index 7b92d238..210cf341 100644 --- a/tests/fixtures/color/simple.svg +++ b/tests/fixtures/color/simple.svg @@ -1,4 +1,4 @@ -<svg width="740px" height="182px" xmlns="http://www.w3.org/2000/svg"> +<svg width="740px" height="164px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -35,8 +35,6 @@ <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">171 |</tspan><tspan> for line in &self.body {</tspan> </tspan> <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">unexpected token</tspan> -</tspan> - <tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> </text> diff --git a/tests/fixtures/color/strip_line.svg b/tests/fixtures/color/strip_line.svg index 9da24fe3..f86250ab 100644 --- a/tests/fixtures/color/strip_line.svg +++ b/tests/fixtures/color/strip_line.svg @@ -1,4 +1,4 @@ -<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg"> +<svg width="740px" height="110px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -28,8 +28,6 @@ <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">LL |</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">...</tspan><tspan> let _: () = 42;</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected (), found integer</tspan> -</tspan> - <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> </text> diff --git a/tests/fixtures/color/strip_line_char.svg b/tests/fixtures/color/strip_line_char.svg index cbafc789..ed9ba736 100644 --- a/tests/fixtures/color/strip_line_char.svg +++ b/tests/fixtures/color/strip_line_char.svg @@ -1,4 +1,4 @@ -<svg width="740px" height="128px" xmlns="http://www.w3.org/2000/svg"> +<svg width="740px" height="110px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -28,8 +28,6 @@ <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">LL |</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">...</tspan><tspan> let _: () = 42ñ</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected (), found integer</tspan> -</tspan> - <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> </text> diff --git a/tests/fixtures/color/strip_line_non_ws.svg b/tests/fixtures/color/strip_line_non_ws.svg index e4f8a856..251dfcae 100644 --- a/tests/fixtures/color/strip_line_non_ws.svg +++ b/tests/fixtures/color/strip_line_non_ws.svg @@ -1,4 +1,4 @@ -<svg width="1196px" height="164px" xmlns="http://www.w3.org/2000/svg"> +<svg width="1196px" height="146px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -32,8 +32,6 @@ <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan> </tspan> <tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected due to this</tspan> -</tspan> - <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> </text> diff --git a/tests/formatter.rs b/tests/formatter.rs index 6faab76d..7aa37d2b 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -16,7 +16,6 @@ error: oops | 2 | Second oops line | ^^^^ oops - | "#]]; let renderer = Renderer::plain(); @@ -37,7 +36,6 @@ error | 1 | こんにちは、世界 | ^^^^ world - | "#]]; let renderer = Renderer::plain(); @@ -60,7 +58,6 @@ error | _____^ 2 | | ございます | |______^ Good morning - | "#]]; let renderer = Renderer::plain(); @@ -84,7 +81,6 @@ error | ^^^^^^ Sushi1 2 | 食べたい🍣 | ---- note: Sushi2 - | "#]]; let renderer = Renderer::plain(); @@ -105,7 +101,6 @@ error | 1 | こんにちは、新しいWorld! | ^^^^^^^^^^^ New world - | "#]]; let renderer = Renderer::plain(); @@ -133,7 +128,6 @@ error | 5402 | This is line 1 5403 | This is line 2 - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -152,11 +146,9 @@ error --> file1.rs | 5402 | This is slice 1 - | ::: file2.rs | 2 | This is slice 2 - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -180,7 +172,6 @@ error 5402 | This is line 1 5403 | This is line 2 | -- info: Test annotation - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -224,7 +215,6 @@ error | 56 | This is an example 57 | of content lines - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -243,7 +233,6 @@ error | 1 | tests | ----- help: Example string - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -266,7 +255,6 @@ error | | | help: Example string | help: Second line - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -281,7 +269,6 @@ fn test_only_source() { error --> file.rs | - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -300,7 +287,6 @@ LL | This is an example LL | of content lines LL | LL | abc - | "#]]; let renderer = Renderer::plain().anonymized_line_numbers(true); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -323,7 +309,6 @@ error: dummy 4 | / bar 5 | | baz | |___^ - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -349,7 +334,6 @@ error 3 | / a" 4 | | // ... | |_______^ - | "#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -371,7 +355,7 @@ error 3 | a | ^ 4 | b - |"#]]; +"#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); assert_data_eq!(renderer.render(input).to_string(), expected); } @@ -392,7 +376,7 @@ error 3 | a | ^ 4 | b - |"#]]; +"#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); assert_data_eq!(renderer.render(input).to_string(), expected); } @@ -413,7 +397,6 @@ error | ^^ 2 | にちは 3 | 世界 - | "#]]; let renderer = Renderer::plain(); @@ -436,7 +419,7 @@ error 3 | a | ^ 4 | b - |"#]]; +"#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); assert_data_eq!(renderer.render(input).to_string(), expected); } @@ -457,7 +440,7 @@ error 3 | a | ^ 4 | b - |"#]]; +"#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); assert_data_eq!(renderer.render(input).to_string(), expected); } @@ -478,7 +461,7 @@ error 3 | a | ^ 4 | b - |"#]]; +"#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); assert_data_eq!(renderer.render(input).to_string(), expected); } @@ -499,7 +482,7 @@ error 3 | a | ^ 4 | b - |"#]]; +"#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); assert_data_eq!(renderer.render(input).to_string(), expected); } @@ -520,7 +503,6 @@ error | ^ 2 | にちは 3 | 世界 - | "#]]; let renderer = Renderer::plain(); @@ -544,7 +526,7 @@ error | __^ 4 | | b | |_^ - |"#]]; +"#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); assert_data_eq!(renderer.render(input).to_string(), expected); } @@ -566,7 +548,7 @@ error | __^ 4 | | b | |_^ - |"#]]; +"#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); assert_data_eq!(renderer.render(input).to_string(), expected); } @@ -588,7 +570,7 @@ error | __^ 4 | | b | |_^ - |"#]]; +"#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); assert_data_eq!(renderer.render(input).to_string(), expected); } @@ -610,7 +592,6 @@ error 2 | | にちは | |__^ 3 | 世界 - | "#]]; let renderer = Renderer::plain(); @@ -635,7 +616,6 @@ error 4 | | b | |__^ 5 | c - | "#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -659,7 +639,6 @@ error 4 | | b | |__^ 5 | c - | "#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -683,7 +662,6 @@ error 4 | | b | |__^ 5 | c - | "#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -706,7 +684,6 @@ error | __^ 4 | | b | |__^ - | "#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -729,7 +706,6 @@ error | ___^ 4 | | に | |___^ - | "#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -761,7 +737,6 @@ error: unused optional dependency | ^^^ --------------- info: This should also be long but not too long | | | I need this to be really long so I can test overlaps - | "#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -799,7 +774,6 @@ error: unused optional dependency 6 | | so is this 7 | | bar = { version = "0.1.0", optional = true } | |__________________________________________^ I need this to be really long so I can test overlaps - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -845,7 +819,6 @@ error: unused optional dependency | ||_________________________^________________^ I need this to be really long so I can test overlaps | |__________________________| | I need this to be really long so I can test overlaps - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -900,7 +873,6 @@ error: unused optional dependency | | I need this to be really long so I can test overlaps 8 | | this is another line | |____^ I need this to be really long so I can test overlaps - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -925,7 +897,6 @@ error: title 3 | ccc | ^^^ annotation 4 | ddd - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -950,7 +921,6 @@ error: title 3 | ccc | ^^ annotation 4 | ddd - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index 54b73211..db17bf4a 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -28,7 +28,6 @@ error: foo | __________^ 3 | | } | |_^ test - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -59,7 +58,6 @@ error: foo 4 | | 5 | | } | |___^ test - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -98,7 +96,6 @@ error: foo | ||____^__- `Y` is a good letter too | |_____| | `X` is a good letter - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -135,7 +132,6 @@ error: foo | ||____-__^ `X` is a good letter | |____| | `Y` is a good letter too - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -175,7 +171,6 @@ error: foo | ||____^ `X` is a good letter 6 | | X3 Y3 Z3 | |____- `Y` is a good letter too - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -217,7 +212,6 @@ error: foo | ||_____|__| | |______| `Y` is a good letter too | `X` is a good letter - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -259,7 +253,6 @@ error: foo | `X` is a good letter | `Y` is a good letter too | `Z` label - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -304,7 +297,6 @@ error: foo | | 6 | | X3 Y3 Z3 | |_______- `Z` - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -343,7 +335,6 @@ error: foo | ______- 6 | | X3 Y3 Z3 | |__________- `Y` is a good letter too - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -384,7 +375,6 @@ error: foo 5 | | X2 Y2 Z2 6 | | X3 Y3 Z3 | |__________- `Y` is a good letter too - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -412,7 +402,6 @@ error: foo | 3 | a { b { c } d } | ----^^^^-^^-- `a` is a good letter - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -439,7 +428,6 @@ error: foo | 3 | a { b { c } d } | ^^^^-------^^ `a` is a good letter - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -469,7 +457,6 @@ error: foo | ----^^^^-^^-- | | | `b` is a good letter - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -498,7 +485,6 @@ error: foo | ^^^^-------^^ | | | `b` is a good letter - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -527,7 +513,6 @@ error: foo | ^^^^---- | | | `a` is a good letter - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -554,7 +539,6 @@ error: foo | 3 | a { b { c } d } | ^^^^-------^^ - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -582,7 +566,6 @@ error: foo | 3 | a { b { c } d } | ----^^^^-^^-- - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -612,7 +595,6 @@ error: foo | | | | | `b` is a good letter | `a` is a good letter - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -638,7 +620,6 @@ error: foo | 3 | a { b { c } d } | ^^^^^^^^^^^^^ `a` is a good letter - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -664,7 +645,6 @@ error: foo | 3 | a { b { c } d } | ^^^^^^^^^^^^^ - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -717,7 +697,6 @@ error: foo 15 | | X2 Y2 Z2 16 | | X3 Y3 Z3 | |__________- `Y` is a good letter too - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); @@ -776,7 +755,6 @@ error: foo 15 | | 10 16 | | X3 Y3 Z3 | |________^ `Y` is a good letter - | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); From 41825aadd7c66663cc320206637d333fb4651ba7 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 12 Feb 2025 09:37:49 -0600 Subject: [PATCH 297/455] style: Make clippy happy --- src/renderer/display_list.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index 460f985b..48fbdd08 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -324,7 +324,7 @@ impl DisplaySet<'_> { } let text = normalize_whitespace(text); - let line_len = text.as_bytes().len(); + let line_len = text.len(); let left = self.margin.left(line_len); let right = self.margin.right(line_len); From 743e4b83e75d65b2cb909aa85b4219c0d82a3acd Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 20 Feb 2025 09:03:29 -0600 Subject: [PATCH 298/455] perf: Optimize release builds --- Cargo.toml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 7e38ce35..6eb3113a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,6 +86,15 @@ verbose_file_reads = "warn" wildcard_imports = "warn" zero_sized_map_values = "warn" +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" +codegen-units = 1 +lto = true +debug = "line-tables-only" + [package] name = "PROJECT" version = "0.0.1" From 5ff020156ae6a49c522f993d6db282340a34b226 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 20 Feb 2025 18:46:46 +0000 Subject: [PATCH 299/455] chore(deps): Update Rust Stable to v1.85 (#180) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2073635e..83a404c4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -104,7 +104,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.84" # STABLE + toolchain: "1.85" # STABLE - uses: Swatinem/rust-cache@v2 - name: Check documentation env: @@ -119,7 +119,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.84" # STABLE + toolchain: "1.85" # STABLE components: rustfmt - uses: Swatinem/rust-cache@v2 - name: Check formatting @@ -135,7 +135,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.84" # STABLE + toolchain: "1.85" # STABLE components: clippy - uses: Swatinem/rust-cache@v2 - name: Install SARIF tools From 5ff8d2c99b632deab5073f9fbf48b8f5650b6a92 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 1 Mar 2025 01:26:04 +0000 Subject: [PATCH 300/455] chore(deps): Update compatible (dev) (#181) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index acadd7b2..a3b2f068 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -543,18 +543,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.217" +version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" dependencies = [ "proc-macro2", "quote", @@ -673,9 +673,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ "serde", "serde_spanned", @@ -694,9 +694,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.22" +version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ "indexmap", "serde", @@ -924,9 +924,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.20" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" dependencies = [ "memchr", ] From a20d3264785d5700c5aa6d076d6f5f310b9afd8b Mon Sep 17 00:00:00 2001 From: Dhruv Manilawala <dhruvmanila@gmail.com> Date: Mon, 3 Mar 2025 13:06:11 +0530 Subject: [PATCH 301/455] chore: Fix typos --- src/renderer/display_list.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index 48fbdd08..53184f2d 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -1230,25 +1230,25 @@ fn fold_body(body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> { const INNER_UNFOLD_SIZE: usize = INNER_CONTEXT * 2 + 1; let mut lines = vec![]; - let mut unhighlighed_lines = vec![]; + let mut unhighlighted_lines = vec![]; for line in body { match &line { DisplayLine::Source { annotations, .. } => { if annotations.is_empty() { - unhighlighed_lines.push(line); + unhighlighted_lines.push(line); } else { if lines.is_empty() { - // Ignore leading unhighlighed lines - unhighlighed_lines.clear(); + // Ignore leading unhighlighted lines + unhighlighted_lines.clear(); } - match unhighlighed_lines.len() { + match unhighlighted_lines.len() { 0 => {} n if n <= INNER_UNFOLD_SIZE => { // Rather than render `...`, don't fold - lines.append(&mut unhighlighed_lines); + lines.append(&mut unhighlighted_lines); } _ => { - lines.extend(unhighlighed_lines.drain(..INNER_CONTEXT)); + lines.extend(unhighlighted_lines.drain(..INNER_CONTEXT)); let inline_marks = lines .last() .and_then(|line| { @@ -1266,16 +1266,16 @@ fn fold_body(body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> { lines.push(DisplayLine::Fold { inline_marks: inline_marks.clone(), }); - unhighlighed_lines - .drain(..unhighlighed_lines.len().saturating_sub(INNER_CONTEXT)); - lines.append(&mut unhighlighed_lines); + unhighlighted_lines + .drain(..unhighlighted_lines.len().saturating_sub(INNER_CONTEXT)); + lines.append(&mut unhighlighted_lines); } } lines.push(line); } } _ => { - unhighlighed_lines.push(line); + unhighlighted_lines.push(line); } } } From 9a2e0cc0952b7e75fbe113fc5e1e4dc09a2c8941 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 5 Mar 2025 11:48:22 -0600 Subject: [PATCH 302/455] chore: Ensure MSRV-aware resolver is used --- .cargo/config.toml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 00000000..4a6a1abd --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[resolver] +incompatible-rust-versions = "fallback" From f4ac56d27673ecaea9bcfef20e72a9e51be05502 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Fri, 14 Mar 2025 10:48:58 -0500 Subject: [PATCH 303/455] chore: Lint for unnameable_types like unreachable_pub --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 6eb3113a..bc8afad3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ include = [ [workspace.lints.rust] rust_2018_idioms = { level = "warn", priority = -1 } +unnameable_types = "warn" unreachable_pub = "warn" unsafe_op_in_unsafe_fn = "warn" unused_lifetimes = "warn" From eb151419e93ec776f50795b0a5f81e050f7d2dc2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 1 Apr 2025 01:36:45 +0000 Subject: [PATCH 304/455] chore(deps): Update Rust crate serde to v1.0.219 (#193) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a3b2f068..c8c4d67b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -543,18 +543,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.218" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", From 5e302a9f188fd003521f0f74dac6a7b401dc3600 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 3 Apr 2025 12:17:10 +0000 Subject: [PATCH 305/455] chore(deps): Update Rust Stable to v1.86 (#194) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 83a404c4..aa6d8bbd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -104,7 +104,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.85" # STABLE + toolchain: "1.86" # STABLE - uses: Swatinem/rust-cache@v2 - name: Check documentation env: @@ -119,7 +119,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.85" # STABLE + toolchain: "1.86" # STABLE components: rustfmt - uses: Swatinem/rust-cache@v2 - name: Check formatting @@ -135,7 +135,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.85" # STABLE + toolchain: "1.86" # STABLE components: clippy - uses: Swatinem/rust-cache@v2 - name: Install SARIF tools From 77d28b23543fd98295b2c43696cc66ce5721e05e Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 3 Apr 2025 12:08:52 -0500 Subject: [PATCH 306/455] style: Extra continues can communicate intent --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index bc8afad3..92d8817d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,7 +62,7 @@ lossy_float_literal = "warn" macro_use_imports = "warn" mem_forget = "warn" mutex_integer = "warn" -needless_continue = "warn" +needless_continue = "allow" needless_for_each = "warn" negative_feature_names = "warn" path_buf_push_overwrite = "warn" From 06061d8558c3d3cee369c7d81b2f4a7a0ea18f1e Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 10 Apr 2025 11:35:50 -0500 Subject: [PATCH 307/455] chore(ci): Report more results --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9fb9591c..7bcbd419 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,7 +64,7 @@ jobs: - uses: Swatinem/rust-cache@v2 - uses: taiki-e/install-action@cargo-hack - name: Default features - run: cargo hack check --feature-powerset --locked --rust-version --ignore-private --workspace --all-targets + run: cargo hack check --feature-powerset --locked --rust-version --ignore-private --workspace --all-targets --keep-going minimal-versions: name: Minimal versions runs-on: ubuntu-latest @@ -82,7 +82,7 @@ jobs: - name: Downgrade dependencies to minimal versions run: cargo +nightly generate-lockfile -Z minimal-versions - name: Compile with minimal versions - run: cargo +stable check --workspace --all-features --locked + run: cargo +stable check --workspace --all-features --locked --keep-going lockfile: runs-on: ubuntu-latest steps: @@ -109,7 +109,7 @@ jobs: - name: Check documentation env: RUSTDOCFLAGS: -D warnings - run: cargo doc --workspace --all-features --no-deps --document-private-items + run: cargo doc --workspace --all-features --no-deps --document-private-items --keep-going rustfmt: name: rustfmt runs-on: ubuntu-latest @@ -155,7 +155,7 @@ jobs: sarif_file: clippy-results.sarif wait-for-processing: true - name: Report status - run: cargo clippy --workspace --all-features --all-targets -- -D warnings --allow deprecated + run: cargo clippy --workspace --all-features --all-targets --keep-going -- -D warnings --allow deprecated coverage: name: Coverage runs-on: ubuntu-latest From a2ce03e1b5672eb8477805a474e31ebada1cb749 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 15 Apr 2025 04:26:32 -0600 Subject: [PATCH 308/455] test: Add more tests from rustc --- tests/rustc_tests.rs | 947 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 947 insertions(+) diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index db17bf4a..f10f479b 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -759,3 +759,950 @@ error: foo let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); } + +#[test] +fn issue_91334() { + let source = r#"// Regression test for the ICE described in issue #91334. + +//@ error-pattern: this file contains an unclosed delimiter + +#![feature(coroutines)] + +fn f(){||yield(((){), +"#; + let input = Level::Error + .title("this file contains an unclosed delimiter") + .snippet( + Snippet::source(source) + .line_start(1) + .origin("$DIR/issue-91334.rs") + .fold(true) + .annotation(Level::Warning.span(151..152).label("unclosed delimiter")) + .annotation(Level::Warning.span(159..160).label("unclosed delimiter")) + .annotation( + Level::Warning + .span(164..164) + .label("missing open `(` for this delimiter"), + ) + .annotation(Level::Error.span(167..167)), + ); + let expected = str![[r#" +error: this file contains an unclosed delimiter + --> $DIR/issue-91334.rs:7:7 + | +LL | fn f(){||yield(((){), + | - - - ^ + | | | | + | | | missing open `(` for this delimiter + | | unclosed delimiter + | unclosed delimiter +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn issue_114529_illegal_break_with_value() { + // tests/ui/typeck/issue-114529-illegal-break-with-value.rs + let source = r#"// Regression test for issue #114529 +// Tests that we do not ICE during const eval for a +// break-with-value in contexts where it is illegal + +#[allow(while_true)] +fn main() { + [(); { + while true { + break 9; //~ ERROR `break` with value from a `while` loop + }; + 51 + }]; + + [(); { + while let Some(v) = Some(9) { + break v; //~ ERROR `break` with value from a `while` loop + }; + 51 + }]; + + while true { + break (|| { //~ ERROR `break` with value from a `while` loop + let local = 9; + }); + } +} +"#; + let input = Level::Error + .title("`break` with value from a `while` loop") + .id("E0571") + .snippet( + Snippet::source(source) + .line_start(1) + .origin("$DIR/issue-114529-illegal-break-with-value.rs") + .fold(true) + .annotation( + Level::Error + .span(483..581) + .label("can only break with a value inside `loop` or breakable block"), + ) + .annotation( + Level::Warning + .span(462..472) + .label("you can't `break` with a value in a `while` loop"), + ), + ) + .footer( + Level::Help + .title("use `break` on its own without a value inside this `while` loop") + .snippet( + Snippet::source(source) + .line_start(1) + .origin("$DIR/issue-114529-illegal-break-with-value.rs") + .fold(true) + .annotation(Level::Help.span(483..581).label("break")), + ), + ); + let expected = str![[r#" +error[E0571]: `break` with value from a `while` loop + --> $DIR/issue-114529-illegal-break-with-value.rs:22:9 + | +LL | while true { + | ---------- you can't `break` with a value in a `while` loop +LL | / break (|| { //~ ERROR `break` with value from a `while` loop +LL | | let local = 9; +LL | | }); + | |__________^ can only break with a value inside `loop` or breakable block +help: use `break` on its own without a value inside this `while` loop + --> $DIR/issue-114529-illegal-break-with-value.rs:22:9 + | +LL | / break (|| { //~ ERROR `break` with value from a `while` loop +LL | | let local = 9; +LL | | }); + | |__________- help: break +"#]]; + + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn primitive_reprs_should_have_correct_length() { + // tests/ui/transmutability/enums/repr/primitive_reprs_should_have_correct_length.rs + let source = r#"//! An enum with a primitive repr should have exactly the size of that primitive. + +#![crate_type = "lib"] +#![feature(transmutability)] +#![allow(dead_code)] + +mod assert { + use std::mem::{Assume, TransmuteFrom}; + + pub fn is_transmutable<Src, Dst>() + where + Dst: TransmuteFrom<Src, { + Assume { + alignment: true, + lifetimes: true, + safety: true, + validity: true, + } + }> + {} +} + +#[repr(C)] +struct Zst; + +#[derive(Clone, Copy)] +#[repr(i8)] enum V0i8 { V } +#[repr(u8)] enum V0u8 { V } +#[repr(i16)] enum V0i16 { V } +#[repr(u16)] enum V0u16 { V } +#[repr(i32)] enum V0i32 { V } +#[repr(u32)] enum V0u32 { V } +#[repr(i64)] enum V0i64 { V } +#[repr(u64)] enum V0u64 { V } +#[repr(isize)] enum V0isize { V } +#[repr(usize)] enum V0usize { V } + +fn n8() { + type Smaller = Zst; + type Analog = u8; + type Larger = u16; + + fn i_should_have_correct_length() { + type Current = V0i8; + + assert::is_transmutable::<Smaller, Current>(); //~ ERROR cannot be safely transmuted + assert::is_transmutable::<Current, Analog>(); + assert::is_transmutable::<Current, Larger>(); //~ ERROR cannot be safely transmuted + } + + fn u_should_have_correct_length() { + type Current = V0u8; + + assert::is_transmutable::<Smaller, Current>(); //~ ERROR cannot be safely transmuted + assert::is_transmutable::<Current, Analog>(); + assert::is_transmutable::<Current, Larger>(); //~ ERROR cannot be safely transmuted + } +} + +fn n16() { + type Smaller = u8; + type Analog = u16; + type Larger = u32; + + fn i_should_have_correct_length() { + type Current = V0i16; + + assert::is_transmutable::<Smaller, Current>(); //~ ERROR cannot be safely transmuted + assert::is_transmutable::<Current, Analog>(); + assert::is_transmutable::<Current, Larger>(); //~ ERROR cannot be safely transmuted + } + + fn u_should_have_correct_length() { + type Current = V0u16; + + assert::is_transmutable::<Smaller, Current>(); //~ ERROR cannot be safely transmuted + assert::is_transmutable::<Current, Analog>(); + assert::is_transmutable::<Current, Larger>(); //~ ERROR cannot be safely transmuted + } +} + +fn n32() { + type Smaller = u16; + type Analog = u32; + type Larger = u64; + + fn i_should_have_correct_length() { + type Current = V0i32; + + assert::is_transmutable::<Smaller, Current>(); //~ ERROR cannot be safely transmuted + assert::is_transmutable::<Current, Analog>(); + assert::is_transmutable::<Current, Larger>(); //~ ERROR cannot be safely transmuted + } + + fn u_should_have_correct_length() { + type Current = V0u32; + + assert::is_transmutable::<Smaller, Current>(); //~ ERROR cannot be safely transmuted + assert::is_transmutable::<Current, Analog>(); + assert::is_transmutable::<Current, Larger>(); //~ ERROR cannot be safely transmuted + } +} + +fn n64() { + type Smaller = u32; + type Analog = u64; + type Larger = u128; + + fn i_should_have_correct_length() { + type Current = V0i64; + + assert::is_transmutable::<Smaller, Current>(); //~ ERROR cannot be safely transmuted + assert::is_transmutable::<Current, Analog>(); + assert::is_transmutable::<Current, Larger>(); //~ ERROR cannot be safely transmuted + } + + fn u_should_have_correct_length() { + type Current = V0u64; + + assert::is_transmutable::<Smaller, Current>(); //~ ERROR cannot be safely transmuted + assert::is_transmutable::<Current, Analog>(); + assert::is_transmutable::<Current, Larger>(); //~ ERROR cannot be safely transmuted + } +} + +fn nsize() { + type Smaller = u8; + type Analog = usize; + type Larger = [usize; 2]; + + fn i_should_have_correct_length() { + type Current = V0isize; + + assert::is_transmutable::<Smaller, Current>(); //~ ERROR cannot be safely transmuted + assert::is_transmutable::<Current, Analog>(); + assert::is_transmutable::<Current, Larger>(); //~ ERROR cannot be safely transmuted + } + + fn u_should_have_correct_length() { + type Current = V0usize; + + assert::is_transmutable::<Smaller, Current>(); //~ ERROR cannot be safely transmuted + assert::is_transmutable::<Current, Analog>(); + assert::is_transmutable::<Current, Larger>(); //~ ERROR cannot be safely transmuted + } +} +"#; + let input = Level::Error + .title("`V0usize` cannot be safely transmuted into `[usize; 2]`") + .id("E0277") + .snippet( + Snippet::source(source) + .line_start(1) + .origin("$DIR/primitive_reprs_should_have_correct_length.rs") + .fold(true) + .annotation( + Level::Error + .span(4375..4381) + .label("the size of `V0usize` is smaller than the size of `[usize; 2]`"), + ), + ) + .footer( + Level::Note + .title("required by a bound in `is_transmutable`") + .snippet( + Snippet::source(source) + .line_start(1) + .origin("$DIR/primitive_reprs_should_have_correct_length.rs") + .fold(true) + .annotation( + Level::Note + .span(225..240) + .label("required by a bound in this function"), + ) + .annotation( + Level::Error + .span(276..470) + .label("required by this bound in `is_transmutable`"), + ), + ), + ); + let expected = str![[r#" +error[E0277]: `V0usize` cannot be safely transmuted into `[usize; 2]` + --> $DIR/primitive_reprs_should_have_correct_length.rs:144:44 + | +LL | assert::is_transmutable::<Current, Larger>(); //~ ERROR cannot be safely transmuted + | ^^^^^^ the size of `V0usize` is smaller than the size of `[usize; 2]` +note: required by a bound in `is_transmutable` + --> $DIR/primitive_reprs_should_have_correct_length.rs:10:12 + | +LL | pub fn is_transmutable<Src, Dst>() + | --------------- note: required by a bound in this function +LL | where +LL | Dst: TransmuteFrom<Src, { + | ______________^ +LL | | Assume { +... | +LL | | } +LL | | }> + | |__________^ required by this bound in `is_transmutable` +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn align_fail() { + // tests/ui/transmutability/alignment/align-fail.rs + let source = r#"//@ check-fail +#![feature(transmutability)] + +mod assert { + use std::mem::{Assume, TransmuteFrom}; + + pub fn is_maybe_transmutable<Src, Dst>() + where + Dst: TransmuteFrom<Src, { + Assume { + alignment: false, + lifetimes: true, + safety: true, + validity: true, + } + }> + {} +} + +fn main() { + assert::is_maybe_transmutable::<&'static [u8; 0], &'static [u16; 0]>(); //~ ERROR `&[u8; 0]` cannot be safely transmuted into `&[u16; 0]` +} +"#; + let input = Level::Error + .title("`&[u8; 0]` cannot be safely transmuted into `&[u16; 0]`").id("E0277") + .snippet( + Snippet::source(source) + .line_start(1) + .fold(true) + .origin("$DIR/align-fail.rs") + .annotation( + Level::Error + .span(442..459) + .label("the minimum alignment of `&[u8; 0]` (1) should be greater than that of `&[u16; 0]` (2)") + ), + ); + let expected = str![[r#" +error[E0277]: `&[u8; 0]` cannot be safely transmuted into `&[u16; 0]` + --> $DIR/align-fail.rs:21:55 + | +LL | ...ic [u8; 0], &'static [u16; 0]>(); //~ ERROR `&[u8; 0]` cannot be safely transmuted into `&[u16; 0]` + | ^^^^^^^^^^^^^^^^^ the minimum alignment of `&[u8; 0]` (1) should be greater than that of `&[u16; 0]` (2) +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn missing_semicolon() { + // tests/ui/suggestions/missing-semicolon.rs + let source = r#"//@ run-rustfix +#![allow(dead_code, unused_variables, path_statements)] +fn a() { + let x = 5; + let y = x //~ ERROR expected function + () //~ ERROR expected `;`, found `}` +} + +fn b() { + let x = 5; + let y = x //~ ERROR expected function + (); +} +fn c() { + let x = 5; + x //~ ERROR expected function + () +} +fn d() { // ok + let x = || (); + x + () +} +fn e() { // ok + let x = || (); + x + (); +} +fn f() + { + let y = 5 //~ ERROR expected function + () //~ ERROR expected `;`, found `}` +} +fn g() { + 5 //~ ERROR expected function + (); +} +fn main() {} +"#; + let input = + Level::Error + .title("expected function, found `{integer}`") + .id("E0618") + .snippet( + Snippet::source(source) + .line_start(1) + .origin("$DIR/missing-semicolon.rs") + .fold(true) + .annotation( + Level::Warning + .span(108..144) + .label("call expression requires function"), + ) + .annotation( + Level::Warning + .span(89..90) + .label("`x` has type `{integer}`"), + ) + .annotation(Level::Warning.span(109..109).label( + "help: consider using a semicolon here to finish the statement: `;`", + )) + .annotation(Level::Error.span(108..109)), + ); + let expected = str![[r#" +error[E0618]: expected function, found `{integer}` + --> $DIR/missing-semicolon.rs:5:13 + | +LL | let x = 5; + | - `x` has type `{integer}` +LL | let y = x //~ ERROR expected function + | ^- help: consider using a semicolon here to finish the statement: `;` + | _____________| + | | +LL | | () //~ ERROR expected `;`, found `}` + | |______- call expression requires function +"#]]; + + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn nested_macro_rules() { + // tests/ui/proc-macro/nested-macro-rules.rs + let source = r#"//@ run-pass +//@ aux-build:nested-macro-rules.rs +//@ proc-macro: test-macros.rs +//@ compile-flags: -Z span-debug -Z macro-backtrace +//@ edition:2018 + +#![no_std] // Don't load unnecessary hygiene information from std +#![warn(non_local_definitions)] + +extern crate std; + +extern crate nested_macro_rules; +extern crate test_macros; + +use test_macros::{print_bang, print_attr}; + +use nested_macro_rules::FirstStruct; +struct SecondStruct; + +fn main() { + nested_macro_rules::inner_macro!(print_bang, print_attr); + + nested_macro_rules::outer_macro!(SecondStruct, SecondAttrStruct); + //~^ WARN non-local `macro_rules!` definition + inner_macro!(print_bang, print_attr); +} +"#; + + let aux_source = r#"pub struct FirstStruct; + +#[macro_export] +macro_rules! outer_macro { + ($name:ident, $attr_struct_name:ident) => { + #[macro_export] + macro_rules! inner_macro { + ($bang_macro:ident, $attr_macro:ident) => { + $bang_macro!($name); + #[$attr_macro] struct $attr_struct_name {} + } + } + } +} + +outer_macro!(FirstStruct, FirstAttrStruct); +"#; + let input = Level::Warning + .title("non-local `macro_rules!` definition, `#[macro_export]` macro should be written at top level module") + .snippet( + Snippet::source(aux_source) + .line_start(1) + .origin("$DIR/auxiliary/nested-macro-rules.rs") + .fold(true) + .annotation( + Level::Warning + .span(41..65) + .label("in this expansion of `nested_macro_rules::outer_macro!`"), + ) + .annotation(Level::Error.span(148..350)), + ) + .snippet( + Snippet::source(source) + .line_start(1) + .origin("$DIR/nested-macro-rules.rs") + .fold(true) + .annotation( + Level::Warning + .span(510..574) + .label("in this macro invocation"), + ), + ) + .footer(Level::Help.title("remove the `#[macro_export]` or move this `macro_rules!` outside the of the current function `main`")) + .footer(Level::Note.title("a `macro_rules!` definition is non-local if it is nested inside an item and has a `#[macro_export]` attribute")) + .footer( + Level::Note.title("the lint level is defined here").snippet( + Snippet::source(source) + .line_start(1) + .origin("$DIR/nested-macro-rules.rs") + .fold(true) + .annotation(Level::Error.span(224..245)), + ), + ); + let expected = str![[r#" +warning: non-local `macro_rules!` definition, `#[macro_export]` macro should be written at top level module + --> $DIR/auxiliary/nested-macro-rules.rs:4:1 + | +LL | macro_rules! outer_macro { + | ------------------------ in this expansion of `nested_macro_rules::outer_macro!` +... +LL | / macro_rules! inner_macro { +LL | | ($bang_macro:ident, $attr_macro:ident) => { +... | +LL | | } +LL | | } + | |_________^ + ::: $DIR/nested-macro-rules.rs:23:5 + | +LL | nested_macro_rules::outer_macro!(SecondStruct, SecondAttrStruct); + | ---------------------------------------------------------------- in this macro invocation + = help: remove the `#[macro_export]` or move this `macro_rules!` outside the of the current function `main` + = note: a `macro_rules!` definition is non-local if it is nested inside an item and has a `#[macro_export]` attribute +note: the lint level is defined here + --> $DIR/nested-macro-rules.rs:8:9 + | +LL | #![warn(non_local_definitions)] + | ^^^^^^^^^^^^^^^^^^^^^ +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn method_on_ambiguous_numeric_type() { + // tests/ui/methods/method-on-ambiguous-numeric-type.rs + let source = r#"//@ aux-build:macro-in-other-crate.rs + +#[macro_use] extern crate macro_in_other_crate; + +macro_rules! local_mac { + ($ident:ident) => { let $ident = 42; } +} +macro_rules! local_mac_tt { + ($tt:tt) => { let $tt = 42; } +} + +fn main() { + let x = 2.0.neg(); + //~^ ERROR can't call method `neg` on ambiguous numeric type `{float}` + + let y = 2.0; + let x = y.neg(); + //~^ ERROR can't call method `neg` on ambiguous numeric type `{float}` + println!("{:?}", x); + + for i in 0..100 { + println!("{}", i.pow(2)); + //~^ ERROR can't call method `pow` on ambiguous numeric type `{integer}` + } + + local_mac!(local_bar); + local_bar.pow(2); + //~^ ERROR can't call method `pow` on ambiguous numeric type `{integer}` + + local_mac_tt!(local_bar_tt); + local_bar_tt.pow(2); + //~^ ERROR can't call method `pow` on ambiguous numeric type `{integer}` +} + +fn qux() { + mac!(bar); + bar.pow(2); + //~^ ERROR can't call method `pow` on ambiguous numeric type `{integer}` +} +"#; + + let aux_source = r#"#[macro_export] +macro_rules! mac { + ($ident:ident) => { let $ident = 42; } +} + +#[macro_export] +macro_rules! inline { + () => () +} +"#; + let input = Level::Error + .title("can't call method `pow` on ambiguous numeric type `{integer}`") + .id("E0689") + .snippet( + Snippet::source(source) + .line_start(1) + .origin("$DIR/method-on-ambiguous-numeric-type.rs") + .fold(true) + .annotation(Level::Error.span(916..919)), + ) + .footer( + Level::Help + .title("you must specify a type for this binding, like `i32`") + .snippet( + Snippet::source(aux_source) + .line_start(1) + .origin("$DIR/auxiliary/macro-in-other-crate.rs") + .fold(true) + .annotation(Level::Help.span(69..69).label(": i32")), + ), + ); + let expected = str![[r#" +error[E0689]: can't call method `pow` on ambiguous numeric type `{integer}` + --> $DIR/method-on-ambiguous-numeric-type.rs:37:9 + | +LL | bar.pow(2); + | ^^^ +help: you must specify a type for this binding, like `i32` + --> $DIR/auxiliary/macro-in-other-crate.rs:3:35 + | +LL | ($ident:ident) => { let $ident = 42; } + | - help: : i32 +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn issue_42234_unknown_receiver_type() { + // tests/ui/span/issue-42234-unknown-receiver-type.rs + let source = r#"//@ revisions: full generic_arg +#![cfg_attr(generic_arg, feature(generic_arg_infer))] + +// When the type of a method call's receiver is unknown, the span should point +// to the receiver (and not the entire call, as was previously the case before +// the fix of which this tests). + +fn shines_a_beacon_through_the_darkness() { + let x: Option<_> = None; //~ ERROR type annotations needed + x.unwrap().method_that_could_exist_on_some_type(); +} + +fn courier_to_des_moines_and_points_west(data: &[u32]) -> String { + data.iter() + .sum::<_>() //~ ERROR type annotations needed + .to_string() +} + +fn main() {} +"#; + + let input = Level::Error + .title("type annotations needed") + .id("E0282") + .snippet( + Snippet::source(source) + .line_start(1) + .origin("$DIR/issue-42234-unknown-receiver-type.rs") + .fold(true) + .annotation(Level::Error.span(536..539).label( + "cannot infer type of the type parameter `S` declared on the method `sum`", + )), + ); + let expected = str![[r#" +error[E0282]: type annotations needed + --> $DIR/issue-42234-unknown-receiver-type.rs:15:10 + | +LL | .sum::<_>() //~ ERROR type annotations needed + | ^^^ cannot infer type of the type parameter `S` declared on the method `sum` +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn pattern_usefulness_empty_match() { + // tests/ui/pattern/usefulness/empty-match.rs + let source = r##"//@ revisions: normal exhaustive_patterns +// +// This tests a match with no arms on various types. +#![feature(never_type)] +#![cfg_attr(exhaustive_patterns, feature(exhaustive_patterns))] +#![deny(unreachable_patterns)] + +fn nonempty<const N: usize>(arrayN_of_empty: [!; N]) { + macro_rules! match_no_arms { + ($e:expr) => { + match $e {} + }; + } + macro_rules! match_guarded_arm { + ($e:expr) => { + match $e { + _ if false => {} + } + }; + } + + struct NonEmptyStruct1; + struct NonEmptyStruct2(bool); + union NonEmptyUnion1 { + foo: (), + } + union NonEmptyUnion2 { + foo: (), + bar: !, + } + enum NonEmptyEnum1 { + Foo(bool), + } + enum NonEmptyEnum2 { + Foo(bool), + Bar, + } + enum NonEmptyEnum5 { + V1, + V2, + V3, + V4, + V5, + } + let array0_of_empty: [!; 0] = []; + + match_no_arms!(0u8); //~ ERROR type `u8` is non-empty + match_no_arms!(0i8); //~ ERROR type `i8` is non-empty + match_no_arms!(0usize); //~ ERROR type `usize` is non-empty + match_no_arms!(0isize); //~ ERROR type `isize` is non-empty + match_no_arms!(NonEmptyStruct1); //~ ERROR type `NonEmptyStruct1` is non-empty + match_no_arms!(NonEmptyStruct2(true)); //~ ERROR type `NonEmptyStruct2` is non-empty + match_no_arms!((NonEmptyUnion1 { foo: () })); //~ ERROR type `NonEmptyUnion1` is non-empty + match_no_arms!((NonEmptyUnion2 { foo: () })); //~ ERROR type `NonEmptyUnion2` is non-empty + match_no_arms!(NonEmptyEnum1::Foo(true)); //~ ERROR `NonEmptyEnum1::Foo(_)` not covered + match_no_arms!(NonEmptyEnum2::Foo(true)); //~ ERROR `NonEmptyEnum2::Foo(_)` and `NonEmptyEnum2::Bar` not covered + match_no_arms!(NonEmptyEnum5::V1); //~ ERROR `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered + match_no_arms!(array0_of_empty); //~ ERROR type `[!; 0]` is non-empty + match_no_arms!(arrayN_of_empty); //~ ERROR type `[!; N]` is non-empty + + match_guarded_arm!(0u8); //~ ERROR `0_u8..=u8::MAX` not covered + match_guarded_arm!(0i8); //~ ERROR `i8::MIN..=i8::MAX` not covered + match_guarded_arm!(0usize); //~ ERROR `0_usize..` not covered + match_guarded_arm!(0isize); //~ ERROR `_` not covered + match_guarded_arm!(NonEmptyStruct1); //~ ERROR `NonEmptyStruct1` not covered + match_guarded_arm!(NonEmptyStruct2(true)); //~ ERROR `NonEmptyStruct2(_)` not covered + match_guarded_arm!((NonEmptyUnion1 { foo: () })); //~ ERROR `NonEmptyUnion1 { .. }` not covered + match_guarded_arm!((NonEmptyUnion2 { foo: () })); //~ ERROR `NonEmptyUnion2 { .. }` not covered + match_guarded_arm!(NonEmptyEnum1::Foo(true)); //~ ERROR `NonEmptyEnum1::Foo(_)` not covered + match_guarded_arm!(NonEmptyEnum2::Foo(true)); //~ ERROR `NonEmptyEnum2::Foo(_)` and `NonEmptyEnum2::Bar` not covered + match_guarded_arm!(NonEmptyEnum5::V1); //~ ERROR `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered + match_guarded_arm!(array0_of_empty); //~ ERROR `[]` not covered + match_guarded_arm!(arrayN_of_empty); //~ ERROR `[]` not covered +} + +fn main() {} +"##; + + let input = Level::Error + .title( + "non-exhaustive patterns: `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered" + ) + .id("E0004") + .snippet( + Snippet::source(source) + .line_start(1) + .origin("$DIR/empty-match.rs") + .fold(true) + .annotation( + Level::Error + .span(2911..2928) + .label("patterns `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered") + ), + ) + .footer(Level::Note.title("`NonEmptyEnum5` defined here") + .snippet( + Snippet::source(source) + .line_start(1) + .origin("$DIR/empty-match.rs") + .fold(true) + .annotation(Level::Error.span(818..831)) + .annotation(Level::Warning.span(842..844).label("not covered")) + .annotation(Level::Warning.span(854..856).label("not covered")) + .annotation(Level::Warning.span(866..868).label("not covered")) + .annotation(Level::Warning.span(878..880).label("not covered")) + .annotation(Level::Warning.span(890..892).label("not covered")) + )) + .footer(Level::Note.title("the matched value is of type `NonEmptyEnum5`")) + .footer(Level::Note.title("match arms with guards don't count towards exhaustivity")) + .footer( + Level::Help + .title("ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown, or multiple match arms") + .snippet( + Snippet::source(source) + .line_start(1) + .origin("$DIR/empty-match.rs") + .fold(true) + .annotation(Level::Help.span(485..485).label(",\n _ => todo!()")) + ) + ); + let expected = str![[r#" +error[E0004]: non-exhaustive patterns: `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered + --> $DIR/empty-match.rs:71:24 + | +LL | match_guarded_arm!(NonEmptyEnum5::V1); //~ ERROR `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered + | ^^^^^^^^^^^^^^^^^ patterns `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered +note: `NonEmptyEnum5` defined here + --> $DIR/empty-match.rs:38:10 + | +LL | enum NonEmptyEnum5 { + | ^^^^^^^^^^^^^ +LL | V1, + | -- not covered +LL | V2, + | -- not covered +LL | V3, + | -- not covered +LL | V4, + | -- not covered +LL | V5, + | -- not covered + = note: the matched value is of type `NonEmptyEnum5` + = note: match arms with guards don't count towards exhaustivity +help: ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown, or multiple match arms + --> $DIR/empty-match.rs:17:33 + | +LL | _ if false => {} + | - help: , + _ => todo!() +"#]]; + let renderer = Renderer::plain() + .anonymized_line_numbers(true) + .term_width(annotate_snippets::renderer::DEFAULT_TERM_WIDTH + 4); + assert_data_eq!(renderer.render(input).to_string(), expected); +} + +#[test] +fn object_fail() { + // tests/ui/traits/alias/object-fail.rs + let source = r#"#![feature(trait_alias)] + +trait EqAlias = Eq; +trait IteratorAlias = Iterator; + +fn main() { + let _: &dyn EqAlias = &123; + //~^ ERROR the trait alias `EqAlias` is not dyn compatible [E0038] + let _: &dyn IteratorAlias = &vec![123].into_iter(); + //~^ ERROR must be specified +} +"#; + let input = Level::Error + .title("the trait alias `EqAlias` is not dyn compatible") + .id("E0038") + .snippet( + Snippet::source(source) + .line_start(1) + .origin("$DIR/object-fail.rs") + .fold(true) + .annotation( + Level::Error + .span(107..114) + .label("`EqAlias` is not dyn compatible"), + ), + + ) + .footer( + Level::Note + .title("for a trait to be dyn compatible it needs to allow building a vtable\nfor more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>") + .snippet( + Snippet::source("") + .line_start(334) + .origin("$SRC_DIR/core/src/cmp.rs") + + ) + .snippet( + Snippet::source(source) + .line_start(1) + .origin("$DIR/object-fail.rs") + .fold(true) + .annotation( + Level::Warning + .span(32..39) + .label("this trait is not dyn compatible..."), + ), + ), + ); + let expected = str![[r#" +error[E0038]: the trait alias `EqAlias` is not dyn compatible + --> $DIR/object-fail.rs:7:17 + | +LL | let _: &dyn EqAlias = &123; + | ^^^^^^^ `EqAlias` is not dyn compatible +note: for a trait to be dyn compatible it needs to allow building a vtable +for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility> + --> $SRC_DIR/core/src/cmp.rs + | + ::: $DIR/object-fail.rs:3:7 + | +LL | trait EqAlias = Eq; + | ------- this trait is not dyn compatible... +"#]]; + + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input).to_string(), expected); +} From ebae628b5fbd7f315756b5797fab17f1960b71da Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 1 Apr 2025 15:54:51 -0600 Subject: [PATCH 309/455] feat: Style `: ` for titles --- examples/expected_type.svg | 2 +- examples/footer.svg | 2 +- examples/format.svg | 2 +- examples/multislice.svg | 2 +- src/renderer/display_list.rs | 13 ++++++++----- tests/fixtures/color/ann_eof.svg | 2 +- tests/fixtures/color/ann_insertion.svg | 2 +- tests/fixtures/color/ann_multiline.svg | 2 +- tests/fixtures/color/ann_multiline2.svg | 2 +- tests/fixtures/color/ann_removed_nl.svg | 2 +- .../fixtures/color/ensure-emoji-highlight-width.svg | 2 +- tests/fixtures/color/fold_ann_multiline.svg | 2 +- tests/fixtures/color/fold_leading.svg | 2 +- tests/fixtures/color/fold_trailing.svg | 2 +- tests/fixtures/color/issue_9.svg | 2 +- tests/fixtures/color/simple.svg | 2 +- tests/fixtures/color/strip_line.svg | 2 +- tests/fixtures/color/strip_line_char.svg | 2 +- tests/fixtures/color/strip_line_non_ws.svg | 2 +- 19 files changed, 26 insertions(+), 23 deletions(-) diff --git a/examples/expected_type.svg b/examples/expected_type.svg index d5de44fc..334be474 100644 --- a/examples/expected_type.svg +++ b/examples/expected_type.svg @@ -19,7 +19,7 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">expected type, found `22`</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected type, found `22`</tspan> </tspan> <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> examples/footer.rs:29:25</tspan> </tspan> diff --git a/examples/footer.svg b/examples/footer.svg index ab9e4dfd..b16d224d 100644 --- a/examples/footer.svg +++ b/examples/footer.svg @@ -20,7 +20,7 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan> </tspan> <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> src/multislice.rs:13:22</tspan> </tspan> diff --git a/examples/format.svg b/examples/format.svg index 0e054572..5a0eba20 100644 --- a/examples/format.svg +++ b/examples/format.svg @@ -20,7 +20,7 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan> </tspan> <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> src/format.rs:51:6</tspan> </tspan> diff --git a/examples/multislice.svg b/examples/multislice.svg index 92ff9dfe..25fe27cb 100644 --- a/examples/multislice.svg +++ b/examples/multislice.svg @@ -19,7 +19,7 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: mismatched types</tspan> </tspan> <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> src/format.rs</tspan> </tspan> diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index 53184f2d..9bbe9bf9 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -160,14 +160,18 @@ impl DisplaySet<'_> { &self, line_offset: usize, label: &[DisplayTextFragment<'_>], + needs_colon: bool, stylesheet: &Stylesheet, buffer: &mut StyledBuffer, ) -> fmt::Result { - for fragment in label { + for (i, fragment) in label.iter().enumerate() { let style = match fragment.style { DisplayTextStyle::Regular => stylesheet.none(), DisplayTextStyle::Emphasis => stylesheet.emphasis(), }; + if i == 0 && needs_colon { + buffer.append(line_offset, ": ", *style); + } buffer.append(line_offset, fragment.content, *style); } Ok(()) @@ -191,10 +195,10 @@ impl DisplaySet<'_> { for _ in 0..formatted_len + 2 { buffer.append(line_offset, " ", Style::new()); } - return self.format_label(line_offset, &annotation.label, stylesheet, buffer); + return self.format_label(line_offset, &annotation.label, false, stylesheet, buffer); } if formatted_len == 0 { - self.format_label(line_offset, &annotation.label, stylesheet, buffer) + self.format_label(line_offset, &annotation.label, false, stylesheet, buffer) } else { let id = match &annotation.id { Some(id) => format!("[{id}]"), @@ -207,8 +211,7 @@ impl DisplaySet<'_> { ); if !is_annotation_empty(annotation) { - buffer.append(line_offset, ": ", stylesheet.none); - self.format_label(line_offset, &annotation.label, stylesheet, buffer)?; + self.format_label(line_offset, &annotation.label, true, stylesheet, buffer)?; } Ok(()) } diff --git a/tests/fixtures/color/ann_eof.svg b/tests/fixtures/color/ann_eof.svg index b0fb8b6c..f559f276 100644 --- a/tests/fixtures/color/ann_eof.svg +++ b/tests/fixtures/color/ann_eof.svg @@ -19,7 +19,7 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">expected `.`, `=`</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected `.`, `=`</tspan> </tspan> <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> Cargo.toml:1:5</tspan> </tspan> diff --git a/tests/fixtures/color/ann_insertion.svg b/tests/fixtures/color/ann_insertion.svg index 35d65a05..dbdac8a0 100644 --- a/tests/fixtures/color/ann_insertion.svg +++ b/tests/fixtures/color/ann_insertion.svg @@ -19,7 +19,7 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">expected `.`, `=`</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected `.`, `=`</tspan> </tspan> <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> Cargo.toml:1:3</tspan> </tspan> diff --git a/tests/fixtures/color/ann_multiline.svg b/tests/fixtures/color/ann_multiline.svg index 949eddcb..4a01d037 100644 --- a/tests/fixtures/color/ann_multiline.svg +++ b/tests/fixtures/color/ann_multiline.svg @@ -19,7 +19,7 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0027]</tspan><tspan>: </tspan><tspan class="bold">pattern does not mention fields `lineno`, `content`</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0027]</tspan><tspan class="bold">: pattern does not mention fields `lineno`, `content`</tspan> </tspan> <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> src/display_list.rs:139:32</tspan> </tspan> diff --git a/tests/fixtures/color/ann_multiline2.svg b/tests/fixtures/color/ann_multiline2.svg index 064826a3..13e1b3f9 100644 --- a/tests/fixtures/color/ann_multiline2.svg +++ b/tests/fixtures/color/ann_multiline2.svg @@ -19,7 +19,7 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E####]</tspan><tspan>: </tspan><tspan class="bold">spacing error found</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E####]</tspan><tspan class="bold">: spacing error found</tspan> </tspan> <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> foo.txt:26:12</tspan> </tspan> diff --git a/tests/fixtures/color/ann_removed_nl.svg b/tests/fixtures/color/ann_removed_nl.svg index b0fb8b6c..f559f276 100644 --- a/tests/fixtures/color/ann_removed_nl.svg +++ b/tests/fixtures/color/ann_removed_nl.svg @@ -19,7 +19,7 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">expected `.`, `=`</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected `.`, `=`</tspan> </tspan> <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> Cargo.toml:1:5</tspan> </tspan> diff --git a/tests/fixtures/color/ensure-emoji-highlight-width.svg b/tests/fixtures/color/ensure-emoji-highlight-width.svg index 077bca20..2b569e60 100644 --- a/tests/fixtures/color/ensure-emoji-highlight-width.svg +++ b/tests/fixtures/color/ensure-emoji-highlight-width.svg @@ -19,7 +19,7 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">invalid character ` ` in package name: `haha this isn't a valid name 🐛`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: invalid character ` ` in package name: `haha this isn't a valid name 🐛`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)</tspan> </tspan> <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> <file>:7:1</tspan> </tspan> diff --git a/tests/fixtures/color/fold_ann_multiline.svg b/tests/fixtures/color/fold_ann_multiline.svg index 39323c5f..4034e744 100644 --- a/tests/fixtures/color/fold_ann_multiline.svg +++ b/tests/fixtures/color/fold_ann_multiline.svg @@ -20,7 +20,7 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan> </tspan> <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> src/format.rs:51:6</tspan> </tspan> diff --git a/tests/fixtures/color/fold_leading.svg b/tests/fixtures/color/fold_leading.svg index 0ff7d158..355dbd46 100644 --- a/tests/fixtures/color/fold_leading.svg +++ b/tests/fixtures/color/fold_leading.svg @@ -19,7 +19,7 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">invalid type: integer `20`, expected a bool</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: invalid type: integer `20`, expected a bool</tspan> </tspan> <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> Cargo.toml:11:13</tspan> </tspan> diff --git a/tests/fixtures/color/fold_trailing.svg b/tests/fixtures/color/fold_trailing.svg index ca9de402..522363ec 100644 --- a/tests/fixtures/color/fold_trailing.svg +++ b/tests/fixtures/color/fold_trailing.svg @@ -19,7 +19,7 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">invalid type: integer `20`, expected a lints table</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: invalid type: integer `20`, expected a lints table</tspan> </tspan> <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> Cargo.toml:1:9</tspan> </tspan> diff --git a/tests/fixtures/color/issue_9.svg b/tests/fixtures/color/issue_9.svg index 05e421e1..aa353b42 100644 --- a/tests/fixtures/color/issue_9.svg +++ b/tests/fixtures/color/issue_9.svg @@ -20,7 +20,7 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">expected one of `.`, `;`, `?`, or an operator, found `for`</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected one of `.`, `;`, `?`, or an operator, found `for`</tspan> </tspan> <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> /code/rust/src/test/ui/annotate-snippet/suggestion.rs:4:5</tspan> </tspan> diff --git a/tests/fixtures/color/simple.svg b/tests/fixtures/color/simple.svg index 210cf341..328c754b 100644 --- a/tests/fixtures/color/simple.svg +++ b/tests/fixtures/color/simple.svg @@ -20,7 +20,7 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan>: </tspan><tspan class="bold">expected one of `.`, `;`, `?`, or an operator, found `for`</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected one of `.`, `;`, `?`, or an operator, found `for`</tspan> </tspan> <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> src/format_color.rs:171:9</tspan> </tspan> diff --git a/tests/fixtures/color/strip_line.svg b/tests/fixtures/color/strip_line.svg index f86250ab..487a8877 100644 --- a/tests/fixtures/color/strip_line.svg +++ b/tests/fixtures/color/strip_line.svg @@ -19,7 +19,7 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan> </tspan> <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> $DIR/whitespace-trimming.rs:4:193</tspan> </tspan> diff --git a/tests/fixtures/color/strip_line_char.svg b/tests/fixtures/color/strip_line_char.svg index ed9ba736..6b608672 100644 --- a/tests/fixtures/color/strip_line_char.svg +++ b/tests/fixtures/color/strip_line_char.svg @@ -19,7 +19,7 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan> </tspan> <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> $DIR/whitespace-trimming.rs:4:193</tspan> </tspan> diff --git a/tests/fixtures/color/strip_line_non_ws.svg b/tests/fixtures/color/strip_line_non_ws.svg index 251dfcae..e5d69fd7 100644 --- a/tests/fixtures/color/strip_line_non_ws.svg +++ b/tests/fixtures/color/strip_line_non_ws.svg @@ -19,7 +19,7 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan>: </tspan><tspan class="bold">mismatched types</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan> </tspan> <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> $DIR/non-whitespace-trimming.rs:4:242</tspan> </tspan> From f2832d253e80c6875046784d289be01d6b69a17c Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 1 Apr 2025 15:54:51 -0600 Subject: [PATCH 310/455] fix: Don't style ' ' before '|' --- examples/expected_type.svg | 8 ++-- examples/footer.svg | 2 +- examples/format.svg | 44 +++++++++---------- examples/multislice.svg | 4 +- src/renderer/display_list.rs | 16 +++---- tests/fixtures/color/ann_eof.svg | 2 +- tests/fixtures/color/ann_insertion.svg | 2 +- tests/fixtures/color/ann_multiline.svg | 6 +-- tests/fixtures/color/ann_multiline2.svg | 6 +-- tests/fixtures/color/ann_removed_nl.svg | 2 +- .../color/ensure-emoji-highlight-width.svg | 2 +- tests/fixtures/color/fold_ann_multiline.svg | 10 ++--- tests/fixtures/color/fold_bad_origin_line.svg | 2 +- tests/fixtures/color/fold_leading.svg | 2 +- tests/fixtures/color/fold_trailing.svg | 2 +- tests/fixtures/color/issue_9.svg | 6 +-- tests/fixtures/color/multiple_annotations.svg | 18 ++++---- tests/fixtures/color/simple.svg | 6 +-- tests/fixtures/color/strip_line.svg | 2 +- tests/fixtures/color/strip_line_char.svg | 2 +- tests/fixtures/color/strip_line_non_ws.svg | 2 +- 21 files changed, 71 insertions(+), 75 deletions(-) diff --git a/examples/expected_type.svg b/examples/expected_type.svg index 334be474..c3da76b0 100644 --- a/examples/expected_type.svg +++ b/examples/expected_type.svg @@ -25,15 +25,15 @@ </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">26 |</tspan><tspan> annotations: vec![SourceAnnotation {</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">26</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> annotations: vec![SourceAnnotation {</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">----------------</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">info: while parsing this struct</tspan> </tspan> - <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">27 |</tspan><tspan> label: "expected struct `annotate_snippets::snippet::Slice`, found reference"</tspan> + <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">27</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> label: "expected struct `annotate_snippets::snippet::Slice`, found reference"</tspan> </tspan> - <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">28 |</tspan><tspan> ,</tspan> + <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">28</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> ,</tspan> </tspan> - <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">29 |</tspan><tspan> range: <22, 25>,</tspan> + <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">29</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> range: <22, 25>,</tspan> </tspan> <tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan> </tspan> diff --git a/examples/footer.svg b/examples/footer.svg index b16d224d..367a6d80 100644 --- a/examples/footer.svg +++ b/examples/footer.svg @@ -26,7 +26,7 @@ </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">13 |</tspan><tspan> slices: vec!["A",</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">13</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> slices: vec!["A",</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan> </tspan> diff --git a/examples/format.svg b/examples/format.svg index 5a0eba20..9ecfc3d6 100644 --- a/examples/format.svg +++ b/examples/format.svg @@ -26,51 +26,51 @@ </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">51 |</tspan><tspan> ) -> Option<String> {</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">51</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> ) -> Option<String> {</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">--------------</tspan><tspan> </tspan><tspan class="fg-yellow bold">expected `Option<String>` because of return type</tspan> </tspan> - <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">52 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">/</tspan><tspan> for ann in annotations {</tspan> + <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">52</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">/</tspan><tspan> for ann in annotations {</tspan> </tspan> - <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">53 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> match (ann.range.0, ann.range.1) {</tspan> + <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">53</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> match (ann.range.0, ann.range.1) {</tspan> </tspan> - <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">54 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> (None, None) => continue,</tspan> + <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">54</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> (None, None) => continue,</tspan> </tspan> - <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">55 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> (Some(start), Some(end)) if start > end_index => continue,</tspan> + <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">55</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> (Some(start), Some(end)) if start > end_index => continue,</tspan> </tspan> - <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">56 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> (Some(start), Some(end)) if start >= start_index => {</tspan> + <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">56</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> (Some(start), Some(end)) if start >= start_index => {</tspan> </tspan> - <tspan x="10px" y="208px"><tspan class="fg-bright-blue bold">57 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> let label = if let Some(ref label) = ann.label {</tspan> + <tspan x="10px" y="208px"><tspan class="fg-bright-blue bold">57</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> let label = if let Some(ref label) = ann.label {</tspan> </tspan> - <tspan x="10px" y="226px"><tspan class="fg-bright-blue bold">58 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> format!(" {}", label)</tspan> + <tspan x="10px" y="226px"><tspan class="fg-bright-blue bold">58</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> format!(" {}", label)</tspan> </tspan> - <tspan x="10px" y="244px"><tspan class="fg-bright-blue bold">59 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> } else {</tspan> + <tspan x="10px" y="244px"><tspan class="fg-bright-blue bold">59</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> } else {</tspan> </tspan> - <tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">60 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> String::from("")</tspan> + <tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">60</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> String::from("")</tspan> </tspan> - <tspan x="10px" y="280px"><tspan class="fg-bright-blue bold">61 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> };</tspan> + <tspan x="10px" y="280px"><tspan class="fg-bright-blue bold">61</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> };</tspan> </tspan> - <tspan x="10px" y="298px"><tspan class="fg-bright-blue bold">62 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan> + <tspan x="10px" y="298px"><tspan class="fg-bright-blue bold">62</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan> </tspan> - <tspan x="10px" y="316px"><tspan class="fg-bright-blue bold">63 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> return Some(format!(</tspan> + <tspan x="10px" y="316px"><tspan class="fg-bright-blue bold">63</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> return Some(format!(</tspan> </tspan> - <tspan x="10px" y="334px"><tspan class="fg-bright-blue bold">64 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> "{}{}{}",</tspan> + <tspan x="10px" y="334px"><tspan class="fg-bright-blue bold">64</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> "{}{}{}",</tspan> </tspan> - <tspan x="10px" y="352px"><tspan class="fg-bright-blue bold">65 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> " ".repeat(start - start_index),</tspan> + <tspan x="10px" y="352px"><tspan class="fg-bright-blue bold">65</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> " ".repeat(start - start_index),</tspan> </tspan> - <tspan x="10px" y="370px"><tspan class="fg-bright-blue bold">66 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> "^".repeat(end - start),</tspan> + <tspan x="10px" y="370px"><tspan class="fg-bright-blue bold">66</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> "^".repeat(end - start),</tspan> </tspan> - <tspan x="10px" y="388px"><tspan class="fg-bright-blue bold">67 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> label</tspan> + <tspan x="10px" y="388px"><tspan class="fg-bright-blue bold">67</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> label</tspan> </tspan> - <tspan x="10px" y="406px"><tspan class="fg-bright-blue bold">68 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> ));</tspan> + <tspan x="10px" y="406px"><tspan class="fg-bright-blue bold">68</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> ));</tspan> </tspan> - <tspan x="10px" y="424px"><tspan class="fg-bright-blue bold">69 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan> + <tspan x="10px" y="424px"><tspan class="fg-bright-blue bold">69</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan> </tspan> - <tspan x="10px" y="442px"><tspan class="fg-bright-blue bold">70 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> _ => continue,</tspan> + <tspan x="10px" y="442px"><tspan class="fg-bright-blue bold">70</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> _ => continue,</tspan> </tspan> - <tspan x="10px" y="460px"><tspan class="fg-bright-blue bold">71 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan> + <tspan x="10px" y="460px"><tspan class="fg-bright-blue bold">71</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan> </tspan> - <tspan x="10px" y="478px"><tspan class="fg-bright-blue bold">72 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan> + <tspan x="10px" y="478px"><tspan class="fg-bright-blue bold">72</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan> </tspan> <tspan x="10px" y="496px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|____^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected enum `std::option::Option`</tspan> </tspan> diff --git a/examples/multislice.svg b/examples/multislice.svg index 25fe27cb..6b325cb8 100644 --- a/examples/multislice.svg +++ b/examples/multislice.svg @@ -25,13 +25,13 @@ </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold"> 51 |</tspan><tspan> Foo</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold"> 51</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> Foo</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">:::</tspan><tspan> src/display.rs</tspan> </tspan> <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">129 |</tspan><tspan> Faa</tspan> + <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">129</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> Faa</tspan> </tspan> <tspan x="10px" y="154px"> </tspan> diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index 9bbe9bf9..7f85a314 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -292,19 +292,15 @@ impl DisplaySet<'_> { } => { let lineno_color = stylesheet.line_no(); if anonymized_line_numbers && lineno.is_some() { - let num = format!("{ANONYMIZED_LINE_NUM:>lineno_width$} |"); + let num = format!("{ANONYMIZED_LINE_NUM:>lineno_width$}"); buffer.puts(line_offset, 0, &num, *lineno_color); } else { - match lineno { - Some(n) => { - let num = format!("{n:>lineno_width$} |"); - buffer.puts(line_offset, 0, &num, *lineno_color); - } - None => { - buffer.putc(line_offset, lineno_width + 1, '|', *lineno_color); - } - }; + if let Some(n) = lineno { + let num = format!("{n:>lineno_width$}"); + buffer.puts(line_offset, 0, &num, *lineno_color); + } } + buffer.putc(line_offset, lineno_width + 1, '|', *lineno_color); if let DisplaySourceLine::Content { text, .. } = line { // The width of the line number, a space, pipe, and a space // `123 | ` is `lineno_width + 3`. diff --git a/tests/fixtures/color/ann_eof.svg b/tests/fixtures/color/ann_eof.svg index f559f276..c9a12c55 100644 --- a/tests/fixtures/color/ann_eof.svg +++ b/tests/fixtures/color/ann_eof.svg @@ -25,7 +25,7 @@ </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1 |</tspan><tspan> asdf</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> asdf</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan> </tspan> diff --git a/tests/fixtures/color/ann_insertion.svg b/tests/fixtures/color/ann_insertion.svg index dbdac8a0..cd42fe45 100644 --- a/tests/fixtures/color/ann_insertion.svg +++ b/tests/fixtures/color/ann_insertion.svg @@ -25,7 +25,7 @@ </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1 |</tspan><tspan> asf</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> asf</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">'d' belongs here</tspan> </tspan> diff --git a/tests/fixtures/color/ann_multiline.svg b/tests/fixtures/color/ann_multiline.svg index 4a01d037..1e2d29ec 100644 --- a/tests/fixtures/color/ann_multiline.svg +++ b/tests/fixtures/color/ann_multiline.svg @@ -25,13 +25,13 @@ </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">139 |</tspan><tspan> if let DisplayLine::Source {</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">139</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> if let DisplayLine::Source {</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">________________________________^</tspan> </tspan> - <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">140 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> ref mut inline_marks,</tspan> + <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">140</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> ref mut inline_marks,</tspan> </tspan> - <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">141 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> } = body[body_idx]</tspan> + <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">141</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> } = body[body_idx]</tspan> </tspan> <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|_________________________^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">missing fields `lineno`, `content`</tspan> </tspan> diff --git a/tests/fixtures/color/ann_multiline2.svg b/tests/fixtures/color/ann_multiline2.svg index 13e1b3f9..bae12b8f 100644 --- a/tests/fixtures/color/ann_multiline2.svg +++ b/tests/fixtures/color/ann_multiline2.svg @@ -25,13 +25,13 @@ </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">26 |</tspan><tspan> This is an example</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">26</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> This is an example</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">this should not be on separate lines</tspan> </tspan> - <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">27 |</tspan><tspan> of an edge case of an annotation overflowing</tspan> + <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">27</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> of an edge case of an annotation overflowing</tspan> </tspan> - <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">28 |</tspan><tspan> to exactly one character on next line.</tspan> + <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">28</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> to exactly one character on next line.</tspan> </tspan> </text> diff --git a/tests/fixtures/color/ann_removed_nl.svg b/tests/fixtures/color/ann_removed_nl.svg index f559f276..c9a12c55 100644 --- a/tests/fixtures/color/ann_removed_nl.svg +++ b/tests/fixtures/color/ann_removed_nl.svg @@ -25,7 +25,7 @@ </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1 |</tspan><tspan> asdf</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> asdf</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan> </tspan> diff --git a/tests/fixtures/color/ensure-emoji-highlight-width.svg b/tests/fixtures/color/ensure-emoji-highlight-width.svg index 2b569e60..b49915a7 100644 --- a/tests/fixtures/color/ensure-emoji-highlight-width.svg +++ b/tests/fixtures/color/ensure-emoji-highlight-width.svg @@ -25,7 +25,7 @@ </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">7 |</tspan><tspan> "haha this isn't a valid name 🐛" = { package = "libc", version = "0.1" }</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">7</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> "haha this isn't a valid name 🐛" = { package = "libc", version = "0.1" }</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^</tspan> </tspan> diff --git a/tests/fixtures/color/fold_ann_multiline.svg b/tests/fixtures/color/fold_ann_multiline.svg index 4034e744..c3a220db 100644 --- a/tests/fixtures/color/fold_ann_multiline.svg +++ b/tests/fixtures/color/fold_ann_multiline.svg @@ -26,19 +26,19 @@ </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">51 |</tspan><tspan> ) -> Option<String> {</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">51</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> ) -> Option<String> {</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">--------------</tspan><tspan> </tspan><tspan class="fg-yellow bold">expected `std::option::Option<std::string::String>` because of return type</tspan> </tspan> - <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">52 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">/</tspan><tspan> for ann in annotations {</tspan> + <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">52</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">/</tspan><tspan> for ann in annotations {</tspan> </tspan> - <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">53 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> match (ann.range.0, ann.range.1) {</tspan> + <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">53</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> match (ann.range.0, ann.range.1) {</tspan> </tspan> <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">...</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan> </tspan> - <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">71 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan> + <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">71</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan> </tspan> - <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">72 |</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan> + <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">72</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan> </tspan> <tspan x="10px" y="208px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|_____^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected enum `std::option::Option`, found ()</tspan> </tspan> diff --git a/tests/fixtures/color/fold_bad_origin_line.svg b/tests/fixtures/color/fold_bad_origin_line.svg index c7fb916f..eb70d5f1 100644 --- a/tests/fixtures/color/fold_bad_origin_line.svg +++ b/tests/fixtures/color/fold_bad_origin_line.svg @@ -26,7 +26,7 @@ </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">3 |</tspan><tspan> invalid syntax</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">3</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> invalid syntax</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">--------------</tspan><tspan> </tspan><tspan class="fg-yellow bold">error here</tspan> </tspan> diff --git a/tests/fixtures/color/fold_leading.svg b/tests/fixtures/color/fold_leading.svg index 355dbd46..b6cc6071 100644 --- a/tests/fixtures/color/fold_leading.svg +++ b/tests/fixtures/color/fold_leading.svg @@ -25,7 +25,7 @@ </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">11 |</tspan><tspan> workspace = 20</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">11</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> workspace = 20</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^</tspan> </tspan> diff --git a/tests/fixtures/color/fold_trailing.svg b/tests/fixtures/color/fold_trailing.svg index 522363ec..54b6d9c6 100644 --- a/tests/fixtures/color/fold_trailing.svg +++ b/tests/fixtures/color/fold_trailing.svg @@ -25,7 +25,7 @@ </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1 |</tspan><tspan> lints = 20</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">1</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> lints = 20</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^</tspan> </tspan> diff --git a/tests/fixtures/color/issue_9.svg b/tests/fixtures/color/issue_9.svg index aa353b42..f1f9044f 100644 --- a/tests/fixtures/color/issue_9.svg +++ b/tests/fixtures/color/issue_9.svg @@ -26,15 +26,15 @@ </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">4 |</tspan><tspan> let x = vec![1];</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">4</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let x = vec![1];</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait</tspan> </tspan> - <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">7 |</tspan><tspan> let y = x;</tspan> + <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">7</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let y = x;</tspan> </tspan> <tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">value moved here</tspan> </tspan> - <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">9 |</tspan><tspan> x;</tspan> + <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">9</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> x;</tspan> </tspan> <tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">value used here after move</tspan> </tspan> diff --git a/tests/fixtures/color/multiple_annotations.svg b/tests/fixtures/color/multiple_annotations.svg index fc6fe68e..71628bd3 100644 --- a/tests/fixtures/color/multiple_annotations.svg +++ b/tests/fixtures/color/multiple_annotations.svg @@ -23,29 +23,29 @@ </tspan> <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="64px"><tspan class="fg-bright-blue bold"> 96 |</tspan><tspan> fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>) {</tspan> + <tspan x="10px" y="64px"><tspan class="fg-bright-blue bold"> 96</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>) {</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold"> 97 |</tspan><tspan> if let Some(annotation) = main_annotation {</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold"> 97</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> if let Some(annotation) = main_annotation {</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">Variable defined here</tspan> </tspan> - <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold"> 98 |</tspan><tspan> result.push(format_title_line(</tspan> + <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold"> 98</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> result.push(format_title_line(</tspan> </tspan> - <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold"> 99 |</tspan><tspan> &annotation.annotation_type,</tspan> + <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold"> 99</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> &annotation.annotation_type,</tspan> </tspan> <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">Referenced here</tspan> </tspan> - <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">100 |</tspan><tspan> None,</tspan> + <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">100</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> None,</tspan> </tspan> - <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">101 |</tspan><tspan> &annotation.label,</tspan> + <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">101</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> &annotation.label,</tspan> </tspan> <tspan x="10px" y="208px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">Referenced again here</tspan> </tspan> - <tspan x="10px" y="226px"><tspan class="fg-bright-blue bold">102 |</tspan><tspan> ));</tspan> + <tspan x="10px" y="226px"><tspan class="fg-bright-blue bold">102</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> ));</tspan> </tspan> - <tspan x="10px" y="244px"><tspan class="fg-bright-blue bold">103 |</tspan><tspan> }</tspan> + <tspan x="10px" y="244px"><tspan class="fg-bright-blue bold">103</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> }</tspan> </tspan> - <tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">104 |</tspan><tspan> }</tspan> + <tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">104</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> }</tspan> </tspan> </text> diff --git a/tests/fixtures/color/simple.svg b/tests/fixtures/color/simple.svg index 328c754b..67074033 100644 --- a/tests/fixtures/color/simple.svg +++ b/tests/fixtures/color/simple.svg @@ -26,13 +26,13 @@ </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">169 |</tspan><tspan> })</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">169</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> })</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">expected one of `.`, `;`, `?`, or an operator here</tspan> </tspan> - <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">170 |</tspan> + <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">170</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">171 |</tspan><tspan> for line in &self.body {</tspan> + <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">171</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> for line in &self.body {</tspan> </tspan> <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">unexpected token</tspan> </tspan> diff --git a/tests/fixtures/color/strip_line.svg b/tests/fixtures/color/strip_line.svg index 487a8877..bcd77aec 100644 --- a/tests/fixtures/color/strip_line.svg +++ b/tests/fixtures/color/strip_line.svg @@ -25,7 +25,7 @@ </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">LL |</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">...</tspan><tspan> let _: () = 42;</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">LL</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">...</tspan><tspan> let _: () = 42;</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected (), found integer</tspan> </tspan> diff --git a/tests/fixtures/color/strip_line_char.svg b/tests/fixtures/color/strip_line_char.svg index 6b608672..f6bfe696 100644 --- a/tests/fixtures/color/strip_line_char.svg +++ b/tests/fixtures/color/strip_line_char.svg @@ -25,7 +25,7 @@ </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">LL |</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">...</tspan><tspan> let _: () = 42ñ</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">LL</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">...</tspan><tspan> let _: () = 42ñ</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected (), found integer</tspan> </tspan> diff --git a/tests/fixtures/color/strip_line_non_ws.svg b/tests/fixtures/color/strip_line_non_ws.svg index e5d69fd7..4e5d33c4 100644 --- a/tests/fixtures/color/strip_line_non_ws.svg +++ b/tests/fixtures/color/strip_line_non_ws.svg @@ -25,7 +25,7 @@ </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">LL |</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">...</tspan><tspan> = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () </tspan><tspan class="fg-bright-blue bold">...</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">LL</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">...</tspan><tspan> = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () </tspan><tspan class="fg-bright-blue bold">...</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected `()`, found integer</tspan> </tspan> From 93717717048a40aa3785cd8f9164665f855ceecc Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 1 Apr 2025 15:54:51 -0600 Subject: [PATCH 311/455] fix: Style ' ' after file sigil --- examples/expected_type.svg | 2 +- examples/footer.svg | 2 +- examples/format.svg | 2 +- examples/multislice.svg | 4 ++-- src/renderer/display_list.rs | 4 ++-- tests/fixtures/color/ann_eof.svg | 2 +- tests/fixtures/color/ann_insertion.svg | 2 +- tests/fixtures/color/ann_multiline.svg | 2 +- tests/fixtures/color/ann_multiline2.svg | 2 +- tests/fixtures/color/ann_removed_nl.svg | 2 +- tests/fixtures/color/ensure-emoji-highlight-width.svg | 2 +- tests/fixtures/color/fold_ann_multiline.svg | 2 +- tests/fixtures/color/fold_bad_origin_line.svg | 2 +- tests/fixtures/color/fold_leading.svg | 2 +- tests/fixtures/color/fold_trailing.svg | 2 +- tests/fixtures/color/issue_9.svg | 2 +- tests/fixtures/color/simple.svg | 2 +- tests/fixtures/color/strip_line.svg | 2 +- tests/fixtures/color/strip_line_char.svg | 2 +- tests/fixtures/color/strip_line_non_ws.svg | 2 +- 20 files changed, 22 insertions(+), 22 deletions(-) diff --git a/examples/expected_type.svg b/examples/expected_type.svg index c3da76b0..17a0a16a 100644 --- a/examples/expected_type.svg +++ b/examples/expected_type.svg @@ -21,7 +21,7 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected type, found `22`</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> examples/footer.rs:29:25</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>examples/footer.rs:29:25</tspan> </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> diff --git a/examples/footer.svg b/examples/footer.svg index 367a6d80..2f8eee72 100644 --- a/examples/footer.svg +++ b/examples/footer.svg @@ -22,7 +22,7 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> src/multislice.rs:13:22</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>src/multislice.rs:13:22</tspan> </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> diff --git a/examples/format.svg b/examples/format.svg index 9ecfc3d6..f9bf80e8 100644 --- a/examples/format.svg +++ b/examples/format.svg @@ -22,7 +22,7 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> src/format.rs:51:6</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>src/format.rs:51:6</tspan> </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> diff --git a/examples/multislice.svg b/examples/multislice.svg index 6b325cb8..bca0a56a 100644 --- a/examples/multislice.svg +++ b/examples/multislice.svg @@ -21,13 +21,13 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: mismatched types</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> src/format.rs</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>src/format.rs</tspan> </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold"> 51</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> Foo</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">:::</tspan><tspan> src/display.rs</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">::: </tspan><tspan>src/display.rs</tspan> </tspan> <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index 7f85a314..50a0fefc 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -233,8 +233,8 @@ impl DisplaySet<'_> { header_type, } => { let header_sigil = match header_type { - DisplayHeaderType::Initial => "-->", - DisplayHeaderType::Continuation => ":::", + DisplayHeaderType::Initial => "--> ", + DisplayHeaderType::Continuation => "::: ", }; let lineno_color = stylesheet.line_no(); buffer.puts(line_offset, lineno_width, header_sigil, *lineno_color); diff --git a/tests/fixtures/color/ann_eof.svg b/tests/fixtures/color/ann_eof.svg index c9a12c55..aeb4f8cf 100644 --- a/tests/fixtures/color/ann_eof.svg +++ b/tests/fixtures/color/ann_eof.svg @@ -21,7 +21,7 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected `.`, `=`</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> Cargo.toml:1:5</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>Cargo.toml:1:5</tspan> </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> diff --git a/tests/fixtures/color/ann_insertion.svg b/tests/fixtures/color/ann_insertion.svg index cd42fe45..57c90a23 100644 --- a/tests/fixtures/color/ann_insertion.svg +++ b/tests/fixtures/color/ann_insertion.svg @@ -21,7 +21,7 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected `.`, `=`</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> Cargo.toml:1:3</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>Cargo.toml:1:3</tspan> </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> diff --git a/tests/fixtures/color/ann_multiline.svg b/tests/fixtures/color/ann_multiline.svg index 1e2d29ec..3e813a08 100644 --- a/tests/fixtures/color/ann_multiline.svg +++ b/tests/fixtures/color/ann_multiline.svg @@ -21,7 +21,7 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0027]</tspan><tspan class="bold">: pattern does not mention fields `lineno`, `content`</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> src/display_list.rs:139:32</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>src/display_list.rs:139:32</tspan> </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> diff --git a/tests/fixtures/color/ann_multiline2.svg b/tests/fixtures/color/ann_multiline2.svg index bae12b8f..24827f66 100644 --- a/tests/fixtures/color/ann_multiline2.svg +++ b/tests/fixtures/color/ann_multiline2.svg @@ -21,7 +21,7 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E####]</tspan><tspan class="bold">: spacing error found</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> foo.txt:26:12</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>foo.txt:26:12</tspan> </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> diff --git a/tests/fixtures/color/ann_removed_nl.svg b/tests/fixtures/color/ann_removed_nl.svg index c9a12c55..aeb4f8cf 100644 --- a/tests/fixtures/color/ann_removed_nl.svg +++ b/tests/fixtures/color/ann_removed_nl.svg @@ -21,7 +21,7 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected `.`, `=`</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> Cargo.toml:1:5</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>Cargo.toml:1:5</tspan> </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> diff --git a/tests/fixtures/color/ensure-emoji-highlight-width.svg b/tests/fixtures/color/ensure-emoji-highlight-width.svg index b49915a7..14624fb6 100644 --- a/tests/fixtures/color/ensure-emoji-highlight-width.svg +++ b/tests/fixtures/color/ensure-emoji-highlight-width.svg @@ -21,7 +21,7 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: invalid character ` ` in package name: `haha this isn't a valid name 🐛`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> <file>:7:1</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan><file>:7:1</tspan> </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> diff --git a/tests/fixtures/color/fold_ann_multiline.svg b/tests/fixtures/color/fold_ann_multiline.svg index c3a220db..b68a5535 100644 --- a/tests/fixtures/color/fold_ann_multiline.svg +++ b/tests/fixtures/color/fold_ann_multiline.svg @@ -22,7 +22,7 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> src/format.rs:51:6</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>src/format.rs:51:6</tspan> </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> diff --git a/tests/fixtures/color/fold_bad_origin_line.svg b/tests/fixtures/color/fold_bad_origin_line.svg index eb70d5f1..4bd5f585 100644 --- a/tests/fixtures/color/fold_bad_origin_line.svg +++ b/tests/fixtures/color/fold_bad_origin_line.svg @@ -22,7 +22,7 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> path/to/error.rs:3:1</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>path/to/error.rs:3:1</tspan> </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> diff --git a/tests/fixtures/color/fold_leading.svg b/tests/fixtures/color/fold_leading.svg index b6cc6071..23b31d4a 100644 --- a/tests/fixtures/color/fold_leading.svg +++ b/tests/fixtures/color/fold_leading.svg @@ -21,7 +21,7 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: invalid type: integer `20`, expected a bool</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> Cargo.toml:11:13</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>Cargo.toml:11:13</tspan> </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> diff --git a/tests/fixtures/color/fold_trailing.svg b/tests/fixtures/color/fold_trailing.svg index 54b6d9c6..46071da6 100644 --- a/tests/fixtures/color/fold_trailing.svg +++ b/tests/fixtures/color/fold_trailing.svg @@ -21,7 +21,7 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: invalid type: integer `20`, expected a lints table</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> Cargo.toml:1:9</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>Cargo.toml:1:9</tspan> </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> diff --git a/tests/fixtures/color/issue_9.svg b/tests/fixtures/color/issue_9.svg index f1f9044f..35aa77b9 100644 --- a/tests/fixtures/color/issue_9.svg +++ b/tests/fixtures/color/issue_9.svg @@ -22,7 +22,7 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected one of `.`, `;`, `?`, or an operator, found `for`</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> /code/rust/src/test/ui/annotate-snippet/suggestion.rs:4:5</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>/code/rust/src/test/ui/annotate-snippet/suggestion.rs:4:5</tspan> </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> diff --git a/tests/fixtures/color/simple.svg b/tests/fixtures/color/simple.svg index 67074033..d1d5d871 100644 --- a/tests/fixtures/color/simple.svg +++ b/tests/fixtures/color/simple.svg @@ -22,7 +22,7 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected one of `.`, `;`, `?`, or an operator, found `for`</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> src/format_color.rs:171:9</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>src/format_color.rs:171:9</tspan> </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> diff --git a/tests/fixtures/color/strip_line.svg b/tests/fixtures/color/strip_line.svg index bcd77aec..14fbf9dc 100644 --- a/tests/fixtures/color/strip_line.svg +++ b/tests/fixtures/color/strip_line.svg @@ -21,7 +21,7 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> $DIR/whitespace-trimming.rs:4:193</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>$DIR/whitespace-trimming.rs:4:193</tspan> </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> diff --git a/tests/fixtures/color/strip_line_char.svg b/tests/fixtures/color/strip_line_char.svg index f6bfe696..b37d4a9a 100644 --- a/tests/fixtures/color/strip_line_char.svg +++ b/tests/fixtures/color/strip_line_char.svg @@ -21,7 +21,7 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> $DIR/whitespace-trimming.rs:4:193</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>$DIR/whitespace-trimming.rs:4:193</tspan> </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> diff --git a/tests/fixtures/color/strip_line_non_ws.svg b/tests/fixtures/color/strip_line_non_ws.svg index 4e5d33c4..89047b3b 100644 --- a/tests/fixtures/color/strip_line_non_ws.svg +++ b/tests/fixtures/color/strip_line_non_ws.svg @@ -21,7 +21,7 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--></tspan><tspan> $DIR/non-whitespace-trimming.rs:4:242</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>$DIR/non-whitespace-trimming.rs:4:242</tspan> </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> From 96a2d7c13c341eba95a57d9a7dfc6ccd8a7b0c87 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 1 Apr 2025 17:20:37 -0600 Subject: [PATCH 312/455] fix: Add pipe to first snippet when not alone --- examples/multislice.svg | 12 +++++++----- src/renderer/display_list.rs | 14 ++++++++++++++ tests/fixtures/color/issue_9.svg | 12 +++++++----- tests/formatter.rs | 1 + tests/rustc_tests.rs | 2 ++ 5 files changed, 31 insertions(+), 10 deletions(-) diff --git a/examples/multislice.svg b/examples/multislice.svg index bca0a56a..f0c1f65c 100644 --- a/examples/multislice.svg +++ b/examples/multislice.svg @@ -1,4 +1,4 @@ -<svg width="740px" height="164px" xmlns="http://www.w3.org/2000/svg"> +<svg width="740px" height="182px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -27,13 +27,15 @@ </tspan> <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold"> 51</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> Foo</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">::: </tspan><tspan>src/display.rs</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> + <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">::: </tspan><tspan>src/display.rs</tspan> </tspan> - <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">129</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> Faa</tspan> + <tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="154px"> + <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">129</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> Faa</tspan> +</tspan> + <tspan x="10px" y="172px"> </tspan> </text> diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index 50a0fefc..1a028834 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -1006,11 +1006,13 @@ fn format_message( format_footer(level, id, title) }; + let num_snippets = snippets.len(); for (idx, snippet) in snippets.into_iter().enumerate() { let snippet = fold_prefix_suffix(snippet); sets.push(format_snippet( snippet, idx == 0, + idx == 0 && num_snippets > 1, term_width, anonymized_line_numbers, )); @@ -1089,6 +1091,7 @@ fn format_label( fn format_snippet( snippet: snippet::Snippet<'_>, is_first: bool, + needs_trailing_pipe: bool, term_width: usize, anonymized_line_numbers: bool, ) -> DisplaySet<'_> { @@ -1098,6 +1101,7 @@ fn format_snippet( let mut body = format_body( snippet, need_empty_header, + needs_trailing_pipe, term_width, anonymized_line_numbers, ); @@ -1285,6 +1289,7 @@ fn fold_body(body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> { fn format_body( snippet: snippet::Snippet<'_>, need_empty_header: bool, + needs_trailing_pipe: bool, term_width: usize, anonymized_line_numbers: bool, ) -> DisplaySet<'_> { @@ -1599,6 +1604,15 @@ fn format_body( ); } + if needs_trailing_pipe { + body.push(DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: DisplaySourceLine::Empty, + annotations: vec![], + }); + } + let max_line_num_len = if anonymized_line_numbers { ANONYMIZED_LINE_NUM.len() } else { diff --git a/tests/fixtures/color/issue_9.svg b/tests/fixtures/color/issue_9.svg index 35aa77b9..6ba0199f 100644 --- a/tests/fixtures/color/issue_9.svg +++ b/tests/fixtures/color/issue_9.svg @@ -1,4 +1,4 @@ -<svg width="911px" height="182px" xmlns="http://www.w3.org/2000/svg"> +<svg width="911px" height="200px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -30,13 +30,15 @@ </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait</tspan> </tspan> - <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">7</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let y = x;</tspan> + <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">value moved here</tspan> + <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">7</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let y = x;</tspan> </tspan> - <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">9</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> x;</tspan> + <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">value moved here</tspan> </tspan> - <tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">value used here after move</tspan> + <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">9</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> x;</tspan> +</tspan> + <tspan x="10px" y="190px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">value used here after move</tspan> </tspan> </text> diff --git a/tests/formatter.rs b/tests/formatter.rs index 7aa37d2b..a4efbccd 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -146,6 +146,7 @@ error --> file1.rs | 5402 | This is slice 1 + | ::: file2.rs | 2 | This is slice 2 diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index f10f479b..dab2e61b 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -1323,6 +1323,7 @@ LL | | ($bang_macro:ident, $attr_macro:ident) => { LL | | } LL | | } | |_________^ + | ::: $DIR/nested-macro-rules.rs:23:5 | LL | nested_macro_rules::outer_macro!(SecondStruct, SecondAttrStruct); @@ -1697,6 +1698,7 @@ note: for a trait to be dyn compatible it needs to allow building a vtable for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility> --> $SRC_DIR/core/src/cmp.rs | + | ::: $DIR/object-fail.rs:3:7 | LL | trait EqAlias = Eq; From 936e9823da77466bfd2f85b346a1cfd55777945a Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 1 Apr 2025 17:35:00 -0600 Subject: [PATCH 313/455] fix: Left align line numbers --- examples/multislice.svg | 2 +- src/renderer/display_list.rs | 5 ++--- tests/fixtures/color/multiple_annotations.svg | 8 ++++---- tests/formatter.rs | 2 +- tests/rustc_tests.rs | 20 +++++++++---------- 5 files changed, 18 insertions(+), 19 deletions(-) diff --git a/examples/multislice.svg b/examples/multislice.svg index f0c1f65c..bbb3fc98 100644 --- a/examples/multislice.svg +++ b/examples/multislice.svg @@ -25,7 +25,7 @@ </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold"> 51</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> Foo</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">51</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> Foo</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs index 1a028834..08d5db31 100644 --- a/src/renderer/display_list.rs +++ b/src/renderer/display_list.rs @@ -292,11 +292,10 @@ impl DisplaySet<'_> { } => { let lineno_color = stylesheet.line_no(); if anonymized_line_numbers && lineno.is_some() { - let num = format!("{ANONYMIZED_LINE_NUM:>lineno_width$}"); - buffer.puts(line_offset, 0, &num, *lineno_color); + buffer.puts(line_offset, 0, ANONYMIZED_LINE_NUM, *lineno_color); } else { if let Some(n) = lineno { - let num = format!("{n:>lineno_width$}"); + let num = format!("{n}"); buffer.puts(line_offset, 0, &num, *lineno_color); } } diff --git a/tests/fixtures/color/multiple_annotations.svg b/tests/fixtures/color/multiple_annotations.svg index 71628bd3..26df4ae9 100644 --- a/tests/fixtures/color/multiple_annotations.svg +++ b/tests/fixtures/color/multiple_annotations.svg @@ -23,15 +23,15 @@ </tspan> <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="64px"><tspan class="fg-bright-blue bold"> 96</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>) {</tspan> + <tspan x="10px" y="64px"><tspan class="fg-bright-blue bold">96</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>) {</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold"> 97</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> if let Some(annotation) = main_annotation {</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">97</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> if let Some(annotation) = main_annotation {</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">Variable defined here</tspan> </tspan> - <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold"> 98</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> result.push(format_title_line(</tspan> + <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">98</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> result.push(format_title_line(</tspan> </tspan> - <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold"> 99</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> &annotation.annotation_type,</tspan> + <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">99</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> &annotation.annotation_type,</tspan> </tspan> <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">Referenced here</tspan> </tspan> diff --git a/tests/formatter.rs b/tests/formatter.rs index a4efbccd..d1694217 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -149,7 +149,7 @@ error | ::: file2.rs | - 2 | This is slice 2 +2 | This is slice 2 "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input).to_string(), expected); diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index dab2e61b..c2e72d3a 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -686,13 +686,13 @@ fn foo() { error: foo --> test.rs:3:6 | - 3 | X0 Y0 Z0 +3 | X0 Y0 Z0 | _______^ - 4 | | X1 Y1 Z1 +4 | | X1 Y1 Z1 | | ____^____- | ||____| | | `X` is a good letter - 5 | | 1 +5 | | 1 ... | 15 | | X2 Y2 Z2 16 | | X3 Y3 Z3 @@ -738,15 +738,15 @@ fn foo() { error: foo --> test.rs:3:6 | - 3 | X0 Y0 Z0 +3 | X0 Y0 Z0 | _______^ - 4 | | 1 - 5 | | 2 - 6 | | 3 - 7 | | X1 Y1 Z1 +4 | | 1 +5 | | 2 +6 | | 3 +7 | | X1 Y1 Z1 | | _________- - 8 | || 4 - 9 | || 5 +8 | || 4 +9 | || 5 10 | || 6 11 | || X2 Y2 Z2 | ||__________- `Z` is a good letter too From 84d07af1e54aaaf08f014edae34a478829e7e9c9 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Sun, 9 Feb 2025 08:54:15 -0700 Subject: [PATCH 314/455] feat: Better match rustc's internal renderer --- Cargo.lock | 12 +- Cargo.toml | 2 + benches/bench.rs | 4 +- examples/expected_type.svg | 16 +- examples/footer.svg | 3 +- src/renderer/display_list.rs | 1735 ----------------- src/renderer/mod.rs | 1428 +++++++++++++- src/renderer/source_map.rs | 446 +++++ src/renderer/styled_buffer.rs | 40 +- src/renderer/stylesheet.rs | 34 - src/snippet.rs | 85 +- tests/fixtures/color/ann_multiline.svg | 2 +- tests/fixtures/color/fold_ann_multiline.svg | 12 +- tests/fixtures/color/fold_bad_origin_line.svg | 2 +- tests/fixtures/color/fold_leading.svg | 8 +- tests/fixtures/color/issue_9.svg | 22 +- tests/fixtures/color/multiple_annotations.svg | 2 +- tests/fixtures/color/simple.svg | 2 +- tests/fixtures/color/strip_line_non_ws.svg | 2 +- tests/fixtures/color/strip_line_non_ws.toml | 4 +- tests/fixtures/main.rs | 2 +- tests/formatter.rs | 182 +- tests/rustc_tests.rs | 116 +- 23 files changed, 2179 insertions(+), 1982 deletions(-) delete mode 100644 src/renderer/display_list.rs create mode 100644 src/renderer/source_map.rs diff --git a/Cargo.lock b/Cargo.lock index c8c4d67b..e8aebe5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,7 +21,9 @@ dependencies = [ "difference", "divan", "glob", + "indexmap", "memchr", + "rustc-hash", "serde", "snapbox", "toml", @@ -334,9 +336,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown", @@ -506,6 +508,12 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +[[package]] +name = "rustc-hash" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" + [[package]] name = "rustix" version = "0.37.27" diff --git a/Cargo.toml b/Cargo.toml index f30c64f9..95067ce7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -117,7 +117,9 @@ maintenance = { status = "actively-developed" } [dependencies] anstyle = "1.0.4" +indexmap = "2.7.0" memchr = { version = "2.7.4", optional = true } +rustc-hash = "2.1.0" unicode-width = "0.2.0" [dev-dependencies] diff --git a/benches/bench.rs b/benches/bench.rs index 9747954b..ed4e82c0 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -41,7 +41,7 @@ fn simple() -> String { ); let renderer = Renderer::plain(); - let rendered = renderer.render(message).to_string(); + let rendered = renderer.render(message); rendered } @@ -79,7 +79,7 @@ fn fold(bencher: divan::Bencher<'_, '_>, context: usize) { ); let renderer = Renderer::plain(); - let rendered = renderer.render(message).to_string(); + let rendered = renderer.render(message); rendered }); } diff --git a/examples/expected_type.svg b/examples/expected_type.svg index 17a0a16a..749fc3ac 100644 --- a/examples/expected_type.svg +++ b/examples/expected_type.svg @@ -1,4 +1,4 @@ -<svg width="860px" height="200px" xmlns="http://www.w3.org/2000/svg"> +<svg width="860px" height="182px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -21,23 +21,21 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected type, found `22`</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>examples/footer.rs:29:25</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>examples/footer.rs:26:35</tspan> </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">26</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> annotations: vec![SourceAnnotation {</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">----------------</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">info: while parsing this struct</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">----------------</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">while parsing this struct</tspan> </tspan> - <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">27</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> label: "expected struct `annotate_snippets::snippet::Slice`, found reference"</tspan> + <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">...</tspan> </tspan> - <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">28</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> ,</tspan> + <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">29</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> range: <22, 25>,</tspan> </tspan> - <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">29</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> range: <22, 25>,</tspan> + <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan> </tspan> - <tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan> -</tspan> - <tspan x="10px" y="190px"> + <tspan x="10px" y="172px"> </tspan> </text> diff --git a/examples/footer.svg b/examples/footer.svg index 2f8eee72..e3bb8923 100644 --- a/examples/footer.svg +++ b/examples/footer.svg @@ -3,7 +3,6 @@ .fg { fill: #AAAAAA } .bg { background: #000000 } .fg-bright-blue { fill: #5555FF } - .fg-bright-green { fill: #55FF55 } .fg-bright-red { fill: #FF5555 } .container { padding: 0 10px; @@ -30,7 +29,7 @@ </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan> </tspan> - <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">= </tspan><tspan class="fg-bright-green bold">note</tspan><tspan>: expected type: `snippet::Annotation`</tspan> + <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">= </tspan><tspan class="bold">note</tspan><tspan>: expected type: `snippet::Annotation`</tspan> </tspan> <tspan x="10px" y="136px"><tspan> found type: `__&__snippet::Annotation`</tspan> </tspan> diff --git a/src/renderer/display_list.rs b/src/renderer/display_list.rs deleted file mode 100644 index 08d5db31..00000000 --- a/src/renderer/display_list.rs +++ /dev/null @@ -1,1735 +0,0 @@ -//! `display_list` module stores the output model for the snippet. -//! -//! `DisplayList` is a central structure in the crate, which contains -//! the structured list of lines to be displayed. -//! -//! It is made of two types of lines: `Source` and `Raw`. All `Source` lines -//! are structured using four columns: -//! -//! ```text -//! /------------ (1) Line number column. -//! | /--------- (2) Line number column delimiter. -//! | | /------- (3) Inline marks column. -//! | | | /--- (4) Content column with the source and annotations for slices. -//! | | | | -//! ============================================================================= -//! error[E0308]: mismatched types -//! --> src/format.rs:51:5 -//! | -//! 151 | / fn test() -> String { -//! 152 | | return "test"; -//! 153 | | } -//! | |___^ error: expected `String`, for `&str`. -//! ``` -//! -//! The first two lines of the example above are `Raw` lines, while the rest -//! are `Source` lines. -//! -//! `DisplayList` does not store column alignment information, and those are -//! only calculated by the implementation of `std::fmt::Display` using information such as -//! styling. -//! -//! The above snippet has been built out of the following structure: -use crate::snippet; -use std::cmp::{max, min, Reverse}; -use std::collections::HashMap; -use std::fmt::Display; -use std::ops::Range; -use std::{cmp, fmt}; - -use crate::renderer::styled_buffer::StyledBuffer; -use crate::renderer::{stylesheet::Stylesheet, Margin, Style, DEFAULT_TERM_WIDTH}; - -const ANONYMIZED_LINE_NUM: &str = "LL"; -const ERROR_TXT: &str = "error"; -const HELP_TXT: &str = "help"; -const INFO_TXT: &str = "info"; -const NOTE_TXT: &str = "note"; -const WARNING_TXT: &str = "warning"; - -/// List of lines to be displayed. -pub(crate) struct DisplayList<'a> { - pub(crate) body: Vec<DisplaySet<'a>>, - pub(crate) stylesheet: &'a Stylesheet, - pub(crate) anonymized_line_numbers: bool, -} - -impl PartialEq for DisplayList<'_> { - fn eq(&self, other: &Self) -> bool { - self.body == other.body && self.anonymized_line_numbers == other.anonymized_line_numbers - } -} - -impl fmt::Debug for DisplayList<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("DisplayList") - .field("body", &self.body) - .field("anonymized_line_numbers", &self.anonymized_line_numbers) - .finish() - } -} - -impl Display for DisplayList<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let lineno_width = self.body.iter().fold(0, |max, set| { - set.display_lines.iter().fold(max, |max, line| match line { - DisplayLine::Source { lineno, .. } => cmp::max(lineno.unwrap_or(0), max), - _ => max, - }) - }); - let lineno_width = if lineno_width == 0 { - lineno_width - } else if self.anonymized_line_numbers { - ANONYMIZED_LINE_NUM.len() - } else { - ((lineno_width as f64).log10().floor() as usize) + 1 - }; - - let multiline_depth = self.body.iter().fold(0, |max, set| { - set.display_lines.iter().fold(max, |max2, line| match line { - DisplayLine::Source { annotations, .. } => cmp::max( - annotations.iter().fold(max2, |max3, line| { - cmp::max( - match line.annotation_part { - DisplayAnnotationPart::Standalone => 0, - DisplayAnnotationPart::LabelContinuation => 0, - DisplayAnnotationPart::MultilineStart(depth) => depth + 1, - DisplayAnnotationPart::MultilineEnd(depth) => depth + 1, - }, - max3, - ) - }), - max, - ), - _ => max2, - }) - }); - let mut buffer = StyledBuffer::new(); - for set in self.body.iter() { - self.format_set(set, lineno_width, multiline_depth, &mut buffer)?; - } - write!(f, "{}", buffer.render(self.stylesheet)?) - } -} - -impl<'a> DisplayList<'a> { - pub(crate) fn new( - message: snippet::Message<'a>, - stylesheet: &'a Stylesheet, - anonymized_line_numbers: bool, - term_width: usize, - ) -> DisplayList<'a> { - let body = format_message(message, term_width, anonymized_line_numbers, true); - - Self { - body, - stylesheet, - anonymized_line_numbers, - } - } - - fn format_set( - &self, - set: &DisplaySet<'_>, - lineno_width: usize, - multiline_depth: usize, - buffer: &mut StyledBuffer, - ) -> fmt::Result { - for line in &set.display_lines { - set.format_line( - line, - lineno_width, - multiline_depth, - self.stylesheet, - self.anonymized_line_numbers, - buffer, - )?; - } - Ok(()) - } -} - -#[derive(Debug, PartialEq)] -pub(crate) struct DisplaySet<'a> { - pub(crate) display_lines: Vec<DisplayLine<'a>>, - pub(crate) margin: Margin, -} - -impl DisplaySet<'_> { - fn format_label( - &self, - line_offset: usize, - label: &[DisplayTextFragment<'_>], - needs_colon: bool, - stylesheet: &Stylesheet, - buffer: &mut StyledBuffer, - ) -> fmt::Result { - for (i, fragment) in label.iter().enumerate() { - let style = match fragment.style { - DisplayTextStyle::Regular => stylesheet.none(), - DisplayTextStyle::Emphasis => stylesheet.emphasis(), - }; - if i == 0 && needs_colon { - buffer.append(line_offset, ": ", *style); - } - buffer.append(line_offset, fragment.content, *style); - } - Ok(()) - } - fn format_annotation( - &self, - line_offset: usize, - annotation: &Annotation<'_>, - continuation: bool, - stylesheet: &Stylesheet, - buffer: &mut StyledBuffer, - ) -> fmt::Result { - let color = get_annotation_style(&annotation.annotation_type, stylesheet); - let formatted_len = if let Some(id) = &annotation.id { - 2 + id.len() + annotation_type_len(&annotation.annotation_type) - } else { - annotation_type_len(&annotation.annotation_type) - }; - - if continuation { - for _ in 0..formatted_len + 2 { - buffer.append(line_offset, " ", Style::new()); - } - return self.format_label(line_offset, &annotation.label, false, stylesheet, buffer); - } - if formatted_len == 0 { - self.format_label(line_offset, &annotation.label, false, stylesheet, buffer) - } else { - let id = match &annotation.id { - Some(id) => format!("[{id}]"), - None => String::new(), - }; - buffer.append( - line_offset, - &format!("{}{}", annotation_type_str(&annotation.annotation_type), id), - *color, - ); - - if !is_annotation_empty(annotation) { - self.format_label(line_offset, &annotation.label, true, stylesheet, buffer)?; - } - Ok(()) - } - } - - #[inline] - fn format_raw_line( - &self, - line_offset: usize, - line: &DisplayRawLine<'_>, - lineno_width: usize, - stylesheet: &Stylesheet, - buffer: &mut StyledBuffer, - ) -> fmt::Result { - match line { - DisplayRawLine::Origin { - path, - pos, - header_type, - } => { - let header_sigil = match header_type { - DisplayHeaderType::Initial => "--> ", - DisplayHeaderType::Continuation => "::: ", - }; - let lineno_color = stylesheet.line_no(); - buffer.puts(line_offset, lineno_width, header_sigil, *lineno_color); - buffer.puts(line_offset, lineno_width + 4, path, stylesheet.none); - if let Some((col, row)) = pos { - buffer.append(line_offset, ":", stylesheet.none); - buffer.append(line_offset, col.to_string().as_str(), stylesheet.none); - buffer.append(line_offset, ":", stylesheet.none); - buffer.append(line_offset, row.to_string().as_str(), stylesheet.none); - } - Ok(()) - } - DisplayRawLine::Annotation { - annotation, - source_aligned, - continuation, - } => { - if *source_aligned { - if *continuation { - for _ in 0..lineno_width + 3 { - buffer.append(line_offset, " ", stylesheet.none); - } - } else { - let lineno_color = stylesheet.line_no(); - for _ in 0..lineno_width + 1 { - buffer.append(line_offset, " ", stylesheet.none); - } - buffer.append(line_offset, "=", *lineno_color); - buffer.append(line_offset, " ", *lineno_color); - } - } - self.format_annotation(line_offset, annotation, *continuation, stylesheet, buffer) - } - } - } - - // Adapted from https://github.com/rust-lang/rust/blob/d371d17496f2ce3a56da76aa083f4ef157572c20/compiler/rustc_errors/src/emitter.rs#L706-L1211 - #[inline] - fn format_line( - &self, - dl: &DisplayLine<'_>, - lineno_width: usize, - multiline_depth: usize, - stylesheet: &Stylesheet, - anonymized_line_numbers: bool, - buffer: &mut StyledBuffer, - ) -> fmt::Result { - let line_offset = buffer.num_lines(); - match dl { - DisplayLine::Source { - lineno, - inline_marks, - line, - annotations, - } => { - let lineno_color = stylesheet.line_no(); - if anonymized_line_numbers && lineno.is_some() { - buffer.puts(line_offset, 0, ANONYMIZED_LINE_NUM, *lineno_color); - } else { - if let Some(n) = lineno { - let num = format!("{n}"); - buffer.puts(line_offset, 0, &num, *lineno_color); - } - } - buffer.putc(line_offset, lineno_width + 1, '|', *lineno_color); - if let DisplaySourceLine::Content { text, .. } = line { - // The width of the line number, a space, pipe, and a space - // `123 | ` is `lineno_width + 3`. - let width_offset = lineno_width + 3; - let code_offset = if multiline_depth == 0 { - width_offset - } else { - width_offset + multiline_depth + 1 - }; - - // Add any inline marks to the code line - if !inline_marks.is_empty() || 0 < multiline_depth { - format_inline_marks( - line_offset, - inline_marks, - lineno_width, - stylesheet, - buffer, - )?; - } - - let text = normalize_whitespace(text); - let line_len = text.len(); - let left = self.margin.left(line_len); - let right = self.margin.right(line_len); - - // On long lines, we strip the source line, accounting for unicode. - let mut taken = 0; - let code: String = text - .chars() - .skip(left) - .take_while(|ch| { - // Make sure that the trimming on the right will fall within the terminal width. - // FIXME: `unicode_width` sometimes disagrees with terminals on how wide a `char` - // is. For now, just accept that sometimes the code line will be longer than - // desired. - let next = unicode_width::UnicodeWidthChar::width(*ch).unwrap_or(1); - if taken + next > right - left { - return false; - } - taken += next; - true - }) - .collect(); - buffer.puts(line_offset, code_offset, &code, Style::new()); - if self.margin.was_cut_left() { - // We have stripped some code/whitespace from the beginning, make it clear. - buffer.puts(line_offset, code_offset, "...", *lineno_color); - } - if self.margin.was_cut_right(line_len) { - buffer.puts(line_offset, code_offset + taken - 3, "...", *lineno_color); - } - - let left: usize = text - .chars() - .take(left) - .map(|ch| unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1)) - .sum(); - - let mut annotations = annotations.clone(); - annotations.sort_by_key(|a| Reverse(a.range.0)); - - let mut annotations_positions = vec![]; - let mut line_len: usize = 0; - let mut p = 0; - for (i, annotation) in annotations.iter().enumerate() { - for (j, next) in annotations.iter().enumerate() { - // This label overlaps with another one and both take space ( - // they have text and are not multiline lines). - if overlaps(next, annotation, 0) - && annotation.has_label() - && j > i - && p == 0 - // We're currently on the first line, move the label one line down - { - // If we're overlapping with an un-labelled annotation with the same span - // we can just merge them in the output - if next.range.0 == annotation.range.0 - && next.range.1 == annotation.range.1 - && !next.has_label() - { - continue; - } - - // This annotation needs a new line in the output. - p += 1; - break; - } - } - annotations_positions.push((p, annotation)); - for (j, next) in annotations.iter().enumerate() { - if j > i { - let l = next - .annotation - .label - .iter() - .map(|label| label.content) - .collect::<Vec<_>>() - .join("") - .len() - + 2; - // Do not allow two labels to be in the same line if they - // overlap including padding, to avoid situations like: - // - // fn foo(x: u32) { - // -------^------ - // | | - // fn_spanx_span - // - // Both labels must have some text, otherwise they are not - // overlapping. Do not add a new line if this annotation or - // the next are vertical line placeholders. If either this - // or the next annotation is multiline start/end, move it - // to a new line so as not to overlap the horizontal lines. - if (overlaps(next, annotation, l) - && annotation.has_label() - && next.has_label()) - || (annotation.takes_space() && next.has_label()) - || (annotation.has_label() && next.takes_space()) - || (annotation.takes_space() && next.takes_space()) - || (overlaps(next, annotation, l) - && next.range.1 <= annotation.range.1 - && next.has_label() - && p == 0) - // Avoid #42595. - { - // This annotation needs a new line in the output. - p += 1; - break; - } - } - } - line_len = max(line_len, p); - } - - if line_len != 0 { - line_len += 1; - } - - if annotations_positions.iter().all(|(_, ann)| { - matches!( - ann.annotation_part, - DisplayAnnotationPart::MultilineStart(_) - ) - }) { - if let Some(max_pos) = - annotations_positions.iter().map(|(pos, _)| *pos).max() - { - // Special case the following, so that we minimize overlapping multiline spans. - // - // 3 │ X0 Y0 Z0 - // │ ┏━━━━━┛ │ │ < We are writing these lines - // │ ┃┌───────┘ │ < by reverting the "depth" of - // │ ┃│┌─────────┘ < their multilne spans. - // 4 │ ┃││ X1 Y1 Z1 - // 5 │ ┃││ X2 Y2 Z2 - // │ ┃│└────╿──│──┘ `Z` label - // │ ┃└─────│──┤ - // │ ┗━━━━━━┥ `Y` is a good letter too - // ╰╴ `X` is a good letter - for (pos, _) in &mut annotations_positions { - *pos = max_pos - *pos; - } - // We know then that we don't need an additional line for the span label, saving us - // one line of vertical space. - line_len = line_len.saturating_sub(1); - } - } - - // This is a special case where we have a multiline - // annotation that is at the start of the line disregarding - // any leading whitespace, and no other multiline - // annotations overlap it. In this case, we want to draw - // - // 2 | fn foo() { - // | _^ - // 3 | | - // 4 | | } - // | |_^ test - // - // we simplify the output to: - // - // 2 | / fn foo() { - // 3 | | - // 4 | | } - // | |_^ test - if multiline_depth == 1 - && annotations_positions.len() == 1 - && annotations_positions - .first() - .map_or(false, |(_, annotation)| { - matches!( - annotation.annotation_part, - DisplayAnnotationPart::MultilineStart(_) - ) && text - .chars() - .take(annotation.range.0) - .all(|c| c.is_whitespace()) - }) - { - let (_, ann) = annotations_positions.remove(0); - let style = get_annotation_style(&ann.annotation_type, stylesheet); - buffer.putc(line_offset, 3 + lineno_width, '/', *style); - } - - // Draw the column separator for any extra lines that were - // created - // - // After this we will have: - // - // 2 | fn foo() { - // | - // | - // | - // 3 | - // 4 | } - // | - if !annotations_positions.is_empty() { - for pos in 0..=line_len { - buffer.putc( - line_offset + pos + 1, - lineno_width + 1, - '|', - stylesheet.line_no, - ); - } - } - - // Write the horizontal lines for multiline annotations - // (only the first and last lines need this). - // - // After this we will have: - // - // 2 | fn foo() { - // | __________ - // | - // | - // 3 | - // 4 | } - // | _ - for &(pos, annotation) in &annotations_positions { - let style = get_annotation_style(&annotation.annotation_type, stylesheet); - let pos = pos + 1; - match annotation.annotation_part { - DisplayAnnotationPart::MultilineStart(depth) - | DisplayAnnotationPart::MultilineEnd(depth) => { - for col in width_offset + depth - ..(code_offset + annotation.range.0).saturating_sub(left) - { - buffer.putc(line_offset + pos, col + 1, '_', *style); - } - } - _ => {} - } - } - - // Write the vertical lines for labels that are on a different line as the underline. - // - // After this we will have: - // - // 2 | fn foo() { - // | __________ - // | | | - // | | - // 3 | | - // 4 | | } - // | |_ - for &(pos, annotation) in &annotations_positions { - let style = get_annotation_style(&annotation.annotation_type, stylesheet); - let pos = pos + 1; - if pos > 1 && (annotation.has_label() || annotation.takes_space()) { - for p in line_offset + 2..=line_offset + pos { - buffer.putc( - p, - (code_offset + annotation.range.0).saturating_sub(left), - '|', - *style, - ); - } - } - match annotation.annotation_part { - DisplayAnnotationPart::MultilineStart(depth) => { - for p in line_offset + pos + 1..line_offset + line_len + 2 { - buffer.putc(p, width_offset + depth, '|', *style); - } - } - DisplayAnnotationPart::MultilineEnd(depth) => { - for p in line_offset..=line_offset + pos { - buffer.putc(p, width_offset + depth, '|', *style); - } - } - _ => {} - } - } - - // Add in any inline marks for any extra lines that have - // been created. Output should look like above. - for inline_mark in inline_marks { - let DisplayMarkType::AnnotationThrough(depth) = inline_mark.mark_type; - let style = get_annotation_style(&inline_mark.annotation_type, stylesheet); - if annotations_positions.is_empty() { - buffer.putc(line_offset, width_offset + depth, '|', *style); - } else { - for p in line_offset..=line_offset + line_len + 1 { - buffer.putc(p, width_offset + depth, '|', *style); - } - } - } - - // Write the labels on the annotations that actually have a label. - // - // After this we will have: - // - // 2 | fn foo() { - // | __________ - // | | - // | something about `foo` - // 3 | - // 4 | } - // | _ test - for &(pos, annotation) in &annotations_positions { - if !is_annotation_empty(&annotation.annotation) { - let style = - get_annotation_style(&annotation.annotation_type, stylesheet); - let mut formatted_len = if let Some(id) = &annotation.annotation.id { - 2 + id.len() - + annotation_type_len(&annotation.annotation.annotation_type) - } else { - annotation_type_len(&annotation.annotation.annotation_type) - }; - let (pos, col) = if pos == 0 { - (pos + 1, (annotation.range.1 + 1).saturating_sub(left)) - } else { - (pos + 2, annotation.range.0.saturating_sub(left)) - }; - if annotation.annotation_part - == DisplayAnnotationPart::LabelContinuation - { - formatted_len = 0; - } else if formatted_len != 0 { - formatted_len += 2; - let id = match &annotation.annotation.id { - Some(id) => format!("[{id}]"), - None => String::new(), - }; - buffer.puts( - line_offset + pos, - col + code_offset, - &format!( - "{}{}: ", - annotation_type_str(&annotation.annotation_type), - id - ), - *style, - ); - } else { - formatted_len = 0; - } - let mut before = 0; - for fragment in &annotation.annotation.label { - let inner_col = before + formatted_len + col + code_offset; - buffer.puts(line_offset + pos, inner_col, fragment.content, *style); - before += fragment.content.len(); - } - } - } - - // Sort from biggest span to smallest span so that smaller spans are - // represented in the output: - // - // x | fn foo() - // | ^^^---^^ - // | | | - // | | something about `foo` - // | something about `fn foo()` - annotations_positions.sort_by_key(|(_, ann)| { - // Decreasing order. When annotations share the same length, prefer `Primary`. - Reverse(ann.len()) - }); - - // Write the underlines. - // - // After this we will have: - // - // 2 | fn foo() { - // | ____-_____^ - // | | - // | something about `foo` - // 3 | - // 4 | } - // | _^ test - for &(_, annotation) in &annotations_positions { - let mark = match annotation.annotation_type { - DisplayAnnotationType::Error => '^', - DisplayAnnotationType::Warning => '-', - DisplayAnnotationType::Info => '-', - DisplayAnnotationType::Note => '-', - DisplayAnnotationType::Help => '-', - DisplayAnnotationType::None => ' ', - }; - let style = get_annotation_style(&annotation.annotation_type, stylesheet); - for p in annotation.range.0..annotation.range.1 { - buffer.putc( - line_offset + 1, - (code_offset + p).saturating_sub(left), - mark, - *style, - ); - } - } - } else if !inline_marks.is_empty() { - format_inline_marks( - line_offset, - inline_marks, - lineno_width, - stylesheet, - buffer, - )?; - } - Ok(()) - } - DisplayLine::Fold { inline_marks } => { - buffer.puts(line_offset, 0, "...", *stylesheet.line_no()); - if !inline_marks.is_empty() || 0 < multiline_depth { - format_inline_marks( - line_offset, - inline_marks, - lineno_width, - stylesheet, - buffer, - )?; - } - Ok(()) - } - DisplayLine::Raw(line) => { - self.format_raw_line(line_offset, line, lineno_width, stylesheet, buffer) - } - } - } -} - -/// Inline annotation which can be used in either Raw or Source line. -#[derive(Clone, Debug, PartialEq)] -pub(crate) struct Annotation<'a> { - pub(crate) annotation_type: DisplayAnnotationType, - pub(crate) id: Option<&'a str>, - pub(crate) label: Vec<DisplayTextFragment<'a>>, -} - -/// A single line used in `DisplayList`. -#[derive(Debug, PartialEq)] -pub(crate) enum DisplayLine<'a> { - /// A line with `lineno` portion of the slice. - Source { - lineno: Option<usize>, - inline_marks: Vec<DisplayMark>, - line: DisplaySourceLine<'a>, - annotations: Vec<DisplaySourceAnnotation<'a>>, - }, - - /// A line indicating a folded part of the slice. - Fold { inline_marks: Vec<DisplayMark> }, - - /// A line which is displayed outside of slices. - Raw(DisplayRawLine<'a>), -} - -/// A source line. -#[derive(Debug, PartialEq)] -pub(crate) enum DisplaySourceLine<'a> { - /// A line with the content of the Snippet. - Content { - text: &'a str, - range: (usize, usize), // meta information for annotation placement. - end_line: EndLine, - }, - /// An empty source line. - Empty, -} - -#[derive(Clone, Debug, PartialEq)] -pub(crate) struct DisplaySourceAnnotation<'a> { - pub(crate) annotation: Annotation<'a>, - pub(crate) range: (usize, usize), - pub(crate) annotation_type: DisplayAnnotationType, - pub(crate) annotation_part: DisplayAnnotationPart, -} - -impl DisplaySourceAnnotation<'_> { - fn has_label(&self) -> bool { - !self - .annotation - .label - .iter() - .all(|label| label.content.is_empty()) - } - - // Length of this annotation as displayed in the stderr output - fn len(&self) -> usize { - // Account for usize underflows - if self.range.1 > self.range.0 { - self.range.1 - self.range.0 - } else { - self.range.0 - self.range.1 - } - } - - fn takes_space(&self) -> bool { - // Multiline annotations always have to keep vertical space. - matches!( - self.annotation_part, - DisplayAnnotationPart::MultilineStart(_) | DisplayAnnotationPart::MultilineEnd(_) - ) - } -} - -/// Raw line - a line which does not have the `lineno` part and is not considered -/// a part of the snippet. -#[derive(Debug, PartialEq)] -pub(crate) enum DisplayRawLine<'a> { - /// A line which provides information about the location of the given - /// slice in the project structure. - Origin { - path: &'a str, - pos: Option<(usize, usize)>, - header_type: DisplayHeaderType, - }, - - /// An annotation line which is not part of any snippet. - Annotation { - annotation: Annotation<'a>, - - /// If set to `true`, the annotation will be aligned to the - /// lineno delimiter of the snippet. - source_aligned: bool, - /// If set to `true`, only the label of the `Annotation` will be - /// displayed. It allows for a multiline annotation to be aligned - /// without displaying the meta information (`type` and `id`) to be - /// displayed on each line. - continuation: bool, - }, -} - -/// An inline text fragment which any label is composed of. -#[derive(Clone, Debug, PartialEq)] -pub(crate) struct DisplayTextFragment<'a> { - pub(crate) content: &'a str, - pub(crate) style: DisplayTextStyle, -} - -/// A style for the `DisplayTextFragment` which can be visually formatted. -/// -/// This information may be used to emphasis parts of the label. -#[derive(Debug, Clone, Copy, PartialEq)] -pub(crate) enum DisplayTextStyle { - Regular, - Emphasis, -} - -/// An indicator of what part of the annotation a given `Annotation` is. -#[derive(Debug, Clone, PartialEq)] -pub(crate) enum DisplayAnnotationPart { - /// A standalone, single-line annotation. - Standalone, - /// A continuation of a multi-line label of an annotation. - LabelContinuation, - /// A line starting a multiline annotation. - MultilineStart(usize), - /// A line ending a multiline annotation. - MultilineEnd(usize), -} - -/// A visual mark used in `inline_marks` field of the `DisplaySourceLine`. -#[derive(Debug, Clone, PartialEq)] -pub(crate) struct DisplayMark { - pub(crate) mark_type: DisplayMarkType, - pub(crate) annotation_type: DisplayAnnotationType, -} - -/// A type of the `DisplayMark`. -#[derive(Debug, Clone, PartialEq)] -pub(crate) enum DisplayMarkType { - /// A mark indicating a multiline annotation going through the current line. - AnnotationThrough(usize), -} - -/// A type of the `Annotation` which may impact the sigils, style or text displayed. -/// -/// There are several ways to uses this information when formatting the `DisplayList`: -/// -/// * An annotation may display the name of the type like `error` or `info`. -/// * An underline for `Error` may be `^^^` while for `Warning` it could be `---`. -/// * `ColorStylesheet` may use different colors for different annotations. -#[derive(Debug, Clone, PartialEq)] -pub(crate) enum DisplayAnnotationType { - None, - Error, - Warning, - Info, - Note, - Help, -} - -impl From<snippet::Level> for DisplayAnnotationType { - fn from(at: snippet::Level) -> Self { - match at { - snippet::Level::Error => DisplayAnnotationType::Error, - snippet::Level::Warning => DisplayAnnotationType::Warning, - snippet::Level::Info => DisplayAnnotationType::Info, - snippet::Level::Note => DisplayAnnotationType::Note, - snippet::Level::Help => DisplayAnnotationType::Help, - } - } -} - -/// Information whether the header is the initial one or a consequitive one -/// for multi-slice cases. -// TODO: private -#[derive(Debug, Clone, PartialEq)] -pub(crate) enum DisplayHeaderType { - /// Initial header is the first header in the snippet. - Initial, - - /// Continuation marks all headers of following slices in the snippet. - Continuation, -} - -struct CursorLines<'a>(&'a str); - -impl CursorLines<'_> { - fn new(src: &str) -> CursorLines<'_> { - CursorLines(src) - } -} - -#[derive(Copy, Clone, Debug, PartialEq)] -pub(crate) enum EndLine { - Eof, - Lf, - Crlf, -} - -impl EndLine { - /// The number of characters this line ending occupies in bytes. - pub(crate) fn len(self) -> usize { - match self { - EndLine::Eof => 0, - EndLine::Lf => 1, - EndLine::Crlf => 2, - } - } -} - -impl<'a> Iterator for CursorLines<'a> { - type Item = (&'a str, EndLine); - - fn next(&mut self) -> Option<Self::Item> { - if self.0.is_empty() { - None - } else { - self.0 - .find('\n') - .map(|x| { - let ret = if 0 < x { - if self.0.as_bytes()[x - 1] == b'\r' { - (&self.0[..x - 1], EndLine::Crlf) - } else { - (&self.0[..x], EndLine::Lf) - } - } else { - ("", EndLine::Lf) - }; - self.0 = &self.0[x + 1..]; - ret - }) - .or_else(|| { - let ret = Some((self.0, EndLine::Eof)); - self.0 = ""; - ret - }) - } - } -} - -fn format_message( - message: snippet::Message<'_>, - term_width: usize, - anonymized_line_numbers: bool, - primary: bool, -) -> Vec<DisplaySet<'_>> { - let snippet::Message { - level, - id, - title, - footer, - snippets, - } = message; - - let mut sets = vec![]; - let body = if !snippets.is_empty() || primary { - vec![format_title(level, id, title)] - } else { - format_footer(level, id, title) - }; - - let num_snippets = snippets.len(); - for (idx, snippet) in snippets.into_iter().enumerate() { - let snippet = fold_prefix_suffix(snippet); - sets.push(format_snippet( - snippet, - idx == 0, - idx == 0 && num_snippets > 1, - term_width, - anonymized_line_numbers, - )); - } - - if let Some(first) = sets.first_mut() { - for line in body { - first.display_lines.insert(0, line); - } - } else { - sets.push(DisplaySet { - display_lines: body, - margin: Margin::new(0, 0, 0, 0, DEFAULT_TERM_WIDTH, 0), - }); - } - - for annotation in footer { - sets.extend(format_message( - annotation, - term_width, - anonymized_line_numbers, - false, - )); - } - - sets -} - -fn format_title<'a>(level: crate::Level, id: Option<&'a str>, label: &'a str) -> DisplayLine<'a> { - DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::from(level), - id, - label: format_label(Some(label), Some(DisplayTextStyle::Emphasis)), - }, - source_aligned: false, - continuation: false, - }) -} - -fn format_footer<'a>( - level: crate::Level, - id: Option<&'a str>, - label: &'a str, -) -> Vec<DisplayLine<'a>> { - let mut result = vec![]; - for (i, line) in label.lines().enumerate() { - result.push(DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: DisplayAnnotationType::from(level), - id, - label: format_label(Some(line), None), - }, - source_aligned: true, - continuation: i != 0, - })); - } - result -} - -fn format_label( - label: Option<&str>, - style: Option<DisplayTextStyle>, -) -> Vec<DisplayTextFragment<'_>> { - let mut result = vec![]; - if let Some(label) = label { - let element_style = style.unwrap_or(DisplayTextStyle::Regular); - result.push(DisplayTextFragment { - content: label, - style: element_style, - }); - } - result -} - -fn format_snippet( - snippet: snippet::Snippet<'_>, - is_first: bool, - needs_trailing_pipe: bool, - term_width: usize, - anonymized_line_numbers: bool, -) -> DisplaySet<'_> { - let main_range = snippet.annotations.first().map(|x| x.range.start); - let origin = snippet.origin; - let need_empty_header = origin.is_some() || is_first; - let mut body = format_body( - snippet, - need_empty_header, - needs_trailing_pipe, - term_width, - anonymized_line_numbers, - ); - let header = format_header(origin, main_range, &body.display_lines, is_first); - - if let Some(header) = header { - body.display_lines.insert(0, header); - } - - body -} - -#[inline] -// TODO: option_zip -fn zip_opt<A, B>(a: Option<A>, b: Option<B>) -> Option<(A, B)> { - a.and_then(|a| b.map(|b| (a, b))) -} - -fn format_header<'a>( - origin: Option<&'a str>, - main_range: Option<usize>, - body: &[DisplayLine<'_>], - is_first: bool, -) -> Option<DisplayLine<'a>> { - let display_header = if is_first { - DisplayHeaderType::Initial - } else { - DisplayHeaderType::Continuation - }; - - if let Some((main_range, path)) = zip_opt(main_range, origin) { - let mut col = 1; - let mut line_offset = 1; - - for item in body { - if let DisplayLine::Source { - line: - DisplaySourceLine::Content { - text, - range, - end_line, - }, - lineno, - .. - } = item - { - if main_range >= range.0 && main_range < range.1 + max(*end_line as usize, 1) { - let char_column = text[0..(main_range - range.0).min(text.len())] - .chars() - .count(); - col = char_column + 1; - line_offset = lineno.unwrap_or(1); - break; - } - } - } - - return Some(DisplayLine::Raw(DisplayRawLine::Origin { - path, - pos: Some((line_offset, col)), - header_type: display_header, - })); - } - - if let Some(path) = origin { - return Some(DisplayLine::Raw(DisplayRawLine::Origin { - path, - pos: None, - header_type: display_header, - })); - } - - None -} - -fn fold_prefix_suffix(mut snippet: snippet::Snippet<'_>) -> snippet::Snippet<'_> { - if !snippet.fold { - return snippet; - } - - let ann_start = snippet - .annotations - .iter() - .map(|ann| ann.range.start) - .min() - .unwrap_or(0); - if let Some(before_new_start) = snippet.source[0..ann_start].rfind('\n') { - let new_start = before_new_start + 1; - - let line_offset = newline_count(&snippet.source[..new_start]); - snippet.line_start += line_offset; - - snippet.source = &snippet.source[new_start..]; - - for ann in &mut snippet.annotations { - let range_start = ann.range.start - new_start; - let range_end = ann.range.end - new_start; - ann.range = range_start..range_end; - } - } - - let ann_end = snippet - .annotations - .iter() - .map(|ann| ann.range.end) - .max() - .unwrap_or(snippet.source.len()); - if let Some(end_offset) = snippet.source[ann_end..].find('\n') { - let new_end = ann_end + end_offset; - snippet.source = &snippet.source[..new_end]; - } - - snippet -} - -fn newline_count(body: &str) -> usize { - #[cfg(feature = "simd")] - { - memchr::memchr_iter(b'\n', body.as_bytes()).count() - } - #[cfg(not(feature = "simd"))] - { - body.lines().count() - } -} - -fn fold_body(body: Vec<DisplayLine<'_>>) -> Vec<DisplayLine<'_>> { - const INNER_CONTEXT: usize = 1; - const INNER_UNFOLD_SIZE: usize = INNER_CONTEXT * 2 + 1; - - let mut lines = vec![]; - let mut unhighlighted_lines = vec![]; - for line in body { - match &line { - DisplayLine::Source { annotations, .. } => { - if annotations.is_empty() { - unhighlighted_lines.push(line); - } else { - if lines.is_empty() { - // Ignore leading unhighlighted lines - unhighlighted_lines.clear(); - } - match unhighlighted_lines.len() { - 0 => {} - n if n <= INNER_UNFOLD_SIZE => { - // Rather than render `...`, don't fold - lines.append(&mut unhighlighted_lines); - } - _ => { - lines.extend(unhighlighted_lines.drain(..INNER_CONTEXT)); - let inline_marks = lines - .last() - .and_then(|line| { - if let DisplayLine::Source { - ref inline_marks, .. - } = line - { - let inline_marks = inline_marks.clone(); - Some(inline_marks) - } else { - None - } - }) - .unwrap_or_default(); - lines.push(DisplayLine::Fold { - inline_marks: inline_marks.clone(), - }); - unhighlighted_lines - .drain(..unhighlighted_lines.len().saturating_sub(INNER_CONTEXT)); - lines.append(&mut unhighlighted_lines); - } - } - lines.push(line); - } - } - _ => { - unhighlighted_lines.push(line); - } - } - } - - lines -} - -fn format_body( - snippet: snippet::Snippet<'_>, - need_empty_header: bool, - needs_trailing_pipe: bool, - term_width: usize, - anonymized_line_numbers: bool, -) -> DisplaySet<'_> { - let source_len = snippet.source.len(); - if let Some(bigger) = snippet.annotations.iter().find_map(|x| { - // Allow highlighting one past the last character in the source. - if source_len + 1 < x.range.end { - Some(&x.range) - } else { - None - } - }) { - panic!("SourceAnnotation range `{bigger:?}` is beyond the end of buffer `{source_len}`") - } - - let mut body = vec![]; - let mut current_line = snippet.line_start; - let mut current_index = 0; - - let mut whitespace_margin = usize::MAX; - let mut span_left_margin = usize::MAX; - let mut span_right_margin = 0; - let mut label_right_margin = 0; - let mut max_line_len = 0; - - let mut depth_map: HashMap<usize, usize> = HashMap::new(); - let mut current_depth = 0; - let mut annotations = snippet.annotations; - let ranges = annotations - .iter() - .map(|a| a.range.clone()) - .collect::<Vec<_>>(); - // We want to merge multiline annotations that have the same range into one - // multiline annotation to save space. This is done by making any duplicate - // multiline annotations into a single-line annotation pointing at the end - // of the range. - // - // 3 | X0 Y0 Z0 - // | _____^ - // | | ____| - // | || ___| - // | ||| - // 4 | ||| X1 Y1 Z1 - // 5 | ||| X2 Y2 Z2 - // | ||| ^ - // | |||____| - // | ||____`X` is a good letter - // | |____`Y` is a good letter too - // | `Z` label - // Should be - // error: foo - // --> test.rs:3:3 - // | - // 3 | / X0 Y0 Z0 - // 4 | | X1 Y1 Z1 - // 5 | | X2 Y2 Z2 - // | | ^ - // | |____| - // | `X` is a good letter - // | `Y` is a good letter too - // | `Z` label - // | - ranges.iter().enumerate().for_each(|(r_idx, range)| { - annotations - .iter_mut() - .enumerate() - .skip(r_idx + 1) - .for_each(|(ann_idx, ann)| { - // Skip if the annotation's index matches the range index - if ann_idx != r_idx - // We only want to merge multiline annotations - && snippet.source[ann.range.clone()].lines().count() > 1 - // We only want to merge annotations that have the same range - && ann.range.start == range.start - && ann.range.end == range.end - { - ann.range.start = ann.range.end.saturating_sub(1); - } - }); - }); - annotations.sort_by_key(|a| a.range.start); - let mut annotations = annotations.into_iter().enumerate().collect::<Vec<_>>(); - - for (idx, (line, end_line)) in CursorLines::new(snippet.source).enumerate() { - let line_length: usize = line.len(); - let line_range = (current_index, current_index + line_length); - let end_line_size = end_line.len(); - body.push(DisplayLine::Source { - lineno: Some(current_line), - inline_marks: vec![], - line: DisplaySourceLine::Content { - text: line, - range: line_range, - end_line, - }, - annotations: vec![], - }); - - let leading_whitespace = line - .chars() - .take_while(|c| c.is_whitespace()) - .map(|c| { - match c { - // Tabs are displayed as 4 spaces - '\t' => 4, - _ => 1, - } - }) - .sum(); - if line.chars().any(|c| !c.is_whitespace()) { - whitespace_margin = min(whitespace_margin, leading_whitespace); - } - max_line_len = max(max_line_len, line_length); - - let line_start_index = line_range.0; - let line_end_index = line_range.1; - current_line += 1; - current_index += line_length + end_line_size; - - // It would be nice to use filter_drain here once it's stable. - annotations.retain(|(key, annotation)| { - let body_idx = idx; - let annotation_type = match annotation.level { - snippet::Level::Error => DisplayAnnotationType::None, - snippet::Level::Warning => DisplayAnnotationType::None, - _ => DisplayAnnotationType::from(annotation.level), - }; - let label_right = annotation.label.map_or(0, |label| label.len() + 1); - match annotation.range { - // This handles if the annotation is on the next line. We add - // the `end_line_size` to account for annotating the line end. - Range { start, .. } if start > line_end_index + end_line_size => true, - // This handles the case where an annotation is contained - // within the current line including any line-end characters. - Range { start, end } - if start >= line_start_index - // We add at least one to `line_end_index` to allow - // highlighting the end of a file - && end <= line_end_index + max(end_line_size, 1) => - { - if let DisplayLine::Source { - ref mut annotations, - .. - } = body[body_idx] - { - let annotation_start_col = line - [0..(start - line_start_index).min(line_length)] - .chars() - .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) - .sum::<usize>(); - let mut annotation_end_col = line - [0..(end - line_start_index).min(line_length)] - .chars() - .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) - .sum::<usize>(); - if annotation_start_col == annotation_end_col { - // At least highlight something - annotation_end_col += 1; - } - - span_left_margin = min(span_left_margin, annotation_start_col); - span_right_margin = max(span_right_margin, annotation_end_col); - label_right_margin = - max(label_right_margin, annotation_end_col + label_right); - - let range = (annotation_start_col, annotation_end_col); - annotations.push(DisplaySourceAnnotation { - annotation: Annotation { - annotation_type, - id: None, - label: format_label(annotation.label, None), - }, - range, - annotation_type: DisplayAnnotationType::from(annotation.level), - annotation_part: DisplayAnnotationPart::Standalone, - }); - } - false - } - // This handles the case where a multiline annotation starts - // somewhere on the current line, including any line-end chars - Range { start, end } - if start >= line_start_index - // The annotation can start on a line ending - && start <= line_end_index + end_line_size.saturating_sub(1) - && end > line_end_index => - { - if let DisplayLine::Source { - ref mut annotations, - .. - } = body[body_idx] - { - let annotation_start_col = line - [0..(start - line_start_index).min(line_length)] - .chars() - .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) - .sum::<usize>(); - let annotation_end_col = annotation_start_col + 1; - - span_left_margin = min(span_left_margin, annotation_start_col); - span_right_margin = max(span_right_margin, annotation_end_col); - label_right_margin = - max(label_right_margin, annotation_end_col + label_right); - - let range = (annotation_start_col, annotation_end_col); - annotations.push(DisplaySourceAnnotation { - annotation: Annotation { - annotation_type, - id: None, - label: vec![], - }, - range, - annotation_type: DisplayAnnotationType::from(annotation.level), - annotation_part: DisplayAnnotationPart::MultilineStart(current_depth), - }); - depth_map.insert(*key, current_depth); - current_depth += 1; - } - true - } - // This handles the case where a multiline annotation starts - // somewhere before this line and ends after it as well - Range { start, end } - if start < line_start_index && end > line_end_index + max(end_line_size, 1) => - { - if let DisplayLine::Source { - ref mut inline_marks, - .. - } = body[body_idx] - { - let depth = depth_map.get(key).cloned().unwrap_or_default(); - inline_marks.push(DisplayMark { - mark_type: DisplayMarkType::AnnotationThrough(depth), - annotation_type: DisplayAnnotationType::from(annotation.level), - }); - } - true - } - // This handles the case where a multiline annotation ends - // somewhere on the current line, including any line-end chars - Range { start, end } - if start < line_start_index - && end >= line_start_index - // We add at least one to `line_end_index` to allow - // highlighting the end of a file - && end <= line_end_index + max(end_line_size, 1) => - { - if let DisplayLine::Source { - ref mut annotations, - .. - } = body[body_idx] - { - let end_mark = line[0..(end - line_start_index).min(line_length)] - .chars() - .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0)) - .sum::<usize>() - .saturating_sub(1); - // If the annotation ends on a line-end character, we - // need to annotate one past the end of the line - let (end_mark, end_plus_one) = if end > line_end_index - // Special case for highlighting the end of a file - || (end == line_end_index + 1 && end_line_size == 0) - { - (end_mark + 1, end_mark + 2) - } else { - (end_mark, end_mark + 1) - }; - - span_left_margin = min(span_left_margin, end_mark); - span_right_margin = max(span_right_margin, end_plus_one); - label_right_margin = max(label_right_margin, end_plus_one + label_right); - - let range = (end_mark, end_plus_one); - let depth = depth_map.remove(key).unwrap_or(0); - annotations.push(DisplaySourceAnnotation { - annotation: Annotation { - annotation_type, - id: None, - label: format_label(annotation.label, None), - }, - range, - annotation_type: DisplayAnnotationType::from(annotation.level), - annotation_part: DisplayAnnotationPart::MultilineEnd(depth), - }); - } - false - } - _ => true, - } - }); - // Reset the depth counter, but only after we've processed all - // annotations for a given line. - let max = depth_map.len(); - if current_depth > max { - current_depth = max; - } - } - - if snippet.fold { - body = fold_body(body); - } - - if need_empty_header { - body.insert( - 0, - DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - annotations: vec![], - }, - ); - } - - if needs_trailing_pipe { - body.push(DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - annotations: vec![], - }); - } - - let max_line_num_len = if anonymized_line_numbers { - ANONYMIZED_LINE_NUM.len() - } else { - current_line.to_string().len() - }; - - let width_offset = 3 + max_line_num_len; - - if span_left_margin == usize::MAX { - span_left_margin = 0; - } - - let margin = Margin::new( - whitespace_margin, - span_left_margin, - span_right_margin, - label_right_margin, - term_width.saturating_sub(width_offset), - max_line_len, - ); - - DisplaySet { - display_lines: body, - margin, - } -} - -#[inline] -fn annotation_type_str(annotation_type: &DisplayAnnotationType) -> &'static str { - match annotation_type { - DisplayAnnotationType::Error => ERROR_TXT, - DisplayAnnotationType::Help => HELP_TXT, - DisplayAnnotationType::Info => INFO_TXT, - DisplayAnnotationType::Note => NOTE_TXT, - DisplayAnnotationType::Warning => WARNING_TXT, - DisplayAnnotationType::None => "", - } -} - -fn annotation_type_len(annotation_type: &DisplayAnnotationType) -> usize { - match annotation_type { - DisplayAnnotationType::Error => ERROR_TXT.len(), - DisplayAnnotationType::Help => HELP_TXT.len(), - DisplayAnnotationType::Info => INFO_TXT.len(), - DisplayAnnotationType::Note => NOTE_TXT.len(), - DisplayAnnotationType::Warning => WARNING_TXT.len(), - DisplayAnnotationType::None => 0, - } -} - -fn get_annotation_style<'a>( - annotation_type: &DisplayAnnotationType, - stylesheet: &'a Stylesheet, -) -> &'a Style { - match annotation_type { - DisplayAnnotationType::Error => stylesheet.error(), - DisplayAnnotationType::Warning => stylesheet.warning(), - DisplayAnnotationType::Info => stylesheet.info(), - DisplayAnnotationType::Note => stylesheet.note(), - DisplayAnnotationType::Help => stylesheet.help(), - DisplayAnnotationType::None => stylesheet.none(), - } -} - -#[inline] -fn is_annotation_empty(annotation: &Annotation<'_>) -> bool { - annotation - .label - .iter() - .all(|fragment| fragment.content.is_empty()) -} - -// We replace some characters so the CLI output is always consistent and underlines aligned. -const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[ - ('\t', " "), // We do our own tab replacement - ('\u{200D}', ""), // Replace ZWJ with nothing for consistent terminal output of grapheme clusters. - ('\u{202A}', ""), // The following unicode text flow control characters are inconsistently - ('\u{202B}', ""), // supported across CLIs and can cause confusion due to the bytes on disk - ('\u{202D}', ""), // not corresponding to the visible source code, so we replace them always. - ('\u{202E}', ""), - ('\u{2066}', ""), - ('\u{2067}', ""), - ('\u{2068}', ""), - ('\u{202C}', ""), - ('\u{2069}', ""), -]; - -fn normalize_whitespace(str: &str) -> String { - let mut s = str.to_owned(); - for (c, replacement) in OUTPUT_REPLACEMENTS { - s = s.replace(*c, replacement); - } - s -} - -fn overlaps( - a1: &DisplaySourceAnnotation<'_>, - a2: &DisplaySourceAnnotation<'_>, - padding: usize, -) -> bool { - (a2.range.0..a2.range.1).contains(&a1.range.0) - || (a1.range.0..a1.range.1 + padding).contains(&a2.range.0) -} - -fn format_inline_marks( - line: usize, - inline_marks: &[DisplayMark], - lineno_width: usize, - stylesheet: &Stylesheet, - buf: &mut StyledBuffer, -) -> fmt::Result { - for mark in inline_marks.iter() { - let annotation_style = get_annotation_style(&mark.annotation_type, stylesheet); - match mark.mark_type { - DisplayMarkType::AnnotationThrough(depth) => { - buf.putc(line, 3 + lineno_width + depth, '|', *annotation_style); - } - }; - } - Ok(()) -} diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index b9edcc6c..5fa9c8ec 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -1,3 +1,5 @@ +// Most of this file is adapted from https://github.com/rust-lang/rust/blob/160905b6253f42967ed4aef4b98002944c7df24c/compiler/rustc_errors/src/emitter.rs + //! The renderer for [`Message`]s //! //! # Example @@ -10,20 +12,29 @@ //! let renderer = Renderer::styled(); //! println!("{}", renderer.render(snippet)); -mod display_list; mod margin; +mod source_map; mod styled_buffer; pub(crate) mod stylesheet; +use crate::renderer::source_map::{AnnotatedLineInfo, Loc, SourceMap}; +use crate::renderer::styled_buffer::StyledBuffer; use crate::snippet::Message; +use crate::{Level, Snippet}; pub use anstyle::*; -use display_list::DisplayList; +use indexmap::IndexMap; use margin::Margin; -use std::fmt::Display; +use rustc_hash::{FxHashMap, FxHasher}; +use std::borrow::Cow; +use std::cmp::{max, min, Ordering, Reverse}; +use std::hash::BuildHasherDefault; use stylesheet::Stylesheet; +const ANONYMIZED_LINE_NUM: &str = "LL"; pub const DEFAULT_TERM_WIDTH: usize = 140; +type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>; + /// A renderer for [`Message`]s #[derive(Clone, Debug)] pub struct Renderer { @@ -150,14 +161,1411 @@ impl Renderer { self.stylesheet.none = style; self } +} + +impl Renderer { + pub fn render(&self, message: Message<'_>) -> String { + let mut buffer = StyledBuffer::new(); + let max_line_num_len = if self.anonymized_line_numbers { + ANONYMIZED_LINE_NUM.len() + } else { + let n = message.max_line_number(); + num_decimal_digits(n) + }; + + self.render_message(&mut buffer, message, max_line_num_len, false); + + buffer.render(&self.stylesheet).unwrap() + } + + fn render_message( + &self, + buffer: &mut StyledBuffer, + message: Message<'_>, + max_line_num_len: usize, + is_secondary: bool, + ) { + self.render_title(buffer, &message, max_line_num_len, is_secondary); + + let primary_origin = message.snippets.first().and_then(|s| s.origin); + + for snippet in message.snippets { + let source_map = SourceMap::new(snippet.source, snippet.line_start); + self.render_snippet_annotations( + buffer, + max_line_num_len, + &snippet, + primary_origin, + &source_map, + ); + } + + for footer in message.footer { + self.render_message(buffer, footer, max_line_num_len, true); + } + } + + fn render_title( + &self, + buffer: &mut StyledBuffer, + message: &Message<'_>, + max_line_num_len: usize, + is_secondary: bool, + ) { + let line_offset = buffer.num_lines(); + if !message.has_primary_spans() && !message.has_span_labels() && is_secondary { + // This is a secondary message with no span info + for _ in 0..max_line_num_len { + buffer.prepend(line_offset, " ", ElementStyle::NoStyle); + } + buffer.puts( + line_offset, + max_line_num_len + 1, + "= ", + ElementStyle::LineNumber, + ); + buffer.append( + line_offset, + message.level.as_str(), + ElementStyle::MainHeaderMsg, + ); + buffer.append(line_offset, ": ", ElementStyle::NoStyle); + self.msgs_to_buffer(buffer, message.title, max_line_num_len, "note", None); + } else { + let mut label_width = 0; + + buffer.append( + line_offset, + message.level.as_str(), + ElementStyle::Level(message.level), + ); + label_width += message.level.as_str().len(); + if let Some(id) = message.id { + buffer.append(line_offset, "[", ElementStyle::Level(message.level)); + buffer.append(line_offset, id, ElementStyle::Level(message.level)); + buffer.append(line_offset, "]", ElementStyle::Level(message.level)); + label_width += 2 + id.len(); + } + let header_style = if is_secondary { + ElementStyle::HeaderMsg + } else { + ElementStyle::MainHeaderMsg + }; + buffer.append(line_offset, ": ", header_style); + label_width += 2; + if !message.title.is_empty() { + for (line, text) in normalize_whitespace(message.title).lines().enumerate() { + buffer.append( + line_offset + line, + &format!( + "{}{}", + if line == 0 { + String::new() + } else { + " ".repeat(label_width) + }, + text + ), + header_style, + ); + } + } + } + } + + /// Adds a left margin to every line but the first, given a padding length and the label being + /// displayed, keeping the provided highlighting. + fn msgs_to_buffer( + &self, + buffer: &mut StyledBuffer, + title: &str, + padding: usize, + label: &str, + override_style: Option<ElementStyle>, + ) -> usize { + // The extra 5 ` ` is padding that's always needed to align to the `note: `: + // + // error: message + // --> file.rs:13:20 + // | + // 13 | <CODE> + // | ^^^^ + // | + // = note: multiline + // message + // ++^^^----xx + // | | | | + // | | | magic `2` + // | | length of label + // | magic `3` + // `max_line_num_len` + let padding = " ".repeat(padding + label.len() + 5); + + let mut line_number = buffer.num_lines().saturating_sub(1); + + // Provided the following diagnostic message: + // + // let msgs = vec![ + // (" + // ("highlighted multiline\nstring to\nsee how it ", Style::NoStyle), + // ("looks", Style::Highlight), + // ("with\nvery ", Style::NoStyle), + // ("weird", Style::Highlight), + // (" formats\n", Style::NoStyle), + // ("see?", Style::Highlight), + // ]; + // + // the expected output on a note is (* surround the highlighted text) + // + // = note: highlighted multiline + // string to + // see how it *looks* with + // very *weird* formats + // see? + let style = if let Some(override_style) = override_style { + override_style + } else { + ElementStyle::NoStyle + }; + let text = &normalize_whitespace(title); + let lines = text.split('\n').collect::<Vec<_>>(); + if lines.len() > 1 { + for (i, line) in lines.iter().enumerate() { + if i != 0 { + line_number += 1; + buffer.append(line_number, &padding, ElementStyle::NoStyle); + } + buffer.append(line_number, line, style); + } + } else { + buffer.append(line_number, text, style); + } + line_number + } + + fn render_snippet_annotations( + &self, + buffer: &mut StyledBuffer, + max_line_num_len: usize, + snippet: &Snippet<'_>, + primary_origin: Option<&str>, + sm: &SourceMap<'_>, + ) { + let annotated_lines = sm.annotated_lines(snippet.annotations.clone(), snippet.fold); + // print out the span location and spacer before we print the annotated source + // to do this, we need to know if this span will be primary + let is_primary = primary_origin == snippet.origin; + + if is_primary { + if let Some(origin) = snippet.origin { + // remember where we are in the output buffer for easy reference + let buffer_msg_line_offset = buffer.num_lines(); + + buffer.prepend( + buffer_msg_line_offset, + self.file_start(), + ElementStyle::LineNumber, + ); + let loc = if let Some(first_line) = + annotated_lines.iter().find(|l| !l.annotations.is_empty()) + { + let col = if let Some(first_annotation) = first_line.annotations.first() { + format!(":{}", first_annotation.start.char + 1) + } else { + String::new() + }; + format!("{}:{}{}", origin, first_line.line_index, col) + } else { + origin.to_owned() + }; + buffer.append(buffer_msg_line_offset, &loc, ElementStyle::LineAndColumn); + for _ in 0..max_line_num_len { + buffer.prepend(buffer_msg_line_offset, " ", ElementStyle::NoStyle); + } + } + } else { + if let Some(origin) = snippet.origin { + // remember where we are in the output buffer for easy reference + let buffer_msg_line_offset = buffer.num_lines(); + + // Add spacing line, as shown: + // --> $DIR/file:54:15 + // | + // LL | code + // | ^^^^ + // | (<- It prints *this* line) + // ::: $DIR/other_file.rs:15:5 + // | + // LL | code + // | ---- + self.draw_col_separator_no_space( + buffer, + buffer_msg_line_offset, + max_line_num_len + 1, + ); + + // Then, the secondary file indicator + buffer.prepend( + buffer_msg_line_offset + 1, + self.secondary_file_start(), + ElementStyle::LineNumber, + ); + let loc = if let Some(first_line) = + annotated_lines.iter().find(|l| !l.annotations.is_empty()) + { + let col = if let Some(first_annotation) = first_line.annotations.first() { + format!(":{}", first_annotation.start.char + 1) + } else { + String::new() + }; + format!("{}:{}{}", origin, first_line.line_index, col) + } else { + origin.to_owned() + }; + buffer.append( + buffer_msg_line_offset + 1, + &loc, + ElementStyle::LineAndColumn, + ); + for _ in 0..max_line_num_len { + buffer.prepend(buffer_msg_line_offset + 1, " ", ElementStyle::NoStyle); + } + } + } + + // Put in the spacer between the location and annotated source + let buffer_msg_line_offset = buffer.num_lines(); + self.draw_col_separator_no_space(buffer, buffer_msg_line_offset, max_line_num_len + 1); + + // Contains the vertical lines' positions for active multiline annotations + let mut multilines = FxIndexMap::default(); + + // Get the left-side margin to remove it + let mut whitespace_margin = usize::MAX; + for line_info in &annotated_lines { + // Whitespace can only be removed (aka considered leading) + // if the lexer considers it whitespace. + // non-rustc_lexer::is_whitespace() chars are reported as an + // error (ex. no-break-spaces \u{a0}), and thus can't be considered + // for removal during error reporting. + let leading_whitespace = line_info + .line + .chars() + .take_while(|c| c.is_whitespace()) + .map(|c| { + match c { + // Tabs are displayed as 4 spaces + '\t' => 4, + _ => 1, + } + }) + .sum(); + if line_info.line.chars().any(|c| !c.is_whitespace()) { + whitespace_margin = min(whitespace_margin, leading_whitespace); + } + } + if whitespace_margin == usize::MAX { + whitespace_margin = 0; + } + + // Left-most column any visible span points at. + let mut span_left_margin = usize::MAX; + for line_info in &annotated_lines { + for ann in &line_info.annotations { + span_left_margin = min(span_left_margin, ann.start.display); + span_left_margin = min(span_left_margin, ann.end.display); + } + } + if span_left_margin == usize::MAX { + span_left_margin = 0; + } + + // Right-most column any visible span points at. + let mut span_right_margin = 0; + let mut label_right_margin = 0; + let mut max_line_len = 0; + for line_info in &annotated_lines { + max_line_len = max(max_line_len, line_info.line.len()); + for ann in &line_info.annotations { + span_right_margin = max(span_right_margin, ann.start.display); + span_right_margin = max(span_right_margin, ann.end.display); + // FIXME: account for labels not in the same line + let label_right = ann.label.as_ref().map_or(0, |l| l.len() + 1); + label_right_margin = max(label_right_margin, ann.end.display + label_right); + } + } + let multiline_depth = annotated_lines.iter().fold(0, |acc, line_info| { + line_info.annotations.iter().fold(acc, |acc2, ann| { + max( + acc2, + match ann.annotation_type { + LineAnnotationType::Singleline => 0, + LineAnnotationType::MultilineStart(depth) => depth, + LineAnnotationType::MultilineEnd(depth) => depth, + LineAnnotationType::MultilineLine(depth) => depth, + }, + ) + }) + }); + let width_offset = 3 + max_line_num_len; + let code_offset = if multiline_depth == 0 { + width_offset + } else { + width_offset + multiline_depth + 1 + }; + + let column_width = self.term_width.saturating_sub(code_offset); + + let margin = Margin::new( + whitespace_margin, + span_left_margin, + span_right_margin, + label_right_margin, + column_width, + max_line_len, + ); + + // Next, output the annotate source for this file + for annotated_line_idx in 0..annotated_lines.len() { + let previous_buffer_line = buffer.num_lines(); + + let depths = self.render_source_line( + &annotated_lines[annotated_line_idx], + buffer, + width_offset, + code_offset, + margin, + ); + + let mut to_add = FxHashMap::default(); + + for (depth, style) in depths { + // FIXME(#120456) - is `swap_remove` correct? + if multilines.swap_remove(&depth).is_none() { + to_add.insert(depth, style); + } + } + + // Set the multiline annotation vertical lines to the left of + // the code in this line. + for (depth, style) in &multilines { + for line in previous_buffer_line..buffer.num_lines() { + self.draw_multiline_line(buffer, line, width_offset, *depth, *style); + } + } + // check to see if we need to print out or elide lines that come between + // this annotated line and the next one. + if annotated_line_idx < (annotated_lines.len() - 1) { + let line_idx_delta = annotated_lines[annotated_line_idx + 1].line_index + - annotated_lines[annotated_line_idx].line_index; + match line_idx_delta.cmp(&2) { + Ordering::Greater => { + let last_buffer_line_num = buffer.num_lines(); + buffer.puts(last_buffer_line_num, 0, "...", ElementStyle::LineNumber); + + // Set the multiline annotation vertical lines on `...` bridging line. + for (depth, style) in &multilines { + self.draw_multiline_line( + buffer, + last_buffer_line_num, + width_offset, + *depth, + *style, + ); + } + if let Some(line) = annotated_lines.get(annotated_line_idx) { + for ann in &line.annotations { + if let LineAnnotationType::MultilineStart(pos) = ann.annotation_type + { + // In the case where we have elided the entire start of the + // multispan because those lines were empty, we still need + // to draw the `|`s across the `...`. + self.draw_multiline_line( + buffer, + last_buffer_line_num, + width_offset, + pos, + ElementStyle::Level(ann.level), + ); + } + } + } + } + + Ordering::Equal => { + let unannotated_line = sm + .get_line(annotated_lines[annotated_line_idx].line_index + 1) + .unwrap_or(""); + + let last_buffer_line_num = buffer.num_lines(); + + self.draw_line( + buffer, + &normalize_whitespace(unannotated_line), + annotated_lines[annotated_line_idx + 1].line_index - 1, + last_buffer_line_num, + width_offset, + code_offset, + margin, + ); + + for (depth, style) in &multilines { + self.draw_multiline_line( + buffer, + last_buffer_line_num, + width_offset, + *depth, + *style, + ); + } + if let Some(line) = annotated_lines.get(annotated_line_idx) { + for ann in &line.annotations { + if let LineAnnotationType::MultilineStart(pos) = ann.annotation_type + { + self.draw_multiline_line( + buffer, + last_buffer_line_num, + width_offset, + pos, + ElementStyle::Level(ann.level), + ); + } + } + } + } + Ordering::Less => {} + } + } + + multilines.extend(&to_add); + } + } + + fn render_source_line( + &self, + line_info: &AnnotatedLineInfo<'_>, + buffer: &mut StyledBuffer, + width_offset: usize, + code_offset: usize, + margin: Margin, + ) -> Vec<(usize, ElementStyle)> { + // Draw: + // + // LL | ... code ... + // | ^^-^ span label + // | | + // | secondary span label + // + // ^^ ^ ^^^ ^^^^ ^^^ we don't care about code too far to the right of a span, we trim it + // | | | | + // | | | actual code found in your source code and the spans we use to mark it + // | | when there's too much wasted space to the left, trim it + // | vertical divider between the column number and the code + // column number + + if line_info.line_index == 0 { + return Vec::new(); + } + + let source_string = normalize_whitespace(line_info.line); + + let line_offset = buffer.num_lines(); + + // Left trim + let left = margin.left(source_string.len()); + + // FIXME: This looks fishy. See #132860. + // Account for unicode characters of width !=0 that were removed. + let left = source_string.chars().take(left).map(char_width).sum(); + + self.draw_line( + buffer, + &source_string, + line_info.line_index, + line_offset, + width_offset, + code_offset, + margin, + ); + + // Special case when there's only one annotation involved, it is the start of a multiline + // span and there's no text at the beginning of the code line. Instead of doing the whole + // graph: + // + // 2 | fn foo() { + // | _^ + // 3 | | + // 4 | | } + // | |_^ test + // + // we simplify the output to: + // + // 2 | / fn foo() { + // 3 | | + // 4 | | } + // | |_^ test + let mut buffer_ops = vec![]; + let mut annotations = vec![]; + let mut short_start = true; + for ann in &line_info.annotations { + if let LineAnnotationType::MultilineStart(depth) = ann.annotation_type { + if source_string + .chars() + .take(ann.start.display) + .all(char::is_whitespace) + { + let style = ElementStyle::Level(ann.level); + annotations.push((depth, style)); + buffer_ops.push((line_offset, width_offset + depth - 1, '/', style)); + } else { + short_start = false; + break; + } + } else if let LineAnnotationType::MultilineLine(_) = ann.annotation_type { + } else { + short_start = false; + break; + } + } + if short_start { + for (y, x, c, s) in buffer_ops { + buffer.putc(y, x, c, s); + } + return annotations; + } + + // We want to display like this: + // + // vec.push(vec.pop().unwrap()); + // --- ^^^ - previous borrow ends here + // | | + // | error occurs here + // previous borrow of `vec` occurs here + // + // But there are some weird edge cases to be aware of: + // + // vec.push(vec.pop().unwrap()); + // -------- - previous borrow ends here + // || + // |this makes no sense + // previous borrow of `vec` occurs here + // + // For this reason, we group the lines into "highlight lines" + // and "annotations lines", where the highlight lines have the `^`. + + // Sort the annotations by (start, end col) + // The labels are reversed, sort and then reversed again. + // Consider a list of annotations (A1, A2, C1, C2, B1, B2) where + // the letter signifies the span. Here we are only sorting by the + // span and hence, the order of the elements with the same span will + // not change. On reversing the ordering (|a, b| but b.cmp(a)), you get + // (C1, C2, B1, B2, A1, A2). All the elements with the same span are + // still ordered first to last, but all the elements with different + // spans are ordered by their spans in last to first order. Last to + // first order is important, because the jiggly lines and | are on + // the left, so the rightmost span needs to be rendered first, + // otherwise the lines would end up needing to go over a message. + + let mut annotations = line_info.annotations.clone(); + annotations.sort_by_key(|a| Reverse(a.start.display)); + + // First, figure out where each label will be positioned. + // + // In the case where you have the following annotations: + // + // vec.push(vec.pop().unwrap()); + // -------- - previous borrow ends here [C] + // || + // |this makes no sense [B] + // previous borrow of `vec` occurs here [A] + // + // `annotations_position` will hold [(2, A), (1, B), (0, C)]. + // + // We try, when possible, to stick the rightmost annotation at the end + // of the highlight line: + // + // vec.push(vec.pop().unwrap()); + // --- --- - previous borrow ends here + // + // But sometimes that's not possible because one of the other + // annotations overlaps it. For example, from the test + // `span_overlap_label`, we have the following annotations + // (written on distinct lines for clarity): + // + // fn foo(x: u32) { + // -------------- + // - + // + // In this case, we can't stick the rightmost-most label on + // the highlight line, or we would get: + // + // fn foo(x: u32) { + // -------- x_span + // | + // fn_span + // + // which is totally weird. Instead we want: + // + // fn foo(x: u32) { + // -------------- + // | | + // | x_span + // fn_span + // + // which is...less weird, at least. In fact, in general, if + // the rightmost span overlaps with any other span, we should + // use the "hang below" version, so we can at least make it + // clear where the span *starts*. There's an exception for this + // logic, when the labels do not have a message: + // + // fn foo(x: u32) { + // -------------- + // | + // x_span + // + // instead of: + // + // fn foo(x: u32) { + // -------------- + // | | + // | x_span + // <EMPTY LINE> + // + let mut annotations_position = vec![]; + let mut line_len: usize = 0; + let mut p = 0; + for (i, annotation) in annotations.iter().enumerate() { + for (j, next) in annotations.iter().enumerate() { + if overlaps(next, annotation, 0) // This label overlaps with another one and both + && annotation.has_label() // take space (they have text and are not + && j > i // multiline lines). + && p == 0 + // We're currently on the first line, move the label one line down + { + // If we're overlapping with an un-labelled annotation with the same span + // we can just merge them in the output + if next.start.display == annotation.start.display + && next.end.display == annotation.end.display + && !next.has_label() + { + continue; + } + + // This annotation needs a new line in the output. + p += 1; + break; + } + } + annotations_position.push((p, annotation)); + for (j, next) in annotations.iter().enumerate() { + if j > i { + let l = next.label.as_ref().map_or(0, |label| label.len() + 2); + if (overlaps(next, annotation, l) // Do not allow two labels to be in the same + // line if they overlap including padding, to + // avoid situations like: + // + // fn foo(x: u32) { + // -------^------ + // | | + // fn_spanx_span + // + && annotation.has_label() // Both labels must have some text, otherwise + && next.has_label()) // they are not overlapping. + // Do not add a new line if this annotation + // or the next are vertical line placeholders. + || (annotation.takes_space() // If either this or the next annotation is + && next.has_label()) // multiline start/end, move it to a new line + || (annotation.has_label() // so as not to overlap the horizontal lines. + && next.takes_space()) + || (annotation.takes_space() && next.takes_space()) + || (overlaps(next, annotation, l) + && next.end.display <= annotation.end.display + && next.has_label() + && p == 0) + // Avoid #42595. + { + // This annotation needs a new line in the output. + p += 1; + break; + } + } + } + line_len = max(line_len, p); + } + + if line_len != 0 { + line_len += 1; + } + + // If there are no annotations or the only annotations on this line are + // MultilineLine, then there's only code being shown, stop processing. + if line_info.annotations.iter().all(LineAnnotation::is_line) { + return vec![]; + } + + if annotations_position + .iter() + .all(|(_, ann)| matches!(ann.annotation_type, LineAnnotationType::MultilineStart(_))) + { + if let Some(max_pos) = annotations_position.iter().map(|(pos, _)| *pos).max() { + // Special case the following, so that we minimize overlapping multiline spans. + // + // 3 │ X0 Y0 Z0 + // │ ┏━━━━━┛ │ │ < We are writing these lines + // │ ┃┌───────┘ │ < by reverting the "depth" of + // │ ┃│┌─────────┘ < their multiline spans. + // 4 │ ┃││ X1 Y1 Z1 + // 5 │ ┃││ X2 Y2 Z2 + // │ ┃│└────╿──│──┘ `Z` label + // │ ┃└─────│──┤ + // │ ┗━━━━━━┥ `Y` is a good letter too + // ╰╴ `X` is a good letter + for (pos, _) in &mut annotations_position { + *pos = max_pos - *pos; + } + // We know then that we don't need an additional line for the span label, saving us + // one line of vertical space. + line_len = line_len.saturating_sub(1); + } + } + + // Write the column separator. + // + // After this we will have: + // + // 2 | fn foo() { + // | + // | + // | + // 3 | + // 4 | } + // | + for pos in 0..=line_len { + self.draw_col_separator_no_space(buffer, line_offset + pos + 1, width_offset - 2); + } + + // Write the horizontal lines for multiline annotations + // (only the first and last lines need this). + // + // After this we will have: + // + // 2 | fn foo() { + // | __________ + // | + // | + // 3 | + // 4 | } + // | _ + for &(pos, annotation) in &annotations_position { + let style = ElementStyle::Level(annotation.level); + let pos = pos + 1; + match annotation.annotation_type { + LineAnnotationType::MultilineStart(depth) + | LineAnnotationType::MultilineEnd(depth) => { + self.draw_range( + buffer, + '_', // underline.multiline_horizontal, + line_offset + pos, + width_offset + depth, + (code_offset + annotation.start.display).saturating_sub(left), + style, + ); + } + _ => {} + } + } + + // Write the vertical lines for labels that are on a different line as the underline. + // + // After this we will have: + // + // 2 | fn foo() { + // | __________ + // | | | + // | | + // 3 | | + // 4 | | } + // | |_ + for &(pos, annotation) in &annotations_position { + let style = ElementStyle::Level(annotation.level); + let pos = pos + 1; + + if pos > 1 && (annotation.has_label() || annotation.takes_space()) { + for p in line_offset + 1..=line_offset + pos { + buffer.putc( + p, + (code_offset + annotation.start.display).saturating_sub(left), + match annotation.annotation_type { + LineAnnotationType::MultilineLine(_) => '|', // underline.multiline_vertical, + _ => '|', // underline.vertical_text_line, + }, + style, + ); + } + if let LineAnnotationType::MultilineStart(_) = annotation.annotation_type { + buffer.putc( + line_offset + pos, + (code_offset + annotation.start.display).saturating_sub(left), + '|', // underline.bottom_right, + style, + ); + } + if matches!( + annotation.annotation_type, + LineAnnotationType::MultilineEnd(_) + ) && annotation.has_label() + { + buffer.putc( + line_offset + pos, + (code_offset + annotation.start.display).saturating_sub(left), + '|', // underline.multiline_bottom_right_with_text, + style, + ); + } + } + match annotation.annotation_type { + LineAnnotationType::MultilineStart(depth) => { + buffer.putc( + line_offset + pos, + width_offset + depth - 1, + ' ', // underline.top_left, + style, + ); + for p in line_offset + pos + 1..line_offset + line_len + 2 { + buffer.putc( + p, + width_offset + depth - 1, + '|', // underline.multiline_vertical, + style, + ); + } + } + LineAnnotationType::MultilineEnd(depth) => { + for p in line_offset..line_offset + pos { + buffer.putc( + p, + width_offset + depth - 1, + '|', // underline.multiline_vertical, + style, + ); + } + buffer.putc( + line_offset + pos, + width_offset + depth - 1, + '|', // underline.bottom_left, + style, + ); + } + _ => (), + } + } + + // Write the labels on the annotations that actually have a label. + // + // After this we will have: + // + // 2 | fn foo() { + // | __________ + // | | + // | something about `foo` + // 3 | + // 4 | } + // | _ test + for &(pos, annotation) in &annotations_position { + let style = ElementStyle::Level(annotation.level); + let (pos, col) = if pos == 0 { + if annotation.end.display == 0 { + (pos + 1, (annotation.end.display + 2).saturating_sub(left)) + } else { + (pos + 1, (annotation.end.display + 1).saturating_sub(left)) + } + } else { + (pos + 2, annotation.start.display.saturating_sub(left)) + }; + if let Some(label) = annotation.label { + buffer.puts(line_offset + pos, code_offset + col, label, style); + } + } + + // Sort from biggest span to smallest span so that smaller spans are + // represented in the output: + // + // x | fn foo() + // | ^^^---^^ + // | | | + // | | something about `foo` + // | something about `fn foo()` + annotations_position.sort_by_key(|(_, ann)| { + // Decreasing order. When annotations share the same length, prefer `Primary`. + Reverse(ann.len()) + }); + + // Write the underlines. + // + // After this we will have: + // + // 2 | fn foo() { + // | ____-_____^ + // | | + // | something about `foo` + // 3 | + // 4 | } + // | _^ test + for &(pos, annotation) in &annotations_position { + let style = ElementStyle::Level(annotation.level); + let underline = if annotation.level == Level::Error { + '^' + } else { + '-' + }; + for p in annotation.start.display..annotation.end.display { + // The default span label underline. + buffer.putc( + line_offset + 1, + (code_offset + p).saturating_sub(left), + underline, + style, + ); + } + + if pos == 0 + && matches!( + annotation.annotation_type, + LineAnnotationType::MultilineStart(_) | LineAnnotationType::MultilineEnd(_) + ) + { + // The beginning of a multiline span with its leftward moving line on the same line. + buffer.putc( + line_offset + 1, + (code_offset + annotation.start.display).saturating_sub(left), + underline, + style, + ); + } else if pos != 0 + && matches!( + annotation.annotation_type, + LineAnnotationType::MultilineStart(_) | LineAnnotationType::MultilineEnd(_) + ) + { + // The beginning of a multiline span with its leftward moving line on another line, + // so we start going down first. + buffer.putc( + line_offset + 1, + (code_offset + annotation.start.display).saturating_sub(left), + underline, + style, + ); + } else if pos != 0 && annotation.has_label() { + // The beginning of a span label with an actual label, we'll point down. + buffer.putc( + line_offset + 1, + (code_offset + annotation.start.display).saturating_sub(left), + underline, + style, + ); + } + } + annotations_position + .iter() + .filter_map(|&(_, annotation)| match annotation.annotation_type { + LineAnnotationType::MultilineStart(p) | LineAnnotationType::MultilineEnd(p) => { + let style = ElementStyle::Level(annotation.level); + Some((p, style)) + } + _ => None, + }) + .collect::<Vec<_>>() + } + + #[allow(clippy::too_many_arguments)] + fn draw_line( + &self, + buffer: &mut StyledBuffer, + source_string: &str, + line_index: usize, + line_offset: usize, + width_offset: usize, + code_offset: usize, + margin: Margin, + ) { + // Tabs are assumed to have been replaced by spaces in calling code. + debug_assert!(!source_string.contains('\t')); + let line_len = source_string.len(); + // Create the source line we will highlight. + let left = margin.left(line_len); + let right = margin.right(line_len); + // FIXME: The following code looks fishy. See #132860. + // On long lines, we strip the source line, accounting for unicode. + let mut taken = 0; + let code: String = source_string + .chars() + .skip(left) + .take_while(|ch| { + // Make sure that the trimming on the right will fall within the terminal width. + let next = char_width(*ch); + if taken + next > right - left { + return false; + } + taken += next; + true + }) + .collect(); + + buffer.puts(line_offset, code_offset, &code, ElementStyle::Quotation); + if margin.was_cut_left() { + // We have stripped some code/whitespace from the beginning, make it clear. + buffer.puts(line_offset, code_offset, "...", ElementStyle::LineNumber); + } + if margin.was_cut_right(line_len) { + // We have stripped some code after the rightmost span end, make it clear we did so. + buffer.puts( + line_offset, + code_offset + taken - 3, + "...", + ElementStyle::LineNumber, + ); + } + buffer.puts( + line_offset, + 0, + &self.maybe_anonymized(line_index), + ElementStyle::LineNumber, + ); + + self.draw_col_separator_no_space(buffer, line_offset, width_offset - 2); + } + + fn draw_range( + &self, + buffer: &mut StyledBuffer, + symbol: char, + line: usize, + col_from: usize, + col_to: usize, + style: ElementStyle, + ) { + for col in col_from..col_to { + buffer.putc(line, col, symbol, style); + } + } + + fn draw_multiline_line( + &self, + buffer: &mut StyledBuffer, + line: usize, + offset: usize, + depth: usize, + style: ElementStyle, + ) { + buffer.putc(line, offset + depth - 1, '|', style); + } + + fn draw_col_separator_no_space(&self, buffer: &mut StyledBuffer, line: usize, col: usize) { + self.draw_col_separator_no_space_with_style( + buffer, + '|', + line, + col, + ElementStyle::LineNumber, + ); + } + + fn draw_col_separator_no_space_with_style( + &self, + buffer: &mut StyledBuffer, + chr: char, + line: usize, + col: usize, + style: ElementStyle, + ) { + buffer.putc(line, col, chr, style); + } + + fn maybe_anonymized(&self, line_num: usize) -> Cow<'static, str> { + if self.anonymized_line_numbers { + Cow::Borrowed(ANONYMIZED_LINE_NUM) + } else { + Cow::Owned(line_num.to_string()) + } + } + + fn file_start(&self) -> &str { + "--> " + } + + fn secondary_file_start(&self) -> &str { + "::: " + } +} + +// instead of taking the String length or dividing by 10 while > 0, we multiply a limit by 10 until +// we're higher. If the loop isn't exited by the `return`, the last multiplication will wrap, which +// is OK, because while we cannot fit a higher power of 10 in a usize, the loop will end anyway. +// This is also why we need the max number of decimal digits within a `usize`. +fn num_decimal_digits(num: usize) -> usize { + #[cfg(target_pointer_width = "64")] + const MAX_DIGITS: usize = 20; + + #[cfg(target_pointer_width = "32")] + const MAX_DIGITS: usize = 10; + + #[cfg(target_pointer_width = "16")] + const MAX_DIGITS: usize = 5; + + let mut lim = 10; + for num_digits in 1..MAX_DIGITS { + if num < lim { + return num_digits; + } + lim = lim.wrapping_mul(10); + } + MAX_DIGITS +} + +pub fn str_width(s: &str) -> usize { + s.chars().map(char_width).sum() +} + +pub fn char_width(ch: char) -> usize { + // FIXME: `unicode_width` sometimes disagrees with terminals on how wide a `char` is. For now, + // just accept that sometimes the code line will be longer than desired. + match ch { + '\t' => 4, + // Keep the following list in sync with `rustc_errors::emitter::OUTPUT_REPLACEMENTS`. These + // are control points that we replace before printing with a visible codepoint for the sake + // of being able to point at them with underlines. + '\u{0000}' | '\u{0001}' | '\u{0002}' | '\u{0003}' | '\u{0004}' | '\u{0005}' + | '\u{0006}' | '\u{0007}' | '\u{0008}' | '\u{000B}' | '\u{000C}' | '\u{000D}' + | '\u{000E}' | '\u{000F}' | '\u{0010}' | '\u{0011}' | '\u{0012}' | '\u{0013}' + | '\u{0014}' | '\u{0015}' | '\u{0016}' | '\u{0017}' | '\u{0018}' | '\u{0019}' + | '\u{001A}' | '\u{001B}' | '\u{001C}' | '\u{001D}' | '\u{001E}' | '\u{001F}' + | '\u{007F}' | '\u{202A}' | '\u{202B}' | '\u{202D}' | '\u{202E}' | '\u{2066}' + | '\u{2067}' | '\u{2068}' | '\u{202C}' | '\u{2069}' => 1, + _ => unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1), + } +} + +fn num_overlap( + a_start: usize, + a_end: usize, + b_start: usize, + b_end: usize, + inclusive: bool, +) -> bool { + let extra = usize::from(inclusive); + (b_start..b_end + extra).contains(&a_start) || (a_start..a_end + extra).contains(&b_start) +} + +fn overlaps(a1: &LineAnnotation<'_>, a2: &LineAnnotation<'_>, padding: usize) -> bool { + num_overlap( + a1.start.display, + a1.end.display + padding, + a2.start.display, + a2.end.display, + false, + ) +} + +#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)] +pub(crate) enum LineAnnotationType { + /// Annotation under a single line of code + Singleline, + + // The Multiline type above is replaced with the following three in order + // to reuse the current label drawing code. + // + // Each of these corresponds to one part of the following diagram: + // + // x | foo(1 + bar(x, + // | _________^ < MultilineStart + // x | | y), < MultilineLine + // | |______________^ label < MultilineEnd + // x | z); + /// Annotation marking the first character of a fully shown multiline span + MultilineStart(usize), + /// Annotation marking the last character of a fully shown multiline span + MultilineEnd(usize), + /// Line at the left enclosing the lines of a fully shown multiline span + // Just a placeholder for the drawing algorithm, to know that it shouldn't skip the first 4 + // and last 2 lines of code. The actual line is drawn in `emit_message_default` and not in + // `draw_multiline_line`. + MultilineLine(usize), +} + +#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)] +pub(crate) struct LineAnnotation<'a> { + /// Start column. + /// Note that it is important that this field goes + /// first, so that when we sort, we sort orderings by start + /// column. + pub start: Loc, + + /// End column within the line (exclusive) + pub end: Loc, + + /// level + pub level: Level, + + /// Optional label to display adjacent to the annotation. + pub label: Option<&'a str>, + + /// Is this a single line, multiline or multiline span minimized down to a + /// smaller span. + pub annotation_type: LineAnnotationType, +} + +impl LineAnnotation<'_> { + /// Whether this annotation is a vertical line placeholder. + pub(crate) fn is_line(&self) -> bool { + matches!(self.annotation_type, LineAnnotationType::MultilineLine(_)) + } - /// Render a snippet into a `Display`able object - pub fn render<'a>(&'a self, msg: Message<'a>) -> impl Display + 'a { - DisplayList::new( - msg, - &self.stylesheet, - self.anonymized_line_numbers, - self.term_width, + /// Length of this annotation as displayed in the stderr output + pub(crate) fn len(&self) -> usize { + // Account for usize underflows + if self.end.display > self.start.display { + self.end.display - self.start.display + } else { + self.start.display - self.end.display + } + } + + pub(crate) fn has_label(&self) -> bool { + if let Some(label) = self.label { + // Consider labels with no text as effectively not being there + // to avoid weird output with unnecessary vertical lines, like: + // + // X | fn foo(x: u32) { + // | -------^------ + // | | | + // | | + // | + // + // Note that this would be the complete output users would see. + !label.is_empty() + } else { + false + } + } + + pub(crate) fn takes_space(&self) -> bool { + // Multiline annotations always have to keep vertical space. + matches!( + self.annotation_type, + LineAnnotationType::MultilineStart(_) | LineAnnotationType::MultilineEnd(_) ) } } + +// We replace some characters so the CLI output is always consistent and underlines aligned. +// Keep the following list in sync with `rustc_span::char_width`. +const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[ + // In terminals without Unicode support the following will be garbled, but in *all* terminals + // the underlying codepoint will be as well. We could gate this replacement behind a "unicode + // support" gate. + ('\0', "␀"), + ('\u{0001}', "␁"), + ('\u{0002}', "␂"), + ('\u{0003}', "␃"), + ('\u{0004}', "␄"), + ('\u{0005}', "␅"), + ('\u{0006}', "␆"), + ('\u{0007}', "␇"), + ('\u{0008}', "␈"), + ('\t', " "), // We do our own tab replacement + ('\u{000b}', "␋"), + ('\u{000c}', "␌"), + ('\u{000d}', "␍"), + ('\u{000e}', "␎"), + ('\u{000f}', "␏"), + ('\u{0010}', "␐"), + ('\u{0011}', "␑"), + ('\u{0012}', "␒"), + ('\u{0013}', "␓"), + ('\u{0014}', "␔"), + ('\u{0015}', "␕"), + ('\u{0016}', "␖"), + ('\u{0017}', "␗"), + ('\u{0018}', "␘"), + ('\u{0019}', "␙"), + ('\u{001a}', "␚"), + ('\u{001b}', "␛"), + ('\u{001c}', "␜"), + ('\u{001d}', "␝"), + ('\u{001e}', "␞"), + ('\u{001f}', "␟"), + ('\u{007f}', "␡"), + ('\u{200d}', ""), // Replace ZWJ for consistent terminal output of grapheme clusters. + ('\u{202a}', "�"), // The following unicode text flow control characters are inconsistently + ('\u{202b}', "�"), // supported across CLIs and can cause confusion due to the bytes on disk + ('\u{202c}', "�"), // not corresponding to the visible source code, so we replace them always. + ('\u{202d}', "�"), + ('\u{202e}', "�"), + ('\u{2066}', "�"), + ('\u{2067}', "�"), + ('\u{2068}', "�"), + ('\u{2069}', "�"), +]; + +fn normalize_whitespace(s: &str) -> String { + // Scan the input string for a character in the ordered table above. + // If it's present, replace it with its alternative string (it can be more than 1 char!). + // Otherwise, retain the input char. + s.chars().fold(String::with_capacity(s.len()), |mut s, c| { + match OUTPUT_REPLACEMENTS.binary_search_by_key(&c, |(k, _)| *k) { + Ok(i) => s.push_str(OUTPUT_REPLACEMENTS[i].1), + _ => s.push(c), + } + s + }) +} + +#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq)] +pub(crate) enum ElementStyle { + MainHeaderMsg, + HeaderMsg, + LineAndColumn, + LineNumber, + Quotation, + NoStyle, + Level(Level), +} + +impl ElementStyle { + fn color_spec(&self, stylesheet: &Stylesheet) -> Style { + match self { + ElementStyle::LineAndColumn => stylesheet.none, + ElementStyle::LineNumber => stylesheet.line_no, + ElementStyle::Quotation => stylesheet.none, + ElementStyle::MainHeaderMsg => stylesheet.emphasis, + ElementStyle::HeaderMsg | ElementStyle::NoStyle => stylesheet.none, + ElementStyle::Level(lvl) => lvl.style(stylesheet), + } + } +} + +#[cfg(test)] +mod test { + use super::OUTPUT_REPLACEMENTS; + use snapbox::IntoData; + + fn format_replacements(replacements: Vec<(char, &str)>) -> String { + replacements + .into_iter() + .map(|r| format!(" {r:?}")) + .collect::<Vec<_>>() + .join("\n") + } + + #[test] + /// The [`OUTPUT_REPLACEMENTS`] array must be sorted (for binary search to + /// work) and must contain no duplicate entries + fn ensure_output_replacements_is_sorted() { + let mut expected = OUTPUT_REPLACEMENTS.to_owned(); + expected.sort_by_key(|r| r.0); + expected.dedup_by_key(|r| r.0); + let expected = format_replacements(expected); + let actual = format_replacements(OUTPUT_REPLACEMENTS.to_owned()); + snapbox::assert_data_eq!(actual, expected.into_data().raw()); + } +} diff --git a/src/renderer/source_map.rs b/src/renderer/source_map.rs new file mode 100644 index 00000000..01695666 --- /dev/null +++ b/src/renderer/source_map.rs @@ -0,0 +1,446 @@ +use crate::renderer::{char_width, num_overlap, LineAnnotation, LineAnnotationType}; +use crate::{Annotation, Level}; +use std::cmp::{max, min}; +use std::ops::Range; + +#[derive(Debug)] +pub(crate) struct SourceMap<'a> { + lines: Vec<LineInfo<'a>>, + source: &'a str, +} + +impl<'a> SourceMap<'a> { + pub(crate) fn new(source: &'a str, line_start: usize) -> Self { + let mut current_index = 0; + + let mut mapping = vec![]; + for (idx, (line, end_line)) in CursorLines::new(source).enumerate() { + let line_length = line.len(); + let line_range = current_index..current_index + line_length; + let end_line_size = end_line.len(); + + mapping.push(LineInfo { + line, + line_index: line_start + idx, + start_byte: line_range.start, + end_byte: line_range.end + end_line_size, + end_line_size, + }); + + current_index += line_length + end_line_size; + } + Self { + lines: mapping, + source, + } + } + + pub(crate) fn get_line(&self, idx: usize) -> Option<&'a str> { + self.lines + .iter() + .find(|l| l.line_index == idx) + .map(|info| info.line) + } + + pub(crate) fn span_to_locations(&self, span: Range<usize>) -> (Loc, Loc) { + let start_info = self + .lines + .iter() + .find(|info| span.start >= info.start_byte && span.start < info.end_byte) + .unwrap_or(self.lines.last().unwrap()); + let (mut start_char_pos, start_display_pos) = start_info.line + [0..(span.start - start_info.start_byte).min(start_info.line.len())] + .chars() + .fold((0, 0), |(char_pos, byte_pos), c| { + let display = char_width(c); + (char_pos + 1, byte_pos + display) + }); + // correct the char pos if we are highlighting the end of a line + if (span.start - start_info.start_byte).saturating_sub(start_info.line.len()) > 0 { + start_char_pos += 1; + } + let start = Loc { + line: start_info.line_index, + char: start_char_pos, + display: start_display_pos, + byte: span.start, + }; + + if span.start == span.end { + return (start, start); + } + + let end_info = self + .lines + .iter() + .find(|info| info.end_byte > span.end.saturating_sub(1)) + .unwrap_or(self.lines.last().unwrap()); + let (mut end_char_pos, end_display_pos) = end_info.line + [0..(span.end - end_info.start_byte).min(end_info.line.len())] + .chars() + .fold((0, 0), |(char_pos, byte_pos), c| { + let display = char_width(c); + (char_pos + 1, byte_pos + display) + }); + + // correct the char pos if we are highlighting the end of a line + if (span.end - end_info.start_byte).saturating_sub(end_info.line.len()) > 0 { + end_char_pos += 1; + } + let mut end = Loc { + line: end_info.line_index, + char: end_char_pos, + display: end_display_pos, + byte: span.end, + }; + if start.line != end.line && end.byte > end_info.end_byte - end_info.end_line_size { + end.char += 1; + end.display += 1; + } + + (start, end) + } + + pub(crate) fn annotated_lines( + &self, + annotations: Vec<Annotation<'a>>, + fold: bool, + ) -> Vec<AnnotatedLineInfo<'a>> { + let source_len = self.source.len(); + if let Some(bigger) = annotations.iter().find_map(|x| { + // Allow highlighting one past the last character in the source. + if source_len + 1 < x.range.end { + Some(&x.range) + } else { + None + } + }) { + panic!("Annotation range `{bigger:?}` is beyond the end of buffer `{source_len}`") + } + + let mut annotated_line_infos = self + .lines + .iter() + .map(|info| AnnotatedLineInfo { + line: info.line, + line_index: info.line_index, + annotations: vec![], + }) + .collect::<Vec<_>>(); + let mut multiline_annotations = vec![]; + + for Annotation { + range, + label, + level, + } in annotations + { + let (lo, mut hi) = self.span_to_locations(range); + + // Watch out for "empty spans". If we get a span like 6..6, we + // want to just display a `^` at 6, so convert that to + // 6..7. This is degenerate input, but it's best to degrade + // gracefully -- and the parser likes to supply a span like + // that for EOF, in particular. + + if lo.display == hi.display && lo.line == hi.line { + hi.display += 1; + } + + if lo.line == hi.line { + let line_ann = LineAnnotation { + start: lo, + end: hi, + level, + label, + annotation_type: LineAnnotationType::Singleline, + }; + self.add_annotation_to_file(&mut annotated_line_infos, lo.line, line_ann); + } else { + multiline_annotations.push(MultilineAnnotation { + depth: 1, + start: lo, + end: hi, + level, + label, + overlaps_exactly: false, + }); + } + } + + // Find overlapping multiline annotations, put them at different depths + multiline_annotations + .sort_by_key(|ml| (ml.start.line, usize::MAX - ml.end.line, ml.start.byte)); + for ann in multiline_annotations.clone() { + for a in &mut multiline_annotations { + // Move all other multiline annotations overlapping with this one + // one level to the right. + if !ann.same_span(a) + && num_overlap(ann.start.line, ann.end.line, a.start.line, a.end.line, true) + { + a.increase_depth(); + } else if ann.same_span(a) && &ann != a { + a.overlaps_exactly = true; + } else { + break; + } + } + } + + let mut max_depth = 0; // max overlapping multiline spans + for ann in &multiline_annotations { + max_depth = max(max_depth, ann.depth); + } + // Change order of multispan depth to minimize the number of overlaps in the ASCII art. + for a in &mut multiline_annotations { + a.depth = max_depth - a.depth + 1; + } + for ann in multiline_annotations { + let mut end_ann = ann.as_end(); + if ann.overlaps_exactly { + end_ann.annotation_type = LineAnnotationType::Singleline; + } else { + // avoid output like + // + // | foo( + // | _____^ + // | |_____| + // | || bar, + // | || ); + // | || ^ + // | ||______| + // | |______foo + // | baz + // + // and instead get + // + // | foo( + // | _____^ + // | | bar, + // | | ); + // | | ^ + // | | | + // | |______foo + // | baz + self.add_annotation_to_file( + &mut annotated_line_infos, + ann.start.line, + ann.as_start(), + ); + // 4 is the minimum vertical length of a multiline span when presented: two lines + // of code and two lines of underline. This is not true for the special case where + // the beginning doesn't have an underline, but the current logic seems to be + // working correctly. + let middle = min(ann.start.line + 4, ann.end.line); + // We'll show up to 4 lines past the beginning of the multispan start. + // We will *not* include the tail of lines that are only whitespace, a comment or + // a bare delimiter. + let filter = |s: &str| { + let s = s.trim(); + // Consider comments as empty, but don't consider docstrings to be empty. + !(s.starts_with("//") && !(s.starts_with("///") || s.starts_with("//!"))) + // Consider lines with nothing but whitespace, a single delimiter as empty. + && !["", "{", "}", "(", ")", "[", "]"].contains(&s) + }; + let until = (ann.start.line..middle) + .rev() + .filter_map(|line| self.get_line(line).map(|s| (line + 1, s))) + .find(|(_, s)| filter(s)) + .map_or(ann.start.line, |(line, _)| line); + for line in ann.start.line + 1..until { + // Every `|` that joins the beginning of the span (`___^`) to the end (`|__^`). + self.add_annotation_to_file(&mut annotated_line_infos, line, ann.as_line()); + } + let line_end = ann.end.line - 1; + let end_is_empty = self.get_line(line_end).map_or(false, |s| !filter(s)); + if middle < line_end && !end_is_empty { + self.add_annotation_to_file(&mut annotated_line_infos, line_end, ann.as_line()); + } + } + self.add_annotation_to_file(&mut annotated_line_infos, end_ann.end.line, end_ann); + } + + if fold { + annotated_line_infos.retain(|l| !l.annotations.is_empty()); + } + + annotated_line_infos + .iter_mut() + .for_each(|l| l.annotations.sort_by(|a, b| a.start.cmp(&b.start))); + + annotated_line_infos + } + + fn add_annotation_to_file( + &self, + annotated_line_infos: &mut Vec<AnnotatedLineInfo<'a>>, + line_index: usize, + line_ann: LineAnnotation<'a>, + ) { + if let Some(line_info) = annotated_line_infos + .iter_mut() + .find(|line_info| line_info.line_index == line_index) + { + line_info.annotations.push(line_ann); + } else { + let info = self + .lines + .iter() + .find(|l| l.line_index == line_index) + .unwrap(); + annotated_line_infos.push(AnnotatedLineInfo { + line: info.line, + line_index, + annotations: vec![line_ann], + }); + annotated_line_infos.sort_by_key(|l| l.line_index); + } + } +} + +#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)] +pub(crate) struct MultilineAnnotation<'a> { + pub depth: usize, + pub start: Loc, + pub end: Loc, + pub level: Level, + pub label: Option<&'a str>, + pub overlaps_exactly: bool, +} + +impl<'a> MultilineAnnotation<'a> { + pub(crate) fn increase_depth(&mut self) { + self.depth += 1; + } + + /// Compare two `MultilineAnnotation`s considering only the `Span` they cover. + pub(crate) fn same_span(&self, other: &MultilineAnnotation<'_>) -> bool { + self.start == other.start && self.end == other.end + } + + pub(crate) fn as_start(&self) -> LineAnnotation<'a> { + LineAnnotation { + start: self.start, + end: Loc { + line: self.start.line, + char: self.start.char + 1, + display: self.start.display + 1, + byte: self.start.byte + 1, + }, + level: self.level, + label: None, + annotation_type: LineAnnotationType::MultilineStart(self.depth), + } + } + + pub(crate) fn as_end(&self) -> LineAnnotation<'a> { + LineAnnotation { + start: Loc { + line: self.end.line, + char: self.end.char.saturating_sub(1), + display: self.end.display.saturating_sub(1), + byte: self.end.byte.saturating_sub(1), + }, + end: self.end, + level: self.level, + label: self.label, + annotation_type: LineAnnotationType::MultilineEnd(self.depth), + } + } + + pub(crate) fn as_line(&self) -> LineAnnotation<'a> { + LineAnnotation { + start: Loc::default(), + end: Loc::default(), + level: self.level, + label: None, + annotation_type: LineAnnotationType::MultilineLine(self.depth), + } + } +} + +#[derive(Debug)] +pub(crate) struct LineInfo<'a> { + pub(crate) line: &'a str, + pub(crate) line_index: usize, + pub(crate) start_byte: usize, + pub(crate) end_byte: usize, + end_line_size: usize, +} + +#[derive(Debug)] +pub(crate) struct AnnotatedLineInfo<'a> { + pub(crate) line: &'a str, + pub(crate) line_index: usize, + pub(crate) annotations: Vec<LineAnnotation<'a>>, +} + +/// A source code location used for error reporting. +#[derive(Clone, Copy, Debug, Default, PartialOrd, Ord, PartialEq, Eq)] +pub(crate) struct Loc { + /// The (1-based) line number. + pub(crate) line: usize, + /// The (0-based) column offset. + pub(crate) char: usize, + /// The (0-based) column offset when displayed. + pub(crate) display: usize, + /// The (0-based) byte offset. + pub(crate) byte: usize, +} + +struct CursorLines<'a>(&'a str); + +impl CursorLines<'_> { + fn new(src: &str) -> CursorLines<'_> { + CursorLines(src) + } +} + +#[derive(Copy, Clone, Debug, PartialEq)] +enum EndLine { + Eof, + Lf, + Crlf, +} + +impl EndLine { + /// The number of characters this line ending occupies in bytes. + pub(crate) fn len(self) -> usize { + match self { + EndLine::Eof => 0, + EndLine::Lf => 1, + EndLine::Crlf => 2, + } + } +} + +impl<'a> Iterator for CursorLines<'a> { + type Item = (&'a str, EndLine); + + fn next(&mut self) -> Option<Self::Item> { + if self.0.is_empty() { + None + } else { + self.0 + .find('\n') + .map(|x| { + let ret = if 0 < x { + if self.0.as_bytes()[x - 1] == b'\r' { + (&self.0[..x - 1], EndLine::Crlf) + } else { + (&self.0[..x], EndLine::Lf) + } + } else { + ("", EndLine::Lf) + }; + self.0 = &self.0[x + 1..]; + ret + }) + .or_else(|| { + let ret = Some((self.0, EndLine::Eof)); + self.0 = ""; + ret + }) + } + } +} diff --git a/src/renderer/styled_buffer.rs b/src/renderer/styled_buffer.rs index ec834e1b..fd72358b 100644 --- a/src/renderer/styled_buffer.rs +++ b/src/renderer/styled_buffer.rs @@ -3,7 +3,7 @@ //! [styled_buffer]: https://github.com/rust-lang/rust/blob/894f7a4ba6554d3797404bbf550d9919df060b97/compiler/rustc_errors/src/styled_buffer.rs use crate::renderer::stylesheet::Stylesheet; -use anstyle::Style; +use crate::renderer::ElementStyle; use std::fmt; use std::fmt::Write; @@ -15,13 +15,13 @@ pub(crate) struct StyledBuffer { #[derive(Clone, Copy, Debug, PartialEq)] pub(crate) struct StyledChar { ch: char, - style: Style, + style: ElementStyle, } impl StyledChar { - pub(crate) const SPACE: Self = StyledChar::new(' ', Style::new()); + pub(crate) const SPACE: Self = StyledChar::new(' ', ElementStyle::NoStyle); - pub(crate) const fn new(ch: char, style: Style) -> StyledChar { + pub(crate) const fn new(ch: char, style: ElementStyle) -> StyledChar { StyledChar { ch, style } } } @@ -41,15 +41,16 @@ impl StyledBuffer { let mut str = String::new(); for (i, line) in self.lines.iter().enumerate() { let mut current_style = stylesheet.none; - for ch in line { - if ch.style != current_style { + for StyledChar { ch, style } in line { + let ch_style = style.color_spec(stylesheet); + if ch_style != current_style { if !line.is_empty() { write!(str, "{}", current_style.render_reset())?; } - current_style = ch.style; + current_style = ch_style; write!(str, "{}", current_style.render())?; } - write!(str, "{}", ch.ch)?; + write!(str, "{ch}")?; } write!(str, "{}", current_style.render_reset())?; if i != self.lines.len() - 1 { @@ -62,7 +63,7 @@ impl StyledBuffer { /// Sets `chr` with `style` for given `line`, `col`. /// If `line` does not exist in our buffer, adds empty lines up to the given /// and fills the last line with unstyled whitespace. - pub(crate) fn putc(&mut self, line: usize, col: usize, chr: char, style: Style) { + pub(crate) fn putc(&mut self, line: usize, col: usize, chr: char, style: ElementStyle) { self.ensure_lines(line); if col >= self.lines[line].len() { self.lines[line].resize(col + 1, StyledChar::SPACE); @@ -73,16 +74,17 @@ impl StyledBuffer { /// Sets `string` with `style` for given `line`, starting from `col`. /// If `line` does not exist in our buffer, adds empty lines up to the given /// and fills the last line with unstyled whitespace. - pub(crate) fn puts(&mut self, line: usize, col: usize, string: &str, style: Style) { + pub(crate) fn puts(&mut self, line: usize, col: usize, string: &str, style: ElementStyle) { let mut n = col; for c in string.chars() { self.putc(line, n, c, style); n += 1; } } + /// For given `line` inserts `string` with `style` after old content of that line, /// adding lines if needed - pub(crate) fn append(&mut self, line: usize, string: &str, style: Style) { + pub(crate) fn append(&mut self, line: usize, string: &str, style: ElementStyle) { if line >= self.lines.len() { self.puts(line, 0, string, style); } else { @@ -91,6 +93,22 @@ impl StyledBuffer { } } + /// For given `line` inserts `string` with `style` before old content of that line, + /// adding lines if needed + pub(crate) fn prepend(&mut self, line: usize, string: &str, style: ElementStyle) { + self.ensure_lines(line); + let string_len = string.chars().count(); + + if !self.lines[line].is_empty() { + // Push the old content over to make room for new content + for _ in 0..string_len { + self.lines[line].insert(0, StyledChar::SPACE); + } + } + + self.puts(line, 0, string, style); + } + pub(crate) fn num_lines(&self) -> usize { self.lines.len() } diff --git a/src/renderer/stylesheet.rs b/src/renderer/stylesheet.rs index ee1ab937..72a5f0ec 100644 --- a/src/renderer/stylesheet.rs +++ b/src/renderer/stylesheet.rs @@ -32,37 +32,3 @@ impl Stylesheet { } } } - -impl Stylesheet { - pub(crate) fn error(&self) -> &Style { - &self.error - } - - pub(crate) fn warning(&self) -> &Style { - &self.warning - } - - pub(crate) fn info(&self) -> &Style { - &self.info - } - - pub(crate) fn note(&self) -> &Style { - &self.note - } - - pub(crate) fn help(&self) -> &Style { - &self.help - } - - pub(crate) fn line_no(&self) -> &Style { - &self.line_no - } - - pub(crate) fn emphasis(&self) -> &Style { - &self.emphasis - } - - pub(crate) fn none(&self) -> &Style { - &self.none - } -} diff --git a/src/snippet.rs b/src/snippet.rs index 8e9a3a88..d9ff5944 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -10,8 +10,16 @@ //! .snippet(Snippet::source("Faa").line_start(129).origin("src/display.rs")); //! ``` +use crate::renderer::stylesheet::Stylesheet; +use anstyle::Style; use std::ops::Range; +pub(crate) const ERROR_TXT: &str = "error"; +pub(crate) const HELP_TXT: &str = "help"; +pub(crate) const INFO_TXT: &str = "info"; +pub(crate) const NOTE_TXT: &str = "note"; +pub(crate) const WARNING_TXT: &str = "warning"; + /// Primary structure provided for formatting /// /// See [`Level::title`] to create a [`Message`] @@ -51,6 +59,46 @@ impl<'a> Message<'a> { } } +impl Message<'_> { + pub(crate) fn has_primary_spans(&self) -> bool { + self.snippets.iter().any(|s| !s.annotations.is_empty()) + } + pub(crate) fn has_span_labels(&self) -> bool { + self.snippets.iter().any(|s| !s.annotations.is_empty()) + } + + pub(crate) fn max_line_number(&self) -> usize { + let mut max = self + .snippets + .iter() + .map(|s| { + let start = s + .annotations + .iter() + .map(|a| a.range.start) + .min() + .unwrap_or(0); + + let end = s + .annotations + .iter() + .map(|a| a.range.end) + .max() + .unwrap_or(s.source.len()) + .min(s.source.len()); + + s.line_start + newline_count(&s.source[start..end]) + }) + .max() + .unwrap_or(1); + + for footer in &self.footer { + max = max.max(footer.max_line_number()); + } + max + } +} + /// Structure containing the slice of text to be annotated and /// basic information about the location of the slice. /// @@ -108,7 +156,7 @@ impl<'a> Snippet<'a> { /// An annotation for a [`Snippet`]. /// /// See [`Level::span`] to create a [`Annotation`] -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Annotation<'a> { /// The byte range of the annotation in the `source` string pub(crate) range: Range<usize>, @@ -124,7 +172,7 @@ impl<'a> Annotation<'a> { } /// Types of annotations. -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq)] pub enum Level { /// Error annotations are displayed using red color and "^" character. Error, @@ -155,3 +203,36 @@ impl Level { } } } + +impl Level { + pub(crate) fn as_str(&self) -> &'static str { + match self { + Level::Error => ERROR_TXT, + Level::Warning => WARNING_TXT, + Level::Info => INFO_TXT, + Level::Note => NOTE_TXT, + Level::Help => HELP_TXT, + } + } + + pub(crate) fn style(&self, stylesheet: &Stylesheet) -> Style { + match self { + Level::Error => stylesheet.error, + Level::Warning => stylesheet.warning, + Level::Info => stylesheet.info, + Level::Note => stylesheet.note, + Level::Help => stylesheet.help, + } + } +} + +fn newline_count(body: &str) -> usize { + #[cfg(feature = "simd")] + { + memchr::memchr_iter(b'\n', body.as_bytes()).count() + } + #[cfg(not(feature = "simd"))] + { + body.lines().count() + } +} diff --git a/tests/fixtures/color/ann_multiline.svg b/tests/fixtures/color/ann_multiline.svg index 3e813a08..2ff0364b 100644 --- a/tests/fixtures/color/ann_multiline.svg +++ b/tests/fixtures/color/ann_multiline.svg @@ -27,7 +27,7 @@ </tspan> <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">139</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> if let DisplayLine::Source {</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">________________________________^</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold"> ________________________________^</tspan> </tspan> <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">140</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> ref mut inline_marks,</tspan> </tspan> diff --git a/tests/fixtures/color/fold_ann_multiline.svg b/tests/fixtures/color/fold_ann_multiline.svg index b68a5535..1afc6527 100644 --- a/tests/fixtures/color/fold_ann_multiline.svg +++ b/tests/fixtures/color/fold_ann_multiline.svg @@ -1,4 +1,4 @@ -<svg width="869px" height="218px" xmlns="http://www.w3.org/2000/svg"> +<svg width="869px" height="236px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -34,13 +34,15 @@ </tspan> <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">53</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> match (ann.range.0, ann.range.1) {</tspan> </tspan> - <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">...</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan> + <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">54</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> (None, None) => continue,</tspan> </tspan> - <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">71</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan> + <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">55</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> (Some(start), Some(end)) if start > end_index || end < start_index => continue,</tspan> </tspan> - <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">72</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan> + <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">...</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan> </tspan> - <tspan x="10px" y="208px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|_____^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected enum `std::option::Option`, found ()</tspan> + <tspan x="10px" y="208px"><tspan class="fg-bright-blue bold">72</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> }</tspan> +</tspan> + <tspan x="10px" y="226px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|_____^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected enum `std::option::Option`, found ()</tspan> </tspan> </text> diff --git a/tests/fixtures/color/fold_bad_origin_line.svg b/tests/fixtures/color/fold_bad_origin_line.svg index 4bd5f585..bd075e42 100644 --- a/tests/fixtures/color/fold_bad_origin_line.svg +++ b/tests/fixtures/color/fold_bad_origin_line.svg @@ -20,7 +20,7 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: </tspan> </tspan> <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>path/to/error.rs:3:1</tspan> </tspan> diff --git a/tests/fixtures/color/fold_leading.svg b/tests/fixtures/color/fold_leading.svg index 23b31d4a..22a66c7c 100644 --- a/tests/fixtures/color/fold_leading.svg +++ b/tests/fixtures/color/fold_leading.svg @@ -21,13 +21,13 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: invalid type: integer `20`, expected a bool</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>Cargo.toml:11:13</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>Cargo.toml:11:13</tspan> </tspan> - <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> + <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">11</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> workspace = 20</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">11|</tspan><tspan> workspace = 20</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^</tspan> </tspan> </text> diff --git a/tests/fixtures/color/issue_9.svg b/tests/fixtures/color/issue_9.svg index 6ba0199f..4e3bf1eb 100644 --- a/tests/fixtures/color/issue_9.svg +++ b/tests/fixtures/color/issue_9.svg @@ -1,4 +1,4 @@ -<svg width="911px" height="200px" xmlns="http://www.w3.org/2000/svg"> +<svg width="919px" height="218px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -22,23 +22,25 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected one of `.`, `;`, `?`, or an operator, found `for`</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>/code/rust/src/test/ui/annotate-snippet/suggestion.rs:4:5</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>/code/rust/src/test/ui/annotate-snippet/suggestion.rs:4:5</tspan> </tspan> - <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> + <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">4</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let x = vec![1];</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">4</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let x = vec![1];</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait</tspan> </tspan> - <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> + <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">7</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let y = x;</tspan> + <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">7</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let y = x;</tspan> </tspan> - <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">value moved here</tspan> + <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">value moved here</tspan> </tspan> - <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">9</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> x;</tspan> + <tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="190px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">value used here after move</tspan> + <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">9</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> x;</tspan> +</tspan> + <tspan x="10px" y="208px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">value used here after move</tspan> </tspan> </text> diff --git a/tests/fixtures/color/multiple_annotations.svg b/tests/fixtures/color/multiple_annotations.svg index 26df4ae9..96101a50 100644 --- a/tests/fixtures/color/multiple_annotations.svg +++ b/tests/fixtures/color/multiple_annotations.svg @@ -19,7 +19,7 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: </tspan> </tspan> <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> diff --git a/tests/fixtures/color/simple.svg b/tests/fixtures/color/simple.svg index d1d5d871..ef59075b 100644 --- a/tests/fixtures/color/simple.svg +++ b/tests/fixtures/color/simple.svg @@ -22,7 +22,7 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected one of `.`, `;`, `?`, or an operator, found `for`</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>src/format_color.rs:171:9</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>src/format_color.rs:169:11</tspan> </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> diff --git a/tests/fixtures/color/strip_line_non_ws.svg b/tests/fixtures/color/strip_line_non_ws.svg index 89047b3b..6f799d35 100644 --- a/tests/fixtures/color/strip_line_non_ws.svg +++ b/tests/fixtures/color/strip_line_non_ws.svg @@ -21,7 +21,7 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>$DIR/non-whitespace-trimming.rs:4:242</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>$DIR/non-whitespace-trimming.rs:4:233</tspan> </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> diff --git a/tests/fixtures/color/strip_line_non_ws.toml b/tests/fixtures/color/strip_line_non_ws.toml index c6573ff4..bfe9f317 100644 --- a/tests/fixtures/color/strip_line_non_ws.toml +++ b/tests/fixtures/color/strip_line_non_ws.toml @@ -13,12 +13,12 @@ origin = "$DIR/non-whitespace-trimming.rs" [[message.snippets.annotations]] label = "expected `()`, found integer" level = "Error" -range = [241, 243] +range = [237, 239] [[message.snippets.annotations]] label = "expected due to this" level = "Error" -range = [236, 238] +range = [232, 234] [renderer] diff --git a/tests/fixtures/main.rs b/tests/fixtures/main.rs index 5ff1105c..27082622 100644 --- a/tests/fixtures/main.rs +++ b/tests/fixtures/main.rs @@ -37,6 +37,6 @@ fn test(input_path: &std::path::Path) -> Result<Data, Box<dyn Error>> { let renderer: Renderer = fixture.renderer.into(); let message: Message<'_> = (&fixture.message).into(); - let actual = renderer.render(message).to_string(); + let actual = renderer.render(message); Ok(Data::from(actual).coerce_to(DataFormat::TermSvg)) } diff --git a/tests/formatter.rs b/tests/formatter.rs index d1694217..3e9246aa 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -19,7 +19,7 @@ error: oops "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(snippets).to_string(), expected); + assert_data_eq!(renderer.render(snippets), expected); } #[test] @@ -31,7 +31,7 @@ fn test_point_to_double_width_characters() { ); let expected = str![[r#" -error +error: --> <current file>:1:7 | 1 | こんにちは、世界 @@ -39,7 +39,7 @@ error "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(snippets).to_string(), expected); + assert_data_eq!(renderer.render(snippets), expected); } #[test] @@ -51,7 +51,7 @@ fn test_point_to_double_width_characters_across_lines() { ); let expected = str![[r#" -error +error: --> <current file>:1:3 | 1 | おはよう @@ -61,7 +61,7 @@ error "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(snippets).to_string(), expected); + assert_data_eq!(renderer.render(snippets), expected); } #[test] @@ -74,17 +74,17 @@ fn test_point_to_double_width_characters_multiple() { ); let expected = str![[r#" -error +error: --> <current file>:1:1 | 1 | お寿司 | ^^^^^^ Sushi1 2 | 食べたい🍣 - | ---- note: Sushi2 + | ---- Sushi2 "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(snippets).to_string(), expected); + assert_data_eq!(renderer.render(snippets), expected); } #[test] @@ -96,7 +96,7 @@ fn test_point_to_double_width_characters_mixed() { ); let expected = str![[r#" -error +error: --> <current file>:1:7 | 1 | こんにちは、新しいWorld! @@ -104,7 +104,7 @@ error "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(snippets).to_string(), expected); + assert_data_eq!(renderer.render(snippets), expected); } #[test] @@ -113,7 +113,7 @@ fn test_format_title() { let expected = str![r#"error[E0001]: This is a title"#]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -124,13 +124,13 @@ fn test_format_snippet_only() { .snippet(Snippet::source(source).line_start(5402)); let expected = str![[r#" -error +error: | 5402 | This is line 1 5403 | This is line 2 "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -142,7 +142,7 @@ fn test_format_snippets_continuation() { .snippet(Snippet::source(src_0).line_start(5402).origin("file1.rs")) .snippet(Snippet::source(src_1).line_start(2).origin("file2.rs")); let expected = str![[r#" -error +error: --> file1.rs | 5402 | This is slice 1 @@ -152,7 +152,7 @@ error 2 | This is slice 2 "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -168,14 +168,14 @@ fn test_format_snippet_annotation_standalone() { .annotation(Level::Info.span(range.clone()).label("Test annotation")), ); let expected = str![[r#" -error +error: | 5402 | This is line 1 5403 | This is line 2 - | -- info: Test annotation + | -- Test annotation "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -184,11 +184,11 @@ fn test_format_footer_title() { .title("") .footer(Level::Error.title("This __is__ a title")); let expected = str![[r#" -error - = error: This __is__ a title +error: + = error: This __is__ a title "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -202,7 +202,7 @@ fn test_i26() { .annotation(Level::Error.span(0..source.len() + 2).label(label)), ); let renderer = Renderer::plain(); - let _ = renderer.render(input).to_string(); + let _ = renderer.render(input); } #[test] @@ -212,13 +212,13 @@ fn test_source_content() { .title("") .snippet(Snippet::source(source).line_start(56)); let expected = str![[r#" -error +error: | 56 | This is an example 57 | of content lines "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -230,13 +230,13 @@ fn test_source_annotation_standalone_singleline() { .annotation(Level::Help.span(0..5).label("Example string")), ); let expected = str![[r#" -error +error: | 1 | tests - | ----- help: Example string + | ----- Example string "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -249,16 +249,16 @@ fn test_source_annotation_standalone_multiline() { .annotation(Level::Help.span(0..5).label("Second line")), ); let expected = str![[r#" -error +error: | 1 | tests | ----- | | - | help: Example string - | help: Second line + | Example string + | Second line "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -267,12 +267,12 @@ fn test_only_source() { .title("") .snippet(Snippet::source("").origin("file.rs")); let expected = str![[r#" -error ---> file.rs - | +error: + --> file.rs + | "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -282,7 +282,7 @@ fn test_anon_lines() { .title("") .snippet(Snippet::source(source).line_start(56)); let expected = str![[r#" -error +error: | LL | This is an example LL | of content lines @@ -290,7 +290,7 @@ LL | LL | abc "#]]; let renderer = Renderer::plain().anonymized_line_numbers(true); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -312,7 +312,7 @@ error: dummy | |___^ "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -329,7 +329,7 @@ a\" .annotation(Level::Error.span(0..10)), // 1..10 works ); let expected = str![[r#" -error +error: --> file/path:3:1 | 3 | / a" @@ -337,7 +337,7 @@ error | |_______^ "#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -350,7 +350,7 @@ fn char_and_nl_annotate_char() { .annotation(Level::Error.span(0..2)), // a\r ); let expected = str![[r#" -error +error: --> file/path:3:1 | 3 | a @@ -358,7 +358,7 @@ error 4 | b "#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -371,7 +371,7 @@ fn char_eol_annotate_char() { .annotation(Level::Error.span(0..3)), // a\r\n ); let expected = str![[r#" -error +error: --> file/path:3:1 | 3 | a @@ -379,7 +379,7 @@ error 4 | b "#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -391,7 +391,7 @@ fn char_eol_annotate_char_double_width() { ); let expected = str![[r#" -error +error: --> <current file>:1:2 | 1 | こん @@ -401,7 +401,7 @@ error "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(snippets).to_string(), expected); + assert_data_eq!(renderer.render(snippets), expected); } #[test] @@ -414,7 +414,7 @@ fn annotate_eol() { .annotation(Level::Error.span(1..2)), // \r ); let expected = str![[r#" -error +error: --> file/path:3:2 | 3 | a @@ -422,7 +422,7 @@ error 4 | b "#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -435,7 +435,7 @@ fn annotate_eol2() { .annotation(Level::Error.span(1..3)), // \r\n ); let expected = str![[r#" -error +error: --> file/path:3:2 | 3 | a @@ -443,7 +443,7 @@ error 4 | b "#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -456,15 +456,15 @@ fn annotate_eol3() { .annotation(Level::Error.span(2..3)), // \n ); let expected = str![[r#" -error - --> file/path:3:2 +error: + --> file/path:3:3 | 3 | a | ^ 4 | b "#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -477,15 +477,15 @@ fn annotate_eol4() { .annotation(Level::Error.span(2..2)), // \n ); let expected = str![[r#" -error - --> file/path:3:2 +error: + --> file/path:3:3 | 3 | a | ^ 4 | b "#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -497,8 +497,8 @@ fn annotate_eol_double_width() { ); let expected = str![[r#" -error - --> <current file>:1:3 +error: + --> <current file>:1:4 | 1 | こん | ^ @@ -507,7 +507,7 @@ error "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(snippets).to_string(), expected); + assert_data_eq!(renderer.render(snippets), expected); } #[test] @@ -520,7 +520,7 @@ fn multiline_eol_start() { .annotation(Level::Error.span(1..4)), // \r\nb ); let expected = str![[r#" -error +error: --> file/path:3:2 | 3 | a @@ -529,7 +529,7 @@ error | |_^ "#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -542,8 +542,8 @@ fn multiline_eol_start2() { .annotation(Level::Error.span(2..4)), // \nb ); let expected = str![[r#" -error - --> file/path:3:2 +error: + --> file/path:3:3 | 3 | a | __^ @@ -551,7 +551,7 @@ error | |_^ "#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -564,7 +564,7 @@ fn multiline_eol_start3() { .annotation(Level::Error.span(1..3)), // \nb ); let expected = str![[r#" -error +error: --> file/path:3:2 | 3 | a @@ -573,7 +573,7 @@ error | |_^ "#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -585,8 +585,8 @@ fn multiline_eol_start_double_width() { ); let expected = str![[r#" -error - --> <current file>:1:3 +error: + --> <current file>:1:4 | 1 | こん | _____^ @@ -596,7 +596,7 @@ error "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(snippets).to_string(), expected); + assert_data_eq!(renderer.render(snippets), expected); } #[test] @@ -609,7 +609,7 @@ fn multiline_eol_start_eol_end() { .annotation(Level::Error.span(1..4)), // \nb\n ); let expected = str![[r#" -error +error: --> file/path:3:2 | 3 | a @@ -619,7 +619,7 @@ error 5 | c "#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -632,8 +632,8 @@ fn multiline_eol_start_eol_end2() { .annotation(Level::Error.span(2..5)), // \nb\r ); let expected = str![[r#" -error - --> file/path:3:2 +error: + --> file/path:3:3 | 3 | a | __^ @@ -642,7 +642,7 @@ error 5 | c "#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -655,8 +655,8 @@ fn multiline_eol_start_eol_end3() { .annotation(Level::Error.span(2..6)), // \nb\r\n ); let expected = str![[r#" -error - --> file/path:3:2 +error: + --> file/path:3:3 | 3 | a | __^ @@ -665,7 +665,7 @@ error 5 | c "#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -678,7 +678,7 @@ fn multiline_eol_start_eof_end() { .annotation(Level::Error.span(1..5)), // \r\nb(EOF) ); let expected = str![[r#" -error +error: --> file/path:3:2 | 3 | a @@ -687,7 +687,7 @@ error | |__^ "#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -700,7 +700,7 @@ fn multiline_eol_start_eof_end_double_width() { .annotation(Level::Error.span(3..9)), // \r\nに(EOF) ); let expected = str![[r#" -error +error: --> file/path:3:2 | 3 | ん @@ -709,7 +709,7 @@ error | |___^ "#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -735,12 +735,12 @@ error: unused optional dependency --> Cargo.toml:4:1 | 4 | bar = { version = "0.1.0", optional = true } - | ^^^ --------------- info: This should also be long but not too long + | ^^^ --------------- This should also be long but not too long | | | I need this to be really long so I can test overlaps "#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -770,14 +770,14 @@ error: unused optional dependency 4 | bar = { version = "0.1.0", optional = true } | ____________________________--------------^ | | | - | | info: This should also be long but not too long + | | This should also be long but not too long 5 | | this is another line 6 | | so is this 7 | | bar = { version = "0.1.0", optional = true } | |__________________________________________^ I need this to be really long so I can test overlaps "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -812,7 +812,7 @@ error: unused optional dependency 4 | bar = { version = "0.1.0", optional = true } | _________^__________________--------------^ | | | | - | |_________| info: This should also be long but not too long + | |_________| This should also be long but not too long | || 5 | || this is another line 6 | || so is this @@ -822,7 +822,7 @@ error: unused optional dependency | I need this to be really long so I can test overlaps "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -863,7 +863,7 @@ error: unused optional dependency 4 | bar = { version = "0.1.0", optional = true } | __________^__________________--------------^ | | | | - | |__________| info: This should also be long but not too long + | |__________| This should also be long but not too long | || 5 | || this is another line | || ____^ @@ -876,7 +876,7 @@ error: unused optional dependency | |____^ I need this to be really long so I can test overlaps "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -900,7 +900,7 @@ error: title 4 | ddd "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -924,5 +924,5 @@ error: title 4 | ddd "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index c2e72d3a..9567265e 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -30,7 +30,7 @@ error: foo | |_^ test "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] fn ends_on_col2() { @@ -54,13 +54,12 @@ error: foo | 2 | fn foo() { | __________^ -3 | | -4 | | +... | 5 | | } | |___^ test "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] fn non_nested() { @@ -98,7 +97,7 @@ error: foo | `X` is a good letter "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] fn nested() { @@ -134,7 +133,7 @@ error: foo | `Y` is a good letter too "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] fn different_overlap() { @@ -173,7 +172,7 @@ error: foo | |____- `Y` is a good letter too "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] fn triple_overlap() { @@ -214,7 +213,7 @@ error: foo | `X` is a good letter "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] fn triple_exact_overlap() { @@ -249,13 +248,13 @@ error: foo 4 | | X1 Y1 Z1 5 | | X2 Y2 Z2 | | - - | |____| - | `X` is a good letter - | `Y` is a good letter too + | | | + | | `X` is a good letter + | |____`Y` is a good letter too | `Z` label "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] fn minimum_depth() { @@ -299,7 +298,7 @@ error: foo | |_______- `Z` "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] fn non_overlapping() { @@ -337,7 +336,7 @@ error: foo | |__________- `Y` is a good letter too "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] fn overlapping_start_and_end() { @@ -377,7 +376,7 @@ error: foo | |__________- `Y` is a good letter too "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] fn multiple_labels_primary_without_message() { @@ -398,13 +397,13 @@ fn foo() { let expected = str![[r#" error: foo - --> test.rs:3:7 + --> test.rs:3:3 | 3 | a { b { c } d } | ----^^^^-^^-- `a` is a good letter "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] fn multiple_labels_secondary_without_message() { @@ -430,7 +429,7 @@ error: foo | ^^^^-------^^ `a` is a good letter "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] fn multiple_labels_primary_without_message_2() { @@ -451,7 +450,7 @@ fn foo() { let expected = str![[r#" error: foo - --> test.rs:3:7 + --> test.rs:3:3 | 3 | a { b { c } d } | ----^^^^-^^-- @@ -459,7 +458,7 @@ error: foo | `b` is a good letter "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] fn multiple_labels_secondary_without_message_2() { @@ -487,7 +486,7 @@ error: foo | `b` is a good letter "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] fn multiple_labels_secondary_without_message_3() { @@ -515,7 +514,7 @@ error: foo | `a` is a good letter "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] fn multiple_labels_without_message() { @@ -541,7 +540,7 @@ error: foo | ^^^^-------^^ "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] fn multiple_labels_without_message_2() { @@ -562,13 +561,13 @@ fn foo() { let expected = str![[r#" error: foo - --> test.rs:3:7 + --> test.rs:3:3 | 3 | a { b { c } d } | ----^^^^-^^-- "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] fn multiple_labels_with_message() { @@ -597,7 +596,7 @@ error: foo | `a` is a good letter "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] fn ingle_label_with_message() { @@ -622,7 +621,7 @@ error: foo | ^^^^^^^^^^^^^ `a` is a good letter "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] fn single_label_without_message() { @@ -647,7 +646,7 @@ error: foo | ^^^^^^^^^^^^^ "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] fn long_snippet() { @@ -693,13 +692,15 @@ error: foo | ||____| | | `X` is a good letter 5 | | 1 +6 | | 2 +7 | | 3 ... | 15 | | X2 Y2 Z2 16 | | X3 Y3 Z3 | |__________- `Y` is a good letter too "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] fn long_snippet_multiple_spans() { @@ -750,14 +751,13 @@ error: foo 10 | || 6 11 | || X2 Y2 Z2 | ||__________- `Z` is a good letter too -12 | | 7 ... | 15 | | 10 16 | | X3 Y3 Z3 | |________^ `Y` is a good letter "#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -798,7 +798,7 @@ LL | fn f(){||yield(((){), | unclosed delimiter "#]]; let renderer = Renderer::plain().anonymized_line_numbers(true); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -863,7 +863,7 @@ fn main() { ); let expected = str![[r#" error[E0571]: `break` with value from a `while` loop - --> $DIR/issue-114529-illegal-break-with-value.rs:22:9 + --> $DIR/issue-114529-illegal-break-with-value.rs:21:5 | LL | while true { | ---------- you can't `break` with a value in a `while` loop @@ -877,11 +877,11 @@ help: use `break` on its own without a value inside this `while` loop LL | / break (|| { //~ ERROR `break` with value from a `while` loop LL | | let local = 9; LL | | }); - | |__________- help: break + | |__________- break "#]]; let renderer = Renderer::plain().anonymized_line_numbers(true); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -1072,24 +1072,25 @@ fn nsize() { error[E0277]: `V0usize` cannot be safely transmuted into `[usize; 2]` --> $DIR/primitive_reprs_should_have_correct_length.rs:144:44 | -LL | assert::is_transmutable::<Current, Larger>(); //~ ERROR cannot be safely transmuted - | ^^^^^^ the size of `V0usize` is smaller than the size of `[usize; 2]` +LL | assert::is_transmutable::<Current, Larger>(); //~ ERROR cannot be safely transmuted + | ^^^^^^ the size of `V0usize` is smaller than the size of `[usize; 2]` note: required by a bound in `is_transmutable` --> $DIR/primitive_reprs_should_have_correct_length.rs:10:12 | LL | pub fn is_transmutable<Src, Dst>() - | --------------- note: required by a bound in this function + | --------------- required by a bound in this function LL | where LL | Dst: TransmuteFrom<Src, { | ______________^ LL | | Assume { +LL | | alignment: true, +LL | | lifetimes: true, ... | -LL | | } LL | | }> | |__________^ required by this bound in `is_transmutable` "#]]; let renderer = Renderer::plain().anonymized_line_numbers(true); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -1139,7 +1140,7 @@ LL | ...ic [u8; 0], &'static [u16; 0]>(); //~ ERROR `&[u8; 0]` cannot be safely | ^^^^^^^^^^^^^^^^^ the minimum alignment of `&[u8; 0]` (1) should be greater than that of `&[u16; 0]` (2) "#]]; let renderer = Renderer::plain().anonymized_line_numbers(true); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -1210,12 +1211,12 @@ fn main() {} ); let expected = str![[r#" error[E0618]: expected function, found `{integer}` - --> $DIR/missing-semicolon.rs:5:13 + --> $DIR/missing-semicolon.rs:4:9 | LL | let x = 5; | - `x` has type `{integer}` LL | let y = x //~ ERROR expected function - | ^- help: consider using a semicolon here to finish the statement: `;` + | -- help: consider using a semicolon here to finish the statement: `;` | _____________| | | LL | | () //~ ERROR expected `;`, found `}` @@ -1223,7 +1224,7 @@ LL | | () //~ ERROR expected `;`, found `}` "#]]; let renderer = Renderer::plain().anonymized_line_numbers(true); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -1319,25 +1320,26 @@ LL | macro_rules! outer_macro { ... LL | / macro_rules! inner_macro { LL | | ($bang_macro:ident, $attr_macro:ident) => { -... | +LL | | $bang_macro!($name); +LL | | #[$attr_macro] struct $attr_struct_name {} LL | | } LL | | } | |_________^ | ::: $DIR/nested-macro-rules.rs:23:5 | -LL | nested_macro_rules::outer_macro!(SecondStruct, SecondAttrStruct); - | ---------------------------------------------------------------- in this macro invocation +LL | nested_macro_rules::outer_macro!(SecondStruct, SecondAttrStruct); + | ---------------------------------------------------------------- in this macro invocation = help: remove the `#[macro_export]` or move this `macro_rules!` outside the of the current function `main` = note: a `macro_rules!` definition is non-local if it is nested inside an item and has a `#[macro_export]` attribute note: the lint level is defined here --> $DIR/nested-macro-rules.rs:8:9 | -LL | #![warn(non_local_definitions)] - | ^^^^^^^^^^^^^^^^^^^^^ +LL | #![warn(non_local_definitions)] + | ^^^^^^^^^^^^^^^^^^^^^ "#]]; let renderer = Renderer::plain().anonymized_line_numbers(true); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -1425,10 +1427,10 @@ help: you must specify a type for this binding, like `i32` --> $DIR/auxiliary/macro-in-other-crate.rs:3:35 | LL | ($ident:ident) => { let $ident = 42; } - | - help: : i32 + | - : i32 "#]]; let renderer = Renderer::plain().anonymized_line_numbers(true); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -1475,7 +1477,7 @@ LL | .sum::<_>() //~ ERROR type annotations needed | ^^^ cannot infer type of the type parameter `S` declared on the method `sum` "#]]; let renderer = Renderer::plain().anonymized_line_numbers(true); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -1628,13 +1630,13 @@ help: ensure that all possible cases are being handled by adding a match arm wit --> $DIR/empty-match.rs:17:33 | LL | _ if false => {} - | - help: , + | - , _ => todo!() "#]]; let renderer = Renderer::plain() .anonymized_line_numbers(true) .term_width(annotate_snippets::renderer::DEFAULT_TERM_WIDTH + 4); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } #[test] @@ -1695,7 +1697,7 @@ error[E0038]: the trait alias `EqAlias` is not dyn compatible LL | let _: &dyn EqAlias = &123; | ^^^^^^^ `EqAlias` is not dyn compatible note: for a trait to be dyn compatible it needs to allow building a vtable -for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility> + for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility> --> $SRC_DIR/core/src/cmp.rs | | @@ -1706,5 +1708,5 @@ LL | trait EqAlias = Eq; "#]]; let renderer = Renderer::plain().anonymized_line_numbers(true); - assert_data_eq!(renderer.render(input).to_string(), expected); + assert_data_eq!(renderer.render(input), expected); } From 9c0eb0cea0f61ca7cbce4a58ee107f7c8dff681b Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 19 Feb 2025 05:38:35 -0700 Subject: [PATCH 315/455] feat: Right align line numbers --- examples/multislice.svg | 2 +- src/renderer/mod.rs | 7 +++++- src/snippet.rs | 6 +++-- tests/fixtures/color/issue_9.svg | 22 ++++++++--------- tests/fixtures/color/multiple_annotations.svg | 8 +++---- tests/formatter.rs | 2 +- tests/rustc_tests.rs | 24 +++++++++---------- 7 files changed, 39 insertions(+), 32 deletions(-) diff --git a/examples/multislice.svg b/examples/multislice.svg index bbb3fc98..f0c1f65c 100644 --- a/examples/multislice.svg +++ b/examples/multislice.svg @@ -25,7 +25,7 @@ </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">51</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> Foo</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold"> 51</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> Foo</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 5fa9c8ec..c588c980 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -534,6 +534,7 @@ impl Renderer { buffer, width_offset, code_offset, + max_line_num_len, margin, ); @@ -606,6 +607,7 @@ impl Renderer { last_buffer_line_num, width_offset, code_offset, + max_line_num_len, margin, ); @@ -647,6 +649,7 @@ impl Renderer { buffer: &mut StyledBuffer, width_offset: usize, code_offset: usize, + max_line_num_len: usize, margin: Margin, ) -> Vec<(usize, ElementStyle)> { // Draw: @@ -685,6 +688,7 @@ impl Renderer { line_offset, width_offset, code_offset, + max_line_num_len, margin, ); @@ -1186,6 +1190,7 @@ impl Renderer { line_offset: usize, width_offset: usize, code_offset: usize, + max_line_num_len: usize, margin: Margin, ) { // Tabs are assumed to have been replaced by spaces in calling code. @@ -1228,7 +1233,7 @@ impl Renderer { buffer.puts( line_offset, 0, - &self.maybe_anonymized(line_index), + &format!("{:>max_line_num_len$}", self.maybe_anonymized(line_index)), ElementStyle::LineNumber, ); diff --git a/src/snippet.rs b/src/snippet.rs index d9ff5944..bf0ae912 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -229,10 +229,12 @@ impl Level { fn newline_count(body: &str) -> usize { #[cfg(feature = "simd")] { - memchr::memchr_iter(b'\n', body.as_bytes()).count() + memchr::memchr_iter(b'\n', body.as_bytes()) + .count() + .saturating_sub(1) } #[cfg(not(feature = "simd"))] { - body.lines().count() + body.lines().count().saturating_sub(1) } } diff --git a/tests/fixtures/color/issue_9.svg b/tests/fixtures/color/issue_9.svg index 4e3bf1eb..6854bc38 100644 --- a/tests/fixtures/color/issue_9.svg +++ b/tests/fixtures/color/issue_9.svg @@ -1,4 +1,4 @@ -<svg width="919px" height="218px" xmlns="http://www.w3.org/2000/svg"> +<svg width="911px" height="218px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -22,25 +22,25 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected one of `.`, `;`, `?`, or an operator, found `for`</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>/code/rust/src/test/ui/annotate-snippet/suggestion.rs:4:5</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>/code/rust/src/test/ui/annotate-snippet/suggestion.rs:4:5</tspan> </tspan> - <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> + <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">4</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let x = vec![1];</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">4</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let x = vec![1];</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait</tspan> </tspan> - <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> + <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">7</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let y = x;</tspan> + <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">7</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let y = x;</tspan> </tspan> - <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">value moved here</tspan> + <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">value moved here</tspan> </tspan> - <tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> + <tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">9</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> x;</tspan> + <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">9</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> x;</tspan> </tspan> - <tspan x="10px" y="208px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">value used here after move</tspan> + <tspan x="10px" y="208px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">value used here after move</tspan> </tspan> </text> diff --git a/tests/fixtures/color/multiple_annotations.svg b/tests/fixtures/color/multiple_annotations.svg index 96101a50..2c5c4a81 100644 --- a/tests/fixtures/color/multiple_annotations.svg +++ b/tests/fixtures/color/multiple_annotations.svg @@ -23,15 +23,15 @@ </tspan> <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="64px"><tspan class="fg-bright-blue bold">96</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>) {</tspan> + <tspan x="10px" y="64px"><tspan class="fg-bright-blue bold"> 96</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>) {</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">97</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> if let Some(annotation) = main_annotation {</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold"> 97</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> if let Some(annotation) = main_annotation {</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">Variable defined here</tspan> </tspan> - <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">98</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> result.push(format_title_line(</tspan> + <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold"> 98</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> result.push(format_title_line(</tspan> </tspan> - <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">99</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> &annotation.annotation_type,</tspan> + <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold"> 99</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> &annotation.annotation_type,</tspan> </tspan> <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">Referenced here</tspan> </tspan> diff --git a/tests/formatter.rs b/tests/formatter.rs index 3e9246aa..df5d0813 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -149,7 +149,7 @@ error: | ::: file2.rs | -2 | This is slice 2 + 2 | This is slice 2 "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input), expected); diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index 9567265e..f8b36d88 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -685,15 +685,15 @@ fn foo() { error: foo --> test.rs:3:6 | -3 | X0 Y0 Z0 + 3 | X0 Y0 Z0 | _______^ -4 | | X1 Y1 Z1 + 4 | | X1 Y1 Z1 | | ____^____- | ||____| | | `X` is a good letter -5 | | 1 -6 | | 2 -7 | | 3 + 5 | | 1 + 6 | | 2 + 7 | | 3 ... | 15 | | X2 Y2 Z2 16 | | X3 Y3 Z3 @@ -739,15 +739,15 @@ fn foo() { error: foo --> test.rs:3:6 | -3 | X0 Y0 Z0 + 3 | X0 Y0 Z0 | _______^ -4 | | 1 -5 | | 2 -6 | | 3 -7 | | X1 Y1 Z1 + 4 | | 1 + 5 | | 2 + 6 | | 3 + 7 | | X1 Y1 Z1 | | _________- -8 | || 4 -9 | || 5 + 8 | || 4 + 9 | || 5 10 | || 6 11 | || X2 Y2 Z2 | ||__________- `Z` is a good letter too From e6fcf0b33370b0a363a1c0ee19dbb2bdca170159 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Sun, 9 Feb 2025 12:41:57 -0700 Subject: [PATCH 316/455] feat!: Move to new API --- benches/bench.rs | 52 +- examples/expected_type.rs | 31 +- examples/expected_type.svg | 2 +- examples/footer.rs | 23 +- examples/footer.svg | 11 +- examples/format.rs | 32 +- examples/format.svg | 5 +- examples/multislice.rs | 27 +- examples/multislice.svg | 2 +- src/lib.rs | 1 + src/renderer/mod.rs | 637 ++++++++---- src/renderer/source_map.rs | 36 +- src/renderer/styled_buffer.rs | 10 +- src/renderer/stylesheet.rs | 2 + src/snippet.rs | 326 +++--- tests/fixtures/color/ann_eof.toml | 10 +- tests/fixtures/color/ann_insertion.toml | 10 +- tests/fixtures/color/ann_multiline.toml | 10 +- tests/fixtures/color/ann_multiline2.toml | 10 +- tests/fixtures/color/ann_removed_nl.toml | 10 +- .../color/ensure-emoji-highlight-width.toml | 10 +- tests/fixtures/color/fold_ann_multiline.svg | 5 +- tests/fixtures/color/fold_ann_multiline.toml | 15 +- tests/fixtures/color/fold_bad_origin_line.svg | 3 +- .../fixtures/color/fold_bad_origin_line.toml | 10 +- tests/fixtures/color/fold_leading.svg | 8 +- tests/fixtures/color/fold_leading.toml | 10 +- tests/fixtures/color/fold_trailing.toml | 10 +- tests/fixtures/color/issue_9.svg | 25 +- tests/fixtures/color/issue_9.toml | 21 +- .../fixtures/color/multiple_annotations.toml | 15 +- tests/fixtures/color/simple.svg | 5 +- tests/fixtures/color/simple.toml | 11 +- tests/fixtures/color/strip_line.toml | 7 +- tests/fixtures/color/strip_line_char.toml | 9 +- tests/fixtures/color/strip_line_non_ws.toml | 11 +- tests/fixtures/deserialize.rs | 84 +- tests/formatter.rs | 542 +++++----- tests/rustc_tests.rs | 931 +++++++++++------- 39 files changed, 1834 insertions(+), 1145 deletions(-) diff --git a/benches/bench.rs b/benches/bench.rs index ed4e82c0..01364af7 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -1,4 +1,4 @@ -use annotate_snippets::{Level, Renderer, Snippet}; +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; #[divan::bench] fn simple() -> String { @@ -24,20 +24,22 @@ fn simple() -> String { _ => continue, } }"#; - let message = Level::Error.title("mismatched types").id("E0308").snippet( - Snippet::source(source) - .line_start(51) - .origin("src/format.rs") - .annotation( - Level::Warning - .span(5..19) - .label("expected `Option<String>` because of return type"), - ) - .annotation( - Level::Error - .span(26..724) - .label("expected enum `std::option::Option`"), - ), + let message = Level::Error.message("mismatched types").id("E0308").group( + Group::new().element( + Snippet::source(source) + .line_start(51) + .origin("src/format.rs") + .annotation( + AnnotationKind::Context + .span(5..19) + .label("expected `Option<String>` because of return type"), + ) + .annotation( + AnnotationKind::Primary + .span(26..724) + .label("expected enum `std::option::Option`"), + ), + ), ); let renderer = Renderer::plain(); @@ -67,15 +69,17 @@ fn fold(bencher: divan::Bencher<'_, '_>, context: usize) { (input, span) }) .bench_values(|(input, span)| { - let message = Level::Error.title("mismatched types").id("E0308").snippet( - Snippet::source(&input) - .fold(true) - .origin("src/format.rs") - .annotation( - Level::Warning - .span(span) - .label("expected `Option<String>` because of return type"), - ), + let message = Level::Error.message("mismatched types").id("E0308").group( + Group::new().element( + Snippet::source(&input) + .fold(true) + .origin("src/format.rs") + .annotation( + AnnotationKind::Context + .span(span) + .label("expected `Option<String>` because of return type"), + ), + ), ); let renderer = Renderer::plain(); diff --git a/examples/expected_type.rs b/examples/expected_type.rs index 0184deeb..f61999da 100644 --- a/examples/expected_type.rs +++ b/examples/expected_type.rs @@ -1,22 +1,27 @@ -use annotate_snippets::{Level, Renderer, Snippet}; +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; fn main() { let source = r#" annotations: vec![SourceAnnotation { label: "expected struct `annotate_snippets::snippet::Slice`, found reference" , range: <22, 25>,"#; - let message = Level::Error.title("expected type, found `22`").snippet( - Snippet::source(source) - .line_start(26) - .origin("examples/footer.rs") - .fold(true) - .annotation( - Level::Error - .span(193..195) - .label("expected struct `annotate_snippets::snippet::Slice`, found reference"), - ) - .annotation(Level::Info.span(34..50).label("while parsing this struct")), - ); + let message = + Level::Error.message("expected type, found `22`").group( + Group::new().element( + Snippet::source(source) + .line_start(26) + .origin("examples/footer.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(193..195).label( + "expected struct `annotate_snippets::snippet::Slice`, found reference", + )) + .annotation( + AnnotationKind::Context + .span(34..50) + .label("while parsing this struct"), + ), + ), + ); let renderer = Renderer::styled(); anstream::println!("{}", renderer.render(message)); diff --git a/examples/expected_type.svg b/examples/expected_type.svg index 749fc3ac..7c1b073d 100644 --- a/examples/expected_type.svg +++ b/examples/expected_type.svg @@ -21,7 +21,7 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected type, found `22`</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>examples/footer.rs:26:35</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>examples/footer.rs:29:25</tspan> </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> diff --git a/examples/footer.rs b/examples/footer.rs index 35809050..29b27c14 100644 --- a/examples/footer.rs +++ b/examples/footer.rs @@ -1,21 +1,22 @@ -use annotate_snippets::{Level, Renderer, Snippet}; +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; fn main() { - let message = - Level::Error - .title("mismatched types") - .id("E0308") - .snippet( + let message = Level::Error + .message("mismatched types") + .id("E0308") + .group( + Group::new().element( Snippet::source(" slices: vec![\"A\",") .line_start(13) .origin("src/multislice.rs") - .annotation(Level::Error.span(21..24).label( + .annotation(AnnotationKind::Primary.span(21..24).label( "expected struct `annotate_snippets::snippet::Slice`, found reference", )), - ) - .footer(Level::Note.title( - "expected type: `snippet::Annotation`\n found type: `__&__snippet::Annotation`", - )); + ), + ) + .group(Group::new().element(Level::Note.title( + "expected type: `snippet::Annotation`\n found type: `__&__snippet::Annotation`", + ))); let renderer = Renderer::styled(); anstream::println!("{}", renderer.render(message)); diff --git a/examples/footer.svg b/examples/footer.svg index e3bb8923..e24ba5f5 100644 --- a/examples/footer.svg +++ b/examples/footer.svg @@ -1,8 +1,9 @@ -<svg width="844px" height="164px" xmlns="http://www.w3.org/2000/svg"> +<svg width="844px" height="182px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } .fg-bright-blue { fill: #5555FF } + .fg-bright-green { fill: #55FF55 } .fg-bright-red { fill: #FF5555 } .container { padding: 0 10px; @@ -29,11 +30,13 @@ </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected struct `annotate_snippets::snippet::Slice`, found reference</tspan> </tspan> - <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">= </tspan><tspan class="bold">note</tspan><tspan>: expected type: `snippet::Annotation`</tspan> + <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="136px"><tspan> found type: `__&__snippet::Annotation`</tspan> + <tspan x="10px" y="136px"><tspan class="fg-bright-green bold">note</tspan><tspan class="bold">: expected type: `snippet::Annotation`</tspan> </tspan> - <tspan x="10px" y="154px"> + <tspan x="10px" y="154px"><tspan class="bold"> found type: `__&__snippet::Annotation`</tspan> +</tspan> + <tspan x="10px" y="172px"> </tspan> </text> diff --git a/examples/format.rs b/examples/format.rs index 1606777b..df6f9274 100644 --- a/examples/format.rs +++ b/examples/format.rs @@ -1,4 +1,4 @@ -use annotate_snippets::{Level, Renderer, Snippet}; +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; fn main() { let source = r#") -> Option<String> { @@ -23,20 +23,22 @@ fn main() { _ => continue, } }"#; - let message = Level::Error.title("mismatched types").id("E0308").snippet( - Snippet::source(source) - .line_start(51) - .origin("src/format.rs") - .annotation( - Level::Warning - .span(5..19) - .label("expected `Option<String>` because of return type"), - ) - .annotation( - Level::Error - .span(26..724) - .label("expected enum `std::option::Option`"), - ), + let message = Level::Error.message("mismatched types").id("E0308").group( + Group::new().element( + Snippet::source(source) + .line_start(51) + .origin("src/format.rs") + .annotation( + AnnotationKind::Context + .span(5..19) + .label("expected `Option<String>` because of return type"), + ) + .annotation( + AnnotationKind::Primary + .span(26..724) + .label("expected enum `std::option::Option`"), + ), + ), ); let renderer = Renderer::styled(); diff --git a/examples/format.svg b/examples/format.svg index f9bf80e8..e4a4042c 100644 --- a/examples/format.svg +++ b/examples/format.svg @@ -4,7 +4,6 @@ .bg { background: #000000 } .fg-bright-blue { fill: #5555FF } .fg-bright-red { fill: #FF5555 } - .fg-yellow { fill: #AA5500 } .container { padding: 0 10px; line-height: 18px; @@ -22,13 +21,13 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>src/format.rs:51:6</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>src/format.rs:52:5</tspan> </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">51</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> ) -> Option<String> {</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">--------------</tspan><tspan> </tspan><tspan class="fg-yellow bold">expected `Option<String>` because of return type</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">--------------</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">expected `Option<String>` because of return type</tspan> </tspan> <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">52</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">/</tspan><tspan> for ann in annotations {</tspan> </tspan> diff --git a/examples/multislice.rs b/examples/multislice.rs index ea31bbd0..d1ad72ac 100644 --- a/examples/multislice.rs +++ b/examples/multislice.rs @@ -1,18 +1,19 @@ -use annotate_snippets::{Level, Renderer, Snippet}; +use annotate_snippets::{Annotation, Group, Level, Renderer, Snippet}; fn main() { - let message = Level::Error - .title("mismatched types") - .snippet( - Snippet::source("Foo") - .line_start(51) - .origin("src/format.rs"), - ) - .snippet( - Snippet::source("Faa") - .line_start(129) - .origin("src/display.rs"), - ); + let message = Level::Error.message("mismatched types").group( + Group::new() + .element( + Snippet::<Annotation<'_>>::source("Foo") + .line_start(51) + .origin("src/format.rs"), + ) + .element( + Snippet::<Annotation<'_>>::source("Faa") + .line_start(129) + .origin("src/display.rs"), + ), + ); let renderer = Renderer::styled(); anstream::println!("{}", renderer.render(message)); diff --git a/examples/multislice.svg b/examples/multislice.svg index f0c1f65c..5bc01454 100644 --- a/examples/multislice.svg +++ b/examples/multislice.svg @@ -29,7 +29,7 @@ </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">::: </tspan><tspan>src/display.rs</tspan> + <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">::: </tspan><tspan>src/display.rs:129</tspan> </tspan> <tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> diff --git a/src/lib.rs b/src/lib.rs index ed9b3f84..533e8f73 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,4 +47,5 @@ mod snippet; #[doc(inline)] pub use renderer::Renderer; +pub use snippet::ColumnSeparator; pub use snippet::*; diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index c588c980..246afb65 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -4,13 +4,36 @@ //! //! # Example //! ``` -//! use annotate_snippets::{Renderer, Snippet, Level}; -//! let snippet = Level::Error.title("mismatched types") -//! .snippet(Snippet::source("Foo").line_start(51).origin("src/format.rs")) -//! .snippet(Snippet::source("Faa").line_start(129).origin("src/display.rs")); +//! use annotate_snippets::*; //! -//! let renderer = Renderer::styled(); -//! println!("{}", renderer.render(snippet)); +//! let source = r#" +//! use baz::zed::bar; +//! +//! mod baz {} +//! mod zed { +//! pub fn bar() { println!("bar3"); } +//! } +//! fn main() { +//! bar(); +//! } +//! "#; +//! Level::Error +//! .message("unresolved import `baz::zed`") +//! .id("E0432") +//! .group( +//! Group::new().element( +//! Snippet::source(source) +//! .origin("temp.rs") +//! .line_start(1) +//! .fold(true) +//! .annotation( +//! AnnotationKind::Primary +//! .span(10..13) +//! .label("could not find `zed` in `baz`"), +//! ) +//! ) +//! ); +//! ``` mod margin; mod source_map; @@ -19,14 +42,14 @@ pub(crate) mod stylesheet; use crate::renderer::source_map::{AnnotatedLineInfo, Loc, SourceMap}; use crate::renderer::styled_buffer::StyledBuffer; -use crate::snippet::Message; -use crate::{Level, Snippet}; +use crate::{Annotation, AnnotationKind, Element, Group, Level, Message, Origin, Snippet, Title}; pub use anstyle::*; use indexmap::IndexMap; use margin::Margin; use rustc_hash::{FxHashMap, FxHasher}; use std::borrow::Cow; use std::cmp::{max, min, Ordering, Reverse}; +use std::collections::VecDeque; use std::hash::BuildHasherDefault; use stylesheet::Stylesheet; @@ -84,6 +107,7 @@ impl Renderer { } .effects(Effects::BOLD), none: Style::new(), + context: BRIGHT_BLUE.effects(Effects::BOLD), }, ..Self::plain() } @@ -164,7 +188,7 @@ impl Renderer { } impl Renderer { - pub fn render(&self, message: Message<'_>) -> String { + pub fn render(&self, mut message: Message<'_>) -> String { let mut buffer = StyledBuffer::new(); let max_line_num_len = if self.anonymized_line_numbers { ANONYMIZED_LINE_NUM.len() @@ -172,10 +196,20 @@ impl Renderer { let n = message.max_line_number(); num_decimal_digits(n) }; + let title = message.groups.remove(0).elements.remove(0); + let level = if let Element::Title(title) = &title { + title.level + } else { + panic!("Expected a title as the first element of the message") + }; + if let Some(first) = message.groups.first_mut() { + first.elements.insert(0, title); + } else { + message.groups.push(Group::new().element(title)); + } + self.render_message(&mut buffer, message, max_line_num_len); - self.render_message(&mut buffer, message, max_line_num_len, false); - - buffer.render(&self.stylesheet).unwrap() + buffer.render(level, &self.stylesheet).unwrap() } fn render_message( @@ -183,67 +217,200 @@ impl Renderer { buffer: &mut StyledBuffer, message: Message<'_>, max_line_num_len: usize, - is_secondary: bool, ) { - self.render_title(buffer, &message, max_line_num_len, is_secondary); - - let primary_origin = message.snippets.first().and_then(|s| s.origin); - - for snippet in message.snippets { - let source_map = SourceMap::new(snippet.source, snippet.line_start); - self.render_snippet_annotations( - buffer, - max_line_num_len, - &snippet, - primary_origin, - &source_map, - ); - } + let group_len = message.groups.len(); + for (g, group) in message.groups.into_iter().enumerate() { + let primary_origin = group + .elements + .iter() + .find_map(|s| match &s { + Element::Cause(cause) => { + if cause.markers.iter().any(|m| m.kind.is_primary()) { + Some(cause.origin) + } else { + None + } + } + Element::Origin(origin) => { + if origin.primary { + Some(Some(origin.origin)) + } else { + None + } + } + _ => None, + }) + .unwrap_or( + group + .elements + .iter() + .find_map(|s| match &s { + Element::Cause(cause) => Some(cause.origin), + Element::Origin(origin) => Some(Some(origin.origin)), + _ => None, + }) + .unwrap_or_default(), + ); + let mut source_map_annotated_lines = VecDeque::new(); + let mut max_depth = 0; + for e in &group.elements { + if let Element::Cause(cause) = e { + let source_map = SourceMap::new(cause.source, cause.line_start); + let (depth, annotated_lines) = + source_map.annotated_lines(cause.markers.clone(), cause.fold); + max_depth = max(max_depth, depth); + source_map_annotated_lines.push_back((source_map, annotated_lines)); + } + } + let mut message_iter = group.elements.iter().enumerate().peekable(); + while let Some((i, section)) = message_iter.next() { + let peek = message_iter.peek().map(|(_, s)| s).copied(); + match §ion { + Element::Title(title) => { + self.render_title( + buffer, + title, + peek, + max_line_num_len, + if i == 0 { false } else { !title.primary }, + message.id.as_ref().and_then(|id| { + if g == 0 && i == 0 { + Some(id) + } else { + None + } + }), + ); + } + Element::Cause(cause) => { + if let Some((source_map, annotated_lines)) = + source_map_annotated_lines.pop_front() + { + self.render_snippet_annotations( + buffer, + max_line_num_len, + cause, + primary_origin, + &source_map, + &annotated_lines, + max_depth, + ); - for footer in message.footer { - self.render_message(buffer, footer, max_line_num_len, true); + if g == 0 && group_len > 1 { + if matches!(peek, Some(Element::Title(level)) if level.level != Level::None) + { + self.draw_col_separator_no_space( + buffer, + buffer.num_lines(), + max_line_num_len + 1, + ); + // We want to draw the separator when it is + // requested, or when it is the last element + } else if peek.is_none() { + self.draw_col_separator_no_space_with_style( + buffer, + '|', + buffer.num_lines(), + max_line_num_len + 1, + ElementStyle::LineNumber, + ); + } + } + } + } + Element::Origin(origin) => { + self.render_origin(buffer, max_line_num_len, origin); + } + Element::ColumnSeparator(_) => { + self.draw_col_separator_no_space( + buffer, + buffer.num_lines(), + max_line_num_len + 1, + ); + } + } + if g == 0 + && (matches!(section, Element::Origin(_)) + || (matches!(section, Element::Title(_)) && i == 0) + || matches!(section, Element::Title(level) if level.level == Level::None)) + { + if peek.is_none() && group_len > 1 { + self.draw_col_separator_no_space_with_style( + buffer, + '|', + buffer.num_lines(), + max_line_num_len + 1, + ElementStyle::LineNumber, + ); + } else if matches!(peek, Some(Element::Title(level)) if level.level != Level::None) + { + self.draw_col_separator_no_space( + buffer, + buffer.num_lines(), + max_line_num_len + 1, + ); + } + } + } } } fn render_title( &self, buffer: &mut StyledBuffer, - message: &Message<'_>, + title: &Title<'_>, + next_section: Option<&Element<'_>>, max_line_num_len: usize, is_secondary: bool, + id: Option<&&str>, ) { let line_offset = buffer.num_lines(); - if !message.has_primary_spans() && !message.has_span_labels() && is_secondary { + + let (has_primary_spans, has_span_labels) = + next_section.map_or((false, false), |s| match s { + Element::Title(_) | Element::ColumnSeparator(_) => (false, false), + Element::Cause(cause) => ( + cause.markers.iter().any(|m| m.kind.is_primary()), + cause.markers.iter().any(|m| m.label.is_some()), + ), + Element::Origin(_) => (false, true), + }); + + if !has_primary_spans && !has_span_labels && is_secondary { // This is a secondary message with no span info for _ in 0..max_line_num_len { buffer.prepend(line_offset, " ", ElementStyle::NoStyle); } - buffer.puts( - line_offset, - max_line_num_len + 1, - "= ", - ElementStyle::LineNumber, - ); - buffer.append( - line_offset, - message.level.as_str(), - ElementStyle::MainHeaderMsg, - ); - buffer.append(line_offset, ": ", ElementStyle::NoStyle); - self.msgs_to_buffer(buffer, message.title, max_line_num_len, "note", None); + if title.level != Level::None { + buffer.puts( + line_offset, + max_line_num_len + 1, + "= ", + ElementStyle::LineNumber, + ); + buffer.append( + line_offset, + title.level.as_str(), + ElementStyle::MainHeaderMsg, + ); + buffer.append(line_offset, ": ", ElementStyle::NoStyle); + } + self.msgs_to_buffer(buffer, title.title, max_line_num_len, "note", None); } else { let mut label_width = 0; - buffer.append( - line_offset, - message.level.as_str(), - ElementStyle::Level(message.level), - ); - label_width += message.level.as_str().len(); - if let Some(id) = message.id { - buffer.append(line_offset, "[", ElementStyle::Level(message.level)); - buffer.append(line_offset, id, ElementStyle::Level(message.level)); - buffer.append(line_offset, "]", ElementStyle::Level(message.level)); + if title.level != Level::None { + buffer.append( + line_offset, + title.level.as_str(), + ElementStyle::Level(title.level), + ); + } + label_width += title.level.as_str().len(); + if let Some(id) = id { + buffer.append(line_offset, "[", ElementStyle::Level(title.level)); + buffer.append(line_offset, id, ElementStyle::Level(title.level)); + buffer.append(line_offset, "]", ElementStyle::Level(title.level)); label_width += 2 + id.len(); } let header_style = if is_secondary { @@ -251,10 +418,12 @@ impl Renderer { } else { ElementStyle::MainHeaderMsg }; - buffer.append(line_offset, ": ", header_style); - label_width += 2; - if !message.title.is_empty() { - for (line, text) in normalize_whitespace(message.title).lines().enumerate() { + if title.level != Level::None { + buffer.append(line_offset, ": ", header_style); + label_width += 2; + } + if !title.title.is_empty() { + for (line, text) in normalize_whitespace(title.title).lines().enumerate() { buffer.append( line_offset + line, &format!( @@ -343,51 +512,106 @@ impl Renderer { line_number } + fn render_origin( + &self, + buffer: &mut StyledBuffer, + max_line_num_len: usize, + origin: &Origin<'_>, + ) { + let buffer_msg_line_offset = buffer.num_lines(); + if origin.primary { + buffer.prepend( + buffer_msg_line_offset, + self.file_start(), + ElementStyle::LineNumber, + ); + } else { + // if !origin.standalone { + // // Add spacing line, as shown: + // // --> $DIR/file:54:15 + // // | + // // LL | code + // // | ^^^^ + // // | (<- It prints *this* line) + // // ::: $DIR/other_file.rs:15:5 + // // | + // // LL | code + // // | ---- + // self.draw_col_separator_no_space( + // buffer, + // buffer_msg_line_offset, + // max_line_num_len + 1, + // ); + // + // buffer_msg_line_offset += 1; + // } + // Then, the secondary file indicator + buffer.prepend( + buffer_msg_line_offset, + self.secondary_file_start(), + ElementStyle::LineNumber, + ); + } + + let str = match (&origin.line, &origin.char_column) { + (Some(line), Some(col)) => { + format!("{}:{}:{}", origin.origin, line, col) + } + (Some(line), None) => format!("{}:{}", origin.origin, line), + _ => origin.origin.to_owned(), + }; + buffer.append(buffer_msg_line_offset, &str, ElementStyle::LineAndColumn); + for _ in 0..max_line_num_len { + buffer.prepend(buffer_msg_line_offset, " ", ElementStyle::NoStyle); + } + + if let Some(label) = &origin.label { + self.draw_col_separator_no_space( + buffer, + buffer_msg_line_offset + 1, + max_line_num_len + 1, + ); + let title = Level::Note.title(label); + self.render_title(buffer, &title, None, max_line_num_len, true, None); + } + } + + #[allow(clippy::too_many_arguments)] fn render_snippet_annotations( &self, buffer: &mut StyledBuffer, max_line_num_len: usize, - snippet: &Snippet<'_>, + snippet: &Snippet<'_, Annotation<'_>>, primary_origin: Option<&str>, sm: &SourceMap<'_>, + annotated_lines: &[AnnotatedLineInfo<'_>], + multiline_depth: usize, ) { - let annotated_lines = sm.annotated_lines(snippet.annotations.clone(), snippet.fold); - // print out the span location and spacer before we print the annotated source - // to do this, we need to know if this span will be primary - let is_primary = primary_origin == snippet.origin; - - if is_primary { - if let Some(origin) = snippet.origin { - // remember where we are in the output buffer for easy reference - let buffer_msg_line_offset = buffer.num_lines(); - - buffer.prepend( - buffer_msg_line_offset, - self.file_start(), - ElementStyle::LineNumber, - ); - let loc = if let Some(first_line) = - annotated_lines.iter().find(|l| !l.annotations.is_empty()) + if let Some(origin) = snippet.origin { + let mut origin = Origin::new(origin); + // print out the span location and spacer before we print the annotated source + // to do this, we need to know if this span will be primary + let is_primary = primary_origin == Some(origin.origin); + + if is_primary { + origin.primary = true; + if let Some(primary_line) = annotated_lines + .iter() + .find(|l| l.annotations.iter().any(LineAnnotation::is_primary)) + .or(annotated_lines.iter().find(|l| !l.annotations.is_empty())) { - let col = if let Some(first_annotation) = first_line.annotations.first() { - format!(":{}", first_annotation.start.char + 1) - } else { - String::new() - }; - format!("{}:{}{}", origin, first_line.line_index, col) - } else { - origin.to_owned() - }; - buffer.append(buffer_msg_line_offset, &loc, ElementStyle::LineAndColumn); - for _ in 0..max_line_num_len { - buffer.prepend(buffer_msg_line_offset, " ", ElementStyle::NoStyle); + origin.line = Some(primary_line.line_index); + if let Some(first_annotation) = primary_line + .annotations + .iter() + .find(|a| a.is_primary()) + .or(primary_line.annotations.first()) + { + origin.char_column = Some(first_annotation.start.char + 1); + } } - } - } else { - if let Some(origin) = snippet.origin { - // remember where we are in the output buffer for easy reference + } else { let buffer_msg_line_offset = buffer.num_lines(); - // Add spacing line, as shown: // --> $DIR/file:54:15 // | @@ -403,34 +627,14 @@ impl Renderer { buffer_msg_line_offset, max_line_num_len + 1, ); - - // Then, the secondary file indicator - buffer.prepend( - buffer_msg_line_offset + 1, - self.secondary_file_start(), - ElementStyle::LineNumber, - ); - let loc = if let Some(first_line) = - annotated_lines.iter().find(|l| !l.annotations.is_empty()) - { - let col = if let Some(first_annotation) = first_line.annotations.first() { - format!(":{}", first_annotation.start.char + 1) - } else { - String::new() - }; - format!("{}:{}{}", origin, first_line.line_index, col) - } else { - origin.to_owned() - }; - buffer.append( - buffer_msg_line_offset + 1, - &loc, - ElementStyle::LineAndColumn, - ); - for _ in 0..max_line_num_len { - buffer.prepend(buffer_msg_line_offset + 1, " ", ElementStyle::NoStyle); + if let Some(first_line) = annotated_lines.first() { + origin.line = Some(first_line.line_index); + if let Some(first_annotation) = first_line.annotations.first() { + origin.char_column = Some(first_annotation.start.char + 1); + } } } + self.render_origin(buffer, max_line_num_len, &origin); } // Put in the spacer between the location and annotated source @@ -442,7 +646,7 @@ impl Renderer { // Get the left-side margin to remove it let mut whitespace_margin = usize::MAX; - for line_info in &annotated_lines { + for line_info in annotated_lines { // Whitespace can only be removed (aka considered leading) // if the lexer considers it whitespace. // non-rustc_lexer::is_whitespace() chars are reported as an @@ -470,7 +674,7 @@ impl Renderer { // Left-most column any visible span points at. let mut span_left_margin = usize::MAX; - for line_info in &annotated_lines { + for line_info in annotated_lines { for ann in &line_info.annotations { span_left_margin = min(span_left_margin, ann.start.display); span_left_margin = min(span_left_margin, ann.end.display); @@ -484,7 +688,7 @@ impl Renderer { let mut span_right_margin = 0; let mut label_right_margin = 0; let mut max_line_len = 0; - for line_info in &annotated_lines { + for line_info in annotated_lines { max_line_len = max(max_line_len, line_info.line.len()); for ann in &line_info.annotations { span_right_margin = max(span_right_margin, ann.start.display); @@ -494,19 +698,6 @@ impl Renderer { label_right_margin = max(label_right_margin, ann.end.display + label_right); } } - let multiline_depth = annotated_lines.iter().fold(0, |acc, line_info| { - line_info.annotations.iter().fold(acc, |acc2, ann| { - max( - acc2, - match ann.annotation_type { - LineAnnotationType::Singleline => 0, - LineAnnotationType::MultilineStart(depth) => depth, - LineAnnotationType::MultilineEnd(depth) => depth, - LineAnnotationType::MultilineLine(depth) => depth, - }, - ) - }) - }); let width_offset = 3 + max_line_num_len; let code_offset = if multiline_depth == 0 { width_offset @@ -586,7 +777,11 @@ impl Renderer { last_buffer_line_num, width_offset, pos, - ElementStyle::Level(ann.level), + if ann.is_primary() { + ElementStyle::UnderlinePrimary + } else { + ElementStyle::UnderlineSecondary + }, ); } } @@ -629,7 +824,11 @@ impl Renderer { last_buffer_line_num, width_offset, pos, - ElementStyle::Level(ann.level), + if ann.is_primary() { + ElementStyle::UnderlinePrimary + } else { + ElementStyle::UnderlineSecondary + }, ); } } @@ -643,6 +842,7 @@ impl Renderer { } } + #[allow(clippy::too_many_arguments)] fn render_source_line( &self, line_info: &AnnotatedLineInfo<'_>, @@ -718,9 +918,10 @@ impl Renderer { .take(ann.start.display) .all(char::is_whitespace) { - let style = ElementStyle::Level(ann.level); - annotations.push((depth, style)); - buffer_ops.push((line_offset, width_offset + depth - 1, '/', style)); + let uline = self.underline(ann.is_primary()); + let chr = uline.multiline_whole_line; + annotations.push((depth, uline.style)); + buffer_ops.push((line_offset, width_offset + depth - 1, chr, uline.style)); } else { short_start = false; break; @@ -961,18 +1162,18 @@ impl Renderer { // 4 | } // | _ for &(pos, annotation) in &annotations_position { - let style = ElementStyle::Level(annotation.level); + let underline = self.underline(annotation.is_primary()); let pos = pos + 1; match annotation.annotation_type { LineAnnotationType::MultilineStart(depth) | LineAnnotationType::MultilineEnd(depth) => { self.draw_range( buffer, - '_', // underline.multiline_horizontal, + underline.multiline_horizontal, line_offset + pos, width_offset + depth, (code_offset + annotation.start.display).saturating_sub(left), - style, + underline.style, ); } _ => {} @@ -991,7 +1192,7 @@ impl Renderer { // 4 | | } // | |_ for &(pos, annotation) in &annotations_position { - let style = ElementStyle::Level(annotation.level); + let underline = self.underline(annotation.is_primary()); let pos = pos + 1; if pos > 1 && (annotation.has_label() || annotation.takes_space()) { @@ -1000,18 +1201,18 @@ impl Renderer { p, (code_offset + annotation.start.display).saturating_sub(left), match annotation.annotation_type { - LineAnnotationType::MultilineLine(_) => '|', // underline.multiline_vertical, - _ => '|', // underline.vertical_text_line, + LineAnnotationType::MultilineLine(_) => underline.multiline_vertical, + _ => underline.vertical_text_line, }, - style, + underline.style, ); } if let LineAnnotationType::MultilineStart(_) = annotation.annotation_type { buffer.putc( line_offset + pos, (code_offset + annotation.start.display).saturating_sub(left), - '|', // underline.bottom_right, - style, + underline.bottom_right, + underline.style, ); } if matches!( @@ -1022,8 +1223,8 @@ impl Renderer { buffer.putc( line_offset + pos, (code_offset + annotation.start.display).saturating_sub(left), - '|', // underline.multiline_bottom_right_with_text, - style, + underline.multiline_bottom_right_with_text, + underline.style, ); } } @@ -1032,15 +1233,15 @@ impl Renderer { buffer.putc( line_offset + pos, width_offset + depth - 1, - ' ', // underline.top_left, - style, + underline.top_left, + underline.style, ); for p in line_offset + pos + 1..line_offset + line_len + 2 { buffer.putc( p, width_offset + depth - 1, - '|', // underline.multiline_vertical, - style, + underline.multiline_vertical, + underline.style, ); } } @@ -1049,15 +1250,15 @@ impl Renderer { buffer.putc( p, width_offset + depth - 1, - '|', // underline.multiline_vertical, - style, + underline.multiline_vertical, + underline.style, ); } buffer.putc( line_offset + pos, width_offset + depth - 1, - '|', // underline.bottom_left, - style, + underline.bottom_left, + underline.style, ); } _ => (), @@ -1076,7 +1277,11 @@ impl Renderer { // 4 | } // | _ test for &(pos, annotation) in &annotations_position { - let style = ElementStyle::Level(annotation.level); + let style = if annotation.is_primary() { + ElementStyle::LabelPrimary + } else { + ElementStyle::LabelSecondary + }; let (pos, col) = if pos == 0 { if annotation.end.display == 0 { (pos + 1, (annotation.end.display + 2).saturating_sub(left)) @@ -1101,7 +1306,7 @@ impl Renderer { // | something about `fn foo()` annotations_position.sort_by_key(|(_, ann)| { // Decreasing order. When annotations share the same length, prefer `Primary`. - Reverse(ann.len()) + (Reverse(ann.len()), ann.is_primary()) }); // Write the underlines. @@ -1116,19 +1321,14 @@ impl Renderer { // 4 | } // | _^ test for &(pos, annotation) in &annotations_position { - let style = ElementStyle::Level(annotation.level); - let underline = if annotation.level == Level::Error { - '^' - } else { - '-' - }; + let uline = self.underline(annotation.is_primary()); for p in annotation.start.display..annotation.end.display { // The default span label underline. buffer.putc( line_offset + 1, (code_offset + p).saturating_sub(left), - underline, - style, + uline.underline, + uline.style, ); } @@ -1142,8 +1342,12 @@ impl Renderer { buffer.putc( line_offset + 1, (code_offset + annotation.start.display).saturating_sub(left), - underline, - style, + match annotation.annotation_type { + LineAnnotationType::MultilineStart(_) => uline.top_right_flat, + LineAnnotationType::MultilineEnd(_) => uline.multiline_end_same_line, + _ => panic!("unexpected annotation type: {annotation:?}"), + }, + uline.style, ); } else if pos != 0 && matches!( @@ -1156,16 +1360,20 @@ impl Renderer { buffer.putc( line_offset + 1, (code_offset + annotation.start.display).saturating_sub(left), - underline, - style, + match annotation.annotation_type { + LineAnnotationType::MultilineStart(_) => uline.multiline_start_down, + LineAnnotationType::MultilineEnd(_) => uline.multiline_end_up, + _ => panic!("unexpected annotation type: {annotation:?}"), + }, + uline.style, ); } else if pos != 0 && annotation.has_label() { // The beginning of a span label with an actual label, we'll point down. buffer.putc( line_offset + 1, (code_offset + annotation.start.display).saturating_sub(left), - underline, - style, + uline.label_start, + uline.style, ); } } @@ -1173,7 +1381,11 @@ impl Renderer { .iter() .filter_map(|&(_, annotation)| match annotation.annotation_type { LineAnnotationType::MultilineStart(p) | LineAnnotationType::MultilineEnd(p) => { - let style = ElementStyle::Level(annotation.level); + let style = if annotation.is_primary() { + ElementStyle::LabelPrimary + } else { + ElementStyle::LabelSecondary + }; Some((p, style)) } _ => None, @@ -1301,6 +1513,46 @@ impl Renderer { fn secondary_file_start(&self) -> &str { "::: " } + + fn underline(&self, is_primary: bool) -> UnderlineParts { + if is_primary { + UnderlineParts { + style: ElementStyle::UnderlinePrimary, + underline: '^', + label_start: '^', + vertical_text_line: '|', + multiline_vertical: '|', + multiline_horizontal: '_', + multiline_whole_line: '/', + multiline_start_down: '^', + bottom_right: '|', + top_left: ' ', + top_right_flat: '^', + bottom_left: '|', + multiline_end_up: '^', + multiline_end_same_line: '^', + multiline_bottom_right_with_text: '|', + } + } else { + UnderlineParts { + style: ElementStyle::UnderlineSecondary, + underline: '-', + label_start: '-', + vertical_text_line: '|', + multiline_vertical: '|', + multiline_horizontal: '_', + multiline_whole_line: '/', + multiline_start_down: '-', + bottom_right: '|', + top_left: ' ', + top_right_flat: '-', + bottom_left: '|', + multiline_end_up: '-', + multiline_end_same_line: '-', + multiline_bottom_right_with_text: '|', + } + } + } } // instead of taking the String length or dividing by 10 while > 0, we multiply a limit by 10 until @@ -1409,7 +1661,7 @@ pub(crate) struct LineAnnotation<'a> { pub end: Loc, /// level - pub level: Level, + pub kind: AnnotationKind, /// Optional label to display adjacent to the annotation. pub label: Option<&'a str>, @@ -1420,6 +1672,10 @@ pub(crate) struct LineAnnotation<'a> { } impl LineAnnotation<'_> { + pub(crate) fn is_primary(&self) -> bool { + self.kind == AnnotationKind::Primary + } + /// Whether this annotation is a vertical line placeholder. pub(crate) fn is_line(&self) -> bool { matches!(self.annotation_type, LineAnnotationType::MultilineLine(_)) @@ -1532,23 +1788,48 @@ pub(crate) enum ElementStyle { LineAndColumn, LineNumber, Quotation, + UnderlinePrimary, + UnderlineSecondary, + LabelPrimary, + LabelSecondary, NoStyle, Level(Level), } impl ElementStyle { - fn color_spec(&self, stylesheet: &Stylesheet) -> Style { + fn color_spec(&self, level: Level, stylesheet: &Stylesheet) -> Style { match self { ElementStyle::LineAndColumn => stylesheet.none, ElementStyle::LineNumber => stylesheet.line_no, ElementStyle::Quotation => stylesheet.none, ElementStyle::MainHeaderMsg => stylesheet.emphasis, + ElementStyle::UnderlinePrimary | ElementStyle::LabelPrimary => level.style(stylesheet), + ElementStyle::UnderlineSecondary | ElementStyle::LabelSecondary => stylesheet.context, ElementStyle::HeaderMsg | ElementStyle::NoStyle => stylesheet.none, ElementStyle::Level(lvl) => lvl.style(stylesheet), } } } +#[derive(Debug, Clone, Copy)] +struct UnderlineParts { + style: ElementStyle, + underline: char, + label_start: char, + vertical_text_line: char, + multiline_vertical: char, + multiline_horizontal: char, + multiline_whole_line: char, + multiline_start_down: char, + bottom_right: char, + top_left: char, + top_right_flat: char, + bottom_left: char, + multiline_end_up: char, + multiline_end_same_line: char, + multiline_bottom_right_with_text: char, +} + #[cfg(test)] mod test { use super::OUTPUT_REPLACEMENTS; diff --git a/src/renderer/source_map.rs b/src/renderer/source_map.rs index 01695666..c29774a8 100644 --- a/src/renderer/source_map.rs +++ b/src/renderer/source_map.rs @@ -1,5 +1,5 @@ use crate::renderer::{char_width, num_overlap, LineAnnotation, LineAnnotationType}; -use crate::{Annotation, Level}; +use crate::{Annotation, AnnotationKind}; use std::cmp::{max, min}; use std::ops::Range; @@ -105,7 +105,7 @@ impl<'a> SourceMap<'a> { &self, annotations: Vec<Annotation<'a>>, fold: bool, - ) -> Vec<AnnotatedLineInfo<'a>> { + ) -> (usize, Vec<AnnotatedLineInfo<'a>>) { let source_len = self.source.len(); if let Some(bigger) = annotations.iter().find_map(|x| { // Allow highlighting one past the last character in the source. @@ -129,12 +129,7 @@ impl<'a> SourceMap<'a> { .collect::<Vec<_>>(); let mut multiline_annotations = vec![]; - for Annotation { - range, - label, - level, - } in annotations - { + for Annotation { range, label, kind } in annotations { let (lo, mut hi) = self.span_to_locations(range); // Watch out for "empty spans". If we get a span like 6..6, we @@ -151,7 +146,7 @@ impl<'a> SourceMap<'a> { let line_ann = LineAnnotation { start: lo, end: hi, - level, + kind, label, annotation_type: LineAnnotationType::Singleline, }; @@ -161,17 +156,22 @@ impl<'a> SourceMap<'a> { depth: 1, start: lo, end: hi, - level, + kind, label, overlaps_exactly: false, }); } } + let mut primary_spans = vec![]; + // Find overlapping multiline annotations, put them at different depths multiline_annotations .sort_by_key(|ml| (ml.start.line, usize::MAX - ml.end.line, ml.start.byte)); for ann in multiline_annotations.clone() { + if ann.kind.is_primary() { + primary_spans.push((ann.start, ann.end)); + } for a in &mut multiline_annotations { // Move all other multiline annotations overlapping with this one // one level to the right. @@ -182,6 +182,12 @@ impl<'a> SourceMap<'a> { } else if ann.same_span(a) && &ann != a { a.overlaps_exactly = true; } else { + if primary_spans + .iter() + .any(|(s, e)| a.start == *s && a.end == *e) + { + a.kind = AnnotationKind::Primary; + } break; } } @@ -268,7 +274,7 @@ impl<'a> SourceMap<'a> { .iter_mut() .for_each(|l| l.annotations.sort_by(|a, b| a.start.cmp(&b.start))); - annotated_line_infos + (max_depth, annotated_line_infos) } fn add_annotation_to_file( @@ -303,7 +309,7 @@ pub(crate) struct MultilineAnnotation<'a> { pub depth: usize, pub start: Loc, pub end: Loc, - pub level: Level, + pub kind: AnnotationKind, pub label: Option<&'a str>, pub overlaps_exactly: bool, } @@ -327,7 +333,7 @@ impl<'a> MultilineAnnotation<'a> { display: self.start.display + 1, byte: self.start.byte + 1, }, - level: self.level, + kind: self.kind, label: None, annotation_type: LineAnnotationType::MultilineStart(self.depth), } @@ -342,7 +348,7 @@ impl<'a> MultilineAnnotation<'a> { byte: self.end.byte.saturating_sub(1), }, end: self.end, - level: self.level, + kind: self.kind, label: self.label, annotation_type: LineAnnotationType::MultilineEnd(self.depth), } @@ -352,7 +358,7 @@ impl<'a> MultilineAnnotation<'a> { LineAnnotation { start: Loc::default(), end: Loc::default(), - level: self.level, + kind: self.kind, label: None, annotation_type: LineAnnotationType::MultilineLine(self.depth), } diff --git a/src/renderer/styled_buffer.rs b/src/renderer/styled_buffer.rs index fd72358b..3f192422 100644 --- a/src/renderer/styled_buffer.rs +++ b/src/renderer/styled_buffer.rs @@ -4,6 +4,8 @@ use crate::renderer::stylesheet::Stylesheet; use crate::renderer::ElementStyle; +use crate::Level; + use std::fmt; use std::fmt::Write; @@ -37,12 +39,16 @@ impl StyledBuffer { } } - pub(crate) fn render(&self, stylesheet: &Stylesheet) -> Result<String, fmt::Error> { + pub(crate) fn render( + &self, + level: Level, + stylesheet: &Stylesheet, + ) -> Result<String, fmt::Error> { let mut str = String::new(); for (i, line) in self.lines.iter().enumerate() { let mut current_style = stylesheet.none; for StyledChar { ch, style } in line { - let ch_style = style.color_spec(stylesheet); + let ch_style = style.color_spec(level, stylesheet); if ch_style != current_style { if !line.is_empty() { write!(str, "{}", current_style.render_reset())?; diff --git a/src/renderer/stylesheet.rs b/src/renderer/stylesheet.rs index 72a5f0ec..c11a2795 100644 --- a/src/renderer/stylesheet.rs +++ b/src/renderer/stylesheet.rs @@ -10,6 +10,7 @@ pub(crate) struct Stylesheet { pub(crate) line_no: Style, pub(crate) emphasis: Style, pub(crate) none: Style, + pub(crate) context: Style, } impl Default for Stylesheet { @@ -29,6 +30,7 @@ impl Stylesheet { line_no: Style::new(), emphasis: Style::new(), none: Style::new(), + context: Style::new(), } } } diff --git a/src/snippet.rs b/src/snippet.rs index bf0ae912..1c3efb64 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -1,14 +1,4 @@ //! Structures used as an input for the library. -//! -//! Example: -//! -//! ``` -//! use annotate_snippets::*; -//! -//! Level::Error.title("mismatched types") -//! .snippet(Snippet::source("Foo").line_start(51).origin("src/format.rs")) -//! .snippet(Snippet::source("Faa").line_start(129).origin("src/display.rs")); -//! ``` use crate::renderer::stylesheet::Stylesheet; use anstyle::Style; @@ -20,16 +10,10 @@ pub(crate) const INFO_TXT: &str = "info"; pub(crate) const NOTE_TXT: &str = "note"; pub(crate) const WARNING_TXT: &str = "warning"; -/// Primary structure provided for formatting -/// -/// See [`Level::title`] to create a [`Message`] #[derive(Debug)] pub struct Message<'a> { - pub(crate) level: Level, - pub(crate) id: Option<&'a str>, - pub(crate) title: &'a str, - pub(crate) snippets: Vec<Snippet<'a>>, - pub(crate) footer: Vec<Message<'a>>, + pub(crate) id: Option<&'a str>, // for "correctness", could be sloppy and be on Title + pub(crate) groups: Vec<Group<'a>>, } impl<'a> Message<'a> { @@ -38,90 +22,189 @@ impl<'a> Message<'a> { self } - pub fn snippet(mut self, slice: Snippet<'a>) -> Self { - self.snippets.push(slice); + pub fn group(mut self, group: Group<'a>) -> Self { + self.groups.push(group); self } - pub fn snippets(mut self, slice: impl IntoIterator<Item = Snippet<'a>>) -> Self { - self.snippets.extend(slice); - self + pub(crate) fn max_line_number(&self) -> usize { + self.groups + .iter() + .map(|v| { + v.elements + .iter() + .map(|s| match s { + Element::Title(_) | Element::Origin(_) | Element::ColumnSeparator(_) => 0, + Element::Cause(cause) => { + let end = cause + .markers + .iter() + .map(|a| a.range.end) + .max() + .unwrap_or(cause.source.len()) + .min(cause.source.len()); + + cause.line_start + newline_count(&cause.source[..end]) + } + }) + .max() + .unwrap_or(1) + }) + .max() + .unwrap_or(1) + } +} + +#[derive(Debug)] +pub struct Group<'a> { + pub(crate) elements: Vec<Element<'a>>, +} + +impl Default for Group<'_> { + fn default() -> Self { + Self::new() + } +} + +impl<'a> Group<'a> { + pub fn new() -> Self { + Self { elements: vec![] } } - pub fn footer(mut self, footer: Message<'a>) -> Self { - self.footer.push(footer); + pub fn element(mut self, section: impl Into<Element<'a>>) -> Self { + self.elements.push(section.into()); self } - pub fn footers(mut self, footer: impl IntoIterator<Item = Message<'a>>) -> Self { - self.footer.extend(footer); + pub fn elements(mut self, sections: impl IntoIterator<Item = impl Into<Element<'a>>>) -> Self { + self.elements.extend(sections.into_iter().map(Into::into)); self } + + pub fn is_empty(&self) -> bool { + self.elements.is_empty() + } } -impl Message<'_> { - pub(crate) fn has_primary_spans(&self) -> bool { - self.snippets.iter().any(|s| !s.annotations.is_empty()) +#[derive(Debug)] +#[non_exhaustive] +pub enum Element<'a> { + Title(Title<'a>), + Cause(Snippet<'a, Annotation<'a>>), + Origin(Origin<'a>), + ColumnSeparator(ColumnSeparator), +} + +impl<'a> From<Title<'a>> for Element<'a> { + fn from(value: Title<'a>) -> Self { + Element::Title(value) } - pub(crate) fn has_span_labels(&self) -> bool { - self.snippets.iter().any(|s| !s.annotations.is_empty()) +} + +impl<'a> From<Snippet<'a, Annotation<'a>>> for Element<'a> { + fn from(value: Snippet<'a, Annotation<'a>>) -> Self { + Element::Cause(value) } +} - pub(crate) fn max_line_number(&self) -> usize { - let mut max = self - .snippets - .iter() - .map(|s| { - let start = s - .annotations - .iter() - .map(|a| a.range.start) - .min() - .unwrap_or(0); +impl<'a> From<Origin<'a>> for Element<'a> { + fn from(value: Origin<'a>) -> Self { + Element::Origin(value) + } +} - let end = s - .annotations - .iter() - .map(|a| a.range.end) - .max() - .unwrap_or(s.source.len()) - .min(s.source.len()); +impl From<ColumnSeparator> for Element<'_> { + fn from(value: ColumnSeparator) -> Self { + Self::ColumnSeparator(value) + } +} - s.line_start + newline_count(&s.source[start..end]) - }) - .max() - .unwrap_or(1); +#[derive(Debug)] +pub struct ColumnSeparator; + +#[derive(Debug)] +pub struct Title<'a> { + pub(crate) level: Level, + pub(crate) title: &'a str, + pub(crate) primary: bool, +} + +impl Title<'_> { + pub fn primary(mut self, primary: bool) -> Self { + self.primary = primary; + self + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum Level { + Error, + Warning, + Info, + Note, + Help, + None, +} - for footer in &self.footer { - max = max.max(footer.max_line_number()); +impl Level { + pub fn message(self, title: &str) -> Message<'_> { + Message { + id: None, + groups: vec![Group::new().element(Element::Title(Title { + level: self, + title, + primary: true, + }))], + } + } + + pub fn title(self, title: &str) -> Title<'_> { + Title { + level: self, + title, + primary: false, + } + } + + pub(crate) fn as_str(self) -> &'static str { + match self { + Level::Error => ERROR_TXT, + Level::Warning => WARNING_TXT, + Level::Info => INFO_TXT, + Level::Note => NOTE_TXT, + Level::Help => HELP_TXT, + Level::None => "", + } + } + + pub(crate) fn style(&self, stylesheet: &Stylesheet) -> Style { + match self { + Level::Error => stylesheet.error, + Level::Warning => stylesheet.warning, + Level::Info => stylesheet.info, + Level::Note => stylesheet.note, + Level::Help => stylesheet.help, + Level::None => stylesheet.none, } - max } } -/// Structure containing the slice of text to be annotated and -/// basic information about the location of the slice. -/// -/// One `Snippet` is meant to represent a single, continuous, -/// slice of source code that you want to annotate. #[derive(Debug)] -pub struct Snippet<'a> { +pub struct Snippet<'a, T> { pub(crate) origin: Option<&'a str>, pub(crate) line_start: usize, - pub(crate) source: &'a str, - pub(crate) annotations: Vec<Annotation<'a>>, - + pub(crate) markers: Vec<T>, pub(crate) fold: bool, } -impl<'a> Snippet<'a> { +impl<'a, T: Clone> Snippet<'a, T> { pub fn source(source: &'a str) -> Self { Self { origin: None, line_start: 1, source, - annotations: vec![], + markers: vec![], fold: false, } } @@ -136,32 +219,29 @@ impl<'a> Snippet<'a> { self } - pub fn annotation(mut self, annotation: Annotation<'a>) -> Self { - self.annotations.push(annotation); + pub fn fold(mut self, fold: bool) -> Self { + self.fold = fold; self } +} - pub fn annotations(mut self, annotation: impl IntoIterator<Item = Annotation<'a>>) -> Self { - self.annotations.extend(annotation); +impl<'a> Snippet<'a, Annotation<'a>> { + pub fn annotation(mut self, annotation: Annotation<'a>) -> Snippet<'a, Annotation<'a>> { + self.markers.push(annotation); self } - /// Hide lines without [`Annotation`]s - pub fn fold(mut self, fold: bool) -> Self { - self.fold = fold; + pub fn annotations(mut self, annotation: impl IntoIterator<Item = Annotation<'a>>) -> Self { + self.markers.extend(annotation); self } } -/// An annotation for a [`Snippet`]. -/// -/// See [`Level::span`] to create a [`Annotation`] #[derive(Clone, Debug)] pub struct Annotation<'a> { - /// The byte range of the annotation in the `source` string pub(crate) range: Range<usize>, pub(crate) label: Option<&'a str>, - pub(crate) level: Level, + pub(crate) kind: AnnotationKind, } impl<'a> Annotation<'a> { @@ -171,58 +251,66 @@ impl<'a> Annotation<'a> { } } -/// Types of annotations. -#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq)] -pub enum Level { - /// Error annotations are displayed using red color and "^" character. - Error, - /// Warning annotations are displayed using blue color and "-" character. - Warning, - Info, - Note, - Help, +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum AnnotationKind { + /// Color to [`Message`]'s [`Level`] + Primary, + /// "secondary"; fixed color + Context, } -impl Level { - pub fn title(self, title: &str) -> Message<'_> { - Message { - level: self, - id: None, - title, - snippets: vec![], - footer: vec![], - } - } - - /// Create a [`Annotation`] with the given span for a [`Snippet`] +impl AnnotationKind { pub fn span<'a>(self, span: Range<usize>) -> Annotation<'a> { Annotation { range: span, label: None, - level: self, + kind: self, } } + + pub(crate) fn is_primary(&self) -> bool { + matches!(self, AnnotationKind::Primary) + } } -impl Level { - pub(crate) fn as_str(&self) -> &'static str { - match self { - Level::Error => ERROR_TXT, - Level::Warning => WARNING_TXT, - Level::Info => INFO_TXT, - Level::Note => NOTE_TXT, - Level::Help => HELP_TXT, +#[derive(Clone, Debug)] +pub struct Origin<'a> { + pub(crate) origin: &'a str, + pub(crate) line: Option<usize>, + pub(crate) char_column: Option<usize>, + pub(crate) primary: bool, + pub(crate) label: Option<&'a str>, +} + +impl<'a> Origin<'a> { + pub fn new(origin: &'a str) -> Self { + Self { + origin, + line: None, + char_column: None, + primary: false, + label: None, } } - pub(crate) fn style(&self, stylesheet: &Stylesheet) -> Style { - match self { - Level::Error => stylesheet.error, - Level::Warning => stylesheet.warning, - Level::Info => stylesheet.info, - Level::Note => stylesheet.note, - Level::Help => stylesheet.help, - } + pub fn line(mut self, line: usize) -> Self { + self.line = Some(line); + self + } + + pub fn char_column(mut self, char_column: usize) -> Self { + self.char_column = Some(char_column); + self + } + + pub fn primary(mut self, primary: bool) -> Self { + self.primary = primary; + self + } + + pub fn label(mut self, label: &'a str) -> Self { + self.label = Some(label); + self } } diff --git a/tests/fixtures/color/ann_eof.toml b/tests/fixtures/color/ann_eof.toml index cee5f0fb..ac129f3f 100644 --- a/tests/fixtures/color/ann_eof.toml +++ b/tests/fixtures/color/ann_eof.toml @@ -2,14 +2,14 @@ level = "Error" title = "expected `.`, `=`" -[[message.snippets]] +[[message.sections]] +type = "Cause" source = "asdf" line_start = 1 origin = "Cargo.toml" -[[message.snippets.annotations]] -label = "" -level = "Error" -range = [4, 4] +annotations = [ + { label = "", kind = "Primary", range = [4, 4] }, +] [renderer] color = true diff --git a/tests/fixtures/color/ann_insertion.toml b/tests/fixtures/color/ann_insertion.toml index bf7411ef..13bc13ca 100644 --- a/tests/fixtures/color/ann_insertion.toml +++ b/tests/fixtures/color/ann_insertion.toml @@ -2,14 +2,14 @@ level = "Error" title = "expected `.`, `=`" -[[message.snippets]] +[[message.sections]] +type = "Cause" source = "asf" line_start = 1 origin = "Cargo.toml" -[[message.snippets.annotations]] -label = "'d' belongs here" -level = "Error" -range = [2, 2] +annotations = [ + { label = "'d' belongs here", kind = "Primary", range = [2, 2] } +] [renderer] color = true diff --git a/tests/fixtures/color/ann_multiline.toml b/tests/fixtures/color/ann_multiline.toml index 9d8c30f9..722c3e18 100644 --- a/tests/fixtures/color/ann_multiline.toml +++ b/tests/fixtures/color/ann_multiline.toml @@ -3,7 +3,8 @@ level = "Error" id = "E0027" title = "pattern does not mention fields `lineno`, `content`" -[[message.snippets]] +[[message.sections]] +type = "Cause" source = """ if let DisplayLine::Source { ref mut inline_marks, @@ -12,10 +13,9 @@ source = """ line_start = 139 origin = "src/display_list.rs" fold = false -[[message.snippets.annotations]] -label = "missing fields `lineno`, `content`" -level = "Error" -range = [31, 128] +annotations = [ + { label = "missing fields `lineno`, `content`", kind = "Primary", range = [31, 128] } +] [renderer] color = true diff --git a/tests/fixtures/color/ann_multiline2.toml b/tests/fixtures/color/ann_multiline2.toml index 259d94b4..329beb49 100644 --- a/tests/fixtures/color/ann_multiline2.toml +++ b/tests/fixtures/color/ann_multiline2.toml @@ -3,7 +3,8 @@ level = "Error" id = "E####" title = "spacing error found" -[[message.snippets]] +[[message.sections]] +type = "Cause" source = """ This is an example of an edge case of an annotation overflowing @@ -12,10 +13,9 @@ to exactly one character on next line. line_start = 26 origin = "foo.txt" fold = false -[[message.snippets.annotations]] -label = "this should not be on separate lines" -level = "Error" -range = [11, 19] +annotations = [ + { label = "this should not be on separate lines", kind = "Primary", range = [11, 19] }, +] [renderer] color = true diff --git a/tests/fixtures/color/ann_removed_nl.toml b/tests/fixtures/color/ann_removed_nl.toml index 36f74ef6..8ed96bcc 100644 --- a/tests/fixtures/color/ann_removed_nl.toml +++ b/tests/fixtures/color/ann_removed_nl.toml @@ -2,14 +2,14 @@ level = "Error" title = "expected `.`, `=`" -[[message.snippets]] +[[message.sections]] +type = "Cause" source = "asdf" line_start = 1 origin = "Cargo.toml" -[[message.snippets.annotations]] -label = "" -level = "Error" -range = [4, 5] +annotations = [ + { label = "", kind = "Primary", range = [4, 5] }, +] [renderer] color = true diff --git a/tests/fixtures/color/ensure-emoji-highlight-width.toml b/tests/fixtures/color/ensure-emoji-highlight-width.toml index 52168b48..8d7a14aa 100644 --- a/tests/fixtures/color/ensure-emoji-highlight-width.toml +++ b/tests/fixtures/color/ensure-emoji-highlight-width.toml @@ -3,16 +3,16 @@ title = "invalid character ` ` in package name: `haha this isn't a valid name level = "Error" -[[message.snippets]] +[[message.sections]] +type = "Cause" source = """ "haha this isn't a valid name 🐛" = { package = "libc", version = "0.1" } """ line_start = 7 origin = "<file>" -[[message.snippets.annotations]] -label = "" -level = "Error" -range = [0, 35] +annotations = [ + { label = "", kind = "Primary", range = [0, 35] }, +] [renderer] color = true diff --git a/tests/fixtures/color/fold_ann_multiline.svg b/tests/fixtures/color/fold_ann_multiline.svg index 1afc6527..80197e5c 100644 --- a/tests/fixtures/color/fold_ann_multiline.svg +++ b/tests/fixtures/color/fold_ann_multiline.svg @@ -4,7 +4,6 @@ .bg { background: #000000 } .fg-bright-blue { fill: #5555FF } .fg-bright-red { fill: #FF5555 } - .fg-yellow { fill: #AA5500 } .container { padding: 0 10px; line-height: 18px; @@ -22,13 +21,13 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>src/format.rs:51:6</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>src/format.rs:52:1</tspan> </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">51</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> ) -> Option<String> {</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">--------------</tspan><tspan> </tspan><tspan class="fg-yellow bold">expected `std::option::Option<std::string::String>` because of return type</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">--------------</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">expected `std::option::Option<std::string::String>` because of return type</tspan> </tspan> <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">52</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">/</tspan><tspan> for ann in annotations {</tspan> </tspan> diff --git a/tests/fixtures/color/fold_ann_multiline.toml b/tests/fixtures/color/fold_ann_multiline.toml index 80edfb55..745ef42e 100644 --- a/tests/fixtures/color/fold_ann_multiline.toml +++ b/tests/fixtures/color/fold_ann_multiline.toml @@ -3,7 +3,8 @@ level = "Error" id = "E0308" title = "mismatched types" -[[message.snippets]] +[[message.sections]] +type = "Cause" source = """ ) -> Option<String> { for ann in annotations { @@ -31,14 +32,10 @@ source = """ line_start = 51 origin = "src/format.rs" fold = true -[[message.snippets.annotations]] -label = "expected `std::option::Option<std::string::String>` because of return type" -level = "Warning" -range = [5, 19] -[[message.snippets.annotations]] -label = "expected enum `std::option::Option`, found ()" -level = "Error" -range = [22, 766] +annotations = [ + { label = "expected `std::option::Option<std::string::String>` because of return type", kind = "Context", range = [5, 19] }, + { label = "expected enum `std::option::Option`, found ()", kind = "Primary", range = [22, 766] }, +] [renderer] color = true diff --git a/tests/fixtures/color/fold_bad_origin_line.svg b/tests/fixtures/color/fold_bad_origin_line.svg index bd075e42..66083276 100644 --- a/tests/fixtures/color/fold_bad_origin_line.svg +++ b/tests/fixtures/color/fold_bad_origin_line.svg @@ -4,7 +4,6 @@ .bg { background: #000000 } .fg-bright-blue { fill: #5555FF } .fg-bright-red { fill: #FF5555 } - .fg-yellow { fill: #AA5500 } .container { padding: 0 10px; line-height: 18px; @@ -28,7 +27,7 @@ </tspan> <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">3</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> invalid syntax</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">--------------</tspan><tspan> </tspan><tspan class="fg-yellow bold">error here</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">--------------</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">error here</tspan> </tspan> </text> diff --git a/tests/fixtures/color/fold_bad_origin_line.toml b/tests/fixtures/color/fold_bad_origin_line.toml index 3e40137a..3b0d827f 100644 --- a/tests/fixtures/color/fold_bad_origin_line.toml +++ b/tests/fixtures/color/fold_bad_origin_line.toml @@ -2,7 +2,8 @@ level = "Error" title = "" -[[message.snippets]] +[[message.sections]] +type = "Cause" source = """ @@ -11,10 +12,9 @@ invalid syntax line_start = 1 origin = "path/to/error.rs" fold = true -[[message.snippets.annotations]] -label = "error here" -level = "Warning" -range = [2,16] +annotations = [ + { label = "error here", kind = "Context", range = [2,16] }, +] [renderer] color = true diff --git a/tests/fixtures/color/fold_leading.svg b/tests/fixtures/color/fold_leading.svg index 22a66c7c..23b31d4a 100644 --- a/tests/fixtures/color/fold_leading.svg +++ b/tests/fixtures/color/fold_leading.svg @@ -21,13 +21,13 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: invalid type: integer `20`, expected a bool</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>Cargo.toml:11:13</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>Cargo.toml:11:13</tspan> </tspan> - <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> + <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">11|</tspan><tspan> workspace = 20</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">11</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> workspace = 20</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^</tspan> </tspan> </text> diff --git a/tests/fixtures/color/fold_leading.toml b/tests/fixtures/color/fold_leading.toml index 90c6c8c4..564187be 100644 --- a/tests/fixtures/color/fold_leading.toml +++ b/tests/fixtures/color/fold_leading.toml @@ -3,7 +3,8 @@ level = "Error" id = "E0308" title = "invalid type: integer `20`, expected a bool" -[[message.snippets]] +[[message.sections]] +type = "Cause" source = """ [workspace] @@ -20,10 +21,9 @@ workspace = 20 line_start = 1 origin = "Cargo.toml" fold = true -[[message.snippets.annotations]] -label = "" -level = "Error" -range = [132, 134] +annotations = [ + { label = "", kind = "Primary", range = [132, 134] }, +] [renderer] color = true diff --git a/tests/fixtures/color/fold_trailing.toml b/tests/fixtures/color/fold_trailing.toml index 10b2240c..eea6365e 100644 --- a/tests/fixtures/color/fold_trailing.toml +++ b/tests/fixtures/color/fold_trailing.toml @@ -3,7 +3,8 @@ level = "Error" id = "E0308" title = "invalid type: integer `20`, expected a lints table" -[[message.snippets]] +[[message.sections]] +type = "Cause" source = """ lints = 20 @@ -19,10 +20,9 @@ edition = "2021" line_start = 1 origin = "Cargo.toml" fold = true -[[message.snippets.annotations]] -label = "" -level = "Error" -range = [8, 10] +annotations = [ + { label = "", kind = "Primary", range = [8, 10] }, +] [renderer] color = true diff --git a/tests/fixtures/color/issue_9.svg b/tests/fixtures/color/issue_9.svg index 6854bc38..5ae5da77 100644 --- a/tests/fixtures/color/issue_9.svg +++ b/tests/fixtures/color/issue_9.svg @@ -1,10 +1,9 @@ -<svg width="911px" height="218px" xmlns="http://www.w3.org/2000/svg"> +<svg width="911px" height="236px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } .fg-bright-blue { fill: #5555FF } .fg-bright-red { fill: #FF5555 } - .fg-yellow { fill: #AA5500 } .container { padding: 0 10px; line-height: 18px; @@ -22,25 +21,27 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected one of `.`, `;`, `?`, or an operator, found `for`</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>/code/rust/src/test/ui/annotate-snippet/suggestion.rs:4:5</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> + <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">::: </tspan><tspan>/code/rust/src/test/ui/annotate-snippet/suggestion.rs:4:5</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">4</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let x = vec![1];</tspan> + <tspan x="10px" y="82px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait</tspan> + <tspan x="10px" y="100px"><tspan class="fg-bright-blue bold">4</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let x = vec![1];</tspan> </tspan> - <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> + <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">-</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait</tspan> </tspan> - <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">7</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let y = x;</tspan> + <tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">value moved here</tspan> + <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">7</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let y = x;</tspan> </tspan> - <tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> + <tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">-</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">value moved here</tspan> </tspan> - <tspan x="10px" y="190px"><tspan class="fg-bright-blue bold">9</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> x;</tspan> + <tspan x="10px" y="190px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="208px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">value used here after move</tspan> + <tspan x="10px" y="208px"><tspan class="fg-bright-blue bold">9</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> x;</tspan> +</tspan> + <tspan x="10px" y="226px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">value used here after move</tspan> </tspan> </text> diff --git a/tests/fixtures/color/issue_9.toml b/tests/fixtures/color/issue_9.toml index 9de17534..96ad2c07 100644 --- a/tests/fixtures/color/issue_9.toml +++ b/tests/fixtures/color/issue_9.toml @@ -2,29 +2,32 @@ level = "Error" title = "expected one of `.`, `;`, `?`, or an operator, found `for`" -[[message.snippets]] +[[message.sections]] +type = "Cause" source = "let x = vec![1];" line_start = 4 origin = "/code/rust/src/test/ui/annotate-snippet/suggestion.rs" -[[message.snippets.annotations]] +[[message.sections.annotations]] label = "move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait" -level = "Warning" +kind = "Context" range = [4, 5] -[[message.snippets]] +[[message.sections]] +type = "Cause" source = "let y = x;" line_start = 7 -[[message.snippets.annotations]] +[[message.sections.annotations]] label = "value moved here" -level = "Warning" +kind = "Context" range = [8, 9] -[[message.snippets]] +[[message.sections]] +type = "Cause" source = "x;" line_start = 9 -[[message.snippets.annotations]] +[[message.sections.annotations]] label = "value used here after move" -level = "Error" +kind = "Primary" range = [0, 1] [renderer] diff --git a/tests/fixtures/color/multiple_annotations.toml b/tests/fixtures/color/multiple_annotations.toml index 824c5305..f4c90a4e 100644 --- a/tests/fixtures/color/multiple_annotations.toml +++ b/tests/fixtures/color/multiple_annotations.toml @@ -2,7 +2,8 @@ level = "Error" title = "" -[[message.snippets]] +[[message.sections]] +type = "Cause" source = """ fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>) { if let Some(annotation) = main_annotation { @@ -15,17 +16,17 @@ fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation> } """ line_start = 96 -[[message.snippets.annotations]] +[[message.sections.annotations]] label = "Variable defined here" -level = "Error" +kind = "Primary" range = [100, 110] -[[message.snippets.annotations]] +[[message.sections.annotations]] label = "Referenced here" -level = "Error" +kind = "Primary" range = [184, 194] -[[message.snippets.annotations]] +[[message.sections.annotations]] label = "Referenced again here" -level = "Error" +kind = "Primary" range = [243, 253] [renderer] diff --git a/tests/fixtures/color/simple.svg b/tests/fixtures/color/simple.svg index ef59075b..b849cf46 100644 --- a/tests/fixtures/color/simple.svg +++ b/tests/fixtures/color/simple.svg @@ -4,7 +4,6 @@ .bg { background: #000000 } .fg-bright-blue { fill: #5555FF } .fg-bright-red { fill: #FF5555 } - .fg-yellow { fill: #AA5500 } .container { padding: 0 10px; line-height: 18px; @@ -22,13 +21,13 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error</tspan><tspan class="bold">: expected one of `.`, `;`, `?`, or an operator, found `for`</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>src/format_color.rs:169:11</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>src/format_color.rs:171:9</tspan> </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">169</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> })</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-yellow bold">-</tspan><tspan> </tspan><tspan class="fg-yellow bold">expected one of `.`, `;`, `?`, or an operator here</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">-</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">expected one of `.`, `;`, `?`, or an operator here</tspan> </tspan> <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">170</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> diff --git a/tests/fixtures/color/simple.toml b/tests/fixtures/color/simple.toml index 2e6969f7..b70f11cc 100644 --- a/tests/fixtures/color/simple.toml +++ b/tests/fixtures/color/simple.toml @@ -2,20 +2,21 @@ level = "Error" title = "expected one of `.`, `;`, `?`, or an operator, found `for`" -[[message.snippets]] +[[message.sections]] +type = "Cause" source = """ }) for line in &self.body {""" line_start = 169 origin = "src/format_color.rs" -[[message.snippets.annotations]] +[[message.sections.annotations]] label = "unexpected token" -level = "Error" +kind = "Primary" range = [20, 23] -[[message.snippets.annotations]] +[[message.sections.annotations]] label = "expected one of `.`, `;`, `?`, or an operator here" -level = "Warning" +kind = "Context" range = [10, 11] [renderer] diff --git a/tests/fixtures/color/strip_line.toml b/tests/fixtures/color/strip_line.toml index 546c96a0..d7af6862 100644 --- a/tests/fixtures/color/strip_line.toml +++ b/tests/fixtures/color/strip_line.toml @@ -3,14 +3,15 @@ level = "Error" id = "E0308" title = "mismatched types" -[[message.snippets]] +[[message.sections]] +type = "Cause" source = " let _: () = 42;" line_start = 4 origin = "$DIR/whitespace-trimming.rs" -[[message.snippets.annotations]] +[[message.sections.annotations]] label = "expected (), found integer" -level = "Error" +kind = "Primary" range = [192, 194] [renderer] diff --git a/tests/fixtures/color/strip_line_char.toml b/tests/fixtures/color/strip_line_char.toml index 863abb3f..6585005c 100644 --- a/tests/fixtures/color/strip_line_char.toml +++ b/tests/fixtures/color/strip_line_char.toml @@ -1,16 +1,17 @@ [message] level = "Error" id = "E0308" -title = "mismatched types" +title = "mismatched types" -[[message.snippets]] +[[message.sections]] +type = "Cause" source = " let _: () = 42ñ" line_start = 4 origin = "$DIR/whitespace-trimming.rs" -[[message.snippets.annotations]] +[[message.sections.annotations]] label = "expected (), found integer" -level = "Error" +kind = "Primary" range = [192, 194] [renderer] diff --git a/tests/fixtures/color/strip_line_non_ws.toml b/tests/fixtures/color/strip_line_non_ws.toml index bfe9f317..1f085b5f 100644 --- a/tests/fixtures/color/strip_line_non_ws.toml +++ b/tests/fixtures/color/strip_line_non_ws.toml @@ -3,21 +3,22 @@ level = "Error" id = "E0308" title = "mismatched types" -[[message.snippets]] +[[message.sections]] +type = "Cause" source = """ let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); """ line_start = 4 origin = "$DIR/non-whitespace-trimming.rs" -[[message.snippets.annotations]] +[[message.sections.annotations]] label = "expected `()`, found integer" -level = "Error" +kind = "Primary" range = [237, 239] -[[message.snippets.annotations]] +[[message.sections.annotations]] label = "expected due to this" -level = "Error" +kind = "Primary" range = [232, 234] diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs index 4dbf341e..fffcf1f2 100644 --- a/tests/fixtures/deserialize.rs +++ b/tests/fixtures/deserialize.rs @@ -2,7 +2,9 @@ use serde::Deserialize; use std::ops::Range; use annotate_snippets::renderer::DEFAULT_TERM_WIDTH; -use annotate_snippets::{Annotation, Level, Message, Renderer, Snippet}; +use annotate_snippets::{ + Annotation, AnnotationKind, Element, Group, Level, Message, Renderer, Snippet, +}; #[derive(Deserialize)] pub(crate) struct Fixture { @@ -19,8 +21,7 @@ pub struct MessageDef { #[serde(default)] pub id: Option<String>, #[serde(default)] - pub footer: Vec<MessageDef>, - pub snippets: Vec<SnippetDef>, + pub sections: Vec<ElementDef>, } impl<'a> From<&'a MessageDef> for Message<'a> { @@ -29,35 +30,60 @@ impl<'a> From<&'a MessageDef> for Message<'a> { level, title, id, - footer, - snippets, + sections, } = val; - let mut message = level.title(title); + let mut message = level.message(title); if let Some(id) = id { message = message.id(id); } - message = message.snippets(snippets.iter().map(Snippet::from)); - message = message.footers(footer.iter().map(Into::into)); + + message = message.group(Group::new().elements(sections.iter().map(|s| match s { + ElementDef::Title(title) => Element::Title(title.level.title(&title.title)), + ElementDef::Cause(cause) => Element::Cause(Snippet::from(cause)), + }))); message } } #[derive(Deserialize)] -pub struct SnippetDef { - pub source: String, - pub line_start: usize, - pub origin: Option<String>, - pub annotations: Vec<AnnotationDef>, +#[serde(tag = "type")] +pub enum ElementDef { + Title(TitleDef), + Cause(SnippetAnnotationDef), +} + +impl<'a> From<&'a ElementDef> for Element<'a> { + fn from(val: &'a ElementDef) -> Self { + match val { + ElementDef::Title(title) => Element::Title(title.level.title(&title.title)), + ElementDef::Cause(cause) => Element::Cause(Snippet::from(cause)), + } + } +} + +#[derive(Deserialize)] +pub struct TitleDef { + pub title: String, + #[serde(with = "LevelDef")] + pub level: Level, +} + +#[derive(Deserialize)] +pub struct SnippetAnnotationDef { + pub(crate) origin: Option<String>, + pub(crate) line_start: usize, + pub(crate) source: String, + pub(crate) annotations: Vec<AnnotationDef>, #[serde(default)] - pub fold: bool, + pub(crate) fold: bool, } -impl<'a> From<&'a SnippetDef> for Snippet<'a> { - fn from(val: &'a SnippetDef) -> Self { - let SnippetDef { - source, - line_start, +impl<'a> From<&'a SnippetAnnotationDef> for Snippet<'a, Annotation<'a>> { + fn from(val: &'a SnippetAnnotationDef) -> Self { + let SnippetAnnotationDef { origin, + line_start, + source, annotations, fold, } = val; @@ -74,21 +100,25 @@ impl<'a> From<&'a SnippetDef> for Snippet<'a> { pub struct AnnotationDef { pub range: Range<usize>, pub label: String, - #[serde(with = "LevelDef")] - pub level: Level, + #[serde(with = "AnnotationKindDef")] + pub kind: AnnotationKind, } impl<'a> From<&'a AnnotationDef> for Annotation<'a> { fn from(val: &'a AnnotationDef) -> Self { - let AnnotationDef { - range, - label, - level, - } = val; - level.span(range.start..range.end).label(label) + let AnnotationDef { range, label, kind } = val; + kind.span(range.start..range.end).label(label) } } +#[allow(dead_code)] +#[derive(Deserialize)] +#[serde(remote = "AnnotationKind")] +enum AnnotationKindDef { + Primary, + Context, +} + #[allow(dead_code)] #[derive(Deserialize)] #[serde(remote = "Level")] diff --git a/tests/formatter.rs b/tests/formatter.rs index df5d0813..33b998f5 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -1,14 +1,16 @@ -use annotate_snippets::{Level, Renderer, Snippet}; +use annotate_snippets::{Annotation, AnnotationKind, Group, Level, Renderer, Snippet}; use snapbox::{assert_data_eq, str}; #[test] fn test_i_29() { - let snippets = Level::Error.title("oops").snippet( - Snippet::source("First line\r\nSecond oops line") - .origin("<current file>") - .annotation(Level::Error.span(19..23).label("oops")) - .fold(true), + let snippets = Level::Error.message("oops").group( + Group::new().element( + Snippet::source("First line\r\nSecond oops line") + .origin("<current file>") + .annotation(AnnotationKind::Primary.span(19..23).label("oops")) + .fold(true), + ), ); let expected = str![[r#" error: oops @@ -24,10 +26,12 @@ error: oops #[test] fn test_point_to_double_width_characters() { - let snippets = Level::Error.title("").snippet( - Snippet::source("こんにちは、世界") - .origin("<current file>") - .annotation(Level::Error.span(18..24).label("world")), + let snippets = Level::Error.message("").group( + Group::new().element( + Snippet::source("こんにちは、世界") + .origin("<current file>") + .annotation(AnnotationKind::Primary.span(18..24).label("world")), + ), ); let expected = str![[r#" @@ -44,10 +48,12 @@ error: #[test] fn test_point_to_double_width_characters_across_lines() { - let snippets = Level::Error.title("").snippet( - Snippet::source("おはよう\nございます") - .origin("<current file>") - .annotation(Level::Error.span(6..22).label("Good morning")), + let snippets = Level::Error.message("").group( + Group::new().element( + Snippet::source("おはよう\nございます") + .origin("<current file>") + .annotation(AnnotationKind::Primary.span(6..22).label("Good morning")), + ), ); let expected = str![[r#" @@ -66,11 +72,13 @@ error: #[test] fn test_point_to_double_width_characters_multiple() { - let snippets = Level::Error.title("").snippet( - Snippet::source("お寿司\n食べたい🍣") - .origin("<current file>") - .annotation(Level::Error.span(0..9).label("Sushi1")) - .annotation(Level::Note.span(16..22).label("Sushi2")), + let snippets = Level::Error.message("").group( + Group::new().element( + Snippet::source("お寿司\n食べたい🍣") + .origin("<current file>") + .annotation(AnnotationKind::Primary.span(0..9).label("Sushi1")) + .annotation(AnnotationKind::Context.span(16..22).label("Sushi2")), + ), ); let expected = str![[r#" @@ -89,10 +97,12 @@ error: #[test] fn test_point_to_double_width_characters_mixed() { - let snippets = Level::Error.title("").snippet( - Snippet::source("こんにちは、新しいWorld!") - .origin("<current file>") - .annotation(Level::Error.span(18..32).label("New world")), + let snippets = Level::Error.message("").group( + Group::new().element( + Snippet::source("こんにちは、新しいWorld!") + .origin("<current file>") + .annotation(AnnotationKind::Primary.span(18..32).label("New world")), + ), ); let expected = str![[r#" @@ -109,7 +119,7 @@ error: #[test] fn test_format_title() { - let input = Level::Error.title("This is a title").id("E0001"); + let input = Level::Error.message("This is a title").id("E0001"); let expected = str![r#"error[E0001]: This is a title"#]; let renderer = Renderer::plain(); @@ -120,8 +130,8 @@ fn test_format_title() { fn test_format_snippet_only() { let source = "This is line 1\nThis is line 2"; let input = Level::Error - .title("") - .snippet(Snippet::source(source).line_start(5402)); + .message("") + .group(Group::new().element(Snippet::<Annotation<'_>>::source(source).line_start(5402))); let expected = str![[r#" error: @@ -137,17 +147,26 @@ error: fn test_format_snippets_continuation() { let src_0 = "This is slice 1"; let src_1 = "This is slice 2"; - let input = Level::Error - .title("") - .snippet(Snippet::source(src_0).line_start(5402).origin("file1.rs")) - .snippet(Snippet::source(src_1).line_start(2).origin("file2.rs")); + let input = Level::Error.message("").group( + Group::new() + .element( + Snippet::<Annotation<'_>>::source(src_0) + .line_start(5402) + .origin("file1.rs"), + ) + .element( + Snippet::<Annotation<'_>>::source(src_1) + .line_start(2) + .origin("file2.rs"), + ), + ); let expected = str![[r#" error: --> file1.rs | 5402 | This is slice 1 | - ::: file2.rs + ::: file2.rs:2 | 2 | This is slice 2 "#]]; @@ -162,10 +181,14 @@ fn test_format_snippet_annotation_standalone() { let source = [line_1, line_2].join("\n"); // In line 2 let range = 22..24; - let input = Level::Error.title("").snippet( - Snippet::source(&source) - .line_start(5402) - .annotation(Level::Info.span(range.clone()).label("Test annotation")), + let input = Level::Error.message("").group( + Group::new().element( + Snippet::source(&source).line_start(5402).annotation( + AnnotationKind::Context + .span(range.clone()) + .label("Test annotation"), + ), + ), ); let expected = str![[r#" error: @@ -181,10 +204,11 @@ error: #[test] fn test_format_footer_title() { let input = Level::Error - .title("") - .footer(Level::Error.title("This __is__ a title")); + .message("") + .group(Group::new().element(Level::Error.title("This __is__ a title"))); let expected = str![[r#" error: + | = error: This __is__ a title "#]]; let renderer = Renderer::plain(); @@ -196,10 +220,14 @@ error: fn test_i26() { let source = "short"; let label = "label"; - let input = Level::Error.title("").snippet( - Snippet::source(source) - .line_start(0) - .annotation(Level::Error.span(0..source.len() + 2).label(label)), + let input = Level::Error.message("").group( + Group::new().element( + Snippet::source(source).line_start(0).annotation( + AnnotationKind::Primary + .span(0..source.len() + 2) + .label(label), + ), + ), ); let renderer = Renderer::plain(); let _ = renderer.render(input); @@ -209,8 +237,8 @@ fn test_i26() { fn test_source_content() { let source = "This is an example\nof content lines"; let input = Level::Error - .title("") - .snippet(Snippet::source(source).line_start(56)); + .message("") + .group(Group::new().element(Snippet::<Annotation<'_>>::source(source).line_start(56))); let expected = str![[r#" error: | @@ -224,10 +252,12 @@ error: #[test] fn test_source_annotation_standalone_singleline() { let source = "tests"; - let input = Level::Error.title("").snippet( - Snippet::source(source) - .line_start(1) - .annotation(Level::Help.span(0..5).label("Example string")), + let input = Level::Error.message("").group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .annotation(AnnotationKind::Context.span(0..5).label("Example string")), + ), ); let expected = str![[r#" error: @@ -242,11 +272,13 @@ error: #[test] fn test_source_annotation_standalone_multiline() { let source = "tests"; - let input = Level::Error.title("").snippet( - Snippet::source(source) - .line_start(1) - .annotation(Level::Help.span(0..5).label("Example string")) - .annotation(Level::Help.span(0..5).label("Second line")), + let input = Level::Error.message("").group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .annotation(AnnotationKind::Context.span(0..5).label("Example string")) + .annotation(AnnotationKind::Context.span(0..5).label("Second line")), + ), ); let expected = str![[r#" error: @@ -264,8 +296,8 @@ error: #[test] fn test_only_source() { let input = Level::Error - .title("") - .snippet(Snippet::source("").origin("file.rs")); + .message("") + .group(Group::new().element(Snippet::<Annotation<'_>>::source("").origin("file.rs"))); let expected = str![[r#" error: --> file.rs @@ -279,8 +311,8 @@ error: fn test_anon_lines() { let source = "This is an example\nof content lines\n\nabc"; let input = Level::Error - .title("") - .snippet(Snippet::source(source).line_start(56)); + .message("") + .group(Group::new().element(Snippet::<Annotation<'_>>::source(source).line_start(56))); let expected = str![[r#" error: | @@ -295,12 +327,14 @@ LL | abc #[test] fn issue_130() { - let input = Level::Error.title("dummy").snippet( - Snippet::source("foo\nbar\nbaz") - .origin("file/path") - .line_start(3) - .fold(true) - .annotation(Level::Error.span(4..11)), // bar\nbaz + let input = Level::Error.message("dummy").group( + Group::new().element( + Snippet::source("foo\nbar\nbaz") + .origin("file/path") + .line_start(3) + .fold(true) + .annotation(AnnotationKind::Primary.span(4..11)), + ), // bar\nbaz ); let expected = str![[r#" @@ -321,12 +355,14 @@ fn unterminated_string_multiline() { a\" // ... "; - let input = Level::Error.title("").snippet( - Snippet::source(source) - .origin("file/path") - .line_start(3) - .fold(true) - .annotation(Level::Error.span(0..10)), // 1..10 works + let input = Level::Error.message("").group( + Group::new().element( + Snippet::source(source) + .origin("file/path") + .line_start(3) + .fold(true) + .annotation(AnnotationKind::Primary.span(0..10)), + ), // 1..10 works ); let expected = str![[r#" error: @@ -343,11 +379,13 @@ error: #[test] fn char_and_nl_annotate_char() { let source = "a\r\nb"; - let input = Level::Error.title("").snippet( - Snippet::source(source) - .origin("file/path") - .line_start(3) - .annotation(Level::Error.span(0..2)), // a\r + let input = Level::Error.message("").group( + Group::new().element( + Snippet::source(source) + .origin("file/path") + .line_start(3) + .annotation(AnnotationKind::Primary.span(0..2)), + ), // a\r ); let expected = str![[r#" error: @@ -364,11 +402,13 @@ error: #[test] fn char_eol_annotate_char() { let source = "a\r\nb"; - let input = Level::Error.title("").snippet( - Snippet::source(source) - .origin("file/path") - .line_start(3) - .annotation(Level::Error.span(0..3)), // a\r\n + let input = Level::Error.message("").group( + Group::new().element( + Snippet::source(source) + .origin("file/path") + .line_start(3) + .annotation(AnnotationKind::Primary.span(0..3)), + ), // a\r\n ); let expected = str![[r#" error: @@ -384,10 +424,12 @@ error: #[test] fn char_eol_annotate_char_double_width() { - let snippets = Level::Error.title("").snippet( - Snippet::source("こん\r\nにちは\r\n世界") - .origin("<current file>") - .annotation(Level::Error.span(3..8)), // ん\r\n + let snippets = Level::Error.message("").group( + Group::new().element( + Snippet::source("こん\r\nにちは\r\n世界") + .origin("<current file>") + .annotation(AnnotationKind::Primary.span(3..8)), + ), // ん\r\n ); let expected = str![[r#" @@ -407,11 +449,13 @@ error: #[test] fn annotate_eol() { let source = "a\r\nb"; - let input = Level::Error.title("").snippet( - Snippet::source(source) - .origin("file/path") - .line_start(3) - .annotation(Level::Error.span(1..2)), // \r + let input = Level::Error.message("").group( + Group::new().element( + Snippet::source(source) + .origin("file/path") + .line_start(3) + .annotation(AnnotationKind::Primary.span(1..2)), + ), // \r ); let expected = str![[r#" error: @@ -428,11 +472,13 @@ error: #[test] fn annotate_eol2() { let source = "a\r\nb"; - let input = Level::Error.title("").snippet( - Snippet::source(source) - .origin("file/path") - .line_start(3) - .annotation(Level::Error.span(1..3)), // \r\n + let input = Level::Error.message("").group( + Group::new().element( + Snippet::source(source) + .origin("file/path") + .line_start(3) + .annotation(AnnotationKind::Primary.span(1..3)), + ), // \r\n ); let expected = str![[r#" error: @@ -449,11 +495,13 @@ error: #[test] fn annotate_eol3() { let source = "a\r\nb"; - let input = Level::Error.title("").snippet( - Snippet::source(source) - .origin("file/path") - .line_start(3) - .annotation(Level::Error.span(2..3)), // \n + let input = Level::Error.message("").group( + Group::new().element( + Snippet::source(source) + .origin("file/path") + .line_start(3) + .annotation(AnnotationKind::Primary.span(2..3)), + ), // \n ); let expected = str![[r#" error: @@ -470,11 +518,13 @@ error: #[test] fn annotate_eol4() { let source = "a\r\nb"; - let input = Level::Error.title("").snippet( - Snippet::source(source) - .origin("file/path") - .line_start(3) - .annotation(Level::Error.span(2..2)), // \n + let input = Level::Error.message("").group( + Group::new().element( + Snippet::source(source) + .origin("file/path") + .line_start(3) + .annotation(AnnotationKind::Primary.span(2..2)), + ), // \n ); let expected = str![[r#" error: @@ -490,10 +540,12 @@ error: #[test] fn annotate_eol_double_width() { - let snippets = Level::Error.title("").snippet( - Snippet::source("こん\r\nにちは\r\n世界") - .origin("<current file>") - .annotation(Level::Error.span(7..8)), // \n + let snippets = Level::Error.message("").group( + Group::new().element( + Snippet::source("こん\r\nにちは\r\n世界") + .origin("<current file>") + .annotation(AnnotationKind::Primary.span(7..8)), + ), // \n ); let expected = str![[r#" @@ -513,11 +565,13 @@ error: #[test] fn multiline_eol_start() { let source = "a\r\nb"; - let input = Level::Error.title("").snippet( - Snippet::source(source) - .origin("file/path") - .line_start(3) - .annotation(Level::Error.span(1..4)), // \r\nb + let input = Level::Error.message("").group( + Group::new().element( + Snippet::source(source) + .origin("file/path") + .line_start(3) + .annotation(AnnotationKind::Primary.span(1..4)), + ), // \r\nb ); let expected = str![[r#" error: @@ -535,11 +589,13 @@ error: #[test] fn multiline_eol_start2() { let source = "a\r\nb"; - let input = Level::Error.title("").snippet( - Snippet::source(source) - .origin("file/path") - .line_start(3) - .annotation(Level::Error.span(2..4)), // \nb + let input = Level::Error.message("").group( + Group::new().element( + Snippet::source(source) + .origin("file/path") + .line_start(3) + .annotation(AnnotationKind::Primary.span(2..4)), + ), // \nb ); let expected = str![[r#" error: @@ -557,11 +613,13 @@ error: #[test] fn multiline_eol_start3() { let source = "a\nb"; - let input = Level::Error.title("").snippet( - Snippet::source(source) - .origin("file/path") - .line_start(3) - .annotation(Level::Error.span(1..3)), // \nb + let input = Level::Error.message("").group( + Group::new().element( + Snippet::source(source) + .origin("file/path") + .line_start(3) + .annotation(AnnotationKind::Primary.span(1..3)), + ), // \nb ); let expected = str![[r#" error: @@ -578,10 +636,12 @@ error: #[test] fn multiline_eol_start_double_width() { - let snippets = Level::Error.title("").snippet( - Snippet::source("こん\r\nにちは\r\n世界") - .origin("<current file>") - .annotation(Level::Error.span(7..11)), // \r\nに + let snippets = Level::Error.message("").group( + Group::new().element( + Snippet::source("こん\r\nにちは\r\n世界") + .origin("<current file>") + .annotation(AnnotationKind::Primary.span(7..11)), + ), // \r\nに ); let expected = str![[r#" @@ -602,11 +662,13 @@ error: #[test] fn multiline_eol_start_eol_end() { let source = "a\nb\nc"; - let input = Level::Error.title("").snippet( - Snippet::source(source) - .origin("file/path") - .line_start(3) - .annotation(Level::Error.span(1..4)), // \nb\n + let input = Level::Error.message("").group( + Group::new().element( + Snippet::source(source) + .origin("file/path") + .line_start(3) + .annotation(AnnotationKind::Primary.span(1..4)), + ), // \nb\n ); let expected = str![[r#" error: @@ -625,11 +687,13 @@ error: #[test] fn multiline_eol_start_eol_end2() { let source = "a\r\nb\r\nc"; - let input = Level::Error.title("").snippet( - Snippet::source(source) - .origin("file/path") - .line_start(3) - .annotation(Level::Error.span(2..5)), // \nb\r + let input = Level::Error.message("").group( + Group::new().element( + Snippet::source(source) + .origin("file/path") + .line_start(3) + .annotation(AnnotationKind::Primary.span(2..5)), + ), // \nb\r ); let expected = str![[r#" error: @@ -648,11 +712,13 @@ error: #[test] fn multiline_eol_start_eol_end3() { let source = "a\r\nb\r\nc"; - let input = Level::Error.title("").snippet( - Snippet::source(source) - .origin("file/path") - .line_start(3) - .annotation(Level::Error.span(2..6)), // \nb\r\n + let input = Level::Error.message("").group( + Group::new().element( + Snippet::source(source) + .origin("file/path") + .line_start(3) + .annotation(AnnotationKind::Primary.span(2..6)), + ), // \nb\r\n ); let expected = str![[r#" error: @@ -671,11 +737,13 @@ error: #[test] fn multiline_eol_start_eof_end() { let source = "a\r\nb"; - let input = Level::Error.title("").snippet( - Snippet::source(source) - .origin("file/path") - .line_start(3) - .annotation(Level::Error.span(1..5)), // \r\nb(EOF) + let input = Level::Error.message("").group( + Group::new().element( + Snippet::source(source) + .origin("file/path") + .line_start(3) + .annotation(AnnotationKind::Primary.span(1..5)), + ), // \r\nb(EOF) ); let expected = str![[r#" error: @@ -693,11 +761,13 @@ error: #[test] fn multiline_eol_start_eof_end_double_width() { let source = "ん\r\nに"; - let input = Level::Error.title("").snippet( - Snippet::source(source) - .origin("file/path") - .line_start(3) - .annotation(Level::Error.span(3..9)), // \r\nに(EOF) + let input = Level::Error.message("").group( + Group::new().element( + Snippet::source(source) + .origin("file/path") + .line_start(3) + .annotation(AnnotationKind::Primary.span(3..9)), + ), // \r\nに(EOF) ); let expected = str![[r#" error: @@ -715,20 +785,22 @@ error: #[test] fn two_single_line_same_line() { let source = r#"bar = { version = "0.1.0", optional = true }"#; - let input = Level::Error.title("unused optional dependency").snippet( - Snippet::source(source) - .origin("Cargo.toml") - .line_start(4) - .annotation( - Level::Error - .span(0..3) - .label("I need this to be really long so I can test overlaps"), - ) - .annotation( - Level::Info - .span(27..42) - .label("This should also be long but not too long"), - ), + let input = Level::Error.message("unused optional dependency").group( + Group::new().element( + Snippet::source(source) + .origin("Cargo.toml") + .line_start(4) + .annotation( + AnnotationKind::Primary + .span(0..3) + .label("I need this to be really long so I can test overlaps"), + ) + .annotation( + AnnotationKind::Context + .span(27..42) + .label("This should also be long but not too long"), + ), + ), ); let expected = str![[r#" error: unused optional dependency @@ -750,19 +822,21 @@ this is another line so is this bar = { version = "0.1.0", optional = true } "#; - let input = Level::Error.title("unused optional dependency").snippet( - Snippet::source(source) - .line_start(4) - .annotation( - Level::Error - .span(41..119) - .label("I need this to be really long so I can test overlaps"), - ) - .annotation( - Level::Info - .span(27..42) - .label("This should also be long but not too long"), - ), + let input = Level::Error.message("unused optional dependency").group( + Group::new().element( + Snippet::source(source) + .line_start(4) + .annotation( + AnnotationKind::Primary + .span(41..119) + .label("I need this to be really long so I can test overlaps"), + ) + .annotation( + AnnotationKind::Context + .span(27..42) + .label("This should also be long but not too long"), + ), + ), ); let expected = str![[r#" error: unused optional dependency @@ -787,24 +861,26 @@ this is another line so is this bar = { version = "0.1.0", optional = true } "#; - let input = Level::Error.title("unused optional dependency").snippet( - Snippet::source(source) - .line_start(4) - .annotation( - Level::Error - .span(41..119) - .label("I need this to be really long so I can test overlaps"), - ) - .annotation( - Level::Error - .span(8..102) - .label("I need this to be really long so I can test overlaps"), - ) - .annotation( - Level::Info - .span(27..42) - .label("This should also be long but not too long"), - ), + let input = Level::Error.message("unused optional dependency").group( + Group::new().element( + Snippet::source(source) + .line_start(4) + .annotation( + AnnotationKind::Primary + .span(41..119) + .label("I need this to be really long so I can test overlaps"), + ) + .annotation( + AnnotationKind::Primary + .span(8..102) + .label("I need this to be really long so I can test overlaps"), + ) + .annotation( + AnnotationKind::Context + .span(27..42) + .label("This should also be long but not too long"), + ), + ), ); let expected = str![[r#" error: unused optional dependency @@ -833,29 +909,31 @@ so is this bar = { version = "0.1.0", optional = true } this is another line "#; - let input = Level::Error.title("unused optional dependency").snippet( - Snippet::source(source) - .line_start(4) - .annotation( - Level::Error - .span(41..119) - .label("I need this to be really long so I can test overlaps"), - ) - .annotation( - Level::Error - .span(8..102) - .label("I need this to be really long so I can test overlaps"), - ) - .annotation( - Level::Error - .span(48..126) - .label("I need this to be really long so I can test overlaps"), - ) - .annotation( - Level::Info - .span(27..42) - .label("This should also be long but not too long"), - ), + let input = Level::Error.message("unused optional dependency").group( + Group::new().element( + Snippet::source(source) + .line_start(4) + .annotation( + AnnotationKind::Primary + .span(41..119) + .label("I need this to be really long so I can test overlaps"), + ) + .annotation( + AnnotationKind::Primary + .span(8..102) + .label("I need this to be really long so I can test overlaps"), + ) + .annotation( + AnnotationKind::Primary + .span(48..126) + .label("I need this to be really long so I can test overlaps"), + ) + .annotation( + AnnotationKind::Context + .span(27..42) + .label("This should also be long but not too long"), + ), + ), ); let expected = str![[r#" error: unused optional dependency @@ -882,11 +960,13 @@ error: unused optional dependency #[test] fn origin_correct_start_line() { let source = "aaa\nbbb\nccc\nddd\n"; - let input = Level::Error.title("title").snippet( - Snippet::source(source) - .origin("origin.txt") - .fold(false) - .annotation(Level::Error.span(8..8 + 3).label("annotation")), + let input = Level::Error.message("title").group( + Group::new().element( + Snippet::source(source) + .origin("origin.txt") + .fold(false) + .annotation(AnnotationKind::Primary.span(8..8 + 3).label("annotation")), + ), ); let expected = str![[r#" @@ -906,11 +986,17 @@ error: title #[test] fn origin_correct_mid_line() { let source = "aaa\nbbb\nccc\nddd\n"; - let input = Level::Error.title("title").snippet( - Snippet::source(source) - .origin("origin.txt") - .fold(false) - .annotation(Level::Error.span(8 + 1..8 + 3).label("annotation")), + let input = Level::Error.message("title").group( + Group::new().element( + Snippet::source(source) + .origin("origin.txt") + .fold(false) + .annotation( + AnnotationKind::Primary + .span(8 + 1..8 + 3) + .label("annotation"), + ), + ), ); let expected = str![[r#" diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index f8b36d88..47a33990 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -2,7 +2,7 @@ //! //! [parser-tests]: https://github.com/rust-lang/rust/blob/894f7a4ba6554d3797404bbf550d9919df060b97/compiler/rustc_parse/src/parser/tests.rs -use annotate_snippets::{Level, Renderer, Snippet}; +use annotate_snippets::{AnnotationKind, Group, Level, Origin, Renderer, Snippet}; use snapbox::{assert_data_eq, str}; @@ -12,12 +12,14 @@ fn ends_on_col0() { fn foo() { } "#; - let input = Level::Error.title("foo").snippet( - Snippet::source(source) - .line_start(1) - .origin("test.rs") - .fold(true) - .annotation(Level::Error.span(10..13).label("test")), + let input = Level::Error.message("foo").group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(10..13).label("test")), + ), ); let expected = str![[r#" @@ -40,12 +42,14 @@ fn foo() { } "#; - let input = Level::Error.title("foo").snippet( - Snippet::source(source) - .line_start(1) - .origin("test.rs") - .fold(true) - .annotation(Level::Error.span(10..17).label("test")), + let input = Level::Error.message("foo").group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(10..17).label("test")), + ), ); let expected = str![[r#" @@ -70,17 +74,23 @@ fn foo() { X2 Y2 } "#; - let input = Level::Error.title("foo").snippet( - Snippet::source(source) - .line_start(1) - .origin("test.rs") - .fold(true) - .annotation(Level::Error.span(14..32).label("`X` is a good letter")) - .annotation( - Level::Warning - .span(17..35) - .label("`Y` is a good letter too"), - ), + let input = Level::Error.message("foo").group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(14..32) + .label("`X` is a good letter"), + ) + .annotation( + AnnotationKind::Context + .span(17..35) + .label("`Y` is a good letter too"), + ), + ), ); let expected = str![[r#" @@ -107,17 +117,23 @@ fn foo() { Y1 X1 } "#; - let input = Level::Error.title("foo").snippet( - Snippet::source(source) - .line_start(1) - .origin("test.rs") - .fold(true) - .annotation(Level::Error.span(14..27).label("`X` is a good letter")) - .annotation( - Level::Warning - .span(17..24) - .label("`Y` is a good letter too"), - ), + let input = Level::Error.message("foo").group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(14..27) + .label("`X` is a good letter"), + ) + .annotation( + AnnotationKind::Context + .span(17..24) + .label("`Y` is a good letter too"), + ), + ), ); let expected = str![[r#" @@ -145,17 +161,23 @@ fn foo() { X3 Y3 Z3 } "#; - let input = Level::Error.title("foo").snippet( - Snippet::source(source) - .line_start(1) - .origin("test.rs") - .fold(true) - .annotation(Level::Error.span(17..38).label("`X` is a good letter")) - .annotation( - Level::Warning - .span(31..49) - .label("`Y` is a good letter too"), - ), + let input = Level::Error.message("foo").group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(17..38) + .label("`X` is a good letter"), + ) + .annotation( + AnnotationKind::Context + .span(31..49) + .label("`Y` is a good letter too"), + ), + ), ); let expected = str![[r#" @@ -183,18 +205,24 @@ fn foo() { X2 Y2 Z2 } "#; - let input = Level::Error.title("foo").snippet( - Snippet::source(source) - .line_start(1) - .origin("test.rs") - .fold(true) - .annotation(Level::Error.span(14..38).label("`X` is a good letter")) - .annotation( - Level::Warning - .span(17..41) - .label("`Y` is a good letter too"), - ) - .annotation(Level::Warning.span(20..44).label("`Z` label")), + let input = Level::Error.message("foo").group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(14..38) + .label("`X` is a good letter"), + ) + .annotation( + AnnotationKind::Context + .span(17..41) + .label("`Y` is a good letter too"), + ) + .annotation(AnnotationKind::Context.span(20..44).label("`Z` label")), + ), ); let expected = str![[r#" @@ -224,18 +252,24 @@ fn foo() { X2 Y2 Z2 } "#; - let input = Level::Error.title("foo").snippet( - Snippet::source(source) - .line_start(1) - .origin("test.rs") - .fold(true) - .annotation(Level::Error.span(14..38).label("`X` is a good letter")) - .annotation( - Level::Warning - .span(14..38) - .label("`Y` is a good letter too"), - ) - .annotation(Level::Warning.span(14..38).label("`Z` label")), + let input = Level::Error.message("foo").group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(14..38) + .label("`X` is a good letter"), + ) + .annotation( + AnnotationKind::Context + .span(14..38) + .label("`Y` is a good letter too"), + ) + .annotation(AnnotationKind::Context.span(14..38).label("`Z` label")), + ), ); // This should have a `^` but we currently don't support the idea of a @@ -247,7 +281,7 @@ error: foo 3 | / X0 Y0 Z0 4 | | X1 Y1 Z1 5 | | X2 Y2 Z2 - | | - + | | ^ | | | | | `X` is a good letter | |____`Y` is a good letter too @@ -266,18 +300,24 @@ fn foo() { X3 Y3 Z3 } "#; - let input = Level::Error.title("foo").snippet( - Snippet::source(source) - .line_start(1) - .origin("test.rs") - .fold(true) - .annotation(Level::Error.span(17..27).label("`X` is a good letter")) - .annotation( - Level::Warning - .span(28..44) - .label("`Y` is a good letter too"), - ) - .annotation(Level::Warning.span(36..52).label("`Z`")), + let input = Level::Error.message("foo").group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(17..27) + .label("`X` is a good letter"), + ) + .annotation( + AnnotationKind::Context + .span(28..44) + .label("`Y` is a good letter too"), + ) + .annotation(AnnotationKind::Context.span(36..52).label("`Z`")), + ), ); let expected = str![[r#" @@ -310,17 +350,23 @@ fn foo() { X3 Y3 Z3 } "#; - let input = Level::Error.title("foo").snippet( - Snippet::source(source) - .line_start(1) - .origin("test.rs") - .fold(true) - .annotation(Level::Error.span(14..27).label("`X` is a good letter")) - .annotation( - Level::Warning - .span(39..55) - .label("`Y` is a good letter too"), - ), + let input = Level::Error.message("foo").group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(14..27) + .label("`X` is a good letter"), + ) + .annotation( + AnnotationKind::Context + .span(39..55) + .label("`Y` is a good letter too"), + ), + ), ); let expected = str![[r#" @@ -348,17 +394,23 @@ fn foo() { X3 Y3 Z3 } "#; - let input = Level::Error.title("foo").snippet( - Snippet::source(source) - .line_start(1) - .origin("test.rs") - .fold(true) - .annotation(Level::Error.span(17..27).label("`X` is a good letter")) - .annotation( - Level::Warning - .span(31..55) - .label("`Y` is a good letter too"), - ), + let input = Level::Error.message("foo").group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(17..27) + .label("`X` is a good letter"), + ) + .annotation( + AnnotationKind::Context + .span(31..55) + .label("`Y` is a good letter too"), + ), + ), ); let expected = str![[r#" @@ -385,19 +437,25 @@ fn foo() { a { b { c } d } } "#; - let input = Level::Error.title("foo").snippet( - Snippet::source(source) - .line_start(1) - .origin("test.rs") - .fold(true) - .annotation(Level::Error.span(18..25).label("")) - .annotation(Level::Warning.span(14..27).label("`a` is a good letter")) - .annotation(Level::Warning.span(22..23).label("")), + let input = Level::Error.message("foo").group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(18..25).label("")) + .annotation( + AnnotationKind::Context + .span(14..27) + .label("`a` is a good letter"), + ) + .annotation(AnnotationKind::Context.span(22..23).label("")), + ), ); let expected = str![[r#" error: foo - --> test.rs:3:3 + --> test.rs:3:7 | 3 | a { b { c } d } | ----^^^^-^^-- `a` is a good letter @@ -412,13 +470,19 @@ fn foo() { a { b { c } d } } "#; - let input = Level::Error.title("foo").snippet( - Snippet::source(source) - .line_start(1) - .origin("test.rs") - .fold(true) - .annotation(Level::Error.span(14..27).label("`a` is a good letter")) - .annotation(Level::Warning.span(18..25).label("")), + let input = Level::Error.message("foo").group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(14..27) + .label("`a` is a good letter"), + ) + .annotation(AnnotationKind::Context.span(18..25).label("")), + ), ); let expected = str![[r#" @@ -438,19 +502,25 @@ fn foo() { a { b { c } d } } "#; - let input = Level::Error.title("foo").snippet( - Snippet::source(source) - .line_start(1) - .origin("test.rs") - .fold(true) - .annotation(Level::Error.span(18..25).label("`b` is a good letter")) - .annotation(Level::Warning.span(14..27).label("")) - .annotation(Level::Warning.span(22..23).label("")), + let input = Level::Error.message("foo").group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(18..25) + .label("`b` is a good letter"), + ) + .annotation(AnnotationKind::Context.span(14..27).label("")) + .annotation(AnnotationKind::Context.span(22..23).label("")), + ), ); let expected = str![[r#" error: foo - --> test.rs:3:3 + --> test.rs:3:7 | 3 | a { b { c } d } | ----^^^^-^^-- @@ -467,13 +537,19 @@ fn foo() { a { b { c } d } } "#; - let input = Level::Error.title("foo").snippet( - Snippet::source(source) - .line_start(1) - .origin("test.rs") - .fold(true) - .annotation(Level::Error.span(14..27).label("")) - .annotation(Level::Warning.span(18..25).label("`b` is a good letter")), + let input = Level::Error.message("foo").group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(14..27).label("")) + .annotation( + AnnotationKind::Context + .span(18..25) + .label("`b` is a good letter"), + ), + ), ); let expected = str![[r#" @@ -495,13 +571,19 @@ fn foo() { a bc d } "#; - let input = Level::Error.title("foo").snippet( - Snippet::source(source) - .line_start(1) - .origin("test.rs") - .fold(true) - .annotation(Level::Error.span(14..18).label("`a` is a good letter")) - .annotation(Level::Warning.span(18..22).label("")), + let input = Level::Error.message("foo").group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(14..18) + .label("`a` is a good letter"), + ) + .annotation(AnnotationKind::Context.span(18..22).label("")), + ), ); let expected = str![[r#" @@ -523,13 +605,15 @@ fn foo() { a { b { c } d } } "#; - let input = Level::Error.title("foo").snippet( - Snippet::source(source) - .line_start(1) - .origin("test.rs") - .fold(true) - .annotation(Level::Error.span(14..27).label("")) - .annotation(Level::Warning.span(18..25).label("")), + let input = Level::Error.message("foo").group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(14..27).label("")) + .annotation(AnnotationKind::Context.span(18..25).label("")), + ), ); let expected = str![[r#" @@ -549,19 +633,21 @@ fn foo() { a { b { c } d } } "#; - let input = Level::Error.title("foo").snippet( - Snippet::source(source) - .line_start(1) - .origin("test.rs") - .fold(true) - .annotation(Level::Error.span(18..25).label("")) - .annotation(Level::Warning.span(14..27).label("")) - .annotation(Level::Warning.span(22..23).label("")), + let input = Level::Error.message("foo").group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(18..25).label("")) + .annotation(AnnotationKind::Context.span(14..27).label("")) + .annotation(AnnotationKind::Context.span(22..23).label("")), + ), ); let expected = str![[r#" error: foo - --> test.rs:3:3 + --> test.rs:3:7 | 3 | a { b { c } d } | ----^^^^-^^-- @@ -576,13 +662,23 @@ fn foo() { a { b { c } d } } "#; - let input = Level::Error.title("foo").snippet( - Snippet::source(source) - .line_start(1) - .origin("test.rs") - .fold(true) - .annotation(Level::Error.span(14..27).label("`a` is a good letter")) - .annotation(Level::Warning.span(18..25).label("`b` is a good letter")), + let input = Level::Error.message("foo").group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(14..27) + .label("`a` is a good letter"), + ) + .annotation( + AnnotationKind::Context + .span(18..25) + .label("`b` is a good letter"), + ), + ), ); let expected = str![[r#" @@ -605,12 +701,18 @@ fn foo() { a { b { c } d } } "#; - let input = Level::Error.title("foo").snippet( - Snippet::source(source) - .line_start(1) - .origin("test.rs") - .fold(true) - .annotation(Level::Error.span(14..27).label("`a` is a good letter")), + let input = Level::Error.message("foo").group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(14..27) + .label("`a` is a good letter"), + ), + ), ); let expected = str![[r#" @@ -630,12 +732,14 @@ fn foo() { a { b { c } d } } "#; - let input = Level::Error.title("foo").snippet( - Snippet::source(source) - .line_start(1) - .origin("test.rs") - .fold(true) - .annotation(Level::Error.span(14..27).label("")), + let input = Level::Error.message("foo").group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(14..27).label("")), + ), ); let expected = str![[r#" @@ -668,17 +772,23 @@ fn foo() { X3 Y3 Z3 } "#; - let input = Level::Error.title("foo").snippet( - Snippet::source(source) - .line_start(1) - .origin("test.rs") - .fold(true) - .annotation(Level::Error.span(17..27).label("`X` is a good letter")) - .annotation( - Level::Warning - .span(31..76) - .label("`Y` is a good letter too"), - ), + let input = Level::Error.message("foo").group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(17..27) + .label("`X` is a good letter"), + ) + .annotation( + AnnotationKind::Context + .span(31..76) + .label("`Y` is a good letter too"), + ), + ), ); let expected = str![[r#" @@ -722,17 +832,23 @@ fn foo() { X3 Y3 Z3 } "#; - let input = Level::Error.title("foo").snippet( - Snippet::source(source) - .line_start(1) - .origin("test.rs") - .fold(true) - .annotation(Level::Error.span(17..73).label("`Y` is a good letter")) - .annotation( - Level::Warning - .span(37..56) - .label("`Z` is a good letter too"), - ), + let input = Level::Error.message("foo").group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .origin("test.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(17..73) + .label("`Y` is a good letter"), + ) + .annotation( + AnnotationKind::Context + .span(37..56) + .label("`Z` is a good letter too"), + ), + ), ); let expected = str![[r#" @@ -771,24 +887,34 @@ fn issue_91334() { fn f(){||yield(((){), "#; let input = Level::Error - .title("this file contains an unclosed delimiter") - .snippet( - Snippet::source(source) - .line_start(1) - .origin("$DIR/issue-91334.rs") - .fold(true) - .annotation(Level::Warning.span(151..152).label("unclosed delimiter")) - .annotation(Level::Warning.span(159..160).label("unclosed delimiter")) - .annotation( - Level::Warning - .span(164..164) - .label("missing open `(` for this delimiter"), - ) - .annotation(Level::Error.span(167..167)), + .message("this file contains an unclosed delimiter") + .group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .origin("$DIR/issue-91334.rs") + .fold(true) + .annotation( + AnnotationKind::Context + .span(151..152) + .label("unclosed delimiter"), + ) + .annotation( + AnnotationKind::Context + .span(159..160) + .label("unclosed delimiter"), + ) + .annotation( + AnnotationKind::Context + .span(164..164) + .label("missing open `(` for this delimiter"), + ) + .annotation(AnnotationKind::Primary.span(167..167)), + ), ); let expected = str![[r#" error: this file contains an unclosed delimiter - --> $DIR/issue-91334.rs:7:7 + --> $DIR/issue-91334.rs:7:23 | LL | fn f(){||yield(((){), | - - - ^ @@ -832,38 +958,43 @@ fn main() { } "#; let input = Level::Error - .title("`break` with value from a `while` loop") + .message("`break` with value from a `while` loop") .id("E0571") - .snippet( - Snippet::source(source) - .line_start(1) - .origin("$DIR/issue-114529-illegal-break-with-value.rs") - .fold(true) - .annotation( - Level::Error - .span(483..581) - .label("can only break with a value inside `loop` or breakable block"), - ) - .annotation( - Level::Warning - .span(462..472) - .label("you can't `break` with a value in a `while` loop"), - ), + .group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .origin("$DIR/issue-114529-illegal-break-with-value.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(483..581) + .label("can only break with a value inside `loop` or breakable block"), + ) + .annotation( + AnnotationKind::Context + .span(462..472) + .label("you can't `break` with a value in a `while` loop"), + ), + ), ) - .footer( - Level::Help - .title("use `break` on its own without a value inside this `while` loop") - .snippet( + .group( + Group::new() + .element( + Level::Help + .title("use `break` on its own without a value inside this `while` loop"), + ) + .element( Snippet::source(source) .line_start(1) .origin("$DIR/issue-114529-illegal-break-with-value.rs") .fold(true) - .annotation(Level::Help.span(483..581).label("break")), + .annotation(AnnotationKind::Context.span(483..581).label("break")), ), ); let expected = str![[r#" error[E0571]: `break` with value from a `while` loop - --> $DIR/issue-114529-illegal-break-with-value.rs:21:5 + --> $DIR/issue-114529-illegal-break-with-value.rs:22:9 | LL | while true { | ---------- you can't `break` with a value in a `while` loop @@ -871,6 +1002,7 @@ LL | / break (|| { //~ ERROR `break` with value from a `while` loop LL | | let local = 9; LL | | }); | |__________^ can only break with a value inside `loop` or breakable block + | help: use `break` on its own without a value inside this `while` loop --> $DIR/issue-114529-illegal-break-with-value.rs:22:9 | @@ -1034,48 +1166,50 @@ fn nsize() { } } "#; - let input = Level::Error - .title("`V0usize` cannot be safely transmuted into `[usize; 2]`") - .id("E0277") - .snippet( - Snippet::source(source) - .line_start(1) - .origin("$DIR/primitive_reprs_should_have_correct_length.rs") - .fold(true) - .annotation( - Level::Error - .span(4375..4381) - .label("the size of `V0usize` is smaller than the size of `[usize; 2]`"), - ), - ) - .footer( - Level::Note - .title("required by a bound in `is_transmutable`") - .snippet( + let input = + Level::Error + .message("`V0usize` cannot be safely transmuted into `[usize; 2]`") + .id("E0277") + .group( + Group::new().element( Snippet::source(source) .line_start(1) .origin("$DIR/primitive_reprs_should_have_correct_length.rs") .fold(true) - .annotation( - Level::Note - .span(225..240) - .label("required by a bound in this function"), - ) - .annotation( - Level::Error - .span(276..470) - .label("required by this bound in `is_transmutable`"), - ), + .annotation(AnnotationKind::Primary.span(4375..4381).label( + "the size of `V0usize` is smaller than the size of `[usize; 2]`", + )), ), - ); + ) + .group( + Group::new() + .element(Level::Note.title("required by a bound in `is_transmutable`")) + .element( + Snippet::source(source) + .line_start(1) + .origin("$DIR/primitive_reprs_should_have_correct_length.rs") + .fold(true) + .annotation( + AnnotationKind::Context + .span(225..240) + .label("required by a bound in this function"), + ) + .annotation( + AnnotationKind::Primary + .span(276..470) + .label("required by this bound in `is_transmutable`"), + ), + ), + ); let expected = str![[r#" error[E0277]: `V0usize` cannot be safely transmuted into `[usize; 2]` --> $DIR/primitive_reprs_should_have_correct_length.rs:144:44 | LL | assert::is_transmutable::<Current, Larger>(); //~ ERROR cannot be safely transmuted | ^^^^^^ the size of `V0usize` is smaller than the size of `[usize; 2]` + | note: required by a bound in `is_transmutable` - --> $DIR/primitive_reprs_should_have_correct_length.rs:10:12 + --> $DIR/primitive_reprs_should_have_correct_length.rs:12:14 | LL | pub fn is_transmutable<Src, Dst>() | --------------- required by a bound in this function @@ -1120,20 +1254,23 @@ fn main() { } "#; let input = Level::Error - .title("`&[u8; 0]` cannot be safely transmuted into `&[u16; 0]`").id("E0277") - .snippet( + .message("`&[u8; 0]` cannot be safely transmuted into `&[u16; 0]`") + .id("E027s7") + .group( + Group::new().element( Snippet::source(source) .line_start(1) .fold(true) .origin("$DIR/align-fail.rs") .annotation( - Level::Error + AnnotationKind::Primary .span(442..459) .label("the minimum alignment of `&[u8; 0]` (1) should be greater than that of `&[u16; 0]` (2)") ), + ), ); let expected = str![[r#" -error[E0277]: `&[u8; 0]` cannot be safely transmuted into `&[u16; 0]` +error[E027s7]: `&[u8; 0]` cannot be safely transmuted into `&[u16; 0]` --> $DIR/align-fail.rs:21:55 | LL | ...ic [u8; 0], &'static [u16; 0]>(); //~ ERROR `&[u8; 0]` cannot be safely transmuted into `&[u16; 0]` @@ -1185,38 +1322,39 @@ fn g() { } fn main() {} "#; - let input = - Level::Error - .title("expected function, found `{integer}`") - .id("E0618") - .snippet( + let input = Level::Error + .message("expected function, found `{integer}`") + .id("E0618") + .group( + Group::new().element( Snippet::source(source) .line_start(1) .origin("$DIR/missing-semicolon.rs") .fold(true) .annotation( - Level::Warning + AnnotationKind::Context .span(108..144) .label("call expression requires function"), ) .annotation( - Level::Warning + AnnotationKind::Context .span(89..90) .label("`x` has type `{integer}`"), ) - .annotation(Level::Warning.span(109..109).label( + .annotation(AnnotationKind::Context.span(109..109).label( "help: consider using a semicolon here to finish the statement: `;`", )) - .annotation(Level::Error.span(108..109)), - ); + .annotation(AnnotationKind::Primary.span(108..109)), + ), + ); let expected = str![[r#" error[E0618]: expected function, found `{integer}` - --> $DIR/missing-semicolon.rs:4:9 + --> $DIR/missing-semicolon.rs:5:13 | LL | let x = 5; | - `x` has type `{integer}` LL | let y = x //~ ERROR expected function - | -- help: consider using a semicolon here to finish the statement: `;` + | ^- help: consider using a semicolon here to finish the statement: `;` | _____________| | | LL | | () //~ ERROR expected `;`, found `}` @@ -1276,44 +1414,55 @@ macro_rules! outer_macro { outer_macro!(FirstStruct, FirstAttrStruct); "#; let input = Level::Warning - .title("non-local `macro_rules!` definition, `#[macro_export]` macro should be written at top level module") - .snippet( - Snippet::source(aux_source) - .line_start(1) - .origin("$DIR/auxiliary/nested-macro-rules.rs") - .fold(true) - .annotation( - Level::Warning - .span(41..65) - .label("in this expansion of `nested_macro_rules::outer_macro!`"), + .message("non-local `macro_rules!` definition, `#[macro_export]` macro should be written at top level module") + .group( + Group::new() + .element( + Snippet::source(aux_source) + .line_start(1) + .origin("$DIR/auxiliary/nested-macro-rules.rs") + .fold(true) + .annotation( + AnnotationKind::Context + .span(41..65) + .label("in this expansion of `nested_macro_rules::outer_macro!`"), + ) + .annotation(AnnotationKind::Primary.span(148..350)), ) - .annotation(Level::Error.span(148..350)), - ) - .snippet( - Snippet::source(source) - .line_start(1) - .origin("$DIR/nested-macro-rules.rs") - .fold(true) - .annotation( - Level::Warning - .span(510..574) - .label("in this macro invocation"), + .element( + Snippet::source(source) + .line_start(1) + .origin("$DIR/nested-macro-rules.rs") + .fold(true) + .annotation( + AnnotationKind::Context + .span(510..574) + .label("in this macro invocation"), + ), + ) + .element( + Level::Help + .title("remove the `#[macro_export]` or move this `macro_rules!` outside the of the current function `main`") + ) + .element( + Level::Note + .title("a `macro_rules!` definition is non-local if it is nested inside an item and has a `#[macro_export]` attribute") ), ) - .footer(Level::Help.title("remove the `#[macro_export]` or move this `macro_rules!` outside the of the current function `main`")) - .footer(Level::Note.title("a `macro_rules!` definition is non-local if it is nested inside an item and has a `#[macro_export]` attribute")) - .footer( - Level::Note.title("the lint level is defined here").snippet( - Snippet::source(source) - .line_start(1) - .origin("$DIR/nested-macro-rules.rs") - .fold(true) - .annotation(Level::Error.span(224..245)), - ), + .group( + Group::new() + .element(Level::Note.title("the lint level is defined here")) + .element( + Snippet::source(source) + .line_start(1) + .origin("$DIR/nested-macro-rules.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(224..245)), + ), ); let expected = str![[r#" warning: non-local `macro_rules!` definition, `#[macro_export]` macro should be written at top level module - --> $DIR/auxiliary/nested-macro-rules.rs:4:1 + --> $DIR/auxiliary/nested-macro-rules.rs:7:9 | LL | macro_rules! outer_macro { | ------------------------ in this expansion of `nested_macro_rules::outer_macro!` @@ -1328,8 +1477,9 @@ LL | | } | ::: $DIR/nested-macro-rules.rs:23:5 | -LL | nested_macro_rules::outer_macro!(SecondStruct, SecondAttrStruct); - | ---------------------------------------------------------------- in this macro invocation +LL | nested_macro_rules::outer_macro!(SecondStruct, SecondAttrStruct); + | ---------------------------------------------------------------- in this macro invocation + | = help: remove the `#[macro_export]` or move this `macro_rules!` outside the of the current function `main` = note: a `macro_rules!` definition is non-local if it is nested inside an item and has a `#[macro_export]` attribute note: the lint level is defined here @@ -1397,24 +1547,26 @@ macro_rules! inline { } "#; let input = Level::Error - .title("can't call method `pow` on ambiguous numeric type `{integer}`") + .message("can't call method `pow` on ambiguous numeric type `{integer}`") .id("E0689") - .snippet( - Snippet::source(source) - .line_start(1) - .origin("$DIR/method-on-ambiguous-numeric-type.rs") - .fold(true) - .annotation(Level::Error.span(916..919)), + .group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .origin("$DIR/method-on-ambiguous-numeric-type.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(916..919)), + ), ) - .footer( - Level::Help - .title("you must specify a type for this binding, like `i32`") - .snippet( + .group( + Group::new() + .element(Level::Help.title("you must specify a type for this binding, like `i32`")) + .element( Snippet::source(aux_source) .line_start(1) .origin("$DIR/auxiliary/macro-in-other-crate.rs") .fold(true) - .annotation(Level::Help.span(69..69).label(": i32")), + .annotation(AnnotationKind::Context.span(69..69).label(": i32")), ), ); let expected = str![[r#" @@ -1423,6 +1575,7 @@ error[E0689]: can't call method `pow` on ambiguous numeric type `{integer}` | LL | bar.pow(2); | ^^^ + | help: you must specify a type for this binding, like `i32` --> $DIR/auxiliary/macro-in-other-crate.rs:3:35 | @@ -1458,16 +1611,18 @@ fn main() {} "#; let input = Level::Error - .title("type annotations needed") + .message("type annotations needed") .id("E0282") - .snippet( - Snippet::source(source) - .line_start(1) - .origin("$DIR/issue-42234-unknown-receiver-type.rs") - .fold(true) - .annotation(Level::Error.span(536..539).label( - "cannot infer type of the type parameter `S` declared on the method `sum`", - )), + .group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .origin("$DIR/issue-42234-unknown-receiver-type.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(536..539).label( + "cannot infer type of the type parameter `S` declared on the method `sum`", + )), + ), ); let expected = str![[r#" error[E0282]: type annotations needed @@ -1562,45 +1717,53 @@ fn main() {} "##; let input = Level::Error - .title( + .message( "non-exhaustive patterns: `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered" ) .id("E0004") - .snippet( - Snippet::source(source) - .line_start(1) - .origin("$DIR/empty-match.rs") - .fold(true) - .annotation( - Level::Error - .span(2911..2928) - .label("patterns `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered") - ), - ) - .footer(Level::Note.title("`NonEmptyEnum5` defined here") - .snippet( + .group( + Group::new().element( Snippet::source(source) .line_start(1) .origin("$DIR/empty-match.rs") .fold(true) - .annotation(Level::Error.span(818..831)) - .annotation(Level::Warning.span(842..844).label("not covered")) - .annotation(Level::Warning.span(854..856).label("not covered")) - .annotation(Level::Warning.span(866..868).label("not covered")) - .annotation(Level::Warning.span(878..880).label("not covered")) - .annotation(Level::Warning.span(890..892).label("not covered")) - )) - .footer(Level::Note.title("the matched value is of type `NonEmptyEnum5`")) - .footer(Level::Note.title("match arms with guards don't count towards exhaustivity")) - .footer( - Level::Help - .title("ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown, or multiple match arms") - .snippet( + .annotation( + AnnotationKind::Primary + .span(2911..2928) + .label("patterns `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered") + ), + ), + ) + .group( + Group::new() + .element(Level::Note.title("`NonEmptyEnum5` defined here")) + .element( + Snippet::source(source) + .line_start(1) + .origin("$DIR/empty-match.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(818..831)) + .annotation(AnnotationKind::Context.span(842..844).label("not covered")) + .annotation(AnnotationKind::Context.span(854..856).label("not covered")) + .annotation(AnnotationKind::Context.span(866..868).label("not covered")) + .annotation(AnnotationKind::Context.span(878..880).label("not covered")) + .annotation(AnnotationKind::Context.span(890..892).label("not covered")) + ) + .element(Level::Note.title("the matched value is of type `NonEmptyEnum5`")) + .element(Level::Note.title("match arms with guards don't count towards exhaustivity")) + ) + .group( + Group::new() + .element( + Level::Help + .title("ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown, or multiple match arms") + ) + .element( Snippet::source(source) .line_start(1) .origin("$DIR/empty-match.rs") .fold(true) - .annotation(Level::Help.span(485..485).label(",\n _ => todo!()")) + .annotation(AnnotationKind::Context.span(485..485).label(",\n _ => todo!()")) ) ); let expected = str![[r#" @@ -1609,6 +1772,7 @@ error[E0004]: non-exhaustive patterns: `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, | LL | match_guarded_arm!(NonEmptyEnum5::V1); //~ ERROR `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered | ^^^^^^^^^^^^^^^^^ patterns `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered + | note: `NonEmptyEnum5` defined here --> $DIR/empty-match.rs:38:10 | @@ -1655,36 +1819,41 @@ fn main() { } "#; let input = Level::Error - .title("the trait alias `EqAlias` is not dyn compatible") + .message("the trait alias `EqAlias` is not dyn compatible") .id("E0038") - .snippet( + .group( + Group::new().element( Snippet::source(source) .line_start(1) .origin("$DIR/object-fail.rs") .fold(true) .annotation( - Level::Error + AnnotationKind::Primary .span(107..114) .label("`EqAlias` is not dyn compatible"), ), - + ), ) - .footer( + .group( + Group::new() + .element( Level::Note - .title("for a trait to be dyn compatible it needs to allow building a vtable\nfor more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>") - .snippet( - Snippet::source("") - .line_start(334) - .origin("$SRC_DIR/core/src/cmp.rs") + .title("for a trait to be dyn compatible it needs to allow building a vtable\nfor more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>")) + .element( + Origin::new("$SRC_DIR/core/src/cmp.rs") + .line(334) + .char_column(14) + .primary(true) + .label("...because it uses `Self` as a type parameter") ) - .snippet( + .element( Snippet::source(source) .line_start(1) .origin("$DIR/object-fail.rs") .fold(true) .annotation( - Level::Warning + AnnotationKind::Context .span(32..39) .label("this trait is not dyn compatible..."), ), @@ -1696,10 +1865,12 @@ error[E0038]: the trait alias `EqAlias` is not dyn compatible | LL | let _: &dyn EqAlias = &123; | ^^^^^^^ `EqAlias` is not dyn compatible + | note: for a trait to be dyn compatible it needs to allow building a vtable for more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility> - --> $SRC_DIR/core/src/cmp.rs + --> $SRC_DIR/core/src/cmp.rs:334:14 | + = note: ...because it uses `Self` as a type parameter | ::: $DIR/object-fail.rs:3:7 | From 05632ab6702719a7a92393c3b1312db6a2cd472c Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 15 Apr 2025 05:53:45 -0600 Subject: [PATCH 317/455] refactor: Remove dependency on rustc_hash --- Cargo.lock | 7 ------- Cargo.toml | 1 - src/renderer/mod.rs | 10 +++------- 3 files changed, 3 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e8aebe5f..7d73c760 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,7 +23,6 @@ dependencies = [ "glob", "indexmap", "memchr", - "rustc-hash", "serde", "snapbox", "toml", @@ -508,12 +507,6 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" -[[package]] -name = "rustc-hash" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" - [[package]] name = "rustix" version = "0.37.27" diff --git a/Cargo.toml b/Cargo.toml index 95067ce7..8ed3a6ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -119,7 +119,6 @@ maintenance = { status = "actively-developed" } anstyle = "1.0.4" indexmap = "2.7.0" memchr = { version = "2.7.4", optional = true } -rustc-hash = "2.1.0" unicode-width = "0.2.0" [dev-dependencies] diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 246afb65..6442ebf2 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -46,18 +46,14 @@ use crate::{Annotation, AnnotationKind, Element, Group, Level, Message, Origin, pub use anstyle::*; use indexmap::IndexMap; use margin::Margin; -use rustc_hash::{FxHashMap, FxHasher}; use std::borrow::Cow; use std::cmp::{max, min, Ordering, Reverse}; -use std::collections::VecDeque; -use std::hash::BuildHasherDefault; +use std::collections::{HashMap, VecDeque}; use stylesheet::Stylesheet; const ANONYMIZED_LINE_NUM: &str = "LL"; pub const DEFAULT_TERM_WIDTH: usize = 140; -type FxIndexMap<K, V> = IndexMap<K, V, BuildHasherDefault<FxHasher>>; - /// A renderer for [`Message`]s #[derive(Clone, Debug)] pub struct Renderer { @@ -642,7 +638,7 @@ impl Renderer { self.draw_col_separator_no_space(buffer, buffer_msg_line_offset, max_line_num_len + 1); // Contains the vertical lines' positions for active multiline annotations - let mut multilines = FxIndexMap::default(); + let mut multilines = IndexMap::new(); // Get the left-side margin to remove it let mut whitespace_margin = usize::MAX; @@ -729,7 +725,7 @@ impl Renderer { margin, ); - let mut to_add = FxHashMap::default(); + let mut to_add = HashMap::new(); for (depth, style) in depths { // FIXME(#120456) - is `swap_remove` correct? From 1d047720fb8ab17e2b24040710139ac419d483c6 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 15 Apr 2025 05:59:59 -0600 Subject: [PATCH 318/455] refactor: Remove dependency on indexmap --- Cargo.lock | 1 - Cargo.toml | 1 - src/renderer/mod.rs | 10 +++++----- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7d73c760..8838e31a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,7 +21,6 @@ dependencies = [ "difference", "divan", "glob", - "indexmap", "memchr", "serde", "snapbox", diff --git a/Cargo.toml b/Cargo.toml index 8ed3a6ab..f30c64f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -117,7 +117,6 @@ maintenance = { status = "actively-developed" } [dependencies] anstyle = "1.0.4" -indexmap = "2.7.0" memchr = { version = "2.7.4", optional = true } unicode-width = "0.2.0" diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 6442ebf2..a9562621 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -44,7 +44,6 @@ use crate::renderer::source_map::{AnnotatedLineInfo, Loc, SourceMap}; use crate::renderer::styled_buffer::StyledBuffer; use crate::{Annotation, AnnotationKind, Element, Group, Level, Message, Origin, Snippet, Title}; pub use anstyle::*; -use indexmap::IndexMap; use margin::Margin; use std::borrow::Cow; use std::cmp::{max, min, Ordering, Reverse}; @@ -638,7 +637,7 @@ impl Renderer { self.draw_col_separator_no_space(buffer, buffer_msg_line_offset, max_line_num_len + 1); // Contains the vertical lines' positions for active multiline annotations - let mut multilines = IndexMap::new(); + let mut multilines = Vec::new(); // Get the left-side margin to remove it let mut whitespace_margin = usize::MAX; @@ -728,8 +727,9 @@ impl Renderer { let mut to_add = HashMap::new(); for (depth, style) in depths { - // FIXME(#120456) - is `swap_remove` correct? - if multilines.swap_remove(&depth).is_none() { + if let Some(index) = multilines.iter().position(|(d, _)| d == &depth) { + multilines.swap_remove(index); + } else { to_add.insert(depth, style); } } @@ -834,7 +834,7 @@ impl Renderer { } } - multilines.extend(&to_add); + multilines.extend(to_add); } } From f0a8091673d07d8ab914472940ea9623ee3868b4 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 11 Feb 2025 12:57:48 -0700 Subject: [PATCH 319/455] feat: Add Suggestions --- src/renderer/mod.rs | 615 +++++++++++++++++++++++++++++- src/renderer/source_map.rs | 206 +++++++++- src/renderer/styled_buffer.rs | 29 ++ src/renderer/stylesheet.rs | 4 + src/snippet.rs | 111 ++++++ tests/fixtures/deserialize.rs | 46 ++- tests/formatter.rs | 680 +++++++++++++++++++++++++++++++++- 7 files changed, 1682 insertions(+), 9 deletions(-) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index a9562621..fdd2b3ef 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -36,18 +36,23 @@ //! ``` mod margin; -mod source_map; +pub(crate) mod source_map; mod styled_buffer; pub(crate) mod stylesheet; -use crate::renderer::source_map::{AnnotatedLineInfo, Loc, SourceMap}; +use crate::renderer::source_map::{ + AnnotatedLineInfo, LineInfo, Loc, SourceMap, SubstitutionHighlight, +}; use crate::renderer::styled_buffer::StyledBuffer; -use crate::{Annotation, AnnotationKind, Element, Group, Level, Message, Origin, Snippet, Title}; +use crate::{ + Annotation, AnnotationKind, Element, Group, Level, Message, Origin, Patch, Snippet, Title, +}; pub use anstyle::*; use margin::Margin; use std::borrow::Cow; use std::cmp::{max, min, Ordering, Reverse}; use std::collections::{HashMap, VecDeque}; +use std::ops::Range; use stylesheet::Stylesheet; const ANONYMIZED_LINE_NUM: &str = "LL"; @@ -103,6 +108,8 @@ impl Renderer { .effects(Effects::BOLD), none: Style::new(), context: BRIGHT_BLUE.effects(Effects::BOLD), + addition: AnsiColor::BrightGreen.on_default(), + removal: AnsiColor::BrightRed.on_default(), }, ..Self::plain() } @@ -213,6 +220,41 @@ impl Renderer { message: Message<'_>, max_line_num_len: usize, ) { + let og_primary_origin = message + .groups + .iter() + .find_map(|group| { + group.elements.iter().find_map(|s| match &s { + Element::Cause(cause) => { + if cause.markers.iter().any(|m| m.kind.is_primary()) { + Some(cause.origin) + } else { + None + } + } + Element::Origin(origin) => { + if origin.primary { + Some(Some(origin.origin)) + } else { + None + } + } + _ => None, + }) + }) + .unwrap_or( + message + .groups + .iter() + .find_map(|group| { + group.elements.iter().find_map(|s| match &s { + Element::Cause(cause) => Some(cause.origin), + Element::Origin(origin) => Some(Some(origin.origin)), + _ => None, + }) + }) + .unwrap_or_default(), + ); let group_len = message.groups.len(); for (g, group) in message.groups.into_iter().enumerate() { let primary_origin = group @@ -258,6 +300,7 @@ impl Renderer { } } let mut message_iter = group.elements.iter().enumerate().peekable(); + let mut last_was_suggestion = false; while let Some((i, section)) = message_iter.next() { let peek = message_iter.peek().map(|(_, s)| s).copied(); match §ion { @@ -276,6 +319,7 @@ impl Renderer { } }), ); + last_was_suggestion = false; } Element::Cause(cause) => { if let Some((source_map, annotated_lines)) = @@ -312,9 +356,25 @@ impl Renderer { } } } + + last_was_suggestion = false; + } + Element::Suggestion(suggestion) => { + let source_map = SourceMap::new(suggestion.source, suggestion.line_start); + self.emit_suggestion_default( + buffer, + suggestion, + max_line_num_len, + &source_map, + primary_origin.or(og_primary_origin), + last_was_suggestion, + ); + last_was_suggestion = true; } + Element::Origin(origin) => { self.render_origin(buffer, max_line_num_len, origin); + last_was_suggestion = false; } Element::ColumnSeparator(_) => { self.draw_col_separator_no_space( @@ -368,6 +428,7 @@ impl Renderer { cause.markers.iter().any(|m| m.kind.is_primary()), cause.markers.iter().any(|m| m.label.is_some()), ), + Element::Suggestion(_) => (true, false), Element::Origin(_) => (false, true), }); @@ -1389,6 +1450,534 @@ impl Renderer { .collect::<Vec<_>>() } + fn emit_suggestion_default( + &self, + buffer: &mut StyledBuffer, + suggestion: &Snippet<'_, Patch<'_>>, + max_line_num_len: usize, + sm: &SourceMap<'_>, + primary_origin: Option<&str>, + is_cont: bool, + ) { + let suggestions = sm.splice_lines(suggestion.markers.clone()); + + let buffer_offset = buffer.num_lines(); + let mut row_num = buffer_offset + usize::from(!is_cont); + for (i, (complete, parts, highlights)) in suggestions.iter().enumerate() { + let has_deletion = parts + .iter() + .any(|p| p.is_deletion(sm) || p.is_destructive_replacement(sm)); + let is_multiline = complete.lines().count() > 1; + + if i == 0 { + self.draw_col_separator_no_space_with_style( + buffer, + '|', + row_num - 1, + max_line_num_len + 1, + ElementStyle::LineNumber, + ); + } else { + buffer.puts( + row_num - 1, + max_line_num_len + 1, + "|", + ElementStyle::LineNumber, + ); + } + if suggestion.origin != primary_origin { + if let Some(origin) = suggestion.origin { + let (loc, _) = sm.span_to_locations(parts[0].range.clone()); + // --> file.rs:line:col + // | + let arrow = self.file_start(); + buffer.puts(row_num - 1, 0, arrow, ElementStyle::LineNumber); + let message = format!("{}:{}:{}", origin, loc.line, loc.char + 1); + if is_cont { + buffer.append(row_num - 1, &message, ElementStyle::LineAndColumn); + } else { + let col = usize::max(max_line_num_len + 1, arrow.len()); + buffer.puts(row_num - 1, col, &message, ElementStyle::LineAndColumn); + } + for _ in 0..max_line_num_len { + buffer.prepend(row_num - 1, " ", ElementStyle::NoStyle); + } + self.draw_col_separator_no_space(buffer, row_num, max_line_num_len + 1); + row_num += 1; + } + } + let show_code_change = if has_deletion && !is_multiline { + DisplaySuggestion::Diff + } else if parts.len() == 1 + && parts.first().map_or(false, |p| { + p.replacement.ends_with('\n') && p.replacement.trim() == complete.trim() + }) + { + // We are adding a line(s) of code before code that was already there. + DisplaySuggestion::Add + } else if (parts.len() != 1 || parts[0].replacement.trim() != complete.trim()) + && !is_multiline + { + DisplaySuggestion::Underline + } else { + DisplaySuggestion::None + }; + + if let DisplaySuggestion::Diff = show_code_change { + row_num += 1; + } + + let file_lines = sm.span_to_lines(parts[0].range.clone()); + let (line_start, line_end) = sm.span_to_locations(parts[0].range.clone()); + let mut lines = complete.lines(); + if lines.clone().next().is_none() { + // Account for a suggestion to completely remove a line(s) with whitespace (#94192). + for line in line_start.line..=line_end.line { + buffer.puts( + row_num - 1 + line - line_start.line, + 0, + &self.maybe_anonymized(line), + ElementStyle::LineNumber, + ); + buffer.puts( + row_num - 1 + line - line_start.line, + max_line_num_len + 1, + "- ", + ElementStyle::Removal, + ); + buffer.puts( + row_num - 1 + line - line_start.line, + max_line_num_len + 3, + &normalize_whitespace(sm.get_line(line).unwrap()), + ElementStyle::Removal, + ); + } + row_num += line_end.line - line_start.line; + } + let mut last_pos = 0; + let mut is_item_attribute = false; + let mut unhighlighted_lines = Vec::new(); + for (line_pos, (line, highlight_parts)) in lines.by_ref().zip(highlights).enumerate() { + last_pos = line_pos; + + // Remember lines that are not highlighted to hide them if needed + if highlight_parts.is_empty() { + unhighlighted_lines.push((line_pos, line)); + continue; + } + if highlight_parts.len() == 1 + && line.trim().starts_with("#[") + && line.trim().ends_with(']') + { + is_item_attribute = true; + } + + match unhighlighted_lines.len() { + 0 => (), + // Since we show first line, "..." line and last line, + // There is no reason to hide if there are 3 or less lines + // (because then we just replace a line with ... which is + // not helpful) + n if n <= 3 => unhighlighted_lines.drain(..).for_each(|(p, l)| { + self.draw_code_line( + buffer, + &mut row_num, + &[], + p + line_start.line, + l, + show_code_change, + max_line_num_len, + &file_lines, + is_multiline, + ); + }), + // Print first unhighlighted line, "..." and last unhighlighted line, like so: + // + // LL | this line was highlighted + // LL | this line is just for context + // ... + // LL | this line is just for context + // LL | this line was highlighted + _ => { + let last_line = unhighlighted_lines.pop(); + let first_line = unhighlighted_lines.drain(..).next(); + + if let Some((p, l)) = first_line { + self.draw_code_line( + buffer, + &mut row_num, + &[], + p + line_start.line, + l, + show_code_change, + max_line_num_len, + &file_lines, + is_multiline, + ); + } + + let placeholder = "..."; + let padding = str_width(placeholder); + buffer.puts( + row_num, + max_line_num_len.saturating_sub(padding), + placeholder, + ElementStyle::LineNumber, + ); + row_num += 1; + + if let Some((p, l)) = last_line { + self.draw_code_line( + buffer, + &mut row_num, + &[], + p + line_start.line, + l, + show_code_change, + max_line_num_len, + &file_lines, + is_multiline, + ); + } + } + } + self.draw_code_line( + buffer, + &mut row_num, + highlight_parts, + line_pos + line_start.line, + line, + show_code_change, + max_line_num_len, + &file_lines, + is_multiline, + ); + } + + if matches!(show_code_change, DisplaySuggestion::Add) && is_item_attribute { + // The suggestion adds an entire line of code, ending on a newline, so we'll also + // print the *following* line, to provide context of what we're advising people to + // do. Otherwise you would only see contextless code that can be confused for + // already existing code, despite the colors and UI elements. + // We special case `#[derive(_)]\n` and other attribute suggestions, because those + // are the ones where context is most useful. + let file_lines = sm.span_to_lines(parts[0].range.end..parts[0].range.end); + let (lo, _) = sm.span_to_locations(parts[0].range.clone()); + let line_num = lo.line; + if let Some(line) = sm.get_line(line_num) { + let line = normalize_whitespace(line); + self.draw_code_line( + buffer, + &mut row_num, + &[], + line_num + last_pos + 1, + &line, + DisplaySuggestion::None, + max_line_num_len, + &file_lines, + is_multiline, + ); + } + } + // This offset and the ones below need to be signed to account for replacement code + // that is shorter than the original code. + let mut offsets: Vec<(usize, isize)> = Vec::new(); + // Only show an underline in the suggestions if the suggestion is not the + // entirety of the code being shown and the displayed code is not multiline. + if let DisplaySuggestion::Diff | DisplaySuggestion::Underline | DisplaySuggestion::Add = + show_code_change + { + for part in parts { + let (span_start, span_end) = sm.span_to_locations(part.range.clone()); + let span_start_pos = span_start.display; + let span_end_pos = span_end.display; + + // If this addition is _only_ whitespace, then don't trim it, + // or else we're just not rendering anything. + let is_whitespace_addition = part.replacement.trim().is_empty(); + + // Do not underline the leading... + let start = if is_whitespace_addition { + 0 + } else { + part.replacement + .len() + .saturating_sub(part.replacement.trim_start().len()) + }; + // ...or trailing spaces. Account for substitutions containing unicode + // characters. + let sub_len: usize = str_width(if is_whitespace_addition { + part.replacement + } else { + part.replacement.trim() + }); + + let offset: isize = offsets + .iter() + .filter_map(|(start, v)| { + if span_start_pos < *start { + None + } else { + Some(v) + } + }) + .sum(); + let underline_start = (span_start_pos + start) as isize + offset; + let underline_end = (span_start_pos + start + sub_len) as isize + offset; + assert!(underline_start >= 0 && underline_end >= 0); + let padding: usize = max_line_num_len + 3; + for p in underline_start..underline_end { + if matches!(show_code_change, DisplaySuggestion::Underline) + && is_different(sm, part.replacement, part.range.clone()) + { + // If this is a replacement, underline with `~`, if this is an addition + // underline with `+`. + buffer.putc( + row_num, + (padding as isize + p) as usize, + if part.is_addition(sm) { '+' } else { '~' }, + ElementStyle::Addition, + ); + } + } + if let DisplaySuggestion::Diff = show_code_change { + // Colorize removal with red in diff format. + buffer.set_style_range( + row_num - 2, + (padding as isize + span_start_pos as isize) as usize, + (padding as isize + span_end_pos as isize) as usize, + ElementStyle::Removal, + true, + ); + } + + // length of the code after substitution + let full_sub_len = str_width(part.replacement) as isize; + + // length of the code to be substituted + let snippet_len = span_end_pos as isize - span_start_pos as isize; + // For multiple substitutions, use the position *after* the previous + // substitutions have happened, only when further substitutions are + // located strictly after. + offsets.push((span_end_pos, full_sub_len - snippet_len)); + } + row_num += 1; + } + + // if we elided some lines, add an ellipsis + if lines.next().is_some() { + let placeholder = "..."; + let padding = str_width(placeholder); + buffer.puts( + row_num, + max_line_num_len.saturating_sub(padding), + placeholder, + ElementStyle::LineNumber, + ); + } else { + let row = match show_code_change { + DisplaySuggestion::Diff + | DisplaySuggestion::Add + | DisplaySuggestion::Underline => row_num - 1, + DisplaySuggestion::None => row_num, + }; + self.draw_col_separator_no_space_with_style( + buffer, + '|', + row, + max_line_num_len + 1, + ElementStyle::LineNumber, + ); + row_num = row + 1; + } + } + } + + #[allow(clippy::too_many_arguments)] + fn draw_code_line( + &self, + buffer: &mut StyledBuffer, + row_num: &mut usize, + highlight_parts: &[SubstitutionHighlight], + line_num: usize, + line_to_add: &str, + show_code_change: DisplaySuggestion, + max_line_num_len: usize, + file_lines: &[&LineInfo<'_>], + is_multiline: bool, + ) { + if let DisplaySuggestion::Diff = show_code_change { + // We need to print more than one line if the span we need to remove is multiline. + // For more info: https://github.com/rust-lang/rust/issues/92741 + let lines_to_remove = file_lines.iter().take(file_lines.len() - 1); + for (index, line_to_remove) in lines_to_remove.enumerate() { + buffer.puts( + *row_num - 1, + 0, + &self.maybe_anonymized(line_num + index), + ElementStyle::LineNumber, + ); + buffer.puts( + *row_num - 1, + max_line_num_len + 1, + "- ", + ElementStyle::Removal, + ); + let line = normalize_whitespace(line_to_remove.line); + buffer.puts( + *row_num - 1, + max_line_num_len + 3, + &line, + ElementStyle::NoStyle, + ); + *row_num += 1; + } + // If the last line is exactly equal to the line we need to add, we can skip both of + // them. This allows us to avoid output like the following: + // 2 - & + // 2 + if true { true } else { false } + // 3 - if true { true } else { false } + // If those lines aren't equal, we print their diff + let last_line = &file_lines.last().unwrap(); + if last_line.line == line_to_add { + *row_num -= 2; + } else { + buffer.puts( + *row_num - 1, + 0, + &self.maybe_anonymized(line_num + file_lines.len() - 1), + ElementStyle::LineNumber, + ); + buffer.puts( + *row_num - 1, + max_line_num_len + 1, + "- ", + ElementStyle::Removal, + ); + buffer.puts( + *row_num - 1, + max_line_num_len + 3, + &normalize_whitespace(last_line.line), + ElementStyle::NoStyle, + ); + if line_to_add.trim().is_empty() { + *row_num -= 1; + } else { + // Check if after the removal, the line is left with only whitespace. If so, we + // will not show an "addition" line, as removing the whole line is what the user + // would really want. + // For example, for the following: + // | + // 2 - .await + // 2 + (note the left over whitespace) + // | + // We really want + // | + // 2 - .await + // | + // *row_num -= 1; + buffer.puts( + *row_num, + 0, + &self.maybe_anonymized(line_num), + ElementStyle::LineNumber, + ); + buffer.puts(*row_num, max_line_num_len + 1, "+ ", ElementStyle::Addition); + buffer.append( + *row_num, + &normalize_whitespace(line_to_add), + ElementStyle::NoStyle, + ); + } + } + } else if is_multiline { + buffer.puts( + *row_num, + 0, + &self.maybe_anonymized(line_num), + ElementStyle::LineNumber, + ); + match &highlight_parts { + [SubstitutionHighlight { start: 0, end }] if *end == line_to_add.len() => { + buffer.puts(*row_num, max_line_num_len + 1, "+ ", ElementStyle::Addition); + } + [] => { + // FIXME: needed? Doesn't get exercised in any test. + self.draw_col_separator_no_space(buffer, *row_num, max_line_num_len + 1); + } + _ => { + buffer.puts(*row_num, max_line_num_len + 1, "~", ElementStyle::Addition); + } + } + // LL | line_to_add + // ++^^^ + // | | + // | magic `3` + // `max_line_num_len` + buffer.puts( + *row_num, + max_line_num_len + 3, + &normalize_whitespace(line_to_add), + ElementStyle::NoStyle, + ); + } else if let DisplaySuggestion::Add = show_code_change { + buffer.puts( + *row_num, + 0, + &self.maybe_anonymized(line_num), + ElementStyle::LineNumber, + ); + buffer.puts(*row_num, max_line_num_len + 1, "+ ", ElementStyle::Addition); + buffer.append( + *row_num, + &normalize_whitespace(line_to_add), + ElementStyle::NoStyle, + ); + } else { + buffer.puts( + *row_num, + 0, + &self.maybe_anonymized(line_num), + ElementStyle::LineNumber, + ); + buffer.puts( + *row_num, + max_line_num_len + 1, + "| ", + ElementStyle::LineNumber, + ); + buffer.append( + *row_num, + &normalize_whitespace(line_to_add), + ElementStyle::NoStyle, + ); + } + + // Colorize addition/replacements with green. + for &SubstitutionHighlight { start, end } in highlight_parts { + // This is a no-op for empty ranges + if start != end { + // Account for tabs when highlighting (#87972). + let tabs: usize = line_to_add + .chars() + .take(start) + .map(|ch| match ch { + '\t' => 3, + _ => 0, + }) + .sum(); + buffer.set_style_range( + *row_num, + max_line_num_len + 3 + start + tabs, + max_line_num_len + 3 + end + tabs, + ElementStyle::Addition, + true, + ); + } + } + *row_num += 1; + } + #[allow(clippy::too_many_arguments)] fn draw_line( &self, @@ -1714,6 +2303,14 @@ impl LineAnnotation<'_> { } } +#[derive(Clone, Copy, Debug)] +pub(crate) enum DisplaySuggestion { + Underline, + Diff, + None, + Add, +} + // We replace some characters so the CLI output is always consistent and underlines aligned. // Keep the following list in sync with `rustc_span::char_width`. const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[ @@ -1790,11 +2387,15 @@ pub(crate) enum ElementStyle { LabelSecondary, NoStyle, Level(Level), + Addition, + Removal, } impl ElementStyle { fn color_spec(&self, level: Level, stylesheet: &Stylesheet) -> Style { match self { + ElementStyle::Addition => stylesheet.addition, + ElementStyle::Removal => stylesheet.removal, ElementStyle::LineAndColumn => stylesheet.none, ElementStyle::LineNumber => stylesheet.line_no, ElementStyle::Quotation => stylesheet.none, @@ -1826,6 +2427,14 @@ struct UnderlineParts { multiline_bottom_right_with_text: char, } +/// Whether the original and suggested code are the same. +pub(crate) fn is_different(sm: &SourceMap<'_>, suggested: &str, range: Range<usize>) -> bool { + match sm.span_to_snippet(range) { + Some(s) => s != suggested, + None => true, + } +} + #[cfg(test)] mod test { use super::OUTPUT_REPLACEMENTS; diff --git a/src/renderer/source_map.rs b/src/renderer/source_map.rs index c29774a8..416337c3 100644 --- a/src/renderer/source_map.rs +++ b/src/renderer/source_map.rs @@ -1,12 +1,12 @@ -use crate::renderer::{char_width, num_overlap, LineAnnotation, LineAnnotationType}; -use crate::{Annotation, AnnotationKind}; +use crate::renderer::{char_width, is_different, num_overlap, LineAnnotation, LineAnnotationType}; +use crate::{Annotation, AnnotationKind, Patch}; use std::cmp::{max, min}; use std::ops::Range; #[derive(Debug)] pub(crate) struct SourceMap<'a> { lines: Vec<LineInfo<'a>>, - source: &'a str, + pub(crate) source: &'a str, } impl<'a> SourceMap<'a> { @@ -101,6 +101,26 @@ impl<'a> SourceMap<'a> { (start, end) } + pub(crate) fn span_to_snippet(&self, span: Range<usize>) -> Option<&str> { + self.source.get(span) + } + + pub(crate) fn span_to_lines(&self, span: Range<usize>) -> Vec<&LineInfo<'a>> { + let mut lines = vec![]; + let start = span.start; + let end = span.end; + for line_info in &self.lines { + if start >= line_info.end_byte { + continue; + } + if end <= line_info.start_byte { + break; + } + lines.push(line_info); + } + lines + } + pub(crate) fn annotated_lines( &self, annotations: Vec<Annotation<'a>>, @@ -130,7 +150,7 @@ impl<'a> SourceMap<'a> { let mut multiline_annotations = vec![]; for Annotation { range, label, kind } in annotations { - let (lo, mut hi) = self.span_to_locations(range); + let (lo, mut hi) = self.span_to_locations(range.clone()); // Watch out for "empty spans". If we get a span like 6..6, we // want to just display a `^` at 6, so convert that to @@ -302,6 +322,176 @@ impl<'a> SourceMap<'a> { annotated_line_infos.sort_by_key(|l| l.line_index); } } + + pub(crate) fn splice_lines<'b>( + &'b self, + mut patches: Vec<Patch<'b>>, + ) -> Vec<(String, Vec<Patch<'b>>, Vec<Vec<SubstitutionHighlight>>)> { + fn push_trailing( + buf: &mut String, + line_opt: Option<&str>, + lo: &Loc, + hi_opt: Option<&Loc>, + ) -> usize { + let mut line_count = 0; + // Convert CharPos to Usize, as CharPose is character offset + // Extract low index and high index + let (lo, hi_opt) = (lo.char, hi_opt.map(|hi| hi.char)); + if let Some(line) = line_opt { + if let Some(lo) = line.char_indices().map(|(i, _)| i).nth(lo) { + // Get high index while account for rare unicode and emoji with char_indices + let hi_opt = hi_opt.and_then(|hi| line.char_indices().map(|(i, _)| i).nth(hi)); + match hi_opt { + // If high index exist, take string from low to high index + Some(hi) if hi > lo => { + // count how many '\n' exist + line_count = line[lo..hi].matches('\n').count(); + buf.push_str(&line[lo..hi]); + } + Some(_) => (), + // If high index absence, take string from low index till end string.len + None => { + // count how many '\n' exist + line_count = line[lo..].matches('\n').count(); + buf.push_str(&line[lo..]); + } + } + } + // If high index is None + if hi_opt.is_none() { + buf.push('\n'); + } + } + line_count + } + // Assumption: all spans are in the same file, and all spans + // are disjoint. Sort in ascending order. + patches.sort_by_key(|p| p.range.start); + + // Find the bounding span. + let Some(lo) = patches.iter().map(|p| p.range.start).min() else { + return Vec::new(); + }; + let Some(hi) = patches.iter().map(|p| p.range.end).max() else { + return Vec::new(); + }; + + let lines = self.span_to_lines(lo..hi); + + let mut highlights = vec![]; + // To build up the result, we do this for each span: + // - push the line segment trailing the previous span + // (at the beginning a "phantom" span pointing at the start of the line) + // - push lines between the previous and current span (if any) + // - if the previous and current span are not on the same line + // push the line segment leading up to the current span + // - splice in the span substitution + // + // Finally push the trailing line segment of the last span + let (mut prev_hi, _) = self.span_to_locations(lo..hi); + prev_hi.char = 0; + let mut prev_line = lines.first().map(|line| line.line); + let mut buf = String::new(); + + let mut line_highlight = vec![]; + // We need to keep track of the difference between the existing code and the added + // or deleted code in order to point at the correct column *after* substitution. + let mut acc = 0; + for part in &mut patches { + // If this is a replacement of, e.g. `"a"` into `"ab"`, adjust the + // suggestion and snippet to look as if we just suggested to add + // `"b"`, which is typically much easier for the user to understand. + part.trim_trivial_replacements(self); + let (cur_lo, cur_hi) = self.span_to_locations(part.range.clone()); + if prev_hi.line == cur_lo.line { + let mut count = push_trailing(&mut buf, prev_line, &prev_hi, Some(&cur_lo)); + while count > 0 { + highlights.push(std::mem::take(&mut line_highlight)); + acc = 0; + count -= 1; + } + } else { + acc = 0; + highlights.push(std::mem::take(&mut line_highlight)); + let mut count = push_trailing(&mut buf, prev_line, &prev_hi, None); + while count > 0 { + highlights.push(std::mem::take(&mut line_highlight)); + count -= 1; + } + // push lines between the previous and current span (if any) + for idx in prev_hi.line + 1..(cur_lo.line) { + if let Some(line) = self.get_line(idx) { + buf.push_str(line.as_ref()); + buf.push('\n'); + highlights.push(std::mem::take(&mut line_highlight)); + } + } + if let Some(cur_line) = self.get_line(cur_lo.line) { + let end = match cur_line.char_indices().nth(cur_lo.char) { + Some((i, _)) => i, + None => cur_line.len(), + }; + buf.push_str(&cur_line[..end]); + } + } + // Add a whole line highlight per line in the snippet. + let len: isize = part + .replacement + .split('\n') + .next() + .unwrap_or(part.replacement) + .chars() + .map(|c| match c { + '\t' => 4, + _ => 1, + }) + .sum(); + if !is_different(self, part.replacement, part.range.clone()) { + // Account for cases where we are suggesting the same code that's already + // there. This shouldn't happen often, but in some cases for multipart + // suggestions it's much easier to handle it here than in the origin. + } else { + line_highlight.push(SubstitutionHighlight { + start: (cur_lo.char as isize + acc) as usize, + end: (cur_lo.char as isize + acc + len) as usize, + }); + } + buf.push_str(part.replacement); + // Account for the difference between the width of the current code and the + // snippet being suggested, so that the *later* suggestions are correctly + // aligned on the screen. Note that cur_hi and cur_lo can be on different + // lines, so cur_hi.col can be smaller than cur_lo.col + acc += len - (cur_hi.char as isize - cur_lo.char as isize); + prev_hi = cur_hi; + prev_line = self.get_line(prev_hi.line); + for line in part.replacement.split('\n').skip(1) { + acc = 0; + highlights.push(std::mem::take(&mut line_highlight)); + let end: usize = line + .chars() + .map(|c| match c { + '\t' => 4, + _ => 1, + }) + .sum(); + line_highlight.push(SubstitutionHighlight { start: 0, end }); + } + } + highlights.push(std::mem::take(&mut line_highlight)); + // if the replacement already ends with a newline, don't print the next line + if !buf.ends_with('\n') { + push_trailing(&mut buf, prev_line, &prev_hi, None); + } + // remove trailing newlines + while buf.ends_with('\n') { + buf.pop(); + } + if highlights.iter().all(|parts| parts.is_empty()) { + Vec::new() + } else { + vec![(buf, patches, highlights)] + } + } } #[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq)] @@ -450,3 +640,11 @@ impl<'a> Iterator for CursorLines<'a> { } } } + +/// Used to translate between `Span`s and byte positions within a single output line in highlighted +/// code of structured suggestions. +#[derive(Debug, Clone, Copy)] +pub(crate) struct SubstitutionHighlight { + pub(crate) start: usize, + pub(crate) end: usize, +} diff --git a/src/renderer/styled_buffer.rs b/src/renderer/styled_buffer.rs index 3f192422..9a5f72a8 100644 --- a/src/renderer/styled_buffer.rs +++ b/src/renderer/styled_buffer.rs @@ -118,4 +118,33 @@ impl StyledBuffer { pub(crate) fn num_lines(&self) -> usize { self.lines.len() } + + /// Set `style` for `line`, `col_start..col_end` range if: + /// 1. That line and column range exist in `StyledBuffer` + /// 2. `overwrite` is `true` or existing style is `Style::NoStyle` or `Style::Quotation` + pub(crate) fn set_style_range( + &mut self, + line: usize, + col_start: usize, + col_end: usize, + style: ElementStyle, + overwrite: bool, + ) { + for col in col_start..col_end { + self.set_style(line, col, style, overwrite); + } + } + + /// Set `style` for `line`, `col` if: + /// 1. That line and column exist in `StyledBuffer` + /// 2. `overwrite` is `true` or existing style is `Style::NoStyle` or `Style::Quotation` + pub(crate) fn set_style(&mut self, line: usize, col: usize, style: ElementStyle, overwrite: bool) { + if let Some(ref mut line) = self.lines.get_mut(line) { + if let Some(StyledChar { style: s, .. }) = line.get_mut(col) { + if overwrite || matches!(s, ElementStyle::NoStyle | ElementStyle::Quotation) { + *s = style; + } + } + } + } } diff --git a/src/renderer/stylesheet.rs b/src/renderer/stylesheet.rs index c11a2795..4aa21a5a 100644 --- a/src/renderer/stylesheet.rs +++ b/src/renderer/stylesheet.rs @@ -11,6 +11,8 @@ pub(crate) struct Stylesheet { pub(crate) emphasis: Style, pub(crate) none: Style, pub(crate) context: Style, + pub(crate) addition: Style, + pub(crate) removal: Style, } impl Default for Stylesheet { @@ -31,6 +33,8 @@ impl Stylesheet { emphasis: Style::new(), none: Style::new(), context: Style::new(), + addition: Style::new(), + removal: Style::new(), } } } diff --git a/src/snippet.rs b/src/snippet.rs index 1c3efb64..d371ab85 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -1,5 +1,6 @@ //! Structures used as an input for the library. +use crate::renderer::source_map::SourceMap; use crate::renderer::stylesheet::Stylesheet; use anstyle::Style; use std::ops::Range; @@ -46,6 +47,17 @@ impl<'a> Message<'a> { cause.line_start + newline_count(&cause.source[..end]) } + Element::Suggestion(suggestion) => { + let end = suggestion + .markers + .iter() + .map(|a| a.range.end) + .max() + .unwrap_or(suggestion.source.len()) + .min(suggestion.source.len()); + + suggestion.line_start + newline_count(&suggestion.source[..end]) + } }) .max() .unwrap_or(1) @@ -91,6 +103,7 @@ impl<'a> Group<'a> { pub enum Element<'a> { Title(Title<'a>), Cause(Snippet<'a, Annotation<'a>>), + Suggestion(Snippet<'a, Patch<'a>>), Origin(Origin<'a>), ColumnSeparator(ColumnSeparator), } @@ -107,6 +120,12 @@ impl<'a> From<Snippet<'a, Annotation<'a>>> for Element<'a> { } } +impl<'a> From<Snippet<'a, Patch<'a>>> for Element<'a> { + fn from(value: Snippet<'a, Patch<'a>>) -> Self { + Element::Suggestion(value) + } +} + impl<'a> From<Origin<'a>> for Element<'a> { fn from(value: Origin<'a>) -> Self { Element::Origin(value) @@ -237,6 +256,18 @@ impl<'a> Snippet<'a, Annotation<'a>> { } } +impl<'a> Snippet<'a, Patch<'a>> { + pub fn patch(mut self, patch: Patch<'a>) -> Snippet<'a, Patch<'a>> { + self.markers.push(patch); + self + } + + pub fn patches(mut self, patches: impl IntoIterator<Item = Patch<'a>>) -> Self { + self.markers.extend(patches); + self + } +} + #[derive(Clone, Debug)] pub struct Annotation<'a> { pub(crate) range: Range<usize>, @@ -273,6 +304,65 @@ impl AnnotationKind { } } +#[derive(Clone, Debug)] +pub struct Patch<'a> { + pub(crate) range: Range<usize>, + pub(crate) replacement: &'a str, +} + +impl<'a> Patch<'a> { + pub fn new(range: Range<usize>, replacement: &'a str) -> Self { + Self { range, replacement } + } + + pub(crate) fn is_addition(&self, sm: &SourceMap<'_>) -> bool { + !self.replacement.is_empty() && !self.replaces_meaningful_content(sm) + } + + pub(crate) fn is_deletion(&self, sm: &SourceMap<'_>) -> bool { + self.replacement.trim().is_empty() && self.replaces_meaningful_content(sm) + } + + pub(crate) fn is_replacement(&self, sm: &SourceMap<'_>) -> bool { + !self.replacement.is_empty() && self.replaces_meaningful_content(sm) + } + + /// Whether this is a replacement that overwrites source with a snippet + /// in a way that isn't a superset of the original string. For example, + /// replacing "abc" with "abcde" is not destructive, but replacing it + /// it with "abx" is, since the "c" character is lost. + pub(crate) fn is_destructive_replacement(&self, sm: &SourceMap<'_>) -> bool { + self.is_replacement(sm) + && !sm + .span_to_snippet(self.range.clone()) + // This should use `is_some_and` when our MSRV is >= 1.70 + .map_or(false, |s| { + as_substr(s.trim(), self.replacement.trim()).is_some() + }) + } + + fn replaces_meaningful_content(&self, sm: &SourceMap<'_>) -> bool { + sm.span_to_snippet(self.range.clone()) + .map_or(!self.range.is_empty(), |snippet| !snippet.trim().is_empty()) + } + + /// Try to turn a replacement into an addition when the span that is being + /// overwritten matches either the prefix or suffix of the replacement. + pub(crate) fn trim_trivial_replacements(&mut self, sm: &'a SourceMap<'a>) { + if self.replacement.is_empty() { + return; + } + let Some(snippet) = sm.span_to_snippet(self.range.clone()) else { + return; + }; + + if let Some((prefix, substr, suffix)) = as_substr(snippet, self.replacement) { + self.range = self.range.start + prefix..self.range.end.saturating_sub(suffix); + self.replacement = substr; + } + } +} + #[derive(Clone, Debug)] pub struct Origin<'a> { pub(crate) origin: &'a str, @@ -326,3 +416,24 @@ fn newline_count(body: &str) -> usize { body.lines().count().saturating_sub(1) } } + +/// Given an original string like `AACC`, and a suggestion like `AABBCC`, try to detect +/// the case where a substring of the suggestion is "sandwiched" in the original, like +/// `BB` is. Return the length of the prefix, the "trimmed" suggestion, and the length +/// of the suffix. +fn as_substr<'a>(original: &'a str, suggestion: &'a str) -> Option<(usize, &'a str, usize)> { + let common_prefix = original + .chars() + .zip(suggestion.chars()) + .take_while(|(c1, c2)| c1 == c2) + .map(|(c, _)| c.len_utf8()) + .sum(); + let original = &original[common_prefix..]; + let suggestion = &suggestion[common_prefix..]; + if let Some(stripped) = suggestion.strip_suffix(original) { + let common_suffix = original.len(); + Some((common_prefix, stripped, common_suffix)) + } else { + None + } +} diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs index fffcf1f2..065c395e 100644 --- a/tests/fixtures/deserialize.rs +++ b/tests/fixtures/deserialize.rs @@ -3,7 +3,7 @@ use std::ops::Range; use annotate_snippets::renderer::DEFAULT_TERM_WIDTH; use annotate_snippets::{ - Annotation, AnnotationKind, Element, Group, Level, Message, Renderer, Snippet, + Annotation, AnnotationKind, Element, Group, Level, Message, Patch, Renderer, Snippet, }; #[derive(Deserialize)] @@ -40,6 +40,7 @@ impl<'a> From<&'a MessageDef> for Message<'a> { message = message.group(Group::new().elements(sections.iter().map(|s| match s { ElementDef::Title(title) => Element::Title(title.level.title(&title.title)), ElementDef::Cause(cause) => Element::Cause(Snippet::from(cause)), + ElementDef::Suggestion(suggestion) => Element::Suggestion(Snippet::from(suggestion)), }))); message } @@ -50,6 +51,7 @@ impl<'a> From<&'a MessageDef> for Message<'a> { pub enum ElementDef { Title(TitleDef), Cause(SnippetAnnotationDef), + Suggestion(SnippetPatchDef), } impl<'a> From<&'a ElementDef> for Element<'a> { @@ -57,6 +59,7 @@ impl<'a> From<&'a ElementDef> for Element<'a> { match val { ElementDef::Title(title) => Element::Title(title.level.title(&title.title)), ElementDef::Cause(cause) => Element::Cause(Snippet::from(cause)), + ElementDef::Suggestion(suggestion) => Element::Suggestion(Snippet::from(suggestion)), } } } @@ -119,6 +122,47 @@ enum AnnotationKindDef { Context, } +#[derive(Deserialize)] +pub struct SnippetPatchDef { + pub(crate) origin: Option<String>, + pub(crate) line_start: usize, + pub(crate) source: String, + pub(crate) patches: Vec<PatchDef>, + #[serde(default)] + pub(crate) fold: bool, +} + +impl<'a> From<&'a SnippetPatchDef> for Snippet<'a, Patch<'a>> { + fn from(val: &'a SnippetPatchDef) -> Self { + let SnippetPatchDef { + origin, + line_start, + source, + patches, + fold, + } = val; + let mut snippet = Snippet::source(source).line_start(*line_start).fold(*fold); + if let Some(origin) = origin { + snippet = snippet.origin(origin); + } + snippet = snippet.patches(patches.iter().map(Into::into)); + snippet + } +} + +#[derive(Deserialize)] +pub struct PatchDef { + pub range: Range<usize>, + pub replacement: String, +} + +impl<'a> From<&'a PatchDef> for Patch<'a> { + fn from(val: &'a PatchDef) -> Self { + let PatchDef { range, replacement } = val; + Patch::new(range.start..range.end, replacement) + } +} + #[allow(dead_code)] #[derive(Deserialize)] #[serde(remote = "Level")] diff --git a/tests/formatter.rs b/tests/formatter.rs index 33b998f5..61f63d1f 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -1,4 +1,4 @@ -use annotate_snippets::{Annotation, AnnotationKind, Group, Level, Renderer, Snippet}; +use annotate_snippets::{Annotation, AnnotationKind, Group, Level, Patch, Renderer, Snippet}; use snapbox::{assert_data_eq, str}; @@ -1012,3 +1012,681 @@ error: title let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input), expected); } + +#[test] +fn two_suggestions_same_span() { + let source = r#" A.foo();"#; + let input_new = Level::Error + .message("expected value, found enum `A`") + .id("E0423") + .group( + Group::new().element( + Snippet::source(source) + .fold(true) + .annotation(AnnotationKind::Primary.span(4..5)), + ), + ) + .group( + Group::new() + .element( + Level::Help + .title("you might have meant to use one of the following enum variants"), + ) + .element( + Snippet::source(source) + .fold(true) + .patch(Patch::new(4..5, "(A::Tuple())")), + ) + .element( + Snippet::source(source) + .fold(true) + .patch(Patch::new(4..5, "A::Unit")), + ), + ); + + let expected = str![[r#" +error[E0423]: expected value, found enum `A` + | +LL | A.foo(); + | ^ + | +help: you might have meant to use one of the following enum variants + | +LL - A.foo(); +LL + (A::Tuple()).foo(); + | +LL | A::Unit.foo(); + | ++++++ +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input_new), expected); +} + +#[test] +fn two_suggestions_same_span2() { + let source = r#" +mod banana { + pub struct Chaenomeles; + + pub trait Apple { + fn pick(&self) {} + } + impl Apple for Chaenomeles {} + + pub trait Peach { + fn pick(&self, a: &mut ()) {} + } + impl<Mango: Peach> Peach for Box<Mango> {} + impl Peach for Chaenomeles {} +} + +fn main() { + banana::Chaenomeles.pick() +}"#; + let input_new = + Level::Error + .message("no method named `pick` found for struct `Chaenomeles` in the current scope") + .id("E0599") + .group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .fold(true) + .annotation( + AnnotationKind::Context + .span(18..40) + .label("method `pick` not found for this struct"), + ) + .annotation( + AnnotationKind::Primary + .span(318..322) + .label("method not found in `Chaenomeles`"), + ), + ), + ) + .group( + Group::new() + .element(Level::Help.title( + "the following traits which provide `pick` are implemented but not in scope; perhaps you want to import one of them", + )) + .element( + Snippet::source(source) + .fold(true) + .patch(Patch::new(1..1, "use banana::Apple;\n")), + ) + .element( + Snippet::source(source) + .fold(true) + .patch(Patch::new(1..1, "use banana::Peach;\n")), + ), + ); + let expected = str![[r#" +error[E0599]: no method named `pick` found for struct `Chaenomeles` in the current scope + | +LL | pub struct Chaenomeles; + | ---------------------- method `pick` not found for this struct +... +LL | banana::Chaenomeles.pick() + | ^^^^ method not found in `Chaenomeles` + | +help: the following traits which provide `pick` are implemented but not in scope; perhaps you want to import one of them + | +LL + use banana::Apple; + | +LL + use banana::Peach; + | +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input_new), expected); +} + +#[test] +fn single_line_non_overlapping_suggestions() { + let source = r#" A.foo();"#; + + let input_new = Level::Error + .message("expected value, found enum `A`") + .id("E0423") + .group( + Group::new().element( + Snippet::source(source) + .fold(true) + .line_start(1) + .annotation(AnnotationKind::Primary.span(4..5)), + ), + ) + .group( + Group::new() + .element(Level::Help.title("make these changes and things will work")) + .element( + Snippet::source(source) + .fold(true) + .fold(true) + .patch(Patch::new(4..5, "(A::Tuple())")) + .patch(Patch::new(6..9, "bar")), + ), + ); + + let expected = str![[r#" +error[E0423]: expected value, found enum `A` + | +LL | A.foo(); + | ^ + | +help: make these changes and things will work + | +LL - A.foo(); +LL + (A::Tuple()).bar(); + | +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input_new), expected); +} + +#[test] +fn single_line_non_overlapping_suggestions2() { + let source = r#" ThisIsVeryLong.foo();"#; + let input_new = Level::Error + .message("Found `ThisIsVeryLong`") + .id("E0423") + .group( + Group::new().element( + Snippet::source(source) + .fold(true) + .line_start(1) + .annotation(AnnotationKind::Primary.span(4..18)), + ), + ) + .group( + Group::new() + .element(Level::Help.title("make these changes and things will work")) + .element( + Snippet::source(source) + .fold(true) + .fold(true) + .patch(Patch::new(4..18, "(A::Tuple())")) + .patch(Patch::new(19..22, "bar")), + ), + ); + + let expected = str![[r#" +error[E0423]: Found `ThisIsVeryLong` + | +LL | ThisIsVeryLong.foo(); + | ^^^^^^^^^^^^^^ + | +help: make these changes and things will work + | +LL - ThisIsVeryLong.foo(); +LL + (A::Tuple()).bar(); + | +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input_new), expected); +} + +#[test] +fn multiple_replacements() { + let source = r#" + let y = || { + self.bar(); + }; + self.qux(); + y(); +"#; + + let input_new = Level::Error + .message("cannot borrow `*self` as mutable because it is also borrowed as immutable") + .id("E0502") + .group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .fold(true) + .annotation( + AnnotationKind::Primary + .span(49..59) + .label("mutable borrow occurs here"), + ) + .annotation( + AnnotationKind::Primary + .span(13..15) + .label("immutable borrow occurs here"), + ) + .annotation( + AnnotationKind::Primary + .span(26..30) + .label("first borrow occurs due to use of `*self` in closure"), + ) + .annotation( + AnnotationKind::Primary + .span(65..66) + .label("immutable borrow later used here"), + ), + ), + ) + .group( + Group::new() + .element( + Level::Help + .title("try explicitly pass `&Self` into the Closure as an argument"), + ) + .element( + Snippet::source(source) + .fold(true) + .patch(Patch::new(14..14, "this: &Self")) + .patch(Patch::new(26..30, "this")) + .patch(Patch::new(66..68, "(self)")), + ), + ); + let expected = str![[r#" +error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable + | +LL | let y = || { + | ^^ immutable borrow occurs here +LL | self.bar(); + | ^^^^ first borrow occurs due to use of `*self` in closure +LL | }; +LL | self.qux(); + | ^^^^^^^^^^ mutable borrow occurs here +LL | y(); + | ^ immutable borrow later used here + | +help: try explicitly pass `&Self` into the Closure as an argument + | +LL ~ let y = |this: &Self| { +LL ~ this.bar(); +LL | }; +LL | self.qux(); +LL ~ y(self); + | +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input_new), expected); +} + +#[test] +fn multiple_replacements2() { + let source = r#" +fn test1() { + let mut chars = "Hello".chars(); + for _c in chars.by_ref() { + chars.next(); + } +} + +fn main() { + test1(); +}"#; + + let input_new = Level::Error + .message("cannot borrow `chars` as mutable more than once at a time") + .id("E0499") + .group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .fold(true) + .annotation( + AnnotationKind::Context + .span(65..70) + .label("first mutable borrow occurs here"), + ) + .annotation( + AnnotationKind::Primary + .span(90..95) + .label("second mutable borrow occurs here"), + ) + .annotation( + AnnotationKind::Context + .span(65..79) + .label("first borrow later used here"), + ), + ), + ) + .group( + Group::new() + .element( + Level::Help + .title("if you want to call `next` on a iterator within the loop, consider using `while let`") + ) + .element( + Snippet::source(source) + .fold(true) + .patch(Patch::new( + 55..59, + "let iter = chars.by_ref();\n while let Some(", + )) + .patch(Patch::new(61..79, ") = iter.next()")) + .patch(Patch::new(90..95, "iter")), + ), + ); + + let expected = str![[r#" +error[E0499]: cannot borrow `chars` as mutable more than once at a time + | +LL | for _c in chars.by_ref() { + | -------------- + | | + | first mutable borrow occurs here + | first borrow later used here +LL | chars.next(); + | ^^^^^ second mutable borrow occurs here + | +help: if you want to call `next` on a iterator within the loop, consider using `while let` + | +LL ~ let iter = chars.by_ref(); +LL ~ while let Some(_c) = iter.next() { +LL ~ iter.next(); + | +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input_new), expected); +} + +#[test] +fn diff_format() { + let source = r#" +use st::cell::Cell; + +mod bar { + pub fn bar() { bar::baz(); } + + fn baz() {} +} + +use bas::bar; + +struct Foo { + bar: st::cell::Cell<bool> +} + +fn main() {}"#; + + let input_new = Level::Error + .message("failed to resolve: use of undeclared crate or module `st`") + .id("E0433") + .group( + Group::new().element( + Snippet::source(source).line_start(1).fold(true).annotation( + AnnotationKind::Primary + .span(122..124) + .label("use of undeclared crate or module `st`"), + ), + ), + ) + .group( + Group::new() + .element(Level::Help.title("there is a crate or module with a similar name")) + .element( + Snippet::source(source) + .fold(true) + .patch(Patch::new(122..124, "std")), + ), + ) + .group( + Group::new() + .element(Level::Help.title("consider importing this module")) + .element( + Snippet::source(source) + .fold(true) + .patch(Patch::new(1..1, "use std::cell;\n")), + ), + ) + .group( + Group::new() + .element(Level::Help.title("if you import `cell`, refer to it directly")) + .element( + Snippet::source(source) + .fold(true) + .patch(Patch::new(122..126, "")), + ), + ); + let expected = str![[r#" +error[E0433]: failed to resolve: use of undeclared crate or module `st` + | +LL | bar: st::cell::Cell<bool> + | ^^ use of undeclared crate or module `st` + | +help: there is a crate or module with a similar name + | +LL | bar: std::cell::Cell<bool> + | + +help: consider importing this module + | +LL + use std::cell; + | +help: if you import `cell`, refer to it directly + | +LL - bar: st::cell::Cell<bool> +LL + bar: cell::Cell<bool> + | +"#]]; + + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input_new), expected); +} + +#[test] +fn multiline_removal() { + let source = r#" +struct Wrapper<T>(T); + +fn foo<T>(foo: Wrapper<T>) + +where + T + : + ? + Sized +{ + // +} + +fn main() {}"#; + + let input_new = Level::Error + .message("the size for values of type `T` cannot be known at compilation time") + .id("E0277") + .group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .fold(true) + .annotation( + AnnotationKind::Primary + .span(39..49) + .label("doesn't have a size known at compile-time"), + ) + .annotation( + AnnotationKind::Context + .span(31..32) + .label("this type parameter needs to be `Sized`"), + ), + ), + ) + .group( + Group::new() + .element(Level::Help.title( + "consider removing the `?Sized` bound to make the type parameter `Sized`", + )) + .element( + Snippet::source(source) + .fold(true) + .patch(Patch::new(52..86, "")), + ), + ); + let expected = str![[r#" +error[E0277]: the size for values of type `T` cannot be known at compilation time + | +LL | fn foo<T>(foo: Wrapper<T>) + | - ^^^^^^^^^^ doesn't have a size known at compile-time + | | + | this type parameter needs to be `Sized` + | +help: consider removing the `?Sized` bound to make the type parameter `Sized` + | +LL - where +LL - T +LL - : +LL - ? +LL - Sized + | +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input_new), expected); +} + +#[test] +fn multiline_replacement() { + let source = r#" +struct Wrapper<T>(T); + +fn foo<T>(foo: Wrapper<T>) + +and where + T + : + ? + Sized +{ + // +} + +fn main() {}"#; + let input_new = Level::Error + .message("the size for values of type `T` cannot be known at compilation time") + .id("E0277") + .group(Group::new().element(Snippet::source(source) + .line_start(1) + .origin("$DIR/removal-of-multiline-trait-bound-in-where-clause.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(39..49) + .label("doesn't have a size known at compile-time"), + ) + .annotation( + AnnotationKind::Context + .span(31..32) + .label("this type parameter needs to be `Sized`"), + ))) + .group(Group::new().element( + Level::Note + .title("required by an implicit `Sized` bound in `Wrapper`") + ).element( + Snippet::source(source) + .line_start(1) + .origin("$DIR/removal-of-multiline-trait-bound-in-where-clause.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(16..17) + .label("required by the implicit `Sized` requirement on this type parameter in `Wrapper`"), + ) + )) + .group(Group::new().element( + Level::Help + .title("you could relax the implicit `Sized` bound on `T` if it were used through indirection like `&T` or `Box<T>`") + ) + .element( + Snippet::source(source) + .line_start(1) + .origin("$DIR/removal-of-multiline-trait-bound-in-where-clause.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(16..17) + .label("this could be changed to `T: ?Sized`..."), + ) + .annotation( + AnnotationKind::Context + .span(19..20) + .label("...if indirection were used here: `Box<T>`"), + ) + + )) + .group(Group::new().element( + Level::Help + .title("consider removing the `?Sized` bound to make the type parameter `Sized`") + ).element( + Snippet::source(source) + .fold(true) + .patch(Patch::new(56..90, "")) + .patch(Patch::new(90..90, "+ Send")) + , + )); + let expected = str![[r#" +error[E0277]: the size for values of type `T` cannot be known at compilation time + --> $DIR/removal-of-multiline-trait-bound-in-where-clause.rs:4:16 + | +LL | fn foo<T>(foo: Wrapper<T>) + | - ^^^^^^^^^^ doesn't have a size known at compile-time + | | + | this type parameter needs to be `Sized` + | +note: required by an implicit `Sized` bound in `Wrapper` + --> $DIR/removal-of-multiline-trait-bound-in-where-clause.rs:2:16 + | +LL | struct Wrapper<T>(T); + | ^ required by the implicit `Sized` requirement on this type parameter in `Wrapper` +help: you could relax the implicit `Sized` bound on `T` if it were used through indirection like `&T` or `Box<T>` + --> $DIR/removal-of-multiline-trait-bound-in-where-clause.rs:2:16 + | +LL | struct Wrapper<T>(T); + | ^ - ...if indirection were used here: `Box<T>` + | | + | this could be changed to `T: ?Sized`... +help: consider removing the `?Sized` bound to make the type parameter `Sized` + | +LL ~ and +LL ~ + Send{ + | +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input_new), expected); +} + +#[test] +fn multiline_removal2() { + let source = r#" +cargo +fuzzy +pizza +jumps +crazy +quack +zappy +"#; + + let input_new = Level::Error + .message("the size for values of type `T` cannot be known at compilation time") + .id("E0277") + .group( + Group::new() + .element(Level::Help.title( + "consider removing the `?Sized` bound to make the type parameter `Sized`", + )) + .element( + Snippet::source(source) + .line_start(7) + .fold(true) + .patch(Patch::new(3..21, "")) + .patch(Patch::new(22..40, "")), + ), + ); + let expected = str![[r#" +error[E0277]: the size for values of type `T` cannot be known at compilation time + | +help: consider removing the `?Sized` bound to make the type parameter `Sized` + | +8 - cargo +9 - fuzzy +10 - pizza +11 - jumps +8 + campy + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input_new), expected); +} From ad60b003b1522944cade4b652730ff7bf70160ce Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Fri, 28 Feb 2025 03:54:49 -0700 Subject: [PATCH 320/455] feat: Add unicode support --- src/renderer/mod.rs | 335 ++++++++++++++++++++++----- src/renderer/styled_buffer.rs | 8 +- tests/formatter.rs | 422 ++++++++++++++++++++++++++++++++++ 3 files changed, 708 insertions(+), 57 deletions(-) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index fdd2b3ef..85de0f5c 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -63,6 +63,7 @@ pub const DEFAULT_TERM_WIDTH: usize = 140; pub struct Renderer { anonymized_line_numbers: bool, term_width: usize, + theme: OutputTheme, stylesheet: Stylesheet, } @@ -72,6 +73,7 @@ impl Renderer { Self { anonymized_line_numbers: false, term_width: DEFAULT_TERM_WIDTH, + theme: OutputTheme::Ascii, stylesheet: Stylesheet::plain(), } } @@ -140,6 +142,11 @@ impl Renderer { self } + pub const fn theme(mut self, output_theme: OutputTheme) -> Self { + self.theme = output_theme; + self + } + /// Set the output style for `error` pub const fn error(mut self, style: Style) -> Self { self.stylesheet.error = style; @@ -318,6 +325,7 @@ impl Renderer { None } }), + matches!(peek, Some(Element::Title(_))), ); last_was_suggestion = false; } @@ -333,6 +341,7 @@ impl Renderer { &source_map, &annotated_lines, max_depth, + peek.is_some() || (g == 0 && group_len > 1), ); if g == 0 && group_len > 1 { @@ -346,12 +355,10 @@ impl Renderer { // We want to draw the separator when it is // requested, or when it is the last element } else if peek.is_none() { - self.draw_col_separator_no_space_with_style( + self.draw_col_separator_end( buffer, - '|', buffer.num_lines(), max_line_num_len + 1, - ElementStyle::LineNumber, ); } } @@ -390,12 +397,10 @@ impl Renderer { || matches!(section, Element::Title(level) if level.level == Level::None)) { if peek.is_none() && group_len > 1 { - self.draw_col_separator_no_space_with_style( + self.draw_col_separator_end( buffer, - '|', buffer.num_lines(), max_line_num_len + 1, - ElementStyle::LineNumber, ); } else if matches!(peek, Some(Element::Title(level)) if level.level != Level::None) { @@ -410,6 +415,7 @@ impl Renderer { } } + #[allow(clippy::too_many_arguments)] fn render_title( &self, buffer: &mut StyledBuffer, @@ -418,6 +424,7 @@ impl Renderer { max_line_num_len: usize, is_secondary: bool, id: Option<&&str>, + is_cont: bool, ) { let line_offset = buffer.num_lines(); @@ -437,13 +444,9 @@ impl Renderer { for _ in 0..max_line_num_len { buffer.prepend(line_offset, " ", ElementStyle::NoStyle); } + if title.level != Level::None { - buffer.puts( - line_offset, - max_line_num_len + 1, - "= ", - ElementStyle::LineNumber, - ); + self.draw_note_separator(buffer, line_offset, max_line_num_len + 1, is_cont); buffer.append( line_offset, title.level.as_str(), @@ -451,7 +454,25 @@ impl Renderer { ); buffer.append(line_offset, ": ", ElementStyle::NoStyle); } - self.msgs_to_buffer(buffer, title.title, max_line_num_len, "note", None); + + let printed_lines = + self.msgs_to_buffer(buffer, title.title, max_line_num_len, "note", None); + if is_cont && matches!(self.theme, OutputTheme::Unicode) { + // There's another note after this one, associated to the subwindow above. + // We write additional vertical lines to join them: + // ╭▸ test.rs:3:3 + // │ + // 3 │ code + // │ ━━━━ + // │ + // ├ note: foo + // │ bar + // ╰ note: foo + // bar + for i in line_offset + 1..=printed_lines { + self.draw_col_separator_no_space(buffer, i, max_line_num_len + 1); + } + } } else { let mut label_width = 0; @@ -628,7 +649,7 @@ impl Renderer { max_line_num_len + 1, ); let title = Level::Note.title(label); - self.render_title(buffer, &title, None, max_line_num_len, true, None); + self.render_title(buffer, &title, None, max_line_num_len, true, None, false); } } @@ -642,6 +663,7 @@ impl Renderer { sm: &SourceMap<'_>, annotated_lines: &[AnnotatedLineInfo<'_>], multiline_depth: usize, + is_cont: bool, ) { if let Some(origin) = snippet.origin { let mut origin = Origin::new(origin); @@ -783,6 +805,7 @@ impl Renderer { code_offset, max_line_num_len, margin, + !is_cont && annotated_line_idx + 1 == annotated_lines.len(), ); let mut to_add = HashMap::new(); @@ -810,7 +833,8 @@ impl Renderer { match line_idx_delta.cmp(&2) { Ordering::Greater => { let last_buffer_line_num = buffer.num_lines(); - buffer.puts(last_buffer_line_num, 0, "...", ElementStyle::LineNumber); + + self.draw_line_separator(buffer, last_buffer_line_num, width_offset); // Set the multiline annotation vertical lines on `...` bridging line. for (depth, style) in &multilines { @@ -908,6 +932,7 @@ impl Renderer { code_offset: usize, max_line_num_len: usize, margin: Margin, + close_window: bool, ) -> Vec<(usize, ElementStyle)> { // Draw: // @@ -1205,7 +1230,9 @@ impl Renderer { for pos in 0..=line_len { self.draw_col_separator_no_space(buffer, line_offset + pos + 1, width_offset - 2); } - + if close_window { + self.draw_col_separator_end(buffer, line_offset + line_len + 1, width_offset - 2); + } // Write the horizontal lines for multiline annotations // (only the first and last lines need this). // @@ -1470,18 +1497,12 @@ impl Renderer { let is_multiline = complete.lines().count() > 1; if i == 0 { - self.draw_col_separator_no_space_with_style( - buffer, - '|', - row_num - 1, - max_line_num_len + 1, - ElementStyle::LineNumber, - ); + self.draw_col_separator_start(buffer, row_num - 1, max_line_num_len + 1); } else { buffer.puts( row_num - 1, max_line_num_len + 1, - "|", + self.multi_suggestion_separator(), ElementStyle::LineNumber, ); } @@ -1616,7 +1637,7 @@ impl Renderer { ); } - let placeholder = "..."; + let placeholder = self.margin(); let padding = str_width(placeholder); buffer.puts( row_num, @@ -1735,7 +1756,11 @@ impl Renderer { buffer.putc( row_num, (padding as isize + p) as usize, - if part.is_addition(sm) { '+' } else { '~' }, + if part.is_addition(sm) { + '+' + } else { + self.diff() + }, ElementStyle::Addition, ); } @@ -1766,7 +1791,7 @@ impl Renderer { // if we elided some lines, add an ellipsis if lines.next().is_some() { - let placeholder = "..."; + let placeholder = self.margin(); let padding = str_width(placeholder); buffer.puts( row_num, @@ -1781,13 +1806,7 @@ impl Renderer { | DisplaySuggestion::Underline => row_num - 1, DisplaySuggestion::None => row_num, }; - self.draw_col_separator_no_space_with_style( - buffer, - '|', - row, - max_line_num_len + 1, - ElementStyle::LineNumber, - ); + self.draw_col_separator_end(buffer, row, max_line_num_len + 1); row_num = row + 1; } } @@ -1906,7 +1925,13 @@ impl Renderer { self.draw_col_separator_no_space(buffer, *row_num, max_line_num_len + 1); } _ => { - buffer.puts(*row_num, max_line_num_len + 1, "~", ElementStyle::Addition); + let diff = self.diff(); + buffer.puts( + *row_num, + max_line_num_len + 1, + &format!("{diff} "), + ElementStyle::Addition, + ); } } // LL | line_to_add @@ -1940,12 +1965,7 @@ impl Renderer { &self.maybe_anonymized(line_num), ElementStyle::LineNumber, ); - buffer.puts( - *row_num, - max_line_num_len + 1, - "| ", - ElementStyle::LineNumber, - ); + self.draw_col_separator(buffer, *row_num, max_line_num_len + 1); buffer.append( *row_num, &normalize_whitespace(line_to_add), @@ -2014,16 +2034,23 @@ impl Renderer { .collect(); buffer.puts(line_offset, code_offset, &code, ElementStyle::Quotation); + let placeholder = self.margin(); if margin.was_cut_left() { // We have stripped some code/whitespace from the beginning, make it clear. - buffer.puts(line_offset, code_offset, "...", ElementStyle::LineNumber); + buffer.puts( + line_offset, + code_offset, + placeholder, + ElementStyle::LineNumber, + ); } if margin.was_cut_right(line_len) { + let padding = str_width(placeholder); // We have stripped some code after the rightmost span end, make it clear we did so. buffer.puts( line_offset, - code_offset + taken - 3, - "...", + code_offset + taken - padding, + placeholder, ElementStyle::LineNumber, ); } @@ -2059,19 +2086,109 @@ impl Renderer { depth: usize, style: ElementStyle, ) { - buffer.putc(line, offset + depth - 1, '|', style); + let chr = match (style, self.theme) { + (ElementStyle::UnderlinePrimary | ElementStyle::LabelPrimary, OutputTheme::Ascii) => { + '|' + } + (_, OutputTheme::Ascii) => '|', + (ElementStyle::UnderlinePrimary | ElementStyle::LabelPrimary, OutputTheme::Unicode) => { + '┃' + } + (_, OutputTheme::Unicode) => '│', + }; + buffer.putc(line, offset + depth - 1, chr, style); + } + + fn col_separator(&self) -> char { + match self.theme { + OutputTheme::Ascii => '|', + OutputTheme::Unicode => '│', + } + } + + fn multi_suggestion_separator(&self) -> &'static str { + match self.theme { + OutputTheme::Ascii => "|", + OutputTheme::Unicode => "├╴", + } + } + + fn draw_col_separator(&self, buffer: &mut StyledBuffer, line: usize, col: usize) { + let chr = self.col_separator(); + buffer.puts(line, col, &format!("{chr} "), ElementStyle::LineNumber); } fn draw_col_separator_no_space(&self, buffer: &mut StyledBuffer, line: usize, col: usize) { + let chr = self.col_separator(); self.draw_col_separator_no_space_with_style( buffer, - '|', + chr, line, col, ElementStyle::LineNumber, ); } + fn draw_col_separator_start(&self, buffer: &mut StyledBuffer, line: usize, col: usize) { + match self.theme { + OutputTheme::Ascii => { + self.draw_col_separator_no_space_with_style( + buffer, + '|', + line, + col, + ElementStyle::LineNumber, + ); + } + OutputTheme::Unicode => { + self.draw_col_separator_no_space_with_style( + buffer, + '╭', + line, + col, + ElementStyle::LineNumber, + ); + self.draw_col_separator_no_space_with_style( + buffer, + '╴', + line, + col + 1, + ElementStyle::LineNumber, + ); + } + } + } + + fn draw_col_separator_end(&self, buffer: &mut StyledBuffer, line: usize, col: usize) { + match self.theme { + OutputTheme::Ascii => { + self.draw_col_separator_no_space_with_style( + buffer, + '|', + line, + col, + ElementStyle::LineNumber, + ); + } + OutputTheme::Unicode => { + self.draw_col_separator_no_space_with_style( + buffer, + '╰', + line, + col, + ElementStyle::LineNumber, + ); + self.draw_col_separator_no_space_with_style( + buffer, + '╴', + line, + col + 1, + ElementStyle::LineNumber, + ); + } + } + } + fn draw_col_separator_no_space_with_style( &self, buffer: &mut StyledBuffer, @@ -2091,17 +2208,84 @@ impl Renderer { } } - fn file_start(&self) -> &str { - "--> " + fn file_start(&self) -> &'static str { + match self.theme { + OutputTheme::Ascii => "--> ", + OutputTheme::Unicode => " ╭▸ ", + } + } + + fn secondary_file_start(&self) -> &'static str { + match self.theme { + OutputTheme::Ascii => "::: ", + OutputTheme::Unicode => " ⸬ ", + } + } + + fn draw_note_separator( + &self, + buffer: &mut StyledBuffer, + line: usize, + col: usize, + is_cont: bool, + ) { + let chr = match self.theme { + OutputTheme::Ascii => "= ", + OutputTheme::Unicode if is_cont => "├ ", + OutputTheme::Unicode => "╰ ", + }; + buffer.puts(line, col, chr, ElementStyle::LineNumber); + } + + fn diff(&self) -> char { + match self.theme { + OutputTheme::Ascii => '~', + OutputTheme::Unicode => '±', + } + } + + fn draw_line_separator(&self, buffer: &mut StyledBuffer, line: usize, col: usize) { + let (column, dots) = match self.theme { + OutputTheme::Ascii => (0, "..."), + OutputTheme::Unicode => (col - 2, "‡"), + }; + buffer.puts(line, column, dots, ElementStyle::LineNumber); } - fn secondary_file_start(&self) -> &str { - "::: " + fn margin(&self) -> &'static str { + match self.theme { + OutputTheme::Ascii => "...", + OutputTheme::Unicode => "…", + } } fn underline(&self, is_primary: bool) -> UnderlineParts { - if is_primary { - UnderlineParts { + // X0 Y0 + // label_start > ┯━━━━ < underline + // │ < vertical_text_line + // text + + // multiline_start_down ⤷ X0 Y0 + // top_left > ┌───╿──┘ < top_right_flat + // top_left > ┏│━━━┙ < top_right + // multiline_vertical > ┃│ + // ┃│ X1 Y1 + // ┃│ X2 Y2 + // ┃└────╿──┘ < multiline_end_same_line + // bottom_left > ┗━━━━━┥ < bottom_right_with_text + // multiline_horizontal ^ `X` is a good letter + + // multiline_whole_line > ┏ X0 Y0 + // ┃ X1 Y1 + // ┗━━━━┛ < multiline_end_same_line + + // multiline_whole_line > ┏ X0 Y0 + // ┃ X1 Y1 + // ┃ ╿ < multiline_end_up + // ┗━━┛ < bottom_right + + match (self.theme, is_primary) { + (OutputTheme::Ascii, true) => UnderlineParts { style: ElementStyle::UnderlinePrimary, underline: '^', label_start: '^', @@ -2117,9 +2301,8 @@ impl Renderer { multiline_end_up: '^', multiline_end_same_line: '^', multiline_bottom_right_with_text: '|', - } - } else { - UnderlineParts { + }, + (OutputTheme::Ascii, false) => UnderlineParts { style: ElementStyle::UnderlineSecondary, underline: '-', label_start: '-', @@ -2135,7 +2318,41 @@ impl Renderer { multiline_end_up: '-', multiline_end_same_line: '-', multiline_bottom_right_with_text: '|', - } + }, + (OutputTheme::Unicode, true) => UnderlineParts { + style: ElementStyle::UnderlinePrimary, + underline: '━', + label_start: '┯', + vertical_text_line: '│', + multiline_vertical: '┃', + multiline_horizontal: '━', + multiline_whole_line: '┏', + multiline_start_down: '╿', + bottom_right: '┙', + top_left: '┏', + top_right_flat: '┛', + bottom_left: '┗', + multiline_end_up: '╿', + multiline_end_same_line: '┛', + multiline_bottom_right_with_text: '┥', + }, + (OutputTheme::Unicode, false) => UnderlineParts { + style: ElementStyle::UnderlineSecondary, + underline: '─', + label_start: '┬', + vertical_text_line: '│', + multiline_vertical: '│', + multiline_horizontal: '─', + multiline_whole_line: '┌', + multiline_start_down: '│', + bottom_right: '┘', + top_left: '┌', + top_right_flat: '┘', + bottom_left: '└', + multiline_end_up: '│', + multiline_end_same_line: '┘', + multiline_bottom_right_with_text: '┤', + }, } } } @@ -2435,6 +2652,12 @@ pub(crate) fn is_different(sm: &SourceMap<'_>, suggested: &str, range: Range<usi } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum OutputTheme { + Ascii, + Unicode, +} + #[cfg(test)] mod test { use super::OUTPUT_REPLACEMENTS; diff --git a/src/renderer/styled_buffer.rs b/src/renderer/styled_buffer.rs index 9a5f72a8..8a4cc672 100644 --- a/src/renderer/styled_buffer.rs +++ b/src/renderer/styled_buffer.rs @@ -138,7 +138,13 @@ impl StyledBuffer { /// Set `style` for `line`, `col` if: /// 1. That line and column exist in `StyledBuffer` /// 2. `overwrite` is `true` or existing style is `Style::NoStyle` or `Style::Quotation` - pub(crate) fn set_style(&mut self, line: usize, col: usize, style: ElementStyle, overwrite: bool) { + pub(crate) fn set_style( + &mut self, + line: usize, + col: usize, + style: ElementStyle, + overwrite: bool, + ) { if let Some(ref mut line) = self.lines.get_mut(line) { if let Some(StyledChar { style: s, .. }) = line.get_mut(col) { if overwrite || matches!(s, ElementStyle::NoStyle | ElementStyle::Quotation) { diff --git a/tests/formatter.rs b/tests/formatter.rs index 61f63d1f..6304cf41 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -1,5 +1,6 @@ use annotate_snippets::{Annotation, AnnotationKind, Group, Level, Patch, Renderer, Snippet}; +use annotate_snippets::renderer::OutputTheme; use snapbox::{assert_data_eq, str}; #[test] @@ -1690,3 +1691,424 @@ help: consider removing the `?Sized` bound to make the type parameter `Sized` let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input_new), expected); } + +#[test] +fn e0271() { + let source = r#" +trait Future { + type Error; +} + +impl<T, E> Future for Result<T, E> { + type Error = E; +} + +impl<T> Future for Option<T> { + type Error = (); +} + +struct Foo; + +fn foo() -> Box<dyn Future<Error=Foo>> { + Box::new( + Ok::<_, ()>( + Err::<(), _>( + Ok::<_, ()>( + Err::<(), _>( + Ok::<_, ()>( + Err::<(), _>(Some(5)) + ) + ) + ) + ) + ) + ) +} +fn main() { +} +"#; + + let input_new = Level::Error + .message("type mismatch resolving `<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ...>>, ...>>, ...> as Future>::Error == Foo`") + .id("E0271") + .group(Group::new().element(Snippet::source(source) + .line_start(4) + .origin("$DIR/E0271.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(208..510) + .label("type mismatch resolving `<Result<Result<(), Result<Result<(), ...>, ...>>, ...> as Future>::Error == Foo`"), + ))) + .group(Group::new().element( + Level::Note.title("expected this to be `Foo`") + ).element( + Snippet::source(source) + .line_start(4) + .origin("$DIR/E0271.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(89..90)) + ).element( + Level::Note + .title("required for the cast from `Box<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ()>>, ()>>, ()>>` to `Box<(dyn Future<Error = Foo> + 'static)>`") + , + )); + + let expected = str![[r#" +error[E0271]: type mismatch resolving `<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ...>>, ...>>, ...> as Future>::Error == Foo` + ╭▸ $DIR/E0271.rs:20:5 + │ +LL │ ┏ Box::new( +LL │ ┃ Ok::<_, ()>( +LL │ ┃ Err::<(), _>( +LL │ ┃ Ok::<_, ()>( + ‡ ┃ +LL │ ┃ ) + │ ┗━━━━━┛ type mismatch resolving `<Result<Result<(), Result<Result<(), ...>, ...>>, ...> as Future>::Error == Foo` + ╰╴ +note: expected this to be `Foo` + ╭▸ $DIR/E0271.rs:10:18 + │ +LL │ type Error = E; + │ ━ + ╰ note: required for the cast from `Box<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ()>>, ()>>, ()>>` to `Box<(dyn Future<Error = Foo> + 'static)>` +"#]]; + let renderer = Renderer::plain() + .term_width(40) + .theme(OutputTheme::Unicode) + .anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input_new), expected); +} + +#[test] +fn e0271_2() { + let source = r#" +trait Future { + type Error; +} + +impl<T, E> Future for Result<T, E> { + type Error = E; +} + +impl<T> Future for Option<T> { + type Error = (); +} + +struct Foo; + +fn foo() -> Box<dyn Future<Error=Foo>> { + Box::new( + Ok::<_, ()>( + Err::<(), _>( + Ok::<_, ()>( + Err::<(), _>( + Ok::<_, ()>( + Err::<(), _>(Some(5)) + ) + ) + ) + ) + ) + ) +} +fn main() { +} +"#; + + let input_new = Level::Error + .message("type mismatch resolving `<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ...>>, ...>>, ...> as Future>::Error == Foo`") + .id("E0271") + .group(Group::new().element(Snippet::source(source) + .line_start(4) + .origin("$DIR/E0271.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(208..510) + .label("type mismatch resolving `<Result<Result<(), Result<Result<(), ...>, ...>>, ...> as Future>::Error == Foo`"), + ))) + .group(Group::new().element( + Level::Note.title("expected this to be `Foo`") + ).element( + Snippet::source(source) + .line_start(4) + .origin("$DIR/E0271.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(89..90)) + ).element( + Level::Note + .title("required for the cast from `Box<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ()>>, ()>>, ()>>` to `Box<(dyn Future<Error = Foo> + 'static)>`") + ).element( + Level::Note.title("a second note"), + )); + + let expected = str![[r#" +error[E0271]: type mismatch resolving `<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ...>>, ...>>, ...> as Future>::Error == Foo` + ╭▸ $DIR/E0271.rs:20:5 + │ +LL │ ┏ Box::new( +LL │ ┃ Ok::<_, ()>( +LL │ ┃ Err::<(), _>( +LL │ ┃ Ok::<_, ()>( + ‡ ┃ +LL │ ┃ ) + │ ┗━━━━━┛ type mismatch resolving `<Result<Result<(), Result<Result<(), ...>, ...>>, ...> as Future>::Error == Foo` + ╰╴ +note: expected this to be `Foo` + ╭▸ $DIR/E0271.rs:10:18 + │ +LL │ type Error = E; + │ ━ + ├ note: required for the cast from `Box<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ()>>, ()>>, ()>>` to `Box<(dyn Future<Error = Foo> + 'static)>` + ╰ note: a second note +"#]]; + let renderer = Renderer::plain() + .term_width(40) + .theme(OutputTheme::Unicode) + .anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input_new), expected); +} + +#[test] +fn long_e0308() { + let source = r#" +mod a { + // Force the "short path for unique types" machinery to trip up + pub struct Atype; + pub struct Btype; + pub struct Ctype; +} + +mod b { + pub struct Atype<T, K>(T, K); + pub struct Btype<T, K>(T, K); + pub struct Ctype<T, K>(T, K); +} + +use b::*; + +fn main() { + let x: Atype< + Btype< + Ctype< + Atype< + Btype< + Ctype< + Atype< + Btype< + Ctype<i32, i32>, + i32 + >, + i32 + >, + i32 + >, + i32 + >, + i32 + >, + i32 + >, + i32 + >, + i32 + > = Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok( + Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok( + Ok("") + )))))))))))))))))))))))))))))) + )))))))))))))))))))))))))))))); + //~^^^^^ ERROR E0308 + + let _ = Some(Ok(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some( + Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some( + Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some( + Some(Some(Some(Some(Some(Some(Some(Some(Some(""))))))))) + ))))))))))))))))) + )))))))))))))))))) + ))))))))))))))))) == Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok( + Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok( + Ok(Ok(Ok(Ok(Ok(Ok(Ok(""))))))) + )))))))))))))))))))))))))))))) + )))))))))))))))))))))))); + //~^^^^^ ERROR E0308 + + let x: Atype< + Btype< + Ctype< + Atype< + Btype< + Ctype< + Atype< + Btype< + Ctype<i32, i32>, + i32 + >, + i32 + >, + i32 + >, + i32 + >, + i32 + >, + i32 + >, + i32 + >, + i32 + > = (); + //~^ ERROR E0308 + + let _: () = Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok( + Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok( + Ok(Ok(Ok(Ok(Ok(Ok(Ok(""))))))) + )))))))))))))))))))))))))))))) + )))))))))))))))))))))))); + //~^^^^^ ERROR E0308 +} +"#; + + let input_new = Level::Error + .message("mismatched types") + .id("E0308") + .group(Group::new().element( + Snippet::source(source) + .line_start(7) + .origin("$DIR/long-E0308.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(719..1001) + .label("expected `Atype<Btype<Ctype<..., i32>, i32>, i32>`, found `Result<Result<Result<..., _>, _>, _>`"), + ) + .annotation( + AnnotationKind::Context + .span(293..716) + .label("expected due to this"), + ) + ).element( + Level::Note + .title("expected struct `Atype<Btype<..., i32>, i32>`\n found enum `Result<Result<..., _>, _>`") + ).element( + Level::Note + .title("the full name for the type has been written to '$TEST_BUILD_DIR/$FILE.long-type-hash.txt'") + ).element( + Level::Note + .title("consider using `--verbose` to print the full type name to the console") + , + )); + + let expected = str![[r#" +error[E0308]: mismatched types + ╭▸ $DIR/long-E0308.rs:48:9 + │ +LL │ let x: Atype< + │ ┌─────────────┘ +LL │ │ Btype< +LL │ │ Ctype< +LL │ │ Atype< + ‡ │ +LL │ │ i32 +LL │ │ > = Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(O… + │ │┏━━━━━│━━━┛ + │ └┃─────┤ + │ ┃ expected due to this +LL │ ┃ Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(O… +LL │ ┃ Ok("") +LL │ ┃ )))))))))))))))))))))))))))))) +LL │ ┃ )))))))))))))))))))))))))))))); + │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ expected `Atype<Btype<Ctype<..., i32>, i32>, i32>`, found `Result<Result<Result<..., _>, _>, _>` + ├ note: expected struct `Atype<Btype<..., i32>, i32>` + │ found enum `Result<Result<..., _>, _>` + ├ note: the full name for the type has been written to '$TEST_BUILD_DIR/$FILE.long-type-hash.txt' + ╰ note: consider using `--verbose` to print the full type name to the console +"#]]; + let renderer = Renderer::plain() + .term_width(60) + .theme(OutputTheme::Unicode) + .anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input_new), expected); +} + +#[test] +fn highlighting() { + let source = r#" +use core::pin::Pin; +use core::future::Future; +use core::any::Any; + +fn query(_: fn(Box<(dyn Any + Send + '_)>) -> Pin<Box<( + dyn Future<Output = Result<Box<(dyn Any + 'static)>, String>> + Send + 'static +)>>) {} + +fn wrapped_fn<'a>(_: Box<(dyn Any + Send)>) -> Pin<Box<( + dyn Future<Output = Result<Box<(dyn Any + 'static)>, String>> + Send + 'static +)>> { + Box::pin(async { Err("nope".into()) }) +} + +fn main() { + query(wrapped_fn); +} +"#; + + let input_new = Level::Error + .message("mismatched types") + .id("E0308") + .group(Group::new().element( + Snippet::source(source) + .line_start(7) + .origin("$DIR/unicode-output.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(430..440) + .label("one type is more general than the other"), + ) + .annotation( + AnnotationKind::Context + .span(424..429) + .label("arguments to this function are incorrect"), + ), + ).element( + Level::Note + .title("expected fn pointer `for<'a> fn(Box<(dyn Any + Send + 'a)>) -> Pin<_>`\n found fn item `fn(Box<(dyn Any + Send + 'static)>) -> Pin<_> {wrapped_fn}`") + , + )) + .group(Group::new().element( + Level::Note.title("function defined here"), + ).element( + Snippet::source(source) + .line_start(7) + .origin("$DIR/unicode-output.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(77..210)) + .annotation(AnnotationKind::Context.span(71..76)), + )); + + let expected = str![[r#" +error[E0308]: mismatched types + ╭▸ $DIR/unicode-output.rs:23:11 + │ +LL │ query(wrapped_fn); + │ ┬──── ━━━━━━━━━━ one type is more general than the other + │ │ + │ arguments to this function are incorrect + │ + ╰ note: expected fn pointer `for<'a> fn(Box<(dyn Any + Send + 'a)>) -> Pin<_>` + found fn item `fn(Box<(dyn Any + Send + 'static)>) -> Pin<_> {wrapped_fn}` +note: function defined here + ╭▸ $DIR/unicode-output.rs:12:10 + │ +LL │ fn query(_: fn(Box<(dyn Any + Send + '_)>) -> Pin<Box<( + │ ┏━━━━─────━┛ +LL │ ┃ dyn Future<Output = Result<Box<(dyn Any + 'static)>, String>> + Send + 'static +LL │ ┃ )>>) {} + ╰╴┗━━━┛ +"#]]; + let renderer = Renderer::plain() + .theme(OutputTheme::Unicode) + .anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input_new), expected); +} From 57340c8c00e0b238df11f27fe4f116c561f9be8f Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Sun, 9 Mar 2025 05:03:55 -0600 Subject: [PATCH 321/455] test: Add a test for source highlighting --- examples/highlight_source.rs | 34 +++++++++++++++++++++++++++++++ examples/highlight_source.svg | 38 +++++++++++++++++++++++++++++++++++ tests/examples.rs | 7 +++++++ 3 files changed, 79 insertions(+) create mode 100644 examples/highlight_source.rs create mode 100644 examples/highlight_source.svg diff --git a/examples/highlight_source.rs b/examples/highlight_source.rs new file mode 100644 index 00000000..d49ff765 --- /dev/null +++ b/examples/highlight_source.rs @@ -0,0 +1,34 @@ +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; + +fn main() { + let source = r#"//@ compile-flags: -Z teach + +#![allow(warnings)] + +const CON: Vec<i32> = vec![1, 2, 3]; //~ ERROR E0010 +//~| ERROR cannot call non-const method +fn main() {} +"#; + let message = Level::Error + .message("allocations are not allowed in constants") + .id("E0010") + .group( + Group::new() + .element( + Snippet::source(source) + .fold(true) + .origin("$DIR/E0010-teach.rs") + .annotation( + AnnotationKind::Primary + .span(72..85) + .label("allocation not allowed in constants") + ), + ) + .element( + Level::Note.title("The runtime heap is not yet available at compile-time, so no runtime heap allocations can be created."), + ), + ); + + let renderer = Renderer::styled().anonymized_line_numbers(true); + anstream::println!("{}", renderer.render(message)); +} diff --git a/examples/highlight_source.svg b/examples/highlight_source.svg new file mode 100644 index 00000000..b6f9e822 --- /dev/null +++ b/examples/highlight_source.svg @@ -0,0 +1,38 @@ +<svg width="961px" height="146px" xmlns="http://www.w3.org/2000/svg"> + <style> + .fg { fill: #AAAAAA } + .bg { background: #000000 } + .fg-bright-blue { fill: #5555FF } + .fg-bright-red { fill: #FF5555 } + .container { + padding: 0 10px; + line-height: 18px; + } + .bold { font-weight: bold; } + tspan { + font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + white-space: pre; + line-height: 18px; + } + </style> + + <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> + + <text xml:space="preserve" class="container fg"> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0010]</tspan><tspan class="bold">: allocations are not allowed in constants</tspan> +</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>$DIR/E0010-teach.rs:5:23</tspan> +</tspan> + <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> +</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">LL</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> const CON: Vec<i32> = vec![1, 2, 3]; //~ ERROR E0010</tspan> +</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">allocation not allowed in constants</tspan> +</tspan> + <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">= </tspan><tspan class="bold">note</tspan><tspan>: The runtime heap is not yet available at compile-time, so no runtime heap allocations can be created.</tspan> +</tspan> + <tspan x="10px" y="136px"> +</tspan> + </text> + +</svg> diff --git a/tests/examples.rs b/tests/examples.rs index b6576629..77087320 100644 --- a/tests/examples.rs +++ b/tests/examples.rs @@ -19,6 +19,13 @@ fn format() { assert_example(target, expected); } +#[test] +fn highlight_source() { + let target = "highlight_source"; + let expected = snapbox::file!["../examples/highlight_source.svg": TermSvg]; + assert_example(target, expected); +} + #[test] fn multislice() { let target = "multislice"; From 222c013d9cb828d1171b26811e4530dc8cb6e068 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Sun, 9 Mar 2025 05:03:55 -0600 Subject: [PATCH 322/455] feat: Add ability to highlight source code --- examples/highlight_source.rs | 1 + examples/highlight_source.svg | 2 +- src/renderer/mod.rs | 12 ++++++++++++ src/renderer/source_map.rs | 14 +++++++++++++- src/snippet.rs | 7 +++++++ 5 files changed, 34 insertions(+), 2 deletions(-) diff --git a/examples/highlight_source.rs b/examples/highlight_source.rs index d49ff765..2bb4ec24 100644 --- a/examples/highlight_source.rs +++ b/examples/highlight_source.rs @@ -22,6 +22,7 @@ fn main() {} AnnotationKind::Primary .span(72..85) .label("allocation not allowed in constants") + .highlight_source(true), ), ) .element( diff --git a/examples/highlight_source.svg b/examples/highlight_source.svg index b6f9e822..391a097d 100644 --- a/examples/highlight_source.svg +++ b/examples/highlight_source.svg @@ -25,7 +25,7 @@ </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">LL</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> const CON: Vec<i32> = vec![1, 2, 3]; //~ ERROR E0010</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">LL</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> const CON: Vec<i32> = </tspan><tspan class="fg-bright-red bold">vec![1, 2, 3]</tspan><tspan>; //~ ERROR E0010</tspan> </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">allocation not allowed in constants</tspan> </tspan> diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 85de0f5c..10f2b59a 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -1260,6 +1260,15 @@ impl Renderer { underline.style, ); } + _ if annotation.highlight_source => { + buffer.set_style_range( + line_offset, + (code_offset + annotation.start.display).saturating_sub(left), + (code_offset + annotation.end.display).saturating_sub(left), + underline.style, + annotation.is_primary(), + ); + } _ => {} } } @@ -2471,6 +2480,9 @@ pub(crate) struct LineAnnotation<'a> { /// Is this a single line, multiline or multiline span minimized down to a /// smaller span. pub annotation_type: LineAnnotationType, + + /// Whether the source code should be highlighted + pub highlight_source: bool, } impl LineAnnotation<'_> { diff --git a/src/renderer/source_map.rs b/src/renderer/source_map.rs index 416337c3..33fe1897 100644 --- a/src/renderer/source_map.rs +++ b/src/renderer/source_map.rs @@ -149,7 +149,13 @@ impl<'a> SourceMap<'a> { .collect::<Vec<_>>(); let mut multiline_annotations = vec![]; - for Annotation { range, label, kind } in annotations { + for Annotation { + range, + label, + kind, + highlight_source, + } in annotations + { let (lo, mut hi) = self.span_to_locations(range.clone()); // Watch out for "empty spans". If we get a span like 6..6, we @@ -169,6 +175,7 @@ impl<'a> SourceMap<'a> { kind, label, annotation_type: LineAnnotationType::Singleline, + highlight_source, }; self.add_annotation_to_file(&mut annotated_line_infos, lo.line, line_ann); } else { @@ -179,6 +186,7 @@ impl<'a> SourceMap<'a> { kind, label, overlaps_exactly: false, + highlight_source, }); } } @@ -502,6 +510,7 @@ pub(crate) struct MultilineAnnotation<'a> { pub kind: AnnotationKind, pub label: Option<&'a str>, pub overlaps_exactly: bool, + pub highlight_source: bool, } impl<'a> MultilineAnnotation<'a> { @@ -526,6 +535,7 @@ impl<'a> MultilineAnnotation<'a> { kind: self.kind, label: None, annotation_type: LineAnnotationType::MultilineStart(self.depth), + highlight_source: self.highlight_source, } } @@ -541,6 +551,7 @@ impl<'a> MultilineAnnotation<'a> { kind: self.kind, label: self.label, annotation_type: LineAnnotationType::MultilineEnd(self.depth), + highlight_source: self.highlight_source, } } @@ -551,6 +562,7 @@ impl<'a> MultilineAnnotation<'a> { kind: self.kind, label: None, annotation_type: LineAnnotationType::MultilineLine(self.depth), + highlight_source: self.highlight_source, } } } diff --git a/src/snippet.rs b/src/snippet.rs index d371ab85..64002842 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -273,6 +273,7 @@ pub struct Annotation<'a> { pub(crate) range: Range<usize>, pub(crate) label: Option<&'a str>, pub(crate) kind: AnnotationKind, + pub(crate) highlight_source: bool, } impl<'a> Annotation<'a> { @@ -280,6 +281,11 @@ impl<'a> Annotation<'a> { self.label = Some(label); self } + + pub fn highlight_source(mut self, highlight_source: bool) -> Self { + self.highlight_source = highlight_source; + self + } } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] @@ -296,6 +302,7 @@ impl AnnotationKind { range: span, label: None, kind: self, + highlight_source: false, } } From 481920d56fd1b573dfac210ab32bd255b8f2ad84 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 15 Apr 2025 22:11:46 -0600 Subject: [PATCH 323/455] test: Add a test for passing precolored text --- examples/highlight_title.rs | 68 ++++++++++++++++++++++++++++++++++++ examples/highlight_title.svg | 44 +++++++++++++++++++++++ tests/examples.rs | 7 ++++ 3 files changed, 119 insertions(+) create mode 100644 examples/highlight_title.rs create mode 100644 examples/highlight_title.svg diff --git a/examples/highlight_title.rs b/examples/highlight_title.rs new file mode 100644 index 00000000..bb09049c --- /dev/null +++ b/examples/highlight_title.rs @@ -0,0 +1,68 @@ +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; +use anstyle::Effects; + +fn main() { + let source = r#"// Make sure "highlighted" code is colored purple + +//@ compile-flags: --error-format=human --color=always +//@ error-pattern:[35mfor<'a> [0m +//@ edition:2018 + +use core::pin::Pin; +use core::future::Future; +use core::any::Any; + +fn query(_: fn(Box<(dyn Any + Send + '_)>) -> Pin<Box<( + dyn Future<Output = Result<Box<(dyn Any + 'static)>, String>> + Send + 'static +)>>) {} + +fn wrapped_fn<'a>(_: Box<(dyn Any + Send)>) -> Pin<Box<( + dyn Future<Output = Result<Box<(dyn Any + 'static)>, String>> + Send + 'static +)>> { + Box::pin(async { Err("nope".into()) }) +} + +fn main() { + query(wrapped_fn); +} +"#; + + let magenta = annotate_snippets::renderer::AnsiColor::Magenta + .on_default() + .effects(Effects::BOLD); + let title = format!( + "expected fn pointer `{}for<'a>{} fn(Box<{}(dyn Any + Send + 'a){}>) -> Pin<_>` + found fn item `fn(Box<{}(dyn Any + Send + 'static){}>) -> Pin<_> {}{{wrapped_fn}}{}`", + magenta.render(), + magenta.render_reset(), + magenta.render(), + magenta.render_reset(), + magenta.render(), + magenta.render_reset(), + magenta.render(), + magenta.render_reset() + ); + + let message = Level::Error.message("mismatched types").id("E0308").group( + Group::new() + .element( + Snippet::source(source) + .fold(true) + .origin("$DIR/highlighting.rs") + .annotation( + AnnotationKind::Primary + .span(589..599) + .label("one type is more general than the other"), + ) + .annotation( + AnnotationKind::Context + .span(583..588) + .label("arguments to this function are incorrect"), + ), + ) + .element(Level::Note.title(&title)), + ); + + let renderer = Renderer::styled().anonymized_line_numbers(true); + anstream::println!("{}", renderer.render(message)); +} diff --git a/examples/highlight_title.svg b/examples/highlight_title.svg new file mode 100644 index 00000000..763d33b8 --- /dev/null +++ b/examples/highlight_title.svg @@ -0,0 +1,44 @@ +<svg width="1003px" height="200px" xmlns="http://www.w3.org/2000/svg"> + <style> + .fg { fill: #AAAAAA } + .bg { background: #000000 } + .fg-bright-blue { fill: #5555FF } + .fg-bright-red { fill: #FF5555 } + .container { + padding: 0 10px; + line-height: 18px; + } + .bold { font-weight: bold; } + tspan { + font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + white-space: pre; + line-height: 18px; + } + </style> + + <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> + + <text xml:space="preserve" class="container fg"> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan> +</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>$DIR/highlighting.rs:22:11</tspan> +</tspan> + <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> +</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">LL</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> query(wrapped_fn);</tspan> +</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">-----</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">one type is more general than the other</tspan> +</tspan> + <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> +</tspan> + <tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">arguments to this function are incorrect</tspan> +</tspan> + <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">= </tspan><tspan class="bold">note</tspan><tspan>: expected fn pointer `␛[1m␛[35mfor<'a>␛[0m fn(Box<␛[1m␛[35m(dyn Any + Send + 'a)␛[0m>) -> Pin<_>`</tspan> +</tspan> + <tspan x="10px" y="172px"><tspan> found fn item `fn(Box<␛[1m␛[35m(dyn Any + Send + 'static)␛[0m>) -> Pin<_> ␛[1m␛[35m{wrapped_fn}␛[0m`</tspan> +</tspan> + <tspan x="10px" y="190px"> +</tspan> + </text> + +</svg> diff --git a/tests/examples.rs b/tests/examples.rs index 77087320..a0504c11 100644 --- a/tests/examples.rs +++ b/tests/examples.rs @@ -26,6 +26,13 @@ fn highlight_source() { assert_example(target, expected); } +#[test] +fn highlight_title() { + let target = "highlight_title"; + let expected = snapbox::file!["../examples/highlight_title.svg": TermSvg]; + assert_example(target, expected); +} + #[test] fn multislice() { let target = "multislice"; From 19f692d1f9b6c3e089556af2cd29811dfe9795cb Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 16 Apr 2025 09:56:43 -0600 Subject: [PATCH 324/455] feat: Don't normailze title text --- examples/highlight_title.svg | 7 ++++--- src/lib.rs | 7 +++++++ src/renderer/mod.rs | 7 +++---- src/snippet.rs | 4 ++++ 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/examples/highlight_title.svg b/examples/highlight_title.svg index 763d33b8..ad358761 100644 --- a/examples/highlight_title.svg +++ b/examples/highlight_title.svg @@ -1,9 +1,10 @@ -<svg width="1003px" height="200px" xmlns="http://www.w3.org/2000/svg"> +<svg width="785px" height="200px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } .fg-bright-blue { fill: #5555FF } .fg-bright-red { fill: #FF5555 } + .fg-magenta { fill: #AA00AA } .container { padding: 0 10px; line-height: 18px; @@ -33,9 +34,9 @@ </tspan> <tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">arguments to this function are incorrect</tspan> </tspan> - <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">= </tspan><tspan class="bold">note</tspan><tspan>: expected fn pointer `␛[1m␛[35mfor<'a>␛[0m fn(Box<␛[1m␛[35m(dyn Any + Send + 'a)␛[0m>) -> Pin<_>`</tspan> + <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">= </tspan><tspan class="bold">note</tspan><tspan>: expected fn pointer `</tspan><tspan class="fg-magenta bold">for<'a></tspan><tspan> fn(Box<</tspan><tspan class="fg-magenta bold">(dyn Any + Send + 'a)</tspan><tspan>>) -> Pin<_>`</tspan> </tspan> - <tspan x="10px" y="172px"><tspan> found fn item `fn(Box<␛[1m␛[35m(dyn Any + Send + 'static)␛[0m>) -> Pin<_> ␛[1m␛[35m{wrapped_fn}␛[0m`</tspan> + <tspan x="10px" y="172px"><tspan> found fn item `fn(Box<</tspan><tspan class="fg-magenta bold">(dyn Any + Send + 'static)</tspan><tspan>>) -> Pin<_> </tspan><tspan class="fg-magenta bold">{wrapped_fn}</tspan><tspan>`</tspan> </tspan> <tspan x="10px" y="190px"> </tspan> diff --git a/src/lib.rs b/src/lib.rs index 533e8f73..e6c49c62 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,6 +45,13 @@ pub mod renderer; mod snippet; +/// Normalize the string to avoid any unicode control characters. +/// This is important for untrusted input, as it can contain +/// invalid unicode sequences. +pub fn normalize_untrusted_str(s: &str) -> String { + renderer::normalize_whitespace(s) +} + #[doc(inline)] pub use renderer::Renderer; pub use snippet::ColumnSeparator; diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 10f2b59a..ab3db82e 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -573,8 +573,7 @@ impl Renderer { } else { ElementStyle::NoStyle }; - let text = &normalize_whitespace(title); - let lines = text.split('\n').collect::<Vec<_>>(); + let lines = title.split('\n').collect::<Vec<_>>(); if lines.len() > 1 { for (i, line) in lines.iter().enumerate() { if i != 0 { @@ -584,7 +583,7 @@ impl Renderer { buffer.append(line_number, line, style); } } else { - buffer.append(line_number, text, style); + buffer.append(line_number, title, style); } line_number } @@ -2590,7 +2589,7 @@ const OUTPUT_REPLACEMENTS: &[(char, &str)] = &[ ('\u{2069}', "�"), ]; -fn normalize_whitespace(s: &str) -> String { +pub(crate) fn normalize_whitespace(s: &str) -> String { // Scan the input string for a character in the ordered table above. // If it's present, replace it with its alternative string (it can be more than 1 char!). // Otherwise, retain the input char. diff --git a/src/snippet.rs b/src/snippet.rs index 64002842..f71c392a 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -177,6 +177,10 @@ impl Level { } } + /// Text passed to this function is allowed to be pre-styled, as such all + /// text is considered "trusted input" and has no normalizations applied to + /// it. [`normalize_untrusted_str`](crate::normalize_untrusted_str) can be + /// used to normalize untrusted text before it is passed to this function. pub fn title(self, title: &str) -> Title<'_> { Title { level: self, From b4373f3effd8a0899c496393f87eb6802d546f23 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 16 Apr 2025 10:02:22 -0600 Subject: [PATCH 325/455] docs: Add when text is normalized --- src/snippet.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/snippet.rs b/src/snippet.rs index f71c392a..e8fa18c4 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -166,6 +166,9 @@ pub enum Level { } impl Level { + /// Text passed to this function is considered "untrusted input", as such + /// all text is passed through a normalization function. Pre-styled text is + /// not allowed to be passed to this function. pub fn message(self, title: &str) -> Message<'_> { Message { id: None, @@ -222,6 +225,9 @@ pub struct Snippet<'a, T> { } impl<'a, T: Clone> Snippet<'a, T> { + /// Text passed to this function is considered "untrusted input", as such + /// all text is passed through a normalization function. Pre-styled text is + /// not allowed to be passed to this function. pub fn source(source: &'a str) -> Self { Self { origin: None, @@ -237,6 +243,9 @@ impl<'a, T: Clone> Snippet<'a, T> { self } + /// Text passed to this function is considered "untrusted input", as such + /// all text is passed through a normalization function. Pre-styled text is + /// not allowed to be passed to this function. pub fn origin(mut self, origin: &'a str) -> Self { self.origin = Some(origin); self @@ -281,6 +290,9 @@ pub struct Annotation<'a> { } impl<'a> Annotation<'a> { + /// Text passed to this function is considered "untrusted input", as such + /// all text is passed through a normalization function. Pre-styled text is + /// not allowed to be passed to this function. pub fn label(mut self, label: &'a str) -> Self { self.label = Some(label); self @@ -322,6 +334,9 @@ pub struct Patch<'a> { } impl<'a> Patch<'a> { + /// Text passed to this function is considered "untrusted input", as such + /// all text is passed through a normalization function. Pre-styled text is + /// not allowed to be passed to this function. pub fn new(range: Range<usize>, replacement: &'a str) -> Self { Self { range, replacement } } @@ -384,6 +399,9 @@ pub struct Origin<'a> { } impl<'a> Origin<'a> { + /// Text passed to this function is considered "untrusted input", as such + /// all text is passed through a normalization function. Pre-styled text is + /// not allowed to be passed to this function. pub fn new(origin: &'a str) -> Self { Self { origin, @@ -409,6 +427,9 @@ impl<'a> Origin<'a> { self } + /// Text passed to this function is considered "untrusted input", as such + /// all text is passed through a normalization function. Pre-styled text is + /// not allowed to be passed to this function. pub fn label(mut self, label: &'a str) -> Self { self.label = Some(label); self From 15fbf20cbc84c822378fa5b5cf8d33dd9aeb9e59 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Sat, 29 Mar 2025 22:31:44 -0600 Subject: [PATCH 326/455] test: Add tests margin cutting --- tests/formatter.rs | 273 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 273 insertions(+) diff --git a/tests/formatter.rs b/tests/formatter.rs index 6304cf41..df6f965b 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -2112,3 +2112,276 @@ LL │ ┃ )>>) {} .anonymized_line_numbers(true); assert_data_eq!(renderer.render(input_new), expected); } + +// This tests that an ellipsis is not inserted into Unicode text when a line +// wasn't actually trimmed. +// +// This is a regression test where `...` was inserted because the code wasn't +// properly accounting for the *rendered* length versus the length in bytes in +// all cases. +#[test] +fn unicode_cut_handling() { + let source = "version = \"0.1.0\"\n# Ensure that the spans from toml handle utf-8 correctly\nauthors = [\n { name = \"Z\u{351}\u{36b}\u{343}\u{36a}\u{302}\u{36b}\u{33d}\u{34f}\u{334}\u{319}\u{324}\u{31e}\u{349}\u{35a}\u{32f}\u{31e}\u{320}\u{34d}A\u{36b}\u{357}\u{334}\u{362}\u{335}\u{31c}\u{330}\u{354}L\u{368}\u{367}\u{369}\u{358}\u{320}G\u{311}\u{357}\u{30e}\u{305}\u{35b}\u{341}\u{334}\u{33b}\u{348}\u{34d}\u{354}\u{339}O\u{342}\u{30c}\u{30c}\u{358}\u{328}\u{335}\u{339}\u{33b}\u{31d}\u{333}\", email = 1 }\n]\n"; + let input = Level::Error.message("title").group( + Group::new().element( + Snippet::source(source) + .fold(false) + .annotation(AnnotationKind::Primary.span(85..228).label("annotation")), + ), + ); + let expected = str![[r#" +error: title + | +1 | version = "0.1.0" +2 | # Ensure that the spans from toml handle utf-8 correctly +3 | authors = [ + | ___________^ +4 | | { name = "Z͑ͫ̓ͪ̂ͫ̽͏̴̙̤̞͉͚̯...A̴̵̜̰͔ͫ͗͢L̠ͨͧͩ͘G̴̻͈͍͔̹̑͗̎̅͛́Ǫ̵̹̻̝̳͂̌̌͘", email = 1 } +5 | | ] + | |_^ annotation +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input), expected); +} + +#[test] +fn unicode_cut_handling2() { + let source = "/*这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?"; + let input = Level::Error + .message("expected item, found `?`") + .group( + Group::new().element( + Snippet::source(source) + .fold(false) + .annotation(AnnotationKind::Primary.span(499..500).label("expected item")) + ).element( + Level::Note.title("for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>") + ) + ); + + let expected = str![[r#" +error: expected item, found `?` + | +1.|.... + |^ expected item + = note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html> +"#]]; + + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input), expected); +} + +#[test] +fn unicode_cut_handling3() { + let source = "/*这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?"; + let input = Level::Error + .message("expected item, found `?`") + .group( + Group::new().element( + Snippet::source(source) + .fold(false) + .annotation(AnnotationKind::Primary.span(251..254).label("expected item")) + ).element( + Level::Note.title("for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>") + ) + ); + + let expected = str![[r#" +error: expected item, found `?` + | +1 | ...的。这是宽的。*/? ... +^ | expected item + = note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html> +"#]]; + + let renderer = Renderer::plain().term_width(43); + assert_data_eq!(renderer.render(input), expected); +} + +#[test] +fn unicode_cut_handling4() { + let source = "/*aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/?"; + let input = Level::Error + .message("expected item, found `?`") + .group( + Group::new().element( + Snippet::source(source) + .fold(false) + .annotation(AnnotationKind::Primary.span(334..335).label("expected item")) + ).element( + Level::Note.title("for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>") + ) + ); + + let expected = str![[r#" +error: expected item, found `?` + | +1 | ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/? + | ^ expected item + = note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html> +"#]]; + + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input), expected); +} + +#[test] +fn diagnostic_width() { + let source = r##"// ignore-tidy-linelength + +fn main() { + let _: &str = "🦀☀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓ ☖☗☘☙☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷☸☹☺☻☼☽☾☿♀♁♂♃♄♅♆♇♏♔♕♖♗♘♙♚♛♜♝♞♟♠♡♢♣♤♥♦♧♨♩♪♫♬♭♮♯♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚈⚉4🦀☀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓☖☗☘☙☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷☸☹☺☻☼☽☾☿♀♁♂♃♄♅♆♇♏♔♕♖♗♘♙♚♛♜♝♞♟♠♡♢♣♤♥♦♧♨♩♪♫♬♭♮♯♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚈⚉4🦀🦀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓☖☗☘☙☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷☸☹☺☻☼☽☾☿♀♁♂♃♄♅♆♇♏♔♕♖♗♘♙♚♛♜♝♞♟♠♡♢♣♤♥♦♧♨♩♪♫♬♭♮♯♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚈⚉4"; let _: () = 42; let _: &str = "🦀☀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓ ☖☗☘☙☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷☸☹☺☻☼☽☾☿♀♁♂♃♄♅♆♇♏♔♕♖♗♘♙♚♛♜♝♞♟♠♡♢♣♤♥♦♧♨♩♪♫♬♭♮♯♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚈⚉4🦀☀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓☖☗☘☙☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷☸☹☺☻☼☽☾☿♀♁♂♃♄♅♆♇♏♔♕♖♗♘♙♚♛♜♝♞♟♠♡♢♣♤♥♦♧♨♩♪♫♬♭♮♯♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚈⚉4🦀🦀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓☖☗☘☙☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷☸☹☺☻☼☽☾☿♀♁♂♃♄♅♆♇♏♔♕♖♗♘♙♚♛♜♝♞♟♠♡♢♣♤♥♦♧♨♩♪♫♬♭♮♯♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚈⚉4"; +//~^ ERROR mismatched types +} +"##; + let input = Level::Error.message("mismatched types").id("E0308").group( + Group::new().element( + Snippet::source(source) + .origin("$DIR/non-whitespace-trimming-unicode.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(1207..1209) + .label("expected `()`, found integer"), + ) + .annotation( + AnnotationKind::Context + .span(1202..1204) + .label("expected due to this"), + ), + ), + ); + + let expected = str![[r#" +error[E0308]: mismatched types + --> $DIR/non-whitespace-trimming-unicode.rs:4:415 + | +LL | ...♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚈⚉4"; let _: () = 42; let _: &str = "🦀☀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓ ☖☗☘☙☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷☸☹☺☻☼☽☾☿♀♁♂... + | -- ^^ expected `()`, found integer + | | + | expected due to this +"#]]; + + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected); +} + +#[test] +fn diagnostic_width2() { + let source = r##"//@ revisions: ascii unicode +//@[unicode] compile-flags: -Zunstable-options --error-format=human-unicode +// ignore-tidy-linelength + +fn main() { + let unicode_is_fun = "‱ஹ௸௵꧄.ဪ꧅⸻𒈙𒐫﷽𒌄𒈟𒍼𒁎𒀱𒌧𒅃 𒈓𒍙𒊎𒄡𒅌𒁏𒀰𒐪𒐩𒈙𒐫𪚥"; + let _ = "ༀ༁༂༃༄༅༆༇༈༉༊་༌།༎༏༐༑༒༓༔༕༖༗༘༙༚༛༜༝༞༟༠༡༢༣༤༥༦༧༨༩༪༫༬༭༮༯༰༱༲༳༴༵༶༷༸༹༺༻༼༽༾༿ཀཁགགྷངཅཆཇཉཊཋཌཌྷཎཏཐདདྷནཔཕབབྷམཙཚཛཛྷཝཞཟའཡརལཤཥསཧཨཀྵཪཫཬཱཱཱིིུུྲྀཷླྀཹེཻོཽཾཿ྄ཱྀྀྂྃ྅྆྇ྈྉྊྋྌྍྎྏྐྑྒྒྷྔྕྖྗྙྚྛྜྜྷྞྟྠྡྡྷྣྤྥྦྦྷྨྩྪྫྫྷྭྮྯྰྱྲླྴྵྶྷྸྐྵྺྻྼ྾྿࿀࿁࿂࿃࿄࿅࿆࿇࿈࿉࿊࿋࿌࿎࿏࿐࿑࿒࿓࿔࿕࿖࿗࿘࿙࿚"; let _a = unicode_is_fun + " really fun!"; + //[ascii]~^ ERROR cannot add `&str` to `&str` +} +"##; + let input = Level::Error + .message("cannot add `&str` to `&str`") + .id("E0369") + .group( + Group::new() + .element( + Snippet::source(source) + .origin("$DIR/non-1-width-unicode-multiline-label.rs") + .fold(true) + .annotation(AnnotationKind::Context.span(970..984).label("&str")) + .annotation(AnnotationKind::Context.span(987..1001).label("&str")) + .annotation( + AnnotationKind::Primary + .span(985..986) + .label("`+` cannot be used to concatenate two `&str` strings"), + ), + ) + .element( + Level::Note + .title("string concatenation requires an owned `String` on the left"), + ), + ) + .group( + Group::new() + .element(Level::Help.title("create an owned `String` from a string reference")) + .element( + Snippet::source(source) + .origin("$DIR/non-1-width-unicode-multiline-label.rs") + .fold(true) + .patch(Patch::new(984..984, ".to_owned()")), + ), + ); + + let expected = str![[r#" +error[E0369]: cannot add `&str` to `&str` + ╭▸ $DIR/non-1-width-unicode-multiline-label.rs:7:260 + │ +LL │ …ཽཾཿ྄ཱྀྀྂྃ྅྆྇ྈྉྊྋྌྍྎྏྐྑྒྒྷྔྕྖྗྙྚྛྜྜྷྞྟྠྡྡྷྣྤྥྦྦྷྨྩྪྫྫྷྭྮྯྰྱྲླྴྵྶྷྸྐྵྺྻྼ྾྿࿀࿁࿂࿃࿄࿅࿆࿇࿈࿉࿊࿋…࿎࿏࿐࿑࿒࿓࿔࿕࿖࿗࿘࿙࿚"; let _a = unicode_is_fun + " really fun!"; + │ ┬───────────── ┯ ────────────── &str + │ │ │ + │ │ `+` cannot be used to concatenate two `&str` strings + │ &str + │ + ╰ note: string concatenation requires an owned `String` on the left +help: create an owned `String` from a string reference + ╭╴ +LL │ let _ = "ༀ༁༂༃༄༅༆༇༈༉༊་༌།༎༏༐༑༒༓༔༕༖༗༘༙༚༛༜༝༞༟༠༡༢༣༤༥༦༧༨༩༪༫༬༭༮༯༰༱༲༳༴༵༶༷༸༹༺༻༼༽༾༿ཀཁགགྷངཅཆཇཉཊཋཌཌྷཎཏཐདདྷནཔཕབབྷམཙཚཛཛྷཝཞཟའཡརལཤཥསཧཨཀྵཪཫཬཱཱཱིིུུྲྀཷླྀཹེཻོཽཾཿ྄ཱྀྀྂྃ྅྆྇ྈྉྊྋྌྍྎྏྐྑྒྒྷྔྕྖྗྙྚྛྜྜྷྞྟྠྡྡྷྣྤྥྦྦྷྨྩྪྫྫྷྭྮྯྰྱྲླྴྵྶྷྸྐྵྺྻྼ྾྿࿀࿁࿂࿃࿄࿅࿆࿇࿈࿉࿊࿋࿌࿎࿏࿐࿑࿒࿓࿔࿕࿖࿗࿘࿙࿚"; let _a = unicode_is_fun.to_owned() + " really fun!"; + ╰╴ +++++++++++ +"#]]; + + let renderer = Renderer::plain() + .anonymized_line_numbers(true) + .theme(OutputTheme::Unicode); + assert_data_eq!(renderer.render(input), expected); +} + +#[test] +fn macros_not_utf8() { + let source = r##"//@ error-pattern: did not contain valid UTF-8 +//@ reference: input.encoding.utf8 +//@ reference: input.encoding.invalid + +fn foo() { + include!("not-utf8.bin"); +} +"##; + let bin_source = "�|�\u{0002}!5�cc\u{0015}\u{0002}�Ӻi��WWj�ȥ�'�}�\u{0012}�J�ȉ��W�\u{001e}O�@����\u{001c}w�V���LO����\u{0014}[ \u{0003}_�'���SQ�~ذ��ų&��-\t��lN~��!@␌ _#���kQ��h�\u{001d}�:�\u{001c}\u{0007}�"; + let input = Level::Error + .message("couldn't read `$DIR/not-utf8.bin`: stream did not contain valid UTF-8") + .group( + Group::new().element( + Snippet::source(source) + .origin("$DIR/not-utf8.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(136..160)), + ), + ) + .group( + Group::new() + .element(Level::Note.title("byte `193` is not valid utf-8")) + .element( + Snippet::source(bin_source) + .origin("$DIR/not-utf8.bin") + .fold(true) + .annotation(AnnotationKind::Primary.span(0..0)), + ) + .element(Level::Note.title("this error originates in the macro `include` (in Nightly builds, run with -Z macro-backtrace for more info)")), + ); + + let expected = str![[r#" +error: couldn't read `$DIR/not-utf8.bin`: stream did not contain valid UTF-8 + --> $DIR/not-utf8.rs:6:5 + | +LL | include!("not-utf8.bin"); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: byte `193` is not valid utf-8 + --> $DIR/not-utf8.bin:1:1 + | +LL | �|�␂!5�cc␕␂�Ӻi��WWj�ȥ�'�}�␒�J�ȉ��W�␞O�@����␜w�V���LO����␔[ ␃_�'���SQ�~ذ��ų&��- ��lN~��!@␌ _#���kQ��h�␝�:�... + | ^ + = note: this error originates in the macro `include` (in Nightly builds, run with -Z macro-backtrace for more info) +"#]]; + + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected); +} From 604b6f95a979ef1b2273d3e13d96907638f1f585 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Sat, 29 Mar 2025 22:31:44 -0600 Subject: [PATCH 327/455] fix: Handle margin cutting when encountering multibyte chars --- src/renderer/mod.rs | 72 +++++++++++++++++++++++++++++++++++---------- tests/formatter.rs | 30 +++++++++---------- 2 files changed, 71 insertions(+), 31 deletions(-) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index ab3db82e..7d4fd0ff 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -956,12 +956,19 @@ impl Renderer { let line_offset = buffer.num_lines(); // Left trim - let left = margin.left(source_string.len()); + let left = margin.left(str_width(&source_string)); // FIXME: This looks fishy. See #132860. // Account for unicode characters of width !=0 that were removed. - let left = source_string.chars().take(left).map(char_width).sum(); + let mut taken = 0; + source_string.chars().for_each(|ch| { + let next = char_width(ch); + if taken + next <= left { + taken += next; + } + }); + let left = taken; self.draw_line( buffer, &source_string, @@ -2020,48 +2027,81 @@ impl Renderer { ) { // Tabs are assumed to have been replaced by spaces in calling code. debug_assert!(!source_string.contains('\t')); - let line_len = source_string.len(); + let line_len = str_width(source_string); // Create the source line we will highlight. let left = margin.left(line_len); let right = margin.right(line_len); // FIXME: The following code looks fishy. See #132860. // On long lines, we strip the source line, accounting for unicode. let mut taken = 0; + let mut skipped = 0; let code: String = source_string .chars() - .skip(left) + .skip_while(|ch| { + skipped += char_width(*ch); + skipped <= left + }) .take_while(|ch| { // Make sure that the trimming on the right will fall within the terminal width. - let next = char_width(*ch); - if taken + next > right - left { - return false; - } - taken += next; - true + taken += char_width(*ch); + taken <= (right - left) }) .collect(); buffer.puts(line_offset, code_offset, &code, ElementStyle::Quotation); let placeholder = self.margin(); - if margin.was_cut_left() { + let padding = str_width(placeholder); + let (width_taken, bytes_taken) = if margin.was_cut_left() { // We have stripped some code/whitespace from the beginning, make it clear. + let mut bytes_taken = 0; + let mut width_taken = 0; + for ch in code.chars() { + width_taken += char_width(ch); + bytes_taken += ch.len_utf8(); + + if width_taken >= padding { + break; + } + } buffer.puts( line_offset, code_offset, - placeholder, + &format!("{placeholder:>width_taken$}"), ElementStyle::LineNumber, ); - } + (width_taken, bytes_taken) + } else { + (0, 0) + }; + + buffer.puts( + line_offset, + code_offset + width_taken, + &code[bytes_taken..], + ElementStyle::Quotation, + ); + if margin.was_cut_right(line_len) { - let padding = str_width(placeholder); - // We have stripped some code after the rightmost span end, make it clear we did so. + // We have stripped some code/whitespace from the beginning, make it clear. + let mut char_taken = 0; + let mut width_taken_inner = 0; + for ch in code.chars().rev() { + width_taken_inner += char_width(ch); + char_taken += 1; + + if width_taken_inner >= padding { + break; + } + } + buffer.puts( line_offset, - code_offset + taken - padding, + code_offset + width_taken + code[bytes_taken..].chars().count() - char_taken, placeholder, ElementStyle::LineNumber, ); } + buffer.puts( line_offset, 0, diff --git a/tests/formatter.rs b/tests/formatter.rs index df6f965b..5deb21ab 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -2136,7 +2136,7 @@ error: title 2 | # Ensure that the spans from toml handle utf-8 correctly 3 | authors = [ | ___________^ -4 | | { name = "Z͑ͫ̓ͪ̂ͫ̽͏̴̙̤̞͉͚̯...A̴̵̜̰͔ͫ͗͢L̠ͨͧͩ͘G̴̻͈͍͔̹̑͗̎̅͛́Ǫ̵̹̻̝̳͂̌̌͘", email = 1 } +4 | | { name = "Z͑ͫ̓ͪ̂ͫ̽͏̴̙̤̞͉͚̯̞̠͍A̴̵̜̰͔ͫ͗͢L̠ͨͧͩ͘G̴̻͈͍͔̹̑͗̎̅͛́Ǫ̵̹̻̝̳͂̌̌͘", email = 1 } 5 | | ] | |_^ annotation "#]]; @@ -2162,8 +2162,8 @@ fn unicode_cut_handling2() { let expected = str![[r#" error: expected item, found `?` | -1.|.... - |^ expected item +1 | ...的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/? + | ^ expected item = note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html> "#]]; @@ -2189,8 +2189,8 @@ fn unicode_cut_handling3() { let expected = str![[r#" error: expected item, found `?` | -1 | ...的。这是宽的。*/? ... -^ | expected item +1 | ...。这是宽的。这是宽的。这是宽的... + | ^^ expected item = note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html> "#]]; @@ -2256,10 +2256,10 @@ fn main() { error[E0308]: mismatched types --> $DIR/non-whitespace-trimming-unicode.rs:4:415 | -LL | ...♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚈⚉4"; let _: () = 42; let _: &str = "🦀☀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓ ☖☗☘☙☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷☸☹☺☻☼☽☾☿♀♁♂... - | -- ^^ expected `()`, found integer - | | - | expected due to this +LL | ...♧♨♩♪♫♬♭♮♯♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚈⚉4"; let _: () = 42; let _: &str = "🦀☀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓ ☖☗☘☙☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷... + | -- ^^ expected `()`, found integer + | | + | expected due to this "#]]; let renderer = Renderer::plain().anonymized_line_numbers(true); @@ -2315,11 +2315,11 @@ fn main() { error[E0369]: cannot add `&str` to `&str` ╭▸ $DIR/non-1-width-unicode-multiline-label.rs:7:260 │ -LL │ …ཽཾཿ྄ཱྀྀྂྃ྅྆྇ྈྉྊྋྌྍྎྏྐྑྒྒྷྔྕྖྗྙྚྛྜྜྷྞྟྠྡྡྷྣྤྥྦྦྷྨྩྪྫྫྷྭྮྯྰྱྲླྴྵྶྷྸྐྵྺྻྼ྾྿࿀࿁࿂࿃࿄࿅࿆࿇࿈࿉࿊࿋…࿎࿏࿐࿑࿒࿓࿔࿕࿖࿗࿘࿙࿚"; let _a = unicode_is_fun + " really fun!"; - │ ┬───────────── ┯ ────────────── &str - │ │ │ - │ │ `+` cannot be used to concatenate two `&str` strings - │ &str +LL │ …࿆࿇࿈࿉࿊࿋࿌࿎࿏࿐࿑࿒࿓࿔࿕࿖࿗࿘࿙࿚"; let _a = unicode_is_fun + " really fun!"; + │ ┬───────────── ┯ ────────────── &str + │ │ │ + │ │ `+` cannot be used to concatenate two `&str` strings + │ &str │ ╰ note: string concatenation requires an owned `String` on the left help: create an owned `String` from a string reference @@ -2377,7 +2377,7 @@ LL | include!("not-utf8.bin"); note: byte `193` is not valid utf-8 --> $DIR/not-utf8.bin:1:1 | -LL | �|�␂!5�cc␕␂�Ӻi��WWj�ȥ�'�}�␒�J�ȉ��W�␞O�@����␜w�V���LO����␔[ ␃_�'���SQ�~ذ��ų&��- ��lN~��!@␌ _#���kQ��h�␝�:�... +LL | �|�␂!5�cc␕␂�Ӻi��WWj�ȥ�'�}�␒�J�ȉ��W�␞O�@����␜w�V���LO����␔[ ␃_�'���SQ�~ذ��ų&��- ��lN~��!@␌ _#���kQ��h�␝�:�␜␇� | ^ = note: this error originates in the macro `include` (in Nightly builds, run with -Z macro-backtrace for more info) "#]]; From ab5ca1802efc7d34a861da156133ff0ba57f0878 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 15 Apr 2025 02:17:40 -0600 Subject: [PATCH 328/455] test: Add tests for custom Levels --- examples/custom_error.rs | 35 ++++++++++++++++++++ examples/custom_error.svg | 35 ++++++++++++++++++++ examples/custom_level.rs | 69 +++++++++++++++++++++++++++++++++++++++ examples/custom_level.svg | 61 ++++++++++++++++++++++++++++++++++ tests/examples.rs | 14 ++++++++ 5 files changed, 214 insertions(+) create mode 100644 examples/custom_error.rs create mode 100644 examples/custom_error.svg create mode 100644 examples/custom_level.rs create mode 100644 examples/custom_level.svg diff --git a/examples/custom_error.rs b/examples/custom_error.rs new file mode 100644 index 00000000..3ba234d3 --- /dev/null +++ b/examples/custom_error.rs @@ -0,0 +1,35 @@ +use annotate_snippets::renderer::OutputTheme; +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; + +fn main() { + let source = r#"//@ compile-flags: -Ztreat-err-as-bug +//@ failure-status: 101 +//@ error-pattern: aborting due to `-Z treat-err-as-bug=1` +//@ error-pattern: [eval_static_initializer] evaluating initializer of static `C` +//@ normalize-stderr: "note: .*\n\n" -> "" +//@ normalize-stderr: "thread 'rustc' panicked.*:\n.*\n" -> "" +//@ rustc-env:RUST_BACKTRACE=0 + +#![crate_type = "rlib"] + +pub static C: u32 = 0 - 1; +//~^ ERROR could not evaluate static initializer +"#; + let message = Level::None + .message("error: internal compiler error[E0080]: could not evaluate static initializer") + .group( + Group::new().element( + Snippet::source(source) + .origin("$DIR/err.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(386..391) + .label("attempt to compute `0_u32 - 1_u32`, which would overflow"), + ), + ), + ); + + let renderer = Renderer::styled().theme(OutputTheme::Unicode); + anstream::println!("{}", renderer.render(message)); +} diff --git a/examples/custom_error.svg b/examples/custom_error.svg new file mode 100644 index 00000000..5cb0c720 --- /dev/null +++ b/examples/custom_error.svg @@ -0,0 +1,35 @@ +<svg width="751px" height="128px" xmlns="http://www.w3.org/2000/svg"> + <style> + .fg { fill: #AAAAAA } + .bg { background: #000000 } + .fg-bright-blue { fill: #5555FF } + .container { + padding: 0 10px; + line-height: 18px; + } + .bold { font-weight: bold; } + tspan { + font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + white-space: pre; + line-height: 18px; + } + </style> + + <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> + + <text xml:space="preserve" class="container fg"> + <tspan x="10px" y="28px"><tspan class="bold">error: internal compiler error[E0080]: could not evaluate static initializer</tspan> +</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>$DIR/err.rs:11:21</tspan> +</tspan> + <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan> +</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">11</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> pub static C: u32 = 0 - 1;</tspan> +</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan><tspan> ━━━━━ attempt to compute `0_u32 - 1_u32`, which would overflow</tspan> +</tspan> + <tspan x="10px" y="118px"> +</tspan> + </text> + +</svg> diff --git a/examples/custom_level.rs b/examples/custom_level.rs new file mode 100644 index 00000000..c19cdc43 --- /dev/null +++ b/examples/custom_level.rs @@ -0,0 +1,69 @@ +use annotate_snippets::renderer::OutputTheme; +use annotate_snippets::{AnnotationKind, Group, Level, Patch, Renderer, Snippet}; + +fn main() { + let source = r#"// Regression test for issue #114529 +// Tests that we do not ICE during const eval for a +// break-with-value in contexts where it is illegal + +#[allow(while_true)] +fn main() { + [(); { + while true { + break 9; //~ ERROR `break` with value from a `while` loop + }; + 51 + }]; + + [(); { + while let Some(v) = Some(9) { + break v; //~ ERROR `break` with value from a `while` loop + }; + 51 + }]; + + while true { + break (|| { //~ ERROR `break` with value from a `while` loop + let local = 9; + }); + } +} +"#; + let message = Level::Error + .message("`break` with value from a `while` loop") + .id("E0571") + .group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .origin("$DIR/issue-114529-illegal-break-with-value.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(483..581) + .label("can only break with a value inside `loop` or breakable block"), + ) + .annotation( + AnnotationKind::Context + .span(462..472) + .label("you can't `break` with a value in a `while` loop"), + ), + ), + ) + .group( + Group::new() + .element(Level::None.title( + "suggestion: use `break` on its own without a value inside this `while` loop", + )) + .element( + Snippet::source(source) + .line_start(1) + .origin("$DIR/issue-114529-illegal-break-with-value.rs") + .fold(true) + .patch(Patch::new(483..581, "break")), + ), + ); + + let renderer = Renderer::styled().theme(OutputTheme::Unicode); + anstream::println!("{}", renderer.render(message)); +} diff --git a/examples/custom_level.svg b/examples/custom_level.svg new file mode 100644 index 00000000..1f31e651 --- /dev/null +++ b/examples/custom_level.svg @@ -0,0 +1,61 @@ +<svg width="740px" height="344px" xmlns="http://www.w3.org/2000/svg"> + <style> + .fg { fill: #AAAAAA } + .bg { background: #000000 } + .fg-bright-blue { fill: #5555FF } + .fg-bright-green { fill: #55FF55 } + .fg-bright-red { fill: #FF5555 } + .container { + padding: 0 10px; + line-height: 18px; + } + .bold { font-weight: bold; } + tspan { + font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + white-space: pre; + line-height: 18px; + } + </style> + + <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> + + <text xml:space="preserve" class="container fg"> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0571]</tspan><tspan class="bold">: `break` with value from a `while` loop</tspan> +</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>$DIR/issue-114529-illegal-break-with-value.rs:22:9</tspan> +</tspan> + <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan> +</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">21</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> while true {</tspan> +</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">──────────</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">you can't `break` with a value in a `while` loop</tspan> +</tspan> + <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">22</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┏</tspan><tspan> break (|| { //~ ERROR `break` with value from a `while` loop</tspan> +</tspan> + <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">23</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan><tspan> let local = 9;</tspan> +</tspan> + <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">24</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┃</tspan><tspan> });</tspan> +</tspan> + <tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┗━━━━━━━━━━┛</tspan><tspan> </tspan><tspan class="fg-bright-red bold">can only break with a value inside `loop` or breakable block</tspan> +</tspan> + <tspan x="10px" y="190px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan> +</tspan> + <tspan x="10px" y="208px"><tspan class="bold">suggestion: use `break` on its own without a value inside this `while` loop</tspan> +</tspan> + <tspan x="10px" y="226px"><tspan> </tspan><tspan class="fg-bright-blue bold">╭╴</tspan> +</tspan> + <tspan x="10px" y="244px"><tspan class="fg-bright-blue bold">22</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan> break (|| { //~ ERROR `break` with value from a `while` loop</tspan> +</tspan> + <tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">23</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan> let local = 9;</tspan> +</tspan> + <tspan x="10px" y="280px"><tspan class="fg-bright-blue bold">24</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan> </tspan><tspan class="fg-bright-red">})</tspan><tspan>;</tspan> +</tspan> + <tspan x="10px" y="298px"><tspan class="fg-bright-blue bold">22</tspan><tspan> </tspan><tspan class="fg-bright-green">+ </tspan><tspan> </tspan><tspan class="fg-bright-green">break</tspan><tspan>;</tspan> +</tspan> + <tspan x="10px" y="316px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan> +</tspan> + <tspan x="10px" y="334px"> +</tspan> + </text> + +</svg> diff --git a/tests/examples.rs b/tests/examples.rs index a0504c11..66dd94be 100644 --- a/tests/examples.rs +++ b/tests/examples.rs @@ -1,3 +1,17 @@ +#[test] +fn custom_error() { + let target = "custom_error"; + let expected = snapbox::file!["../examples/custom_error.svg": TermSvg]; + assert_example(target, expected); +} + +#[test] +fn custom_level() { + let target = "custom_level"; + let expected = snapbox::file!["../examples/custom_level.svg": TermSvg]; + assert_example(target, expected); +} + #[test] fn expected_type() { let target = "expected_type"; From 353a040447d6284df6711c7b9cb8ed86302971d8 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 1 Apr 2025 18:27:45 -0600 Subject: [PATCH 329/455] feat: Add custom Levels --- benches/bench.rs | 6 +- examples/custom_error.rs | 8 +- examples/custom_error.svg | 5 +- examples/custom_level.rs | 12 ++- examples/custom_level.svg | 3 +- examples/expected_type.rs | 4 +- examples/footer.rs | 6 +- examples/format.rs | 4 +- examples/highlight_source.rs | 6 +- examples/highlight_title.rs | 6 +- examples/multislice.rs | 4 +- src/level.rs | 128 +++++++++++++++++++++++ src/lib.rs | 1 + src/renderer/mod.rs | 36 +++---- src/renderer/styled_buffer.rs | 6 +- src/snippet.rs | 65 +----------- tests/fixtures/deserialize.rs | 35 +++++-- tests/formatter.rs | 192 +++++++++++++++++----------------- tests/rustc_tests.rs | 88 ++++++++-------- 19 files changed, 352 insertions(+), 263 deletions(-) create mode 100644 src/level.rs diff --git a/benches/bench.rs b/benches/bench.rs index 01364af7..d50cf435 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -1,4 +1,4 @@ -use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; +use annotate_snippets::{level::Level, AnnotationKind, Group, Renderer, Snippet}; #[divan::bench] fn simple() -> String { @@ -24,7 +24,7 @@ fn simple() -> String { _ => continue, } }"#; - let message = Level::Error.message("mismatched types").id("E0308").group( + let message = Level::ERROR.message("mismatched types").id("E0308").group( Group::new().element( Snippet::source(source) .line_start(51) @@ -69,7 +69,7 @@ fn fold(bencher: divan::Bencher<'_, '_>, context: usize) { (input, span) }) .bench_values(|(input, span)| { - let message = Level::Error.message("mismatched types").id("E0308").group( + let message = Level::ERROR.message("mismatched types").id("E0308").group( Group::new().element( Snippet::source(&input) .fold(true) diff --git a/examples/custom_error.rs b/examples/custom_error.rs index 3ba234d3..4050d400 100644 --- a/examples/custom_error.rs +++ b/examples/custom_error.rs @@ -1,5 +1,5 @@ use annotate_snippets::renderer::OutputTheme; -use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; +use annotate_snippets::{level::Level, AnnotationKind, Group, Renderer, Snippet}; fn main() { let source = r#"//@ compile-flags: -Ztreat-err-as-bug @@ -15,8 +15,10 @@ fn main() { pub static C: u32 = 0 - 1; //~^ ERROR could not evaluate static initializer "#; - let message = Level::None - .message("error: internal compiler error[E0080]: could not evaluate static initializer") + let message = Level::ERROR + .text(Some("error: internal compiler error")) + .message("could not evaluate static initializer") + .id("E0080") .group( Group::new().element( Snippet::source(source) diff --git a/examples/custom_error.svg b/examples/custom_error.svg index 5cb0c720..af3611a9 100644 --- a/examples/custom_error.svg +++ b/examples/custom_error.svg @@ -3,6 +3,7 @@ .fg { fill: #AAAAAA } .bg { background: #000000 } .fg-bright-blue { fill: #5555FF } + .fg-bright-red { fill: #FF5555 } .container { padding: 0 10px; line-height: 18px; @@ -18,7 +19,7 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan class="bold">error: internal compiler error[E0080]: could not evaluate static initializer</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error: internal compiler error[E0080]</tspan><tspan class="bold">: could not evaluate static initializer</tspan> </tspan> <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>$DIR/err.rs:11:21</tspan> </tspan> @@ -26,7 +27,7 @@ </tspan> <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">11</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> pub static C: u32 = 0 - 1;</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan><tspan> ━━━━━ attempt to compute `0_u32 - 1_u32`, which would overflow</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan><tspan> </tspan><tspan class="fg-bright-red bold">━━━━━</tspan><tspan> </tspan><tspan class="fg-bright-red bold">attempt to compute `0_u32 - 1_u32`, which would overflow</tspan> </tspan> <tspan x="10px" y="118px"> </tspan> diff --git a/examples/custom_level.rs b/examples/custom_level.rs index c19cdc43..804f7741 100644 --- a/examples/custom_level.rs +++ b/examples/custom_level.rs @@ -1,5 +1,5 @@ use annotate_snippets::renderer::OutputTheme; -use annotate_snippets::{AnnotationKind, Group, Level, Patch, Renderer, Snippet}; +use annotate_snippets::{level::Level, AnnotationKind, Group, Patch, Renderer, Snippet}; fn main() { let source = r#"// Regression test for issue #114529 @@ -29,7 +29,7 @@ fn main() { } } "#; - let message = Level::Error + let message = Level::ERROR .message("`break` with value from a `while` loop") .id("E0571") .group( @@ -52,9 +52,11 @@ fn main() { ) .group( Group::new() - .element(Level::None.title( - "suggestion: use `break` on its own without a value inside this `while` loop", - )) + .element( + Level::HELP + .text(Some("suggestion")) + .title("use `break` on its own without a value inside this `while` loop"), + ) .element( Snippet::source(source) .line_start(1) diff --git a/examples/custom_level.svg b/examples/custom_level.svg index 1f31e651..eebff280 100644 --- a/examples/custom_level.svg +++ b/examples/custom_level.svg @@ -3,6 +3,7 @@ .fg { fill: #AAAAAA } .bg { background: #000000 } .fg-bright-blue { fill: #5555FF } + .fg-bright-cyan { fill: #55FFFF } .fg-bright-green { fill: #55FF55 } .fg-bright-red { fill: #FF5555 } .container { @@ -40,7 +41,7 @@ </tspan> <tspan x="10px" y="190px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan> </tspan> - <tspan x="10px" y="208px"><tspan class="bold">suggestion: use `break` on its own without a value inside this `while` loop</tspan> + <tspan x="10px" y="208px"><tspan class="fg-bright-cyan bold">suggestion</tspan><tspan class="bold">: use `break` on its own without a value inside this `while` loop</tspan> </tspan> <tspan x="10px" y="226px"><tspan> </tspan><tspan class="fg-bright-blue bold">╭╴</tspan> </tspan> diff --git a/examples/expected_type.rs b/examples/expected_type.rs index f61999da..9a51dce5 100644 --- a/examples/expected_type.rs +++ b/examples/expected_type.rs @@ -1,4 +1,4 @@ -use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; +use annotate_snippets::{level::Level, AnnotationKind, Group, Renderer, Snippet}; fn main() { let source = r#" annotations: vec![SourceAnnotation { @@ -6,7 +6,7 @@ fn main() { , range: <22, 25>,"#; let message = - Level::Error.message("expected type, found `22`").group( + Level::ERROR.message("expected type, found `22`").group( Group::new().element( Snippet::source(source) .line_start(26) diff --git a/examples/footer.rs b/examples/footer.rs index 29b27c14..d61e84b4 100644 --- a/examples/footer.rs +++ b/examples/footer.rs @@ -1,7 +1,7 @@ -use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; +use annotate_snippets::{level::Level, AnnotationKind, Group, Renderer, Snippet}; fn main() { - let message = Level::Error + let message = Level::ERROR .message("mismatched types") .id("E0308") .group( @@ -14,7 +14,7 @@ fn main() { )), ), ) - .group(Group::new().element(Level::Note.title( + .group(Group::new().element(Level::NOTE.title( "expected type: `snippet::Annotation`\n found type: `__&__snippet::Annotation`", ))); diff --git a/examples/format.rs b/examples/format.rs index df6f9274..5324413c 100644 --- a/examples/format.rs +++ b/examples/format.rs @@ -1,4 +1,4 @@ -use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; +use annotate_snippets::{level::Level, AnnotationKind, Group, Renderer, Snippet}; fn main() { let source = r#") -> Option<String> { @@ -23,7 +23,7 @@ fn main() { _ => continue, } }"#; - let message = Level::Error.message("mismatched types").id("E0308").group( + let message = Level::ERROR.message("mismatched types").id("E0308").group( Group::new().element( Snippet::source(source) .line_start(51) diff --git a/examples/highlight_source.rs b/examples/highlight_source.rs index 2bb4ec24..9962542d 100644 --- a/examples/highlight_source.rs +++ b/examples/highlight_source.rs @@ -1,4 +1,4 @@ -use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; +use annotate_snippets::{level::Level, AnnotationKind, Group, Renderer, Snippet}; fn main() { let source = r#"//@ compile-flags: -Z teach @@ -9,7 +9,7 @@ const CON: Vec<i32> = vec![1, 2, 3]; //~ ERROR E0010 //~| ERROR cannot call non-const method fn main() {} "#; - let message = Level::Error + let message = Level::ERROR .message("allocations are not allowed in constants") .id("E0010") .group( @@ -26,7 +26,7 @@ fn main() {} ), ) .element( - Level::Note.title("The runtime heap is not yet available at compile-time, so no runtime heap allocations can be created."), + Level::NOTE.title("The runtime heap is not yet available at compile-time, so no runtime heap allocations can be created."), ), ); diff --git a/examples/highlight_title.rs b/examples/highlight_title.rs index bb09049c..e2da54ff 100644 --- a/examples/highlight_title.rs +++ b/examples/highlight_title.rs @@ -1,4 +1,4 @@ -use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; +use annotate_snippets::{level::Level, AnnotationKind, Group, Renderer, Snippet}; use anstyle::Effects; fn main() { @@ -43,7 +43,7 @@ fn main() { magenta.render_reset() ); - let message = Level::Error.message("mismatched types").id("E0308").group( + let message = Level::ERROR.message("mismatched types").id("E0308").group( Group::new() .element( Snippet::source(source) @@ -60,7 +60,7 @@ fn main() { .label("arguments to this function are incorrect"), ), ) - .element(Level::Note.title(&title)), + .element(Level::NOTE.title(&title)), ); let renderer = Renderer::styled().anonymized_line_numbers(true); diff --git a/examples/multislice.rs b/examples/multislice.rs index d1ad72ac..ddd938e4 100644 --- a/examples/multislice.rs +++ b/examples/multislice.rs @@ -1,7 +1,7 @@ -use annotate_snippets::{Annotation, Group, Level, Renderer, Snippet}; +use annotate_snippets::{level::Level, Annotation, Group, Renderer, Snippet}; fn main() { - let message = Level::Error.message("mismatched types").group( + let message = Level::ERROR.message("mismatched types").group( Group::new() .element( Snippet::<Annotation<'_>>::source("Foo") diff --git a/src/level.rs b/src/level.rs new file mode 100644 index 00000000..7f920249 --- /dev/null +++ b/src/level.rs @@ -0,0 +1,128 @@ +use crate::renderer::stylesheet::Stylesheet; +use crate::snippet::{ERROR_TXT, HELP_TXT, INFO_TXT, NOTE_TXT, WARNING_TXT}; +use crate::{Element, Group, Message, Title}; +use anstyle::Style; + +pub const ERROR: Level<'_> = Level { + name: None, + level: LevelInner::Error, +}; + +pub const WARNING: Level<'_> = Level { + name: None, + level: LevelInner::Warning, +}; + +pub const INFO: Level<'_> = Level { + name: None, + level: LevelInner::Info, +}; + +pub const NOTE: Level<'_> = Level { + name: None, + level: LevelInner::Note, +}; + +pub const HELP: Level<'_> = Level { + name: None, + level: LevelInner::Help, +}; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Level<'a> { + pub(crate) name: Option<Option<&'a str>>, + pub(crate) level: LevelInner, +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum Level2<'a> { + Builtin(LevelInner), + Custom { + name: Option<&'a str>, + level: LevelInner, + }, + None, +} + +impl<'a> Level<'a> { + pub const ERROR: Level<'a> = ERROR; + pub const WARNING: Level<'a> = WARNING; + pub const INFO: Level<'a> = INFO; + pub const NOTE: Level<'a> = NOTE; + pub const HELP: Level<'a> = HELP; + + /// Text passed to this function is considered "untrusted input", as such + /// all text is passed through a normalization function. Pre-styled text is + /// not allowed to be passed to this function. + pub fn text(self, text: Option<&'a str>) -> Level<'a> { + Level { + name: Some(text), + level: self.level, + } + } +} + +impl<'a> Level<'a> { + /// Text passed to this function is considered "untrusted input", as such + /// all text is passed through a normalization function. Pre-styled text is + /// not allowed to be passed to this function. + pub fn message(self, title: &'a str) -> Message<'a> { + Message { + id: None, + groups: vec![Group::new().element(Element::Title(Title { + level: self, + title, + primary: true, + }))], + } + } + + /// Text passed to this function is allowed to be pre-styled, as such all + /// text is considered "trusted input" and has no normalizations applied to + /// it. [`normalize_untrusted_str`](crate::normalize_untrusted_str) can be + /// used to normalize untrusted text before it is passed to this function. + pub fn title(self, title: &'a str) -> Title<'a> { + Title { + level: self, + title, + primary: false, + } + } + + pub(crate) fn as_str(&self) -> &'a str { + match (self.name, self.level) { + (Some(Some(name)), _) => name, + (Some(None), _) => "", + (None, LevelInner::Error) => ERROR_TXT, + (None, LevelInner::Warning) => WARNING_TXT, + (None, LevelInner::Info) => INFO_TXT, + (None, LevelInner::Note) => NOTE_TXT, + (None, LevelInner::Help) => HELP_TXT, + } + } + + pub(crate) fn style(&self, stylesheet: &Stylesheet) -> Style { + self.level.style(stylesheet) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum LevelInner { + Error, + Warning, + Info, + Note, + Help, +} + +impl LevelInner { + pub(crate) fn style(self, stylesheet: &Stylesheet) -> Style { + match self { + LevelInner::Error => stylesheet.error, + LevelInner::Warning => stylesheet.warning, + LevelInner::Info => stylesheet.info, + LevelInner::Note => stylesheet.note, + LevelInner::Help => stylesheet.help, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index e6c49c62..eee2b6f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,6 +42,7 @@ #![warn(clippy::print_stdout)] #![warn(missing_debug_implementations)] +pub mod level; pub mod renderer; mod snippet; diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 7d4fd0ff..8efcff84 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -5,6 +5,7 @@ //! # Example //! ``` //! use annotate_snippets::*; +//! use annotate_snippets::level::Level; //! //! let source = r#" //! use baz::zed::bar; @@ -17,7 +18,7 @@ //! bar(); //! } //! "#; -//! Level::Error +//! Level::ERROR //! .message("unresolved import `baz::zed`") //! .id("E0432") //! .group( @@ -40,13 +41,12 @@ pub(crate) mod source_map; mod styled_buffer; pub(crate) mod stylesheet; +use crate::level::{Level, LevelInner}; use crate::renderer::source_map::{ AnnotatedLineInfo, LineInfo, Loc, SourceMap, SubstitutionHighlight, }; use crate::renderer::styled_buffer::StyledBuffer; -use crate::{ - Annotation, AnnotationKind, Element, Group, Level, Message, Origin, Patch, Snippet, Title, -}; +use crate::{Annotation, AnnotationKind, Element, Group, Message, Origin, Patch, Snippet, Title}; pub use anstyle::*; use margin::Margin; use std::borrow::Cow; @@ -207,7 +207,7 @@ impl Renderer { }; let title = message.groups.remove(0).elements.remove(0); let level = if let Element::Title(title) = &title { - title.level + title.level.clone() } else { panic!("Expected a title as the first element of the message") }; @@ -345,7 +345,7 @@ impl Renderer { ); if g == 0 && group_len > 1 { - if matches!(peek, Some(Element::Title(level)) if level.level != Level::None) + if matches!(peek, Some(Element::Title(level)) if level.level.name != Some(None)) { self.draw_col_separator_no_space( buffer, @@ -394,7 +394,7 @@ impl Renderer { if g == 0 && (matches!(section, Element::Origin(_)) || (matches!(section, Element::Title(_)) && i == 0) - || matches!(section, Element::Title(level) if level.level == Level::None)) + || matches!(section, Element::Title(level) if level.level.name == Some(None))) { if peek.is_none() && group_len > 1 { self.draw_col_separator_end( @@ -402,7 +402,7 @@ impl Renderer { buffer.num_lines(), max_line_num_len + 1, ); - } else if matches!(peek, Some(Element::Title(level)) if level.level != Level::None) + } else if matches!(peek, Some(Element::Title(level)) if level.level.name != Some(None)) { self.draw_col_separator_no_space( buffer, @@ -445,7 +445,7 @@ impl Renderer { buffer.prepend(line_offset, " ", ElementStyle::NoStyle); } - if title.level != Level::None { + if title.level.name != Some(None) { self.draw_note_separator(buffer, line_offset, max_line_num_len + 1, is_cont); buffer.append( line_offset, @@ -476,18 +476,18 @@ impl Renderer { } else { let mut label_width = 0; - if title.level != Level::None { + if title.level.name != Some(None) { buffer.append( line_offset, title.level.as_str(), - ElementStyle::Level(title.level), + ElementStyle::Level(title.level.level), ); } label_width += title.level.as_str().len(); if let Some(id) = id { - buffer.append(line_offset, "[", ElementStyle::Level(title.level)); - buffer.append(line_offset, id, ElementStyle::Level(title.level)); - buffer.append(line_offset, "]", ElementStyle::Level(title.level)); + buffer.append(line_offset, "[", ElementStyle::Level(title.level.level)); + buffer.append(line_offset, id, ElementStyle::Level(title.level.level)); + buffer.append(line_offset, "]", ElementStyle::Level(title.level.level)); label_width += 2 + id.len(); } let header_style = if is_secondary { @@ -495,7 +495,7 @@ impl Renderer { } else { ElementStyle::MainHeaderMsg }; - if title.level != Level::None { + if title.level.name != Some(None) { buffer.append(line_offset, ": ", header_style); label_width += 2; } @@ -647,7 +647,7 @@ impl Renderer { buffer_msg_line_offset + 1, max_line_num_len + 1, ); - let title = Level::Note.title(label); + let title = Level::NOTE.title(label); self.render_title(buffer, &title, None, max_line_num_len, true, None, false); } } @@ -2654,13 +2654,13 @@ pub(crate) enum ElementStyle { LabelPrimary, LabelSecondary, NoStyle, - Level(Level), + Level(LevelInner), Addition, Removal, } impl ElementStyle { - fn color_spec(&self, level: Level, stylesheet: &Stylesheet) -> Style { + fn color_spec(&self, level: &Level<'_>, stylesheet: &Stylesheet) -> Style { match self { ElementStyle::Addition => stylesheet.addition, ElementStyle::Removal => stylesheet.removal, diff --git a/src/renderer/styled_buffer.rs b/src/renderer/styled_buffer.rs index 8a4cc672..7114683b 100644 --- a/src/renderer/styled_buffer.rs +++ b/src/renderer/styled_buffer.rs @@ -2,9 +2,9 @@ //! //! [styled_buffer]: https://github.com/rust-lang/rust/blob/894f7a4ba6554d3797404bbf550d9919df060b97/compiler/rustc_errors/src/styled_buffer.rs +use crate::level::Level; use crate::renderer::stylesheet::Stylesheet; use crate::renderer::ElementStyle; -use crate::Level; use std::fmt; use std::fmt::Write; @@ -41,14 +41,14 @@ impl StyledBuffer { pub(crate) fn render( &self, - level: Level, + level: Level<'_>, stylesheet: &Stylesheet, ) -> Result<String, fmt::Error> { let mut str = String::new(); for (i, line) in self.lines.iter().enumerate() { let mut current_style = stylesheet.none; for StyledChar { ch, style } in line { - let ch_style = style.color_spec(level, stylesheet); + let ch_style = style.color_spec(&level, stylesheet); if ch_style != current_style { if !line.is_empty() { write!(str, "{}", current_style.render_reset())?; diff --git a/src/snippet.rs b/src/snippet.rs index e8fa18c4..fe1239d4 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -1,8 +1,7 @@ //! Structures used as an input for the library. +use crate::level::Level; use crate::renderer::source_map::SourceMap; -use crate::renderer::stylesheet::Stylesheet; -use anstyle::Style; use std::ops::Range; pub(crate) const ERROR_TXT: &str = "error"; @@ -143,7 +142,7 @@ pub struct ColumnSeparator; #[derive(Debug)] pub struct Title<'a> { - pub(crate) level: Level, + pub(crate) level: Level<'a>, pub(crate) title: &'a str, pub(crate) primary: bool, } @@ -155,66 +154,6 @@ impl Title<'_> { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub enum Level { - Error, - Warning, - Info, - Note, - Help, - None, -} - -impl Level { - /// Text passed to this function is considered "untrusted input", as such - /// all text is passed through a normalization function. Pre-styled text is - /// not allowed to be passed to this function. - pub fn message(self, title: &str) -> Message<'_> { - Message { - id: None, - groups: vec![Group::new().element(Element::Title(Title { - level: self, - title, - primary: true, - }))], - } - } - - /// Text passed to this function is allowed to be pre-styled, as such all - /// text is considered "trusted input" and has no normalizations applied to - /// it. [`normalize_untrusted_str`](crate::normalize_untrusted_str) can be - /// used to normalize untrusted text before it is passed to this function. - pub fn title(self, title: &str) -> Title<'_> { - Title { - level: self, - title, - primary: false, - } - } - - pub(crate) fn as_str(self) -> &'static str { - match self { - Level::Error => ERROR_TXT, - Level::Warning => WARNING_TXT, - Level::Info => INFO_TXT, - Level::Note => NOTE_TXT, - Level::Help => HELP_TXT, - Level::None => "", - } - } - - pub(crate) fn style(&self, stylesheet: &Stylesheet) -> Style { - match self { - Level::Error => stylesheet.error, - Level::Warning => stylesheet.warning, - Level::Info => stylesheet.info, - Level::Note => stylesheet.note, - Level::Help => stylesheet.help, - Level::None => stylesheet.none, - } - } -} - #[derive(Debug)] pub struct Snippet<'a, T> { pub(crate) origin: Option<&'a str>, diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs index 065c395e..8f45b363 100644 --- a/tests/fixtures/deserialize.rs +++ b/tests/fixtures/deserialize.rs @@ -3,7 +3,7 @@ use std::ops::Range; use annotate_snippets::renderer::DEFAULT_TERM_WIDTH; use annotate_snippets::{ - Annotation, AnnotationKind, Element, Group, Level, Message, Patch, Renderer, Snippet, + level::Level, Annotation, AnnotationKind, Element, Group, Message, Patch, Renderer, Snippet, }; #[derive(Deserialize)] @@ -15,8 +15,7 @@ pub(crate) struct Fixture { #[derive(Deserialize)] pub struct MessageDef { - #[serde(with = "LevelDef")] - pub level: Level, + pub level: LevelDef, pub title: String, #[serde(default)] pub id: Option<String>, @@ -32,13 +31,15 @@ impl<'a> From<&'a MessageDef> for Message<'a> { id, sections, } = val; - let mut message = level.message(title); + let mut message = Level::from(level).message(title); if let Some(id) = id { message = message.id(id); } message = message.group(Group::new().elements(sections.iter().map(|s| match s { - ElementDef::Title(title) => Element::Title(title.level.title(&title.title)), + ElementDef::Title(title) => { + Element::Title(Level::from(&title.level).title(&title.title)) + } ElementDef::Cause(cause) => Element::Cause(Snippet::from(cause)), ElementDef::Suggestion(suggestion) => Element::Suggestion(Snippet::from(suggestion)), }))); @@ -57,7 +58,9 @@ pub enum ElementDef { impl<'a> From<&'a ElementDef> for Element<'a> { fn from(val: &'a ElementDef) -> Self { match val { - ElementDef::Title(title) => Element::Title(title.level.title(&title.title)), + ElementDef::Title(title) => { + Element::Title(Level::from(&title.level).title(&title.title)) + } ElementDef::Cause(cause) => Element::Cause(Snippet::from(cause)), ElementDef::Suggestion(suggestion) => Element::Suggestion(Snippet::from(suggestion)), } @@ -67,8 +70,7 @@ impl<'a> From<&'a ElementDef> for Element<'a> { #[derive(Deserialize)] pub struct TitleDef { pub title: String, - #[serde(with = "LevelDef")] - pub level: Level, + pub level: LevelDef, } #[derive(Deserialize)] @@ -164,9 +166,8 @@ impl<'a> From<&'a PatchDef> for Patch<'a> { } #[allow(dead_code)] -#[derive(Deserialize)] -#[serde(remote = "Level")] -enum LevelDef { +#[derive(Clone, Copy, Deserialize)] +pub enum LevelDef { Error, Warning, Info, @@ -174,6 +175,18 @@ enum LevelDef { Help, } +impl<'a> From<&'a LevelDef> for Level<'a> { + fn from(val: &'a LevelDef) -> Self { + match val { + LevelDef::Error => Level::ERROR, + LevelDef::Warning => Level::WARNING, + LevelDef::Info => Level::INFO, + LevelDef::Note => Level::NOTE, + LevelDef::Help => Level::HELP, + } + } +} + #[derive(Default, Deserialize)] pub struct RendererDef { #[serde(default)] diff --git a/tests/formatter.rs b/tests/formatter.rs index 5deb21ab..3f0e7cd4 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -1,11 +1,13 @@ -use annotate_snippets::{Annotation, AnnotationKind, Group, Level, Patch, Renderer, Snippet}; +use annotate_snippets::{ + level::Level, Annotation, AnnotationKind, Group, Patch, Renderer, Snippet, +}; use annotate_snippets::renderer::OutputTheme; use snapbox::{assert_data_eq, str}; #[test] fn test_i_29() { - let snippets = Level::Error.message("oops").group( + let snippets = Level::ERROR.message("oops").group( Group::new().element( Snippet::source("First line\r\nSecond oops line") .origin("<current file>") @@ -27,7 +29,7 @@ error: oops #[test] fn test_point_to_double_width_characters() { - let snippets = Level::Error.message("").group( + let snippets = Level::ERROR.message("").group( Group::new().element( Snippet::source("こんにちは、世界") .origin("<current file>") @@ -49,7 +51,7 @@ error: #[test] fn test_point_to_double_width_characters_across_lines() { - let snippets = Level::Error.message("").group( + let snippets = Level::ERROR.message("").group( Group::new().element( Snippet::source("おはよう\nございます") .origin("<current file>") @@ -73,7 +75,7 @@ error: #[test] fn test_point_to_double_width_characters_multiple() { - let snippets = Level::Error.message("").group( + let snippets = Level::ERROR.message("").group( Group::new().element( Snippet::source("お寿司\n食べたい🍣") .origin("<current file>") @@ -98,7 +100,7 @@ error: #[test] fn test_point_to_double_width_characters_mixed() { - let snippets = Level::Error.message("").group( + let snippets = Level::ERROR.message("").group( Group::new().element( Snippet::source("こんにちは、新しいWorld!") .origin("<current file>") @@ -120,7 +122,7 @@ error: #[test] fn test_format_title() { - let input = Level::Error.message("This is a title").id("E0001"); + let input = Level::ERROR.message("This is a title").id("E0001"); let expected = str![r#"error[E0001]: This is a title"#]; let renderer = Renderer::plain(); @@ -130,7 +132,7 @@ fn test_format_title() { #[test] fn test_format_snippet_only() { let source = "This is line 1\nThis is line 2"; - let input = Level::Error + let input = Level::ERROR .message("") .group(Group::new().element(Snippet::<Annotation<'_>>::source(source).line_start(5402))); @@ -148,7 +150,7 @@ error: fn test_format_snippets_continuation() { let src_0 = "This is slice 1"; let src_1 = "This is slice 2"; - let input = Level::Error.message("").group( + let input = Level::ERROR.message("").group( Group::new() .element( Snippet::<Annotation<'_>>::source(src_0) @@ -182,7 +184,7 @@ fn test_format_snippet_annotation_standalone() { let source = [line_1, line_2].join("\n"); // In line 2 let range = 22..24; - let input = Level::Error.message("").group( + let input = Level::ERROR.message("").group( Group::new().element( Snippet::source(&source).line_start(5402).annotation( AnnotationKind::Context @@ -204,9 +206,9 @@ error: #[test] fn test_format_footer_title() { - let input = Level::Error + let input = Level::ERROR .message("") - .group(Group::new().element(Level::Error.title("This __is__ a title"))); + .group(Group::new().element(Level::ERROR.title("This __is__ a title"))); let expected = str![[r#" error: | @@ -221,7 +223,7 @@ error: fn test_i26() { let source = "short"; let label = "label"; - let input = Level::Error.message("").group( + let input = Level::ERROR.message("").group( Group::new().element( Snippet::source(source).line_start(0).annotation( AnnotationKind::Primary @@ -237,7 +239,7 @@ fn test_i26() { #[test] fn test_source_content() { let source = "This is an example\nof content lines"; - let input = Level::Error + let input = Level::ERROR .message("") .group(Group::new().element(Snippet::<Annotation<'_>>::source(source).line_start(56))); let expected = str![[r#" @@ -253,7 +255,7 @@ error: #[test] fn test_source_annotation_standalone_singleline() { let source = "tests"; - let input = Level::Error.message("").group( + let input = Level::ERROR.message("").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -273,7 +275,7 @@ error: #[test] fn test_source_annotation_standalone_multiline() { let source = "tests"; - let input = Level::Error.message("").group( + let input = Level::ERROR.message("").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -296,7 +298,7 @@ error: #[test] fn test_only_source() { - let input = Level::Error + let input = Level::ERROR .message("") .group(Group::new().element(Snippet::<Annotation<'_>>::source("").origin("file.rs"))); let expected = str![[r#" @@ -311,7 +313,7 @@ error: #[test] fn test_anon_lines() { let source = "This is an example\nof content lines\n\nabc"; - let input = Level::Error + let input = Level::ERROR .message("") .group(Group::new().element(Snippet::<Annotation<'_>>::source(source).line_start(56))); let expected = str![[r#" @@ -328,7 +330,7 @@ LL | abc #[test] fn issue_130() { - let input = Level::Error.message("dummy").group( + let input = Level::ERROR.message("dummy").group( Group::new().element( Snippet::source("foo\nbar\nbaz") .origin("file/path") @@ -356,7 +358,7 @@ fn unterminated_string_multiline() { a\" // ... "; - let input = Level::Error.message("").group( + let input = Level::ERROR.message("").group( Group::new().element( Snippet::source(source) .origin("file/path") @@ -380,7 +382,7 @@ error: #[test] fn char_and_nl_annotate_char() { let source = "a\r\nb"; - let input = Level::Error.message("").group( + let input = Level::ERROR.message("").group( Group::new().element( Snippet::source(source) .origin("file/path") @@ -403,7 +405,7 @@ error: #[test] fn char_eol_annotate_char() { let source = "a\r\nb"; - let input = Level::Error.message("").group( + let input = Level::ERROR.message("").group( Group::new().element( Snippet::source(source) .origin("file/path") @@ -425,7 +427,7 @@ error: #[test] fn char_eol_annotate_char_double_width() { - let snippets = Level::Error.message("").group( + let snippets = Level::ERROR.message("").group( Group::new().element( Snippet::source("こん\r\nにちは\r\n世界") .origin("<current file>") @@ -450,7 +452,7 @@ error: #[test] fn annotate_eol() { let source = "a\r\nb"; - let input = Level::Error.message("").group( + let input = Level::ERROR.message("").group( Group::new().element( Snippet::source(source) .origin("file/path") @@ -473,7 +475,7 @@ error: #[test] fn annotate_eol2() { let source = "a\r\nb"; - let input = Level::Error.message("").group( + let input = Level::ERROR.message("").group( Group::new().element( Snippet::source(source) .origin("file/path") @@ -496,7 +498,7 @@ error: #[test] fn annotate_eol3() { let source = "a\r\nb"; - let input = Level::Error.message("").group( + let input = Level::ERROR.message("").group( Group::new().element( Snippet::source(source) .origin("file/path") @@ -519,7 +521,7 @@ error: #[test] fn annotate_eol4() { let source = "a\r\nb"; - let input = Level::Error.message("").group( + let input = Level::ERROR.message("").group( Group::new().element( Snippet::source(source) .origin("file/path") @@ -541,7 +543,7 @@ error: #[test] fn annotate_eol_double_width() { - let snippets = Level::Error.message("").group( + let snippets = Level::ERROR.message("").group( Group::new().element( Snippet::source("こん\r\nにちは\r\n世界") .origin("<current file>") @@ -566,7 +568,7 @@ error: #[test] fn multiline_eol_start() { let source = "a\r\nb"; - let input = Level::Error.message("").group( + let input = Level::ERROR.message("").group( Group::new().element( Snippet::source(source) .origin("file/path") @@ -590,7 +592,7 @@ error: #[test] fn multiline_eol_start2() { let source = "a\r\nb"; - let input = Level::Error.message("").group( + let input = Level::ERROR.message("").group( Group::new().element( Snippet::source(source) .origin("file/path") @@ -614,7 +616,7 @@ error: #[test] fn multiline_eol_start3() { let source = "a\nb"; - let input = Level::Error.message("").group( + let input = Level::ERROR.message("").group( Group::new().element( Snippet::source(source) .origin("file/path") @@ -637,7 +639,7 @@ error: #[test] fn multiline_eol_start_double_width() { - let snippets = Level::Error.message("").group( + let snippets = Level::ERROR.message("").group( Group::new().element( Snippet::source("こん\r\nにちは\r\n世界") .origin("<current file>") @@ -663,7 +665,7 @@ error: #[test] fn multiline_eol_start_eol_end() { let source = "a\nb\nc"; - let input = Level::Error.message("").group( + let input = Level::ERROR.message("").group( Group::new().element( Snippet::source(source) .origin("file/path") @@ -688,7 +690,7 @@ error: #[test] fn multiline_eol_start_eol_end2() { let source = "a\r\nb\r\nc"; - let input = Level::Error.message("").group( + let input = Level::ERROR.message("").group( Group::new().element( Snippet::source(source) .origin("file/path") @@ -713,7 +715,7 @@ error: #[test] fn multiline_eol_start_eol_end3() { let source = "a\r\nb\r\nc"; - let input = Level::Error.message("").group( + let input = Level::ERROR.message("").group( Group::new().element( Snippet::source(source) .origin("file/path") @@ -738,7 +740,7 @@ error: #[test] fn multiline_eol_start_eof_end() { let source = "a\r\nb"; - let input = Level::Error.message("").group( + let input = Level::ERROR.message("").group( Group::new().element( Snippet::source(source) .origin("file/path") @@ -762,7 +764,7 @@ error: #[test] fn multiline_eol_start_eof_end_double_width() { let source = "ん\r\nに"; - let input = Level::Error.message("").group( + let input = Level::ERROR.message("").group( Group::new().element( Snippet::source(source) .origin("file/path") @@ -786,7 +788,7 @@ error: #[test] fn two_single_line_same_line() { let source = r#"bar = { version = "0.1.0", optional = true }"#; - let input = Level::Error.message("unused optional dependency").group( + let input = Level::ERROR.message("unused optional dependency").group( Group::new().element( Snippet::source(source) .origin("Cargo.toml") @@ -823,7 +825,7 @@ this is another line so is this bar = { version = "0.1.0", optional = true } "#; - let input = Level::Error.message("unused optional dependency").group( + let input = Level::ERROR.message("unused optional dependency").group( Group::new().element( Snippet::source(source) .line_start(4) @@ -862,7 +864,7 @@ this is another line so is this bar = { version = "0.1.0", optional = true } "#; - let input = Level::Error.message("unused optional dependency").group( + let input = Level::ERROR.message("unused optional dependency").group( Group::new().element( Snippet::source(source) .line_start(4) @@ -910,7 +912,7 @@ so is this bar = { version = "0.1.0", optional = true } this is another line "#; - let input = Level::Error.message("unused optional dependency").group( + let input = Level::ERROR.message("unused optional dependency").group( Group::new().element( Snippet::source(source) .line_start(4) @@ -961,7 +963,7 @@ error: unused optional dependency #[test] fn origin_correct_start_line() { let source = "aaa\nbbb\nccc\nddd\n"; - let input = Level::Error.message("title").group( + let input = Level::ERROR.message("title").group( Group::new().element( Snippet::source(source) .origin("origin.txt") @@ -987,7 +989,7 @@ error: title #[test] fn origin_correct_mid_line() { let source = "aaa\nbbb\nccc\nddd\n"; - let input = Level::Error.message("title").group( + let input = Level::ERROR.message("title").group( Group::new().element( Snippet::source(source) .origin("origin.txt") @@ -1017,7 +1019,7 @@ error: title #[test] fn two_suggestions_same_span() { let source = r#" A.foo();"#; - let input_new = Level::Error + let input_new = Level::ERROR .message("expected value, found enum `A`") .id("E0423") .group( @@ -1030,7 +1032,7 @@ fn two_suggestions_same_span() { .group( Group::new() .element( - Level::Help + Level::HELP .title("you might have meant to use one of the following enum variants"), ) .element( @@ -1085,7 +1087,7 @@ fn main() { banana::Chaenomeles.pick() }"#; let input_new = - Level::Error + Level::ERROR .message("no method named `pick` found for struct `Chaenomeles` in the current scope") .id("E0599") .group( @@ -1107,7 +1109,7 @@ fn main() { ) .group( Group::new() - .element(Level::Help.title( + .element(Level::HELP.title( "the following traits which provide `pick` are implemented but not in scope; perhaps you want to import one of them", )) .element( @@ -1145,7 +1147,7 @@ LL + use banana::Peach; fn single_line_non_overlapping_suggestions() { let source = r#" A.foo();"#; - let input_new = Level::Error + let input_new = Level::ERROR .message("expected value, found enum `A`") .id("E0423") .group( @@ -1158,7 +1160,7 @@ fn single_line_non_overlapping_suggestions() { ) .group( Group::new() - .element(Level::Help.title("make these changes and things will work")) + .element(Level::HELP.title("make these changes and things will work")) .element( Snippet::source(source) .fold(true) @@ -1187,7 +1189,7 @@ LL + (A::Tuple()).bar(); #[test] fn single_line_non_overlapping_suggestions2() { let source = r#" ThisIsVeryLong.foo();"#; - let input_new = Level::Error + let input_new = Level::ERROR .message("Found `ThisIsVeryLong`") .id("E0423") .group( @@ -1200,7 +1202,7 @@ fn single_line_non_overlapping_suggestions2() { ) .group( Group::new() - .element(Level::Help.title("make these changes and things will work")) + .element(Level::HELP.title("make these changes and things will work")) .element( Snippet::source(source) .fold(true) @@ -1236,7 +1238,7 @@ fn multiple_replacements() { y(); "#; - let input_new = Level::Error + let input_new = Level::ERROR .message("cannot borrow `*self` as mutable because it is also borrowed as immutable") .id("E0502") .group( @@ -1269,7 +1271,7 @@ fn multiple_replacements() { .group( Group::new() .element( - Level::Help + Level::HELP .title("try explicitly pass `&Self` into the Closure as an argument"), ) .element( @@ -1320,7 +1322,7 @@ fn main() { test1(); }"#; - let input_new = Level::Error + let input_new = Level::ERROR .message("cannot borrow `chars` as mutable more than once at a time") .id("E0499") .group( @@ -1348,7 +1350,7 @@ fn main() { .group( Group::new() .element( - Level::Help + Level::HELP .title("if you want to call `next` on a iterator within the loop, consider using `while let`") ) .element( @@ -1404,7 +1406,7 @@ struct Foo { fn main() {}"#; - let input_new = Level::Error + let input_new = Level::ERROR .message("failed to resolve: use of undeclared crate or module `st`") .id("E0433") .group( @@ -1418,7 +1420,7 @@ fn main() {}"#; ) .group( Group::new() - .element(Level::Help.title("there is a crate or module with a similar name")) + .element(Level::HELP.title("there is a crate or module with a similar name")) .element( Snippet::source(source) .fold(true) @@ -1427,7 +1429,7 @@ fn main() {}"#; ) .group( Group::new() - .element(Level::Help.title("consider importing this module")) + .element(Level::HELP.title("consider importing this module")) .element( Snippet::source(source) .fold(true) @@ -1436,7 +1438,7 @@ fn main() {}"#; ) .group( Group::new() - .element(Level::Help.title("if you import `cell`, refer to it directly")) + .element(Level::HELP.title("if you import `cell`, refer to it directly")) .element( Snippet::source(source) .fold(true) @@ -1486,7 +1488,7 @@ where fn main() {}"#; - let input_new = Level::Error + let input_new = Level::ERROR .message("the size for values of type `T` cannot be known at compilation time") .id("E0277") .group( @@ -1508,7 +1510,7 @@ fn main() {}"#; ) .group( Group::new() - .element(Level::Help.title( + .element(Level::HELP.title( "consider removing the `?Sized` bound to make the type parameter `Sized`", )) .element( @@ -1555,7 +1557,7 @@ and where } fn main() {}"#; - let input_new = Level::Error + let input_new = Level::ERROR .message("the size for values of type `T` cannot be known at compilation time") .id("E0277") .group(Group::new().element(Snippet::source(source) @@ -1573,7 +1575,7 @@ fn main() {}"#; .label("this type parameter needs to be `Sized`"), ))) .group(Group::new().element( - Level::Note + Level::NOTE .title("required by an implicit `Sized` bound in `Wrapper`") ).element( Snippet::source(source) @@ -1587,7 +1589,7 @@ fn main() {}"#; ) )) .group(Group::new().element( - Level::Help + Level::HELP .title("you could relax the implicit `Sized` bound on `T` if it were used through indirection like `&T` or `Box<T>`") ) .element( @@ -1608,7 +1610,7 @@ fn main() {}"#; )) .group(Group::new().element( - Level::Help + Level::HELP .title("consider removing the `?Sized` bound to make the type parameter `Sized`") ).element( Snippet::source(source) @@ -1660,12 +1662,12 @@ quack zappy "#; - let input_new = Level::Error + let input_new = Level::ERROR .message("the size for values of type `T` cannot be known at compilation time") .id("E0277") .group( Group::new() - .element(Level::Help.title( + .element(Level::HELP.title( "consider removing the `?Sized` bound to make the type parameter `Sized`", )) .element( @@ -1728,7 +1730,7 @@ fn main() { } "#; - let input_new = Level::Error + let input_new = Level::ERROR .message("type mismatch resolving `<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ...>>, ...>>, ...> as Future>::Error == Foo`") .id("E0271") .group(Group::new().element(Snippet::source(source) @@ -1741,7 +1743,7 @@ fn main() { .label("type mismatch resolving `<Result<Result<(), Result<Result<(), ...>, ...>>, ...> as Future>::Error == Foo`"), ))) .group(Group::new().element( - Level::Note.title("expected this to be `Foo`") + Level::NOTE.title("expected this to be `Foo`") ).element( Snippet::source(source) .line_start(4) @@ -1749,7 +1751,7 @@ fn main() { .fold(true) .annotation(AnnotationKind::Primary.span(89..90)) ).element( - Level::Note + Level::NOTE .title("required for the cast from `Box<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ()>>, ()>>, ()>>` to `Box<(dyn Future<Error = Foo> + 'static)>`") , )); @@ -1816,7 +1818,7 @@ fn main() { } "#; - let input_new = Level::Error + let input_new = Level::ERROR .message("type mismatch resolving `<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ...>>, ...>>, ...> as Future>::Error == Foo`") .id("E0271") .group(Group::new().element(Snippet::source(source) @@ -1829,7 +1831,7 @@ fn main() { .label("type mismatch resolving `<Result<Result<(), Result<Result<(), ...>, ...>>, ...> as Future>::Error == Foo`"), ))) .group(Group::new().element( - Level::Note.title("expected this to be `Foo`") + Level::NOTE.title("expected this to be `Foo`") ).element( Snippet::source(source) .line_start(4) @@ -1837,10 +1839,10 @@ fn main() { .fold(true) .annotation(AnnotationKind::Primary.span(89..90)) ).element( - Level::Note + Level::NOTE .title("required for the cast from `Box<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ()>>, ()>>, ()>>` to `Box<(dyn Future<Error = Foo> + 'static)>`") ).element( - Level::Note.title("a second note"), + Level::NOTE.title("a second note"), )); let expected = str![[r#" @@ -1969,7 +1971,7 @@ fn main() { } "#; - let input_new = Level::Error + let input_new = Level::ERROR .message("mismatched types") .id("E0308") .group(Group::new().element( @@ -1988,13 +1990,13 @@ fn main() { .label("expected due to this"), ) ).element( - Level::Note + Level::NOTE .title("expected struct `Atype<Btype<..., i32>, i32>`\n found enum `Result<Result<..., _>, _>`") ).element( - Level::Note + Level::NOTE .title("the full name for the type has been written to '$TEST_BUILD_DIR/$FILE.long-type-hash.txt'") ).element( - Level::Note + Level::NOTE .title("consider using `--verbose` to print the full type name to the console") , )); @@ -2053,7 +2055,7 @@ fn main() { } "#; - let input_new = Level::Error + let input_new = Level::ERROR .message("mismatched types") .id("E0308") .group(Group::new().element( @@ -2072,12 +2074,12 @@ fn main() { .label("arguments to this function are incorrect"), ), ).element( - Level::Note + Level::NOTE .title("expected fn pointer `for<'a> fn(Box<(dyn Any + Send + 'a)>) -> Pin<_>`\n found fn item `fn(Box<(dyn Any + Send + 'static)>) -> Pin<_> {wrapped_fn}`") , )) .group(Group::new().element( - Level::Note.title("function defined here"), + Level::NOTE.title("function defined here"), ).element( Snippet::source(source) .line_start(7) @@ -2122,7 +2124,7 @@ LL │ ┃ )>>) {} #[test] fn unicode_cut_handling() { let source = "version = \"0.1.0\"\n# Ensure that the spans from toml handle utf-8 correctly\nauthors = [\n { name = \"Z\u{351}\u{36b}\u{343}\u{36a}\u{302}\u{36b}\u{33d}\u{34f}\u{334}\u{319}\u{324}\u{31e}\u{349}\u{35a}\u{32f}\u{31e}\u{320}\u{34d}A\u{36b}\u{357}\u{334}\u{362}\u{335}\u{31c}\u{330}\u{354}L\u{368}\u{367}\u{369}\u{358}\u{320}G\u{311}\u{357}\u{30e}\u{305}\u{35b}\u{341}\u{334}\u{33b}\u{348}\u{34d}\u{354}\u{339}O\u{342}\u{30c}\u{30c}\u{358}\u{328}\u{335}\u{339}\u{33b}\u{31d}\u{333}\", email = 1 }\n]\n"; - let input = Level::Error.message("title").group( + let input = Level::ERROR.message("title").group( Group::new().element( Snippet::source(source) .fold(false) @@ -2147,7 +2149,7 @@ error: title #[test] fn unicode_cut_handling2() { let source = "/*这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?"; - let input = Level::Error + let input = Level::ERROR .message("expected item, found `?`") .group( Group::new().element( @@ -2155,7 +2157,7 @@ fn unicode_cut_handling2() { .fold(false) .annotation(AnnotationKind::Primary.span(499..500).label("expected item")) ).element( - Level::Note.title("for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>") + Level::NOTE.title("for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>") ) ); @@ -2174,7 +2176,7 @@ error: expected item, found `?` #[test] fn unicode_cut_handling3() { let source = "/*这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?"; - let input = Level::Error + let input = Level::ERROR .message("expected item, found `?`") .group( Group::new().element( @@ -2182,7 +2184,7 @@ fn unicode_cut_handling3() { .fold(false) .annotation(AnnotationKind::Primary.span(251..254).label("expected item")) ).element( - Level::Note.title("for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>") + Level::NOTE.title("for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>") ) ); @@ -2201,7 +2203,7 @@ error: expected item, found `?` #[test] fn unicode_cut_handling4() { let source = "/*aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/?"; - let input = Level::Error + let input = Level::ERROR .message("expected item, found `?`") .group( Group::new().element( @@ -2209,7 +2211,7 @@ fn unicode_cut_handling4() { .fold(false) .annotation(AnnotationKind::Primary.span(334..335).label("expected item")) ).element( - Level::Note.title("for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>") + Level::NOTE.title("for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>") ) ); @@ -2234,7 +2236,7 @@ fn main() { //~^ ERROR mismatched types } "##; - let input = Level::Error.message("mismatched types").id("E0308").group( + let input = Level::ERROR.message("mismatched types").id("E0308").group( Group::new().element( Snippet::source(source) .origin("$DIR/non-whitespace-trimming-unicode.rs") @@ -2278,7 +2280,7 @@ fn main() { //[ascii]~^ ERROR cannot add `&str` to `&str` } "##; - let input = Level::Error + let input = Level::ERROR .message("cannot add `&str` to `&str`") .id("E0369") .group( @@ -2296,13 +2298,13 @@ fn main() { ), ) .element( - Level::Note + Level::NOTE .title("string concatenation requires an owned `String` on the left"), ), ) .group( Group::new() - .element(Level::Help.title("create an owned `String` from a string reference")) + .element(Level::HELP.title("create an owned `String` from a string reference")) .element( Snippet::source(source) .origin("$DIR/non-1-width-unicode-multiline-label.rs") @@ -2345,7 +2347,7 @@ fn foo() { } "##; let bin_source = "�|�\u{0002}!5�cc\u{0015}\u{0002}�Ӻi��WWj�ȥ�'�}�\u{0012}�J�ȉ��W�\u{001e}O�@����\u{001c}w�V���LO����\u{0014}[ \u{0003}_�'���SQ�~ذ��ų&��-\t��lN~��!@␌ _#���kQ��h�\u{001d}�:�\u{001c}\u{0007}�"; - let input = Level::Error + let input = Level::ERROR .message("couldn't read `$DIR/not-utf8.bin`: stream did not contain valid UTF-8") .group( Group::new().element( @@ -2357,14 +2359,14 @@ fn foo() { ) .group( Group::new() - .element(Level::Note.title("byte `193` is not valid utf-8")) + .element(Level::NOTE.title("byte `193` is not valid utf-8")) .element( Snippet::source(bin_source) .origin("$DIR/not-utf8.bin") .fold(true) .annotation(AnnotationKind::Primary.span(0..0)), ) - .element(Level::Note.title("this error originates in the macro `include` (in Nightly builds, run with -Z macro-backtrace for more info)")), + .element(Level::NOTE.title("this error originates in the macro `include` (in Nightly builds, run with -Z macro-backtrace for more info)")), ); let expected = str![[r#" diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index 47a33990..7a795dbc 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -2,7 +2,7 @@ //! //! [parser-tests]: https://github.com/rust-lang/rust/blob/894f7a4ba6554d3797404bbf550d9919df060b97/compiler/rustc_parse/src/parser/tests.rs -use annotate_snippets::{AnnotationKind, Group, Level, Origin, Renderer, Snippet}; +use annotate_snippets::{level::Level, AnnotationKind, Group, Origin, Renderer, Snippet}; use snapbox::{assert_data_eq, str}; @@ -12,7 +12,7 @@ fn ends_on_col0() { fn foo() { } "#; - let input = Level::Error.message("foo").group( + let input = Level::ERROR.message("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -42,7 +42,7 @@ fn foo() { } "#; - let input = Level::Error.message("foo").group( + let input = Level::ERROR.message("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -74,7 +74,7 @@ fn foo() { X2 Y2 } "#; - let input = Level::Error.message("foo").group( + let input = Level::ERROR.message("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -117,7 +117,7 @@ fn foo() { Y1 X1 } "#; - let input = Level::Error.message("foo").group( + let input = Level::ERROR.message("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -161,7 +161,7 @@ fn foo() { X3 Y3 Z3 } "#; - let input = Level::Error.message("foo").group( + let input = Level::ERROR.message("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -205,7 +205,7 @@ fn foo() { X2 Y2 Z2 } "#; - let input = Level::Error.message("foo").group( + let input = Level::ERROR.message("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -252,7 +252,7 @@ fn foo() { X2 Y2 Z2 } "#; - let input = Level::Error.message("foo").group( + let input = Level::ERROR.message("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -300,7 +300,7 @@ fn foo() { X3 Y3 Z3 } "#; - let input = Level::Error.message("foo").group( + let input = Level::ERROR.message("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -350,7 +350,7 @@ fn foo() { X3 Y3 Z3 } "#; - let input = Level::Error.message("foo").group( + let input = Level::ERROR.message("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -394,7 +394,7 @@ fn foo() { X3 Y3 Z3 } "#; - let input = Level::Error.message("foo").group( + let input = Level::ERROR.message("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -437,7 +437,7 @@ fn foo() { a { b { c } d } } "#; - let input = Level::Error.message("foo").group( + let input = Level::ERROR.message("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -470,7 +470,7 @@ fn foo() { a { b { c } d } } "#; - let input = Level::Error.message("foo").group( + let input = Level::ERROR.message("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -502,7 +502,7 @@ fn foo() { a { b { c } d } } "#; - let input = Level::Error.message("foo").group( + let input = Level::ERROR.message("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -537,7 +537,7 @@ fn foo() { a { b { c } d } } "#; - let input = Level::Error.message("foo").group( + let input = Level::ERROR.message("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -571,7 +571,7 @@ fn foo() { a bc d } "#; - let input = Level::Error.message("foo").group( + let input = Level::ERROR.message("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -605,7 +605,7 @@ fn foo() { a { b { c } d } } "#; - let input = Level::Error.message("foo").group( + let input = Level::ERROR.message("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -633,7 +633,7 @@ fn foo() { a { b { c } d } } "#; - let input = Level::Error.message("foo").group( + let input = Level::ERROR.message("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -662,7 +662,7 @@ fn foo() { a { b { c } d } } "#; - let input = Level::Error.message("foo").group( + let input = Level::ERROR.message("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -701,7 +701,7 @@ fn foo() { a { b { c } d } } "#; - let input = Level::Error.message("foo").group( + let input = Level::ERROR.message("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -732,7 +732,7 @@ fn foo() { a { b { c } d } } "#; - let input = Level::Error.message("foo").group( + let input = Level::ERROR.message("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -772,7 +772,7 @@ fn foo() { X3 Y3 Z3 } "#; - let input = Level::Error.message("foo").group( + let input = Level::ERROR.message("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -832,7 +832,7 @@ fn foo() { X3 Y3 Z3 } "#; - let input = Level::Error.message("foo").group( + let input = Level::ERROR.message("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -886,7 +886,7 @@ fn issue_91334() { fn f(){||yield(((){), "#; - let input = Level::Error + let input = Level::ERROR .message("this file contains an unclosed delimiter") .group( Group::new().element( @@ -957,7 +957,7 @@ fn main() { } } "#; - let input = Level::Error + let input = Level::ERROR .message("`break` with value from a `while` loop") .id("E0571") .group( @@ -981,7 +981,7 @@ fn main() { .group( Group::new() .element( - Level::Help + Level::HELP .title("use `break` on its own without a value inside this `while` loop"), ) .element( @@ -1167,7 +1167,7 @@ fn nsize() { } "#; let input = - Level::Error + Level::ERROR .message("`V0usize` cannot be safely transmuted into `[usize; 2]`") .id("E0277") .group( @@ -1183,7 +1183,7 @@ fn nsize() { ) .group( Group::new() - .element(Level::Note.title("required by a bound in `is_transmutable`")) + .element(Level::NOTE.title("required by a bound in `is_transmutable`")) .element( Snippet::source(source) .line_start(1) @@ -1253,7 +1253,7 @@ fn main() { assert::is_maybe_transmutable::<&'static [u8; 0], &'static [u16; 0]>(); //~ ERROR `&[u8; 0]` cannot be safely transmuted into `&[u16; 0]` } "#; - let input = Level::Error + let input = Level::ERROR .message("`&[u8; 0]` cannot be safely transmuted into `&[u16; 0]`") .id("E027s7") .group( @@ -1322,7 +1322,7 @@ fn g() { } fn main() {} "#; - let input = Level::Error + let input = Level::ERROR .message("expected function, found `{integer}`") .id("E0618") .group( @@ -1413,7 +1413,7 @@ macro_rules! outer_macro { outer_macro!(FirstStruct, FirstAttrStruct); "#; - let input = Level::Warning + let input = Level::WARNING .message("non-local `macro_rules!` definition, `#[macro_export]` macro should be written at top level module") .group( Group::new() @@ -1441,17 +1441,17 @@ outer_macro!(FirstStruct, FirstAttrStruct); ), ) .element( - Level::Help + Level::HELP .title("remove the `#[macro_export]` or move this `macro_rules!` outside the of the current function `main`") ) .element( - Level::Note + Level::NOTE .title("a `macro_rules!` definition is non-local if it is nested inside an item and has a `#[macro_export]` attribute") ), ) .group( Group::new() - .element(Level::Note.title("the lint level is defined here")) + .element(Level::NOTE.title("the lint level is defined here")) .element( Snippet::source(source) .line_start(1) @@ -1546,7 +1546,7 @@ macro_rules! inline { () => () } "#; - let input = Level::Error + let input = Level::ERROR .message("can't call method `pow` on ambiguous numeric type `{integer}`") .id("E0689") .group( @@ -1560,7 +1560,7 @@ macro_rules! inline { ) .group( Group::new() - .element(Level::Help.title("you must specify a type for this binding, like `i32`")) + .element(Level::HELP.title("you must specify a type for this binding, like `i32`")) .element( Snippet::source(aux_source) .line_start(1) @@ -1610,7 +1610,7 @@ fn courier_to_des_moines_and_points_west(data: &[u32]) -> String { fn main() {} "#; - let input = Level::Error + let input = Level::ERROR .message("type annotations needed") .id("E0282") .group( @@ -1716,7 +1716,7 @@ fn nonempty<const N: usize>(arrayN_of_empty: [!; N]) { fn main() {} "##; - let input = Level::Error + let input = Level::ERROR .message( "non-exhaustive patterns: `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered" ) @@ -1736,7 +1736,7 @@ fn main() {} ) .group( Group::new() - .element(Level::Note.title("`NonEmptyEnum5` defined here")) + .element(Level::NOTE.title("`NonEmptyEnum5` defined here")) .element( Snippet::source(source) .line_start(1) @@ -1749,13 +1749,13 @@ fn main() {} .annotation(AnnotationKind::Context.span(878..880).label("not covered")) .annotation(AnnotationKind::Context.span(890..892).label("not covered")) ) - .element(Level::Note.title("the matched value is of type `NonEmptyEnum5`")) - .element(Level::Note.title("match arms with guards don't count towards exhaustivity")) + .element(Level::NOTE.title("the matched value is of type `NonEmptyEnum5`")) + .element(Level::NOTE.title("match arms with guards don't count towards exhaustivity")) ) .group( Group::new() .element( - Level::Help + Level::HELP .title("ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown, or multiple match arms") ) .element( @@ -1818,7 +1818,7 @@ fn main() { //~^ ERROR must be specified } "#; - let input = Level::Error + let input = Level::ERROR .message("the trait alias `EqAlias` is not dyn compatible") .id("E0038") .group( @@ -1837,7 +1837,7 @@ fn main() { .group( Group::new() .element( - Level::Note + Level::NOTE .title("for a trait to be dyn compatible it needs to allow building a vtable\nfor more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>")) .element( Origin::new("$SRC_DIR/core/src/cmp.rs") From a1a11a53235087853a98c9404627abf90a8d383d Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 16 Apr 2025 15:09:13 -0500 Subject: [PATCH 330/455] fix: Remove unused Level2 --- src/level.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/level.rs b/src/level.rs index 7f920249..81aea8c9 100644 --- a/src/level.rs +++ b/src/level.rs @@ -34,16 +34,6 @@ pub struct Level<'a> { pub(crate) level: LevelInner, } -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub enum Level2<'a> { - Builtin(LevelInner), - Custom { - name: Option<&'a str>, - level: LevelInner, - }, - None, -} - impl<'a> Level<'a> { pub const ERROR: Level<'a> = ERROR; pub const WARNING: Level<'a> = WARNING; From a792c392cd720e11a3a03e2fa2d1fa609979821c Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 16 Apr 2025 15:09:46 -0500 Subject: [PATCH 331/455] fix: Hide LevelInner --- src/level.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/level.rs b/src/level.rs index 81aea8c9..46eaef14 100644 --- a/src/level.rs +++ b/src/level.rs @@ -97,7 +97,7 @@ impl<'a> Level<'a> { } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub enum LevelInner { +pub(crate) enum LevelInner { Error, Warning, Info, From 1d802c3480aedbebff07ad950c51b86422635b42 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 16 Apr 2025 15:12:03 -0500 Subject: [PATCH 332/455] fix: Re-export Level at the top-level --- benches/bench.rs | 2 +- examples/custom_error.rs | 2 +- examples/custom_level.rs | 2 +- examples/expected_type.rs | 2 +- examples/footer.rs | 2 +- examples/format.rs | 2 +- examples/highlight_source.rs | 2 +- examples/highlight_title.rs | 2 +- examples/multislice.rs | 2 +- src/lib.rs | 2 ++ src/renderer/mod.rs | 2 +- src/renderer/styled_buffer.rs | 2 +- src/snippet.rs | 2 +- tests/fixtures/deserialize.rs | 2 +- tests/formatter.rs | 4 +--- tests/rustc_tests.rs | 2 +- 16 files changed, 17 insertions(+), 17 deletions(-) diff --git a/benches/bench.rs b/benches/bench.rs index d50cf435..a6cdb37d 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -1,4 +1,4 @@ -use annotate_snippets::{level::Level, AnnotationKind, Group, Renderer, Snippet}; +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; #[divan::bench] fn simple() -> String { diff --git a/examples/custom_error.rs b/examples/custom_error.rs index 4050d400..418d3425 100644 --- a/examples/custom_error.rs +++ b/examples/custom_error.rs @@ -1,5 +1,5 @@ use annotate_snippets::renderer::OutputTheme; -use annotate_snippets::{level::Level, AnnotationKind, Group, Renderer, Snippet}; +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; fn main() { let source = r#"//@ compile-flags: -Ztreat-err-as-bug diff --git a/examples/custom_level.rs b/examples/custom_level.rs index 804f7741..87cad9ad 100644 --- a/examples/custom_level.rs +++ b/examples/custom_level.rs @@ -1,5 +1,5 @@ use annotate_snippets::renderer::OutputTheme; -use annotate_snippets::{level::Level, AnnotationKind, Group, Patch, Renderer, Snippet}; +use annotate_snippets::{AnnotationKind, Group, Level, Patch, Renderer, Snippet}; fn main() { let source = r#"// Regression test for issue #114529 diff --git a/examples/expected_type.rs b/examples/expected_type.rs index 9a51dce5..5c801c71 100644 --- a/examples/expected_type.rs +++ b/examples/expected_type.rs @@ -1,4 +1,4 @@ -use annotate_snippets::{level::Level, AnnotationKind, Group, Renderer, Snippet}; +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; fn main() { let source = r#" annotations: vec![SourceAnnotation { diff --git a/examples/footer.rs b/examples/footer.rs index d61e84b4..95de15c6 100644 --- a/examples/footer.rs +++ b/examples/footer.rs @@ -1,4 +1,4 @@ -use annotate_snippets::{level::Level, AnnotationKind, Group, Renderer, Snippet}; +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; fn main() { let message = Level::ERROR diff --git a/examples/format.rs b/examples/format.rs index 5324413c..eb68d828 100644 --- a/examples/format.rs +++ b/examples/format.rs @@ -1,4 +1,4 @@ -use annotate_snippets::{level::Level, AnnotationKind, Group, Renderer, Snippet}; +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; fn main() { let source = r#") -> Option<String> { diff --git a/examples/highlight_source.rs b/examples/highlight_source.rs index 9962542d..165dd860 100644 --- a/examples/highlight_source.rs +++ b/examples/highlight_source.rs @@ -1,4 +1,4 @@ -use annotate_snippets::{level::Level, AnnotationKind, Group, Renderer, Snippet}; +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; fn main() { let source = r#"//@ compile-flags: -Z teach diff --git a/examples/highlight_title.rs b/examples/highlight_title.rs index e2da54ff..ea746129 100644 --- a/examples/highlight_title.rs +++ b/examples/highlight_title.rs @@ -1,4 +1,4 @@ -use annotate_snippets::{level::Level, AnnotationKind, Group, Renderer, Snippet}; +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; use anstyle::Effects; fn main() { diff --git a/examples/multislice.rs b/examples/multislice.rs index ddd938e4..9be82e96 100644 --- a/examples/multislice.rs +++ b/examples/multislice.rs @@ -1,4 +1,4 @@ -use annotate_snippets::{level::Level, Annotation, Group, Renderer, Snippet}; +use annotate_snippets::{Annotation, Group, Level, Renderer, Snippet}; fn main() { let message = Level::ERROR.message("mismatched types").group( diff --git a/src/lib.rs b/src/lib.rs index eee2b6f3..0d23b29a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,6 +53,8 @@ pub fn normalize_untrusted_str(s: &str) -> String { renderer::normalize_whitespace(s) } +#[doc(inline)] +pub use level::Level; #[doc(inline)] pub use renderer::Renderer; pub use snippet::ColumnSeparator; diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 8efcff84..12fc5d96 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -5,7 +5,7 @@ //! # Example //! ``` //! use annotate_snippets::*; -//! use annotate_snippets::level::Level; +//! use annotate_snippets::Level; //! //! let source = r#" //! use baz::zed::bar; diff --git a/src/renderer/styled_buffer.rs b/src/renderer/styled_buffer.rs index 7114683b..c9b805a0 100644 --- a/src/renderer/styled_buffer.rs +++ b/src/renderer/styled_buffer.rs @@ -2,9 +2,9 @@ //! //! [styled_buffer]: https://github.com/rust-lang/rust/blob/894f7a4ba6554d3797404bbf550d9919df060b97/compiler/rustc_errors/src/styled_buffer.rs -use crate::level::Level; use crate::renderer::stylesheet::Stylesheet; use crate::renderer::ElementStyle; +use crate::Level; use std::fmt; use std::fmt::Write; diff --git a/src/snippet.rs b/src/snippet.rs index fe1239d4..aa5f9ff4 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -1,7 +1,7 @@ //! Structures used as an input for the library. -use crate::level::Level; use crate::renderer::source_map::SourceMap; +use crate::Level; use std::ops::Range; pub(crate) const ERROR_TXT: &str = "error"; diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs index 8f45b363..55374df0 100644 --- a/tests/fixtures/deserialize.rs +++ b/tests/fixtures/deserialize.rs @@ -3,7 +3,7 @@ use std::ops::Range; use annotate_snippets::renderer::DEFAULT_TERM_WIDTH; use annotate_snippets::{ - level::Level, Annotation, AnnotationKind, Element, Group, Message, Patch, Renderer, Snippet, + Annotation, AnnotationKind, Element, Group, Level, Message, Patch, Renderer, Snippet, }; #[derive(Deserialize)] diff --git a/tests/formatter.rs b/tests/formatter.rs index 3f0e7cd4..22b517b6 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -1,6 +1,4 @@ -use annotate_snippets::{ - level::Level, Annotation, AnnotationKind, Group, Patch, Renderer, Snippet, -}; +use annotate_snippets::{Annotation, AnnotationKind, Group, Level, Patch, Renderer, Snippet}; use annotate_snippets::renderer::OutputTheme; use snapbox::{assert_data_eq, str}; diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index 7a795dbc..93860d0e 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -2,7 +2,7 @@ //! //! [parser-tests]: https://github.com/rust-lang/rust/blob/894f7a4ba6554d3797404bbf550d9919df060b97/compiler/rustc_parse/src/parser/tests.rs -use annotate_snippets::{level::Level, AnnotationKind, Group, Origin, Renderer, Snippet}; +use annotate_snippets::{AnnotationKind, Group, Level, Origin, Renderer, Snippet}; use snapbox::{assert_data_eq, str}; From 4c113cca128ffe8e1d62625daddd58ed6181a7db Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 16 Apr 2025 15:23:22 -0500 Subject: [PATCH 333/455] fix!: Rename `Renderer::line_no` to `Renderer::line_num` Fixes #175 --- src/renderer/mod.rs | 8 ++++---- src/renderer/stylesheet.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 12fc5d96..f421c2df 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -101,7 +101,7 @@ impl Renderer { info: BRIGHT_BLUE.effects(Effects::BOLD), note: AnsiColor::BrightGreen.on_default().effects(Effects::BOLD), help: AnsiColor::BrightCyan.on_default().effects(Effects::BOLD), - line_no: BRIGHT_BLUE.effects(Effects::BOLD), + line_num: BRIGHT_BLUE.effects(Effects::BOLD), emphasis: if USE_WINDOWS_COLORS { AnsiColor::BrightWhite.on_default() } else { @@ -178,8 +178,8 @@ impl Renderer { } /// Set the output style for line numbers - pub const fn line_no(mut self, style: Style) -> Self { - self.stylesheet.line_no = style; + pub const fn line_num(mut self, style: Style) -> Self { + self.stylesheet.line_num = style; self } @@ -2665,7 +2665,7 @@ impl ElementStyle { ElementStyle::Addition => stylesheet.addition, ElementStyle::Removal => stylesheet.removal, ElementStyle::LineAndColumn => stylesheet.none, - ElementStyle::LineNumber => stylesheet.line_no, + ElementStyle::LineNumber => stylesheet.line_num, ElementStyle::Quotation => stylesheet.none, ElementStyle::MainHeaderMsg => stylesheet.emphasis, ElementStyle::UnderlinePrimary | ElementStyle::LabelPrimary => level.style(stylesheet), diff --git a/src/renderer/stylesheet.rs b/src/renderer/stylesheet.rs index 4aa21a5a..075cad42 100644 --- a/src/renderer/stylesheet.rs +++ b/src/renderer/stylesheet.rs @@ -7,7 +7,7 @@ pub(crate) struct Stylesheet { pub(crate) info: Style, pub(crate) note: Style, pub(crate) help: Style, - pub(crate) line_no: Style, + pub(crate) line_num: Style, pub(crate) emphasis: Style, pub(crate) none: Style, pub(crate) context: Style, @@ -29,7 +29,7 @@ impl Stylesheet { info: Style::new(), note: Style::new(), help: Style::new(), - line_no: Style::new(), + line_num: Style::new(), emphasis: Style::new(), none: Style::new(), context: Style::new(), From f6d191440268c5309c5c70755f1eaf49d2e1db33 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 16 Apr 2025 15:36:57 -0500 Subject: [PATCH 334/455] fix: Clarify ColumnSeparator is Padding --- src/lib.rs | 2 +- src/renderer/mod.rs | 4 ++-- src/snippet.rs | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0d23b29a..021ed524 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,5 +57,5 @@ pub fn normalize_untrusted_str(s: &str) -> String { pub use level::Level; #[doc(inline)] pub use renderer::Renderer; -pub use snippet::ColumnSeparator; +pub use snippet::Padding; pub use snippet::*; diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index f421c2df..1e88bca9 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -383,7 +383,7 @@ impl Renderer { self.render_origin(buffer, max_line_num_len, origin); last_was_suggestion = false; } - Element::ColumnSeparator(_) => { + Element::Padding(_) => { self.draw_col_separator_no_space( buffer, buffer.num_lines(), @@ -430,7 +430,7 @@ impl Renderer { let (has_primary_spans, has_span_labels) = next_section.map_or((false, false), |s| match s { - Element::Title(_) | Element::ColumnSeparator(_) => (false, false), + Element::Title(_) | Element::Padding(_) => (false, false), Element::Cause(cause) => ( cause.markers.iter().any(|m| m.kind.is_primary()), cause.markers.iter().any(|m| m.label.is_some()), diff --git a/src/snippet.rs b/src/snippet.rs index aa5f9ff4..bf22c5be 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -34,7 +34,7 @@ impl<'a> Message<'a> { v.elements .iter() .map(|s| match s { - Element::Title(_) | Element::Origin(_) | Element::ColumnSeparator(_) => 0, + Element::Title(_) | Element::Origin(_) | Element::Padding(_) => 0, Element::Cause(cause) => { let end = cause .markers @@ -104,7 +104,7 @@ pub enum Element<'a> { Cause(Snippet<'a, Annotation<'a>>), Suggestion(Snippet<'a, Patch<'a>>), Origin(Origin<'a>), - ColumnSeparator(ColumnSeparator), + Padding(Padding), } impl<'a> From<Title<'a>> for Element<'a> { @@ -131,14 +131,14 @@ impl<'a> From<Origin<'a>> for Element<'a> { } } -impl From<ColumnSeparator> for Element<'_> { - fn from(value: ColumnSeparator) -> Self { - Self::ColumnSeparator(value) +impl From<Padding> for Element<'_> { + fn from(value: Padding) -> Self { + Self::Padding(value) } } #[derive(Debug)] -pub struct ColumnSeparator; +pub struct Padding; #[derive(Debug)] pub struct Title<'a> { From d3b79b6466562c810d55b3d256de94ce5c352812 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 16 Apr 2025 15:59:54 -0500 Subject: [PATCH 335/455] fix!: Rename Level::title/Level::message to Level::header This doesn't completely take care of #118 because of #196 --- benches/bench.rs | 4 +- examples/custom_error.rs | 2 +- examples/custom_level.rs | 2 +- examples/expected_type.rs | 2 +- examples/footer.rs | 2 +- examples/format.rs | 2 +- examples/highlight_source.rs | 2 +- examples/highlight_title.rs | 2 +- examples/multislice.rs | 2 +- src/level.rs | 4 +- src/renderer/mod.rs | 2 +- tests/fixtures/color/ann_eof.toml | 2 +- tests/fixtures/color/ann_insertion.toml | 2 +- tests/fixtures/color/ann_multiline.toml | 2 +- tests/fixtures/color/ann_multiline2.toml | 2 +- tests/fixtures/color/ann_removed_nl.toml | 2 +- .../color/ensure-emoji-highlight-width.toml | 2 +- tests/fixtures/color/fold_ann_multiline.toml | 2 +- .../fixtures/color/fold_bad_origin_line.toml | 2 +- tests/fixtures/color/fold_leading.toml | 2 +- tests/fixtures/color/fold_trailing.toml | 2 +- tests/fixtures/color/issue_9.toml | 2 +- .../fixtures/color/multiple_annotations.toml | 2 +- tests/fixtures/color/simple.toml | 2 +- tests/fixtures/color/strip_line.toml | 2 +- tests/fixtures/color/strip_line_char.toml | 2 +- tests/fixtures/color/strip_line_non_ws.toml | 2 +- tests/fixtures/deserialize.rs | 6 +- tests/formatter.rs | 124 +++++++++--------- tests/rustc_tests.rs | 64 ++++----- 30 files changed, 126 insertions(+), 126 deletions(-) diff --git a/benches/bench.rs b/benches/bench.rs index a6cdb37d..c3799fbd 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -24,7 +24,7 @@ fn simple() -> String { _ => continue, } }"#; - let message = Level::ERROR.message("mismatched types").id("E0308").group( + let message = Level::ERROR.header("mismatched types").id("E0308").group( Group::new().element( Snippet::source(source) .line_start(51) @@ -69,7 +69,7 @@ fn fold(bencher: divan::Bencher<'_, '_>, context: usize) { (input, span) }) .bench_values(|(input, span)| { - let message = Level::ERROR.message("mismatched types").id("E0308").group( + let message = Level::ERROR.header("mismatched types").id("E0308").group( Group::new().element( Snippet::source(&input) .fold(true) diff --git a/examples/custom_error.rs b/examples/custom_error.rs index 418d3425..b9e27b31 100644 --- a/examples/custom_error.rs +++ b/examples/custom_error.rs @@ -17,7 +17,7 @@ pub static C: u32 = 0 - 1; "#; let message = Level::ERROR .text(Some("error: internal compiler error")) - .message("could not evaluate static initializer") + .header("could not evaluate static initializer") .id("E0080") .group( Group::new().element( diff --git a/examples/custom_level.rs b/examples/custom_level.rs index 87cad9ad..b2af361a 100644 --- a/examples/custom_level.rs +++ b/examples/custom_level.rs @@ -30,7 +30,7 @@ fn main() { } "#; let message = Level::ERROR - .message("`break` with value from a `while` loop") + .header("`break` with value from a `while` loop") .id("E0571") .group( Group::new().element( diff --git a/examples/expected_type.rs b/examples/expected_type.rs index 5c801c71..02abdecf 100644 --- a/examples/expected_type.rs +++ b/examples/expected_type.rs @@ -6,7 +6,7 @@ fn main() { , range: <22, 25>,"#; let message = - Level::ERROR.message("expected type, found `22`").group( + Level::ERROR.header("expected type, found `22`").group( Group::new().element( Snippet::source(source) .line_start(26) diff --git a/examples/footer.rs b/examples/footer.rs index 95de15c6..ca6f1dc7 100644 --- a/examples/footer.rs +++ b/examples/footer.rs @@ -2,7 +2,7 @@ use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; fn main() { let message = Level::ERROR - .message("mismatched types") + .header("mismatched types") .id("E0308") .group( Group::new().element( diff --git a/examples/format.rs b/examples/format.rs index eb68d828..4b688d4b 100644 --- a/examples/format.rs +++ b/examples/format.rs @@ -23,7 +23,7 @@ fn main() { _ => continue, } }"#; - let message = Level::ERROR.message("mismatched types").id("E0308").group( + let message = Level::ERROR.header("mismatched types").id("E0308").group( Group::new().element( Snippet::source(source) .line_start(51) diff --git a/examples/highlight_source.rs b/examples/highlight_source.rs index 165dd860..92d8114f 100644 --- a/examples/highlight_source.rs +++ b/examples/highlight_source.rs @@ -10,7 +10,7 @@ const CON: Vec<i32> = vec![1, 2, 3]; //~ ERROR E0010 fn main() {} "#; let message = Level::ERROR - .message("allocations are not allowed in constants") + .header("allocations are not allowed in constants") .id("E0010") .group( Group::new() diff --git a/examples/highlight_title.rs b/examples/highlight_title.rs index ea746129..12c106a6 100644 --- a/examples/highlight_title.rs +++ b/examples/highlight_title.rs @@ -43,7 +43,7 @@ fn main() { magenta.render_reset() ); - let message = Level::ERROR.message("mismatched types").id("E0308").group( + let message = Level::ERROR.header("mismatched types").id("E0308").group( Group::new() .element( Snippet::source(source) diff --git a/examples/multislice.rs b/examples/multislice.rs index 9be82e96..a7d340ad 100644 --- a/examples/multislice.rs +++ b/examples/multislice.rs @@ -1,7 +1,7 @@ use annotate_snippets::{Annotation, Group, Level, Renderer, Snippet}; fn main() { - let message = Level::ERROR.message("mismatched types").group( + let message = Level::ERROR.header("mismatched types").group( Group::new() .element( Snippet::<Annotation<'_>>::source("Foo") diff --git a/src/level.rs b/src/level.rs index 46eaef14..3fd504de 100644 --- a/src/level.rs +++ b/src/level.rs @@ -56,12 +56,12 @@ impl<'a> Level<'a> { /// Text passed to this function is considered "untrusted input", as such /// all text is passed through a normalization function. Pre-styled text is /// not allowed to be passed to this function. - pub fn message(self, title: &'a str) -> Message<'a> { + pub fn header(self, header: &'a str) -> Message<'a> { Message { id: None, groups: vec![Group::new().element(Element::Title(Title { level: self, - title, + title: header, primary: true, }))], } diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 1e88bca9..e5572e5e 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -19,7 +19,7 @@ //! } //! "#; //! Level::ERROR -//! .message("unresolved import `baz::zed`") +//! .header("unresolved import `baz::zed`") //! .id("E0432") //! .group( //! Group::new().element( diff --git a/tests/fixtures/color/ann_eof.toml b/tests/fixtures/color/ann_eof.toml index ac129f3f..ef711dee 100644 --- a/tests/fixtures/color/ann_eof.toml +++ b/tests/fixtures/color/ann_eof.toml @@ -1,6 +1,6 @@ [message] level = "Error" -title = "expected `.`, `=`" +header = "expected `.`, `=`" [[message.sections]] type = "Cause" diff --git a/tests/fixtures/color/ann_insertion.toml b/tests/fixtures/color/ann_insertion.toml index 13bc13ca..30af1bfb 100644 --- a/tests/fixtures/color/ann_insertion.toml +++ b/tests/fixtures/color/ann_insertion.toml @@ -1,6 +1,6 @@ [message] level = "Error" -title = "expected `.`, `=`" +header = "expected `.`, `=`" [[message.sections]] type = "Cause" diff --git a/tests/fixtures/color/ann_multiline.toml b/tests/fixtures/color/ann_multiline.toml index 722c3e18..2a5f206b 100644 --- a/tests/fixtures/color/ann_multiline.toml +++ b/tests/fixtures/color/ann_multiline.toml @@ -1,7 +1,7 @@ [message] level = "Error" id = "E0027" -title = "pattern does not mention fields `lineno`, `content`" +header = "pattern does not mention fields `lineno`, `content`" [[message.sections]] type = "Cause" diff --git a/tests/fixtures/color/ann_multiline2.toml b/tests/fixtures/color/ann_multiline2.toml index 329beb49..854b38a7 100644 --- a/tests/fixtures/color/ann_multiline2.toml +++ b/tests/fixtures/color/ann_multiline2.toml @@ -1,7 +1,7 @@ [message] level = "Error" id = "E####" -title = "spacing error found" +header = "spacing error found" [[message.sections]] type = "Cause" diff --git a/tests/fixtures/color/ann_removed_nl.toml b/tests/fixtures/color/ann_removed_nl.toml index 8ed96bcc..6ffeb7a0 100644 --- a/tests/fixtures/color/ann_removed_nl.toml +++ b/tests/fixtures/color/ann_removed_nl.toml @@ -1,6 +1,6 @@ [message] level = "Error" -title = "expected `.`, `=`" +header = "expected `.`, `=`" [[message.sections]] type = "Cause" diff --git a/tests/fixtures/color/ensure-emoji-highlight-width.toml b/tests/fixtures/color/ensure-emoji-highlight-width.toml index 8d7a14aa..669959f6 100644 --- a/tests/fixtures/color/ensure-emoji-highlight-width.toml +++ b/tests/fixtures/color/ensure-emoji-highlight-width.toml @@ -1,5 +1,5 @@ [message] -title = "invalid character ` ` in package name: `haha this isn't a valid name 🐛`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)" +header = "invalid character ` ` in package name: `haha this isn't a valid name 🐛`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)" level = "Error" diff --git a/tests/fixtures/color/fold_ann_multiline.toml b/tests/fixtures/color/fold_ann_multiline.toml index 745ef42e..2cee27d6 100644 --- a/tests/fixtures/color/fold_ann_multiline.toml +++ b/tests/fixtures/color/fold_ann_multiline.toml @@ -1,7 +1,7 @@ [message] level = "Error" id = "E0308" -title = "mismatched types" +header = "mismatched types" [[message.sections]] type = "Cause" diff --git a/tests/fixtures/color/fold_bad_origin_line.toml b/tests/fixtures/color/fold_bad_origin_line.toml index 3b0d827f..2fab2d64 100644 --- a/tests/fixtures/color/fold_bad_origin_line.toml +++ b/tests/fixtures/color/fold_bad_origin_line.toml @@ -1,6 +1,6 @@ [message] level = "Error" -title = "" +header = "" [[message.sections]] type = "Cause" diff --git a/tests/fixtures/color/fold_leading.toml b/tests/fixtures/color/fold_leading.toml index 564187be..0ef043c9 100644 --- a/tests/fixtures/color/fold_leading.toml +++ b/tests/fixtures/color/fold_leading.toml @@ -1,7 +1,7 @@ [message] level = "Error" id = "E0308" -title = "invalid type: integer `20`, expected a bool" +header = "invalid type: integer `20`, expected a bool" [[message.sections]] type = "Cause" diff --git a/tests/fixtures/color/fold_trailing.toml b/tests/fixtures/color/fold_trailing.toml index eea6365e..91e4ab4f 100644 --- a/tests/fixtures/color/fold_trailing.toml +++ b/tests/fixtures/color/fold_trailing.toml @@ -1,7 +1,7 @@ [message] level = "Error" id = "E0308" -title = "invalid type: integer `20`, expected a lints table" +header = "invalid type: integer `20`, expected a lints table" [[message.sections]] type = "Cause" diff --git a/tests/fixtures/color/issue_9.toml b/tests/fixtures/color/issue_9.toml index 96ad2c07..f4239154 100644 --- a/tests/fixtures/color/issue_9.toml +++ b/tests/fixtures/color/issue_9.toml @@ -1,6 +1,6 @@ [message] level = "Error" -title = "expected one of `.`, `;`, `?`, or an operator, found `for`" +header = "expected one of `.`, `;`, `?`, or an operator, found `for`" [[message.sections]] type = "Cause" diff --git a/tests/fixtures/color/multiple_annotations.toml b/tests/fixtures/color/multiple_annotations.toml index f4c90a4e..367c53ee 100644 --- a/tests/fixtures/color/multiple_annotations.toml +++ b/tests/fixtures/color/multiple_annotations.toml @@ -1,6 +1,6 @@ [message] level = "Error" -title = "" +header = "" [[message.sections]] type = "Cause" diff --git a/tests/fixtures/color/simple.toml b/tests/fixtures/color/simple.toml index b70f11cc..d5a36474 100644 --- a/tests/fixtures/color/simple.toml +++ b/tests/fixtures/color/simple.toml @@ -1,6 +1,6 @@ [message] level = "Error" -title = "expected one of `.`, `;`, `?`, or an operator, found `for`" +header = "expected one of `.`, `;`, `?`, or an operator, found `for`" [[message.sections]] type = "Cause" diff --git a/tests/fixtures/color/strip_line.toml b/tests/fixtures/color/strip_line.toml index d7af6862..18a7805d 100644 --- a/tests/fixtures/color/strip_line.toml +++ b/tests/fixtures/color/strip_line.toml @@ -1,7 +1,7 @@ [message] level = "Error" id = "E0308" -title = "mismatched types" +header = "mismatched types" [[message.sections]] type = "Cause" diff --git a/tests/fixtures/color/strip_line_char.toml b/tests/fixtures/color/strip_line_char.toml index 6585005c..3174cedc 100644 --- a/tests/fixtures/color/strip_line_char.toml +++ b/tests/fixtures/color/strip_line_char.toml @@ -1,7 +1,7 @@ [message] level = "Error" id = "E0308" -title = "mismatched types" +header = "mismatched types" [[message.sections]] type = "Cause" diff --git a/tests/fixtures/color/strip_line_non_ws.toml b/tests/fixtures/color/strip_line_non_ws.toml index 1f085b5f..b7844ec2 100644 --- a/tests/fixtures/color/strip_line_non_ws.toml +++ b/tests/fixtures/color/strip_line_non_ws.toml @@ -1,7 +1,7 @@ [message] level = "Error" id = "E0308" -title = "mismatched types" +header = "mismatched types" [[message.sections]] type = "Cause" diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs index 55374df0..20429665 100644 --- a/tests/fixtures/deserialize.rs +++ b/tests/fixtures/deserialize.rs @@ -16,7 +16,7 @@ pub(crate) struct Fixture { #[derive(Deserialize)] pub struct MessageDef { pub level: LevelDef, - pub title: String, + pub header: String, #[serde(default)] pub id: Option<String>, #[serde(default)] @@ -27,11 +27,11 @@ impl<'a> From<&'a MessageDef> for Message<'a> { fn from(val: &'a MessageDef) -> Self { let MessageDef { level, - title, + header, id, sections, } = val; - let mut message = Level::from(level).message(title); + let mut message = Level::from(level).header(header); if let Some(id) = id { message = message.id(id); } diff --git a/tests/formatter.rs b/tests/formatter.rs index 22b517b6..75cf8532 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -5,7 +5,7 @@ use snapbox::{assert_data_eq, str}; #[test] fn test_i_29() { - let snippets = Level::ERROR.message("oops").group( + let snippets = Level::ERROR.header("oops").group( Group::new().element( Snippet::source("First line\r\nSecond oops line") .origin("<current file>") @@ -27,7 +27,7 @@ error: oops #[test] fn test_point_to_double_width_characters() { - let snippets = Level::ERROR.message("").group( + let snippets = Level::ERROR.header("").group( Group::new().element( Snippet::source("こんにちは、世界") .origin("<current file>") @@ -49,7 +49,7 @@ error: #[test] fn test_point_to_double_width_characters_across_lines() { - let snippets = Level::ERROR.message("").group( + let snippets = Level::ERROR.header("").group( Group::new().element( Snippet::source("おはよう\nございます") .origin("<current file>") @@ -73,7 +73,7 @@ error: #[test] fn test_point_to_double_width_characters_multiple() { - let snippets = Level::ERROR.message("").group( + let snippets = Level::ERROR.header("").group( Group::new().element( Snippet::source("お寿司\n食べたい🍣") .origin("<current file>") @@ -98,7 +98,7 @@ error: #[test] fn test_point_to_double_width_characters_mixed() { - let snippets = Level::ERROR.message("").group( + let snippets = Level::ERROR.header("").group( Group::new().element( Snippet::source("こんにちは、新しいWorld!") .origin("<current file>") @@ -120,7 +120,7 @@ error: #[test] fn test_format_title() { - let input = Level::ERROR.message("This is a title").id("E0001"); + let input = Level::ERROR.header("This is a title").id("E0001"); let expected = str![r#"error[E0001]: This is a title"#]; let renderer = Renderer::plain(); @@ -131,7 +131,7 @@ fn test_format_title() { fn test_format_snippet_only() { let source = "This is line 1\nThis is line 2"; let input = Level::ERROR - .message("") + .header("") .group(Group::new().element(Snippet::<Annotation<'_>>::source(source).line_start(5402))); let expected = str![[r#" @@ -148,7 +148,7 @@ error: fn test_format_snippets_continuation() { let src_0 = "This is slice 1"; let src_1 = "This is slice 2"; - let input = Level::ERROR.message("").group( + let input = Level::ERROR.header("").group( Group::new() .element( Snippet::<Annotation<'_>>::source(src_0) @@ -182,7 +182,7 @@ fn test_format_snippet_annotation_standalone() { let source = [line_1, line_2].join("\n"); // In line 2 let range = 22..24; - let input = Level::ERROR.message("").group( + let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(&source).line_start(5402).annotation( AnnotationKind::Context @@ -205,7 +205,7 @@ error: #[test] fn test_format_footer_title() { let input = Level::ERROR - .message("") + .header("") .group(Group::new().element(Level::ERROR.title("This __is__ a title"))); let expected = str![[r#" error: @@ -221,7 +221,7 @@ error: fn test_i26() { let source = "short"; let label = "label"; - let input = Level::ERROR.message("").group( + let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source).line_start(0).annotation( AnnotationKind::Primary @@ -238,7 +238,7 @@ fn test_i26() { fn test_source_content() { let source = "This is an example\nof content lines"; let input = Level::ERROR - .message("") + .header("") .group(Group::new().element(Snippet::<Annotation<'_>>::source(source).line_start(56))); let expected = str![[r#" error: @@ -253,7 +253,7 @@ error: #[test] fn test_source_annotation_standalone_singleline() { let source = "tests"; - let input = Level::ERROR.message("").group( + let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -273,7 +273,7 @@ error: #[test] fn test_source_annotation_standalone_multiline() { let source = "tests"; - let input = Level::ERROR.message("").group( + let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -297,7 +297,7 @@ error: #[test] fn test_only_source() { let input = Level::ERROR - .message("") + .header("") .group(Group::new().element(Snippet::<Annotation<'_>>::source("").origin("file.rs"))); let expected = str![[r#" error: @@ -312,7 +312,7 @@ error: fn test_anon_lines() { let source = "This is an example\nof content lines\n\nabc"; let input = Level::ERROR - .message("") + .header("") .group(Group::new().element(Snippet::<Annotation<'_>>::source(source).line_start(56))); let expected = str![[r#" error: @@ -328,7 +328,7 @@ LL | abc #[test] fn issue_130() { - let input = Level::ERROR.message("dummy").group( + let input = Level::ERROR.header("dummy").group( Group::new().element( Snippet::source("foo\nbar\nbaz") .origin("file/path") @@ -356,7 +356,7 @@ fn unterminated_string_multiline() { a\" // ... "; - let input = Level::ERROR.message("").group( + let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) .origin("file/path") @@ -380,7 +380,7 @@ error: #[test] fn char_and_nl_annotate_char() { let source = "a\r\nb"; - let input = Level::ERROR.message("").group( + let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) .origin("file/path") @@ -403,7 +403,7 @@ error: #[test] fn char_eol_annotate_char() { let source = "a\r\nb"; - let input = Level::ERROR.message("").group( + let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) .origin("file/path") @@ -425,7 +425,7 @@ error: #[test] fn char_eol_annotate_char_double_width() { - let snippets = Level::ERROR.message("").group( + let snippets = Level::ERROR.header("").group( Group::new().element( Snippet::source("こん\r\nにちは\r\n世界") .origin("<current file>") @@ -450,7 +450,7 @@ error: #[test] fn annotate_eol() { let source = "a\r\nb"; - let input = Level::ERROR.message("").group( + let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) .origin("file/path") @@ -473,7 +473,7 @@ error: #[test] fn annotate_eol2() { let source = "a\r\nb"; - let input = Level::ERROR.message("").group( + let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) .origin("file/path") @@ -496,7 +496,7 @@ error: #[test] fn annotate_eol3() { let source = "a\r\nb"; - let input = Level::ERROR.message("").group( + let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) .origin("file/path") @@ -519,7 +519,7 @@ error: #[test] fn annotate_eol4() { let source = "a\r\nb"; - let input = Level::ERROR.message("").group( + let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) .origin("file/path") @@ -541,7 +541,7 @@ error: #[test] fn annotate_eol_double_width() { - let snippets = Level::ERROR.message("").group( + let snippets = Level::ERROR.header("").group( Group::new().element( Snippet::source("こん\r\nにちは\r\n世界") .origin("<current file>") @@ -566,7 +566,7 @@ error: #[test] fn multiline_eol_start() { let source = "a\r\nb"; - let input = Level::ERROR.message("").group( + let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) .origin("file/path") @@ -590,7 +590,7 @@ error: #[test] fn multiline_eol_start2() { let source = "a\r\nb"; - let input = Level::ERROR.message("").group( + let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) .origin("file/path") @@ -614,7 +614,7 @@ error: #[test] fn multiline_eol_start3() { let source = "a\nb"; - let input = Level::ERROR.message("").group( + let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) .origin("file/path") @@ -637,7 +637,7 @@ error: #[test] fn multiline_eol_start_double_width() { - let snippets = Level::ERROR.message("").group( + let snippets = Level::ERROR.header("").group( Group::new().element( Snippet::source("こん\r\nにちは\r\n世界") .origin("<current file>") @@ -663,7 +663,7 @@ error: #[test] fn multiline_eol_start_eol_end() { let source = "a\nb\nc"; - let input = Level::ERROR.message("").group( + let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) .origin("file/path") @@ -688,7 +688,7 @@ error: #[test] fn multiline_eol_start_eol_end2() { let source = "a\r\nb\r\nc"; - let input = Level::ERROR.message("").group( + let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) .origin("file/path") @@ -713,7 +713,7 @@ error: #[test] fn multiline_eol_start_eol_end3() { let source = "a\r\nb\r\nc"; - let input = Level::ERROR.message("").group( + let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) .origin("file/path") @@ -738,7 +738,7 @@ error: #[test] fn multiline_eol_start_eof_end() { let source = "a\r\nb"; - let input = Level::ERROR.message("").group( + let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) .origin("file/path") @@ -762,7 +762,7 @@ error: #[test] fn multiline_eol_start_eof_end_double_width() { let source = "ん\r\nに"; - let input = Level::ERROR.message("").group( + let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) .origin("file/path") @@ -786,7 +786,7 @@ error: #[test] fn two_single_line_same_line() { let source = r#"bar = { version = "0.1.0", optional = true }"#; - let input = Level::ERROR.message("unused optional dependency").group( + let input = Level::ERROR.header("unused optional dependency").group( Group::new().element( Snippet::source(source) .origin("Cargo.toml") @@ -823,7 +823,7 @@ this is another line so is this bar = { version = "0.1.0", optional = true } "#; - let input = Level::ERROR.message("unused optional dependency").group( + let input = Level::ERROR.header("unused optional dependency").group( Group::new().element( Snippet::source(source) .line_start(4) @@ -862,7 +862,7 @@ this is another line so is this bar = { version = "0.1.0", optional = true } "#; - let input = Level::ERROR.message("unused optional dependency").group( + let input = Level::ERROR.header("unused optional dependency").group( Group::new().element( Snippet::source(source) .line_start(4) @@ -910,7 +910,7 @@ so is this bar = { version = "0.1.0", optional = true } this is another line "#; - let input = Level::ERROR.message("unused optional dependency").group( + let input = Level::ERROR.header("unused optional dependency").group( Group::new().element( Snippet::source(source) .line_start(4) @@ -961,7 +961,7 @@ error: unused optional dependency #[test] fn origin_correct_start_line() { let source = "aaa\nbbb\nccc\nddd\n"; - let input = Level::ERROR.message("title").group( + let input = Level::ERROR.header("title").group( Group::new().element( Snippet::source(source) .origin("origin.txt") @@ -987,7 +987,7 @@ error: title #[test] fn origin_correct_mid_line() { let source = "aaa\nbbb\nccc\nddd\n"; - let input = Level::ERROR.message("title").group( + let input = Level::ERROR.header("title").group( Group::new().element( Snippet::source(source) .origin("origin.txt") @@ -1018,7 +1018,7 @@ error: title fn two_suggestions_same_span() { let source = r#" A.foo();"#; let input_new = Level::ERROR - .message("expected value, found enum `A`") + .header("expected value, found enum `A`") .id("E0423") .group( Group::new().element( @@ -1086,7 +1086,7 @@ fn main() { }"#; let input_new = Level::ERROR - .message("no method named `pick` found for struct `Chaenomeles` in the current scope") + .header("no method named `pick` found for struct `Chaenomeles` in the current scope") .id("E0599") .group( Group::new().element( @@ -1146,7 +1146,7 @@ fn single_line_non_overlapping_suggestions() { let source = r#" A.foo();"#; let input_new = Level::ERROR - .message("expected value, found enum `A`") + .header("expected value, found enum `A`") .id("E0423") .group( Group::new().element( @@ -1188,7 +1188,7 @@ LL + (A::Tuple()).bar(); fn single_line_non_overlapping_suggestions2() { let source = r#" ThisIsVeryLong.foo();"#; let input_new = Level::ERROR - .message("Found `ThisIsVeryLong`") + .header("Found `ThisIsVeryLong`") .id("E0423") .group( Group::new().element( @@ -1237,7 +1237,7 @@ fn multiple_replacements() { "#; let input_new = Level::ERROR - .message("cannot borrow `*self` as mutable because it is also borrowed as immutable") + .header("cannot borrow `*self` as mutable because it is also borrowed as immutable") .id("E0502") .group( Group::new().element( @@ -1321,7 +1321,7 @@ fn main() { }"#; let input_new = Level::ERROR - .message("cannot borrow `chars` as mutable more than once at a time") + .header("cannot borrow `chars` as mutable more than once at a time") .id("E0499") .group( Group::new().element( @@ -1405,7 +1405,7 @@ struct Foo { fn main() {}"#; let input_new = Level::ERROR - .message("failed to resolve: use of undeclared crate or module `st`") + .header("failed to resolve: use of undeclared crate or module `st`") .id("E0433") .group( Group::new().element( @@ -1487,7 +1487,7 @@ where fn main() {}"#; let input_new = Level::ERROR - .message("the size for values of type `T` cannot be known at compilation time") + .header("the size for values of type `T` cannot be known at compilation time") .id("E0277") .group( Group::new().element( @@ -1556,7 +1556,7 @@ and where fn main() {}"#; let input_new = Level::ERROR - .message("the size for values of type `T` cannot be known at compilation time") + .header("the size for values of type `T` cannot be known at compilation time") .id("E0277") .group(Group::new().element(Snippet::source(source) .line_start(1) @@ -1661,7 +1661,7 @@ zappy "#; let input_new = Level::ERROR - .message("the size for values of type `T` cannot be known at compilation time") + .header("the size for values of type `T` cannot be known at compilation time") .id("E0277") .group( Group::new() @@ -1729,7 +1729,7 @@ fn main() { "#; let input_new = Level::ERROR - .message("type mismatch resolving `<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ...>>, ...>>, ...> as Future>::Error == Foo`") + .header("type mismatch resolving `<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ...>>, ...>>, ...> as Future>::Error == Foo`") .id("E0271") .group(Group::new().element(Snippet::source(source) .line_start(4) @@ -1817,7 +1817,7 @@ fn main() { "#; let input_new = Level::ERROR - .message("type mismatch resolving `<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ...>>, ...>>, ...> as Future>::Error == Foo`") + .header("type mismatch resolving `<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ...>>, ...>>, ...> as Future>::Error == Foo`") .id("E0271") .group(Group::new().element(Snippet::source(source) .line_start(4) @@ -1970,7 +1970,7 @@ fn main() { "#; let input_new = Level::ERROR - .message("mismatched types") + .header("mismatched types") .id("E0308") .group(Group::new().element( Snippet::source(source) @@ -2054,7 +2054,7 @@ fn main() { "#; let input_new = Level::ERROR - .message("mismatched types") + .header("mismatched types") .id("E0308") .group(Group::new().element( Snippet::source(source) @@ -2122,7 +2122,7 @@ LL │ ┃ )>>) {} #[test] fn unicode_cut_handling() { let source = "version = \"0.1.0\"\n# Ensure that the spans from toml handle utf-8 correctly\nauthors = [\n { name = \"Z\u{351}\u{36b}\u{343}\u{36a}\u{302}\u{36b}\u{33d}\u{34f}\u{334}\u{319}\u{324}\u{31e}\u{349}\u{35a}\u{32f}\u{31e}\u{320}\u{34d}A\u{36b}\u{357}\u{334}\u{362}\u{335}\u{31c}\u{330}\u{354}L\u{368}\u{367}\u{369}\u{358}\u{320}G\u{311}\u{357}\u{30e}\u{305}\u{35b}\u{341}\u{334}\u{33b}\u{348}\u{34d}\u{354}\u{339}O\u{342}\u{30c}\u{30c}\u{358}\u{328}\u{335}\u{339}\u{33b}\u{31d}\u{333}\", email = 1 }\n]\n"; - let input = Level::ERROR.message("title").group( + let input = Level::ERROR.header("title").group( Group::new().element( Snippet::source(source) .fold(false) @@ -2148,7 +2148,7 @@ error: title fn unicode_cut_handling2() { let source = "/*这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?"; let input = Level::ERROR - .message("expected item, found `?`") + .header("expected item, found `?`") .group( Group::new().element( Snippet::source(source) @@ -2175,7 +2175,7 @@ error: expected item, found `?` fn unicode_cut_handling3() { let source = "/*这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?"; let input = Level::ERROR - .message("expected item, found `?`") + .header("expected item, found `?`") .group( Group::new().element( Snippet::source(source) @@ -2202,7 +2202,7 @@ error: expected item, found `?` fn unicode_cut_handling4() { let source = "/*aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/?"; let input = Level::ERROR - .message("expected item, found `?`") + .header("expected item, found `?`") .group( Group::new().element( Snippet::source(source) @@ -2234,7 +2234,7 @@ fn main() { //~^ ERROR mismatched types } "##; - let input = Level::ERROR.message("mismatched types").id("E0308").group( + let input = Level::ERROR.header("mismatched types").id("E0308").group( Group::new().element( Snippet::source(source) .origin("$DIR/non-whitespace-trimming-unicode.rs") @@ -2279,7 +2279,7 @@ fn main() { } "##; let input = Level::ERROR - .message("cannot add `&str` to `&str`") + .header("cannot add `&str` to `&str`") .id("E0369") .group( Group::new() @@ -2346,7 +2346,7 @@ fn foo() { "##; let bin_source = "�|�\u{0002}!5�cc\u{0015}\u{0002}�Ӻi��WWj�ȥ�'�}�\u{0012}�J�ȉ��W�\u{001e}O�@����\u{001c}w�V���LO����\u{0014}[ \u{0003}_�'���SQ�~ذ��ų&��-\t��lN~��!@␌ _#���kQ��h�\u{001d}�:�\u{001c}\u{0007}�"; let input = Level::ERROR - .message("couldn't read `$DIR/not-utf8.bin`: stream did not contain valid UTF-8") + .header("couldn't read `$DIR/not-utf8.bin`: stream did not contain valid UTF-8") .group( Group::new().element( Snippet::source(source) diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index 93860d0e..69986292 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -12,7 +12,7 @@ fn ends_on_col0() { fn foo() { } "#; - let input = Level::ERROR.message("foo").group( + let input = Level::ERROR.header("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -42,7 +42,7 @@ fn foo() { } "#; - let input = Level::ERROR.message("foo").group( + let input = Level::ERROR.header("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -74,7 +74,7 @@ fn foo() { X2 Y2 } "#; - let input = Level::ERROR.message("foo").group( + let input = Level::ERROR.header("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -117,7 +117,7 @@ fn foo() { Y1 X1 } "#; - let input = Level::ERROR.message("foo").group( + let input = Level::ERROR.header("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -161,7 +161,7 @@ fn foo() { X3 Y3 Z3 } "#; - let input = Level::ERROR.message("foo").group( + let input = Level::ERROR.header("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -205,7 +205,7 @@ fn foo() { X2 Y2 Z2 } "#; - let input = Level::ERROR.message("foo").group( + let input = Level::ERROR.header("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -252,7 +252,7 @@ fn foo() { X2 Y2 Z2 } "#; - let input = Level::ERROR.message("foo").group( + let input = Level::ERROR.header("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -300,7 +300,7 @@ fn foo() { X3 Y3 Z3 } "#; - let input = Level::ERROR.message("foo").group( + let input = Level::ERROR.header("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -350,7 +350,7 @@ fn foo() { X3 Y3 Z3 } "#; - let input = Level::ERROR.message("foo").group( + let input = Level::ERROR.header("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -394,7 +394,7 @@ fn foo() { X3 Y3 Z3 } "#; - let input = Level::ERROR.message("foo").group( + let input = Level::ERROR.header("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -437,7 +437,7 @@ fn foo() { a { b { c } d } } "#; - let input = Level::ERROR.message("foo").group( + let input = Level::ERROR.header("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -470,7 +470,7 @@ fn foo() { a { b { c } d } } "#; - let input = Level::ERROR.message("foo").group( + let input = Level::ERROR.header("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -502,7 +502,7 @@ fn foo() { a { b { c } d } } "#; - let input = Level::ERROR.message("foo").group( + let input = Level::ERROR.header("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -537,7 +537,7 @@ fn foo() { a { b { c } d } } "#; - let input = Level::ERROR.message("foo").group( + let input = Level::ERROR.header("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -571,7 +571,7 @@ fn foo() { a bc d } "#; - let input = Level::ERROR.message("foo").group( + let input = Level::ERROR.header("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -605,7 +605,7 @@ fn foo() { a { b { c } d } } "#; - let input = Level::ERROR.message("foo").group( + let input = Level::ERROR.header("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -633,7 +633,7 @@ fn foo() { a { b { c } d } } "#; - let input = Level::ERROR.message("foo").group( + let input = Level::ERROR.header("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -662,7 +662,7 @@ fn foo() { a { b { c } d } } "#; - let input = Level::ERROR.message("foo").group( + let input = Level::ERROR.header("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -701,7 +701,7 @@ fn foo() { a { b { c } d } } "#; - let input = Level::ERROR.message("foo").group( + let input = Level::ERROR.header("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -732,7 +732,7 @@ fn foo() { a { b { c } d } } "#; - let input = Level::ERROR.message("foo").group( + let input = Level::ERROR.header("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -772,7 +772,7 @@ fn foo() { X3 Y3 Z3 } "#; - let input = Level::ERROR.message("foo").group( + let input = Level::ERROR.header("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -832,7 +832,7 @@ fn foo() { X3 Y3 Z3 } "#; - let input = Level::ERROR.message("foo").group( + let input = Level::ERROR.header("foo").group( Group::new().element( Snippet::source(source) .line_start(1) @@ -887,7 +887,7 @@ fn issue_91334() { fn f(){||yield(((){), "#; let input = Level::ERROR - .message("this file contains an unclosed delimiter") + .header("this file contains an unclosed delimiter") .group( Group::new().element( Snippet::source(source) @@ -958,7 +958,7 @@ fn main() { } "#; let input = Level::ERROR - .message("`break` with value from a `while` loop") + .header("`break` with value from a `while` loop") .id("E0571") .group( Group::new().element( @@ -1168,7 +1168,7 @@ fn nsize() { "#; let input = Level::ERROR - .message("`V0usize` cannot be safely transmuted into `[usize; 2]`") + .header("`V0usize` cannot be safely transmuted into `[usize; 2]`") .id("E0277") .group( Group::new().element( @@ -1254,7 +1254,7 @@ fn main() { } "#; let input = Level::ERROR - .message("`&[u8; 0]` cannot be safely transmuted into `&[u16; 0]`") + .header("`&[u8; 0]` cannot be safely transmuted into `&[u16; 0]`") .id("E027s7") .group( Group::new().element( @@ -1323,7 +1323,7 @@ fn g() { fn main() {} "#; let input = Level::ERROR - .message("expected function, found `{integer}`") + .header("expected function, found `{integer}`") .id("E0618") .group( Group::new().element( @@ -1414,7 +1414,7 @@ macro_rules! outer_macro { outer_macro!(FirstStruct, FirstAttrStruct); "#; let input = Level::WARNING - .message("non-local `macro_rules!` definition, `#[macro_export]` macro should be written at top level module") + .header("non-local `macro_rules!` definition, `#[macro_export]` macro should be written at top level module") .group( Group::new() .element( @@ -1547,7 +1547,7 @@ macro_rules! inline { } "#; let input = Level::ERROR - .message("can't call method `pow` on ambiguous numeric type `{integer}`") + .header("can't call method `pow` on ambiguous numeric type `{integer}`") .id("E0689") .group( Group::new().element( @@ -1611,7 +1611,7 @@ fn main() {} "#; let input = Level::ERROR - .message("type annotations needed") + .header("type annotations needed") .id("E0282") .group( Group::new().element( @@ -1717,7 +1717,7 @@ fn main() {} "##; let input = Level::ERROR - .message( + .header( "non-exhaustive patterns: `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered" ) .id("E0004") @@ -1819,7 +1819,7 @@ fn main() { } "#; let input = Level::ERROR - .message("the trait alias `EqAlias` is not dyn compatible") + .header("the trait alias `EqAlias` is not dyn compatible") .id("E0038") .group( Group::new().element( From a928524d03391efbc6952b09670737b84d6c8d8a Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 16 Apr 2025 16:11:38 -0500 Subject: [PATCH 336/455] refactor: Remove redundant re-export --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 021ed524..76836d0c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,5 +57,4 @@ pub fn normalize_untrusted_str(s: &str) -> String { pub use level::Level; #[doc(inline)] pub use renderer::Renderer; -pub use snippet::Padding; pub use snippet::*; From 21ca62eaf23d9fabd75ad787b9c8cde541d60213 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 16 Apr 2025 16:33:28 -0500 Subject: [PATCH 337/455] docs: Rename 'range' parameter to 'span' --- src/renderer/mod.rs | 14 +++++++------- src/renderer/source_map.rs | 18 +++++++++--------- src/snippet.rs | 24 ++++++++++++------------ 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index e5572e5e..be2abab4 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -1523,7 +1523,7 @@ impl Renderer { } if suggestion.origin != primary_origin { if let Some(origin) = suggestion.origin { - let (loc, _) = sm.span_to_locations(parts[0].range.clone()); + let (loc, _) = sm.span_to_locations(parts[0].span.clone()); // --> file.rs:line:col // | let arrow = self.file_start(); @@ -1563,8 +1563,8 @@ impl Renderer { row_num += 1; } - let file_lines = sm.span_to_lines(parts[0].range.clone()); - let (line_start, line_end) = sm.span_to_locations(parts[0].range.clone()); + let file_lines = sm.span_to_lines(parts[0].span.clone()); + let (line_start, line_end) = sm.span_to_locations(parts[0].span.clone()); let mut lines = complete.lines(); if lines.clone().next().is_none() { // Account for a suggestion to completely remove a line(s) with whitespace (#94192). @@ -1697,8 +1697,8 @@ impl Renderer { // already existing code, despite the colors and UI elements. // We special case `#[derive(_)]\n` and other attribute suggestions, because those // are the ones where context is most useful. - let file_lines = sm.span_to_lines(parts[0].range.end..parts[0].range.end); - let (lo, _) = sm.span_to_locations(parts[0].range.clone()); + let file_lines = sm.span_to_lines(parts[0].span.end..parts[0].span.end); + let (lo, _) = sm.span_to_locations(parts[0].span.clone()); let line_num = lo.line; if let Some(line) = sm.get_line(line_num) { let line = normalize_whitespace(line); @@ -1724,7 +1724,7 @@ impl Renderer { show_code_change { for part in parts { - let (span_start, span_end) = sm.span_to_locations(part.range.clone()); + let (span_start, span_end) = sm.span_to_locations(part.span.clone()); let span_start_pos = span_start.display; let span_end_pos = span_end.display; @@ -1764,7 +1764,7 @@ impl Renderer { let padding: usize = max_line_num_len + 3; for p in underline_start..underline_end { if matches!(show_code_change, DisplaySuggestion::Underline) - && is_different(sm, part.replacement, part.range.clone()) + && is_different(sm, part.replacement, part.span.clone()) { // If this is a replacement, underline with `~`, if this is an addition // underline with `+`. diff --git a/src/renderer/source_map.rs b/src/renderer/source_map.rs index 33fe1897..d014bb01 100644 --- a/src/renderer/source_map.rs +++ b/src/renderer/source_map.rs @@ -129,8 +129,8 @@ impl<'a> SourceMap<'a> { let source_len = self.source.len(); if let Some(bigger) = annotations.iter().find_map(|x| { // Allow highlighting one past the last character in the source. - if source_len + 1 < x.range.end { - Some(&x.range) + if source_len + 1 < x.span.end { + Some(&x.span) } else { None } @@ -150,13 +150,13 @@ impl<'a> SourceMap<'a> { let mut multiline_annotations = vec![]; for Annotation { - range, + span, label, kind, highlight_source, } in annotations { - let (lo, mut hi) = self.span_to_locations(range.clone()); + let (lo, mut hi) = self.span_to_locations(span.clone()); // Watch out for "empty spans". If we get a span like 6..6, we // want to just display a `^` at 6, so convert that to @@ -374,13 +374,13 @@ impl<'a> SourceMap<'a> { } // Assumption: all spans are in the same file, and all spans // are disjoint. Sort in ascending order. - patches.sort_by_key(|p| p.range.start); + patches.sort_by_key(|p| p.span.start); // Find the bounding span. - let Some(lo) = patches.iter().map(|p| p.range.start).min() else { + let Some(lo) = patches.iter().map(|p| p.span.start).min() else { return Vec::new(); }; - let Some(hi) = patches.iter().map(|p| p.range.end).max() else { + let Some(hi) = patches.iter().map(|p| p.span.end).max() else { return Vec::new(); }; @@ -410,7 +410,7 @@ impl<'a> SourceMap<'a> { // suggestion and snippet to look as if we just suggested to add // `"b"`, which is typically much easier for the user to understand. part.trim_trivial_replacements(self); - let (cur_lo, cur_hi) = self.span_to_locations(part.range.clone()); + let (cur_lo, cur_hi) = self.span_to_locations(part.span.clone()); if prev_hi.line == cur_lo.line { let mut count = push_trailing(&mut buf, prev_line, &prev_hi, Some(&cur_lo)); while count > 0 { @@ -454,7 +454,7 @@ impl<'a> SourceMap<'a> { _ => 1, }) .sum(); - if !is_different(self, part.replacement, part.range.clone()) { + if !is_different(self, part.replacement, part.span.clone()) { // Account for cases where we are suggesting the same code that's already // there. This shouldn't happen often, but in some cases for multipart // suggestions it's much easier to handle it here than in the origin. diff --git a/src/snippet.rs b/src/snippet.rs index bf22c5be..f37a0482 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -39,7 +39,7 @@ impl<'a> Message<'a> { let end = cause .markers .iter() - .map(|a| a.range.end) + .map(|a| a.span.end) .max() .unwrap_or(cause.source.len()) .min(cause.source.len()); @@ -50,7 +50,7 @@ impl<'a> Message<'a> { let end = suggestion .markers .iter() - .map(|a| a.range.end) + .map(|a| a.span.end) .max() .unwrap_or(suggestion.source.len()) .min(suggestion.source.len()); @@ -222,7 +222,7 @@ impl<'a> Snippet<'a, Patch<'a>> { #[derive(Clone, Debug)] pub struct Annotation<'a> { - pub(crate) range: Range<usize>, + pub(crate) span: Range<usize>, pub(crate) label: Option<&'a str>, pub(crate) kind: AnnotationKind, pub(crate) highlight_source: bool, @@ -254,7 +254,7 @@ pub enum AnnotationKind { impl AnnotationKind { pub fn span<'a>(self, span: Range<usize>) -> Annotation<'a> { Annotation { - range: span, + span, label: None, kind: self, highlight_source: false, @@ -268,7 +268,7 @@ impl AnnotationKind { #[derive(Clone, Debug)] pub struct Patch<'a> { - pub(crate) range: Range<usize>, + pub(crate) span: Range<usize>, pub(crate) replacement: &'a str, } @@ -276,8 +276,8 @@ impl<'a> Patch<'a> { /// Text passed to this function is considered "untrusted input", as such /// all text is passed through a normalization function. Pre-styled text is /// not allowed to be passed to this function. - pub fn new(range: Range<usize>, replacement: &'a str) -> Self { - Self { range, replacement } + pub fn new(span: Range<usize>, replacement: &'a str) -> Self { + Self { span, replacement } } pub(crate) fn is_addition(&self, sm: &SourceMap<'_>) -> bool { @@ -299,7 +299,7 @@ impl<'a> Patch<'a> { pub(crate) fn is_destructive_replacement(&self, sm: &SourceMap<'_>) -> bool { self.is_replacement(sm) && !sm - .span_to_snippet(self.range.clone()) + .span_to_snippet(self.span.clone()) // This should use `is_some_and` when our MSRV is >= 1.70 .map_or(false, |s| { as_substr(s.trim(), self.replacement.trim()).is_some() @@ -307,8 +307,8 @@ impl<'a> Patch<'a> { } fn replaces_meaningful_content(&self, sm: &SourceMap<'_>) -> bool { - sm.span_to_snippet(self.range.clone()) - .map_or(!self.range.is_empty(), |snippet| !snippet.trim().is_empty()) + sm.span_to_snippet(self.span.clone()) + .map_or(!self.span.is_empty(), |snippet| !snippet.trim().is_empty()) } /// Try to turn a replacement into an addition when the span that is being @@ -317,12 +317,12 @@ impl<'a> Patch<'a> { if self.replacement.is_empty() { return; } - let Some(snippet) = sm.span_to_snippet(self.range.clone()) else { + let Some(snippet) = sm.span_to_snippet(self.span.clone()) else { return; }; if let Some((prefix, substr, suffix)) = as_substr(snippet, self.replacement) { - self.range = self.range.start + prefix..self.range.end.saturating_sub(suffix); + self.span = self.span.start + prefix..self.span.end.saturating_sub(suffix); self.replacement = substr; } } From 66369feec323d89b8209e69ecdf365321a01ac47 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 16 Apr 2025 16:46:45 -0500 Subject: [PATCH 338/455] docs: Make an attempt --- src/level.rs | 20 ++++++++++++++ src/snippet.rs | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/src/level.rs b/src/level.rs index 3fd504de..87d1a9f6 100644 --- a/src/level.rs +++ b/src/level.rs @@ -1,33 +1,41 @@ +//! [`Level`] constants for easy importing + use crate::renderer::stylesheet::Stylesheet; use crate::snippet::{ERROR_TXT, HELP_TXT, INFO_TXT, NOTE_TXT, WARNING_TXT}; use crate::{Element, Group, Message, Title}; use anstyle::Style; +/// Default `error:` [`Level`] pub const ERROR: Level<'_> = Level { name: None, level: LevelInner::Error, }; +/// Default `warning:` [`Level`] pub const WARNING: Level<'_> = Level { name: None, level: LevelInner::Warning, }; +/// Default `info:` [`Level`] pub const INFO: Level<'_> = Level { name: None, level: LevelInner::Info, }; +/// Default `note:` [`Level`] pub const NOTE: Level<'_> = Level { name: None, level: LevelInner::Note, }; +/// Default `help:` [`Level`] pub const HELP: Level<'_> = Level { name: None, level: LevelInner::Help, }; +/// [`Message`] or [`Title`] severity level #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct Level<'a> { pub(crate) name: Option<Option<&'a str>>, @@ -41,9 +49,13 @@ impl<'a> Level<'a> { pub const NOTE: Level<'a> = NOTE; pub const HELP: Level<'a> = HELP; + /// <div class="warning"> + /// /// Text passed to this function is considered "untrusted input", as such /// all text is passed through a normalization function. Pre-styled text is /// not allowed to be passed to this function. + /// + /// </div> pub fn text(self, text: Option<&'a str>) -> Level<'a> { Level { name: Some(text), @@ -53,9 +65,13 @@ impl<'a> Level<'a> { } impl<'a> Level<'a> { + /// <div class="warning"> + /// /// Text passed to this function is considered "untrusted input", as such /// all text is passed through a normalization function. Pre-styled text is /// not allowed to be passed to this function. + /// + /// </div> pub fn header(self, header: &'a str) -> Message<'a> { Message { id: None, @@ -67,10 +83,14 @@ impl<'a> Level<'a> { } } + /// <div class="warning"> + /// /// Text passed to this function is allowed to be pre-styled, as such all /// text is considered "trusted input" and has no normalizations applied to /// it. [`normalize_untrusted_str`](crate::normalize_untrusted_str) can be /// used to normalize untrusted text before it is passed to this function. + /// + /// </div> pub fn title(self, title: &'a str) -> Title<'a> { Title { level: self, diff --git a/src/snippet.rs b/src/snippet.rs index f37a0482..03bfe3dd 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -10,6 +10,7 @@ pub(crate) const INFO_TXT: &str = "info"; pub(crate) const NOTE_TXT: &str = "note"; pub(crate) const WARNING_TXT: &str = "warning"; +/// Top-level user message #[derive(Debug)] pub struct Message<'a> { pub(crate) id: Option<&'a str>, // for "correctness", could be sloppy and be on Title @@ -17,11 +18,19 @@ pub struct Message<'a> { } impl<'a> Message<'a> { + /// <div class="warning"> + /// + /// Text passed to this function is considered "untrusted input", as such + /// all text is passed through a normalization function. Pre-styled text is + /// not allowed to be passed to this function. + /// + /// </div> pub fn id(mut self, id: &'a str) -> Self { self.id = Some(id); self } + /// Add an [`Element`] container pub fn group(mut self, group: Group<'a>) -> Self { self.groups.push(group); self @@ -66,6 +75,7 @@ impl<'a> Message<'a> { } } +/// An [`Element`] container #[derive(Debug)] pub struct Group<'a> { pub(crate) elements: Vec<Element<'a>>, @@ -97,6 +107,7 @@ impl<'a> Group<'a> { } } +/// A section of content within a [`Group`] #[derive(Debug)] #[non_exhaustive] pub enum Element<'a> { @@ -137,9 +148,13 @@ impl From<Padding> for Element<'_> { } } +/// A whitespace [`Element`] in a [`Group`] #[derive(Debug)] pub struct Padding; +/// A text [`Element`] in a [`Group`] +/// +/// See [`Level::title`] to create this. #[derive(Debug)] pub struct Title<'a> { pub(crate) level: Level<'a>, @@ -154,6 +169,7 @@ impl Title<'_> { } } +/// A source view [`Element`] in a [`Group`] #[derive(Debug)] pub struct Snippet<'a, T> { pub(crate) origin: Option<&'a str>, @@ -164,9 +180,15 @@ pub struct Snippet<'a, T> { } impl<'a, T: Clone> Snippet<'a, T> { + /// The source code to be rendered + /// + /// <div class="warning"> + /// /// Text passed to this function is considered "untrusted input", as such /// all text is passed through a normalization function. Pre-styled text is /// not allowed to be passed to this function. + /// + /// </div> pub fn source(source: &'a str) -> Self { Self { origin: None, @@ -177,19 +199,28 @@ impl<'a, T: Clone> Snippet<'a, T> { } } + /// When manually [`fold`][Self::fold]ing, + /// the [`source`][Self::source]s line offset from the original start pub fn line_start(mut self, line_start: usize) -> Self { self.line_start = line_start; self } + /// The location of the [`source`][Self::source] (e.g. a path) + /// + /// <div class="warning"> + /// /// Text passed to this function is considered "untrusted input", as such /// all text is passed through a normalization function. Pre-styled text is /// not allowed to be passed to this function. + /// + /// </div> pub fn origin(mut self, origin: &'a str) -> Self { self.origin = Some(origin); self } + /// Hide lines without [`Annotation`]s pub fn fold(mut self, fold: bool) -> Self { self.fold = fold; self @@ -197,11 +228,13 @@ impl<'a, T: Clone> Snippet<'a, T> { } impl<'a> Snippet<'a, Annotation<'a>> { + /// Highlight and describe a span of text within the [`source`][Self::source] pub fn annotation(mut self, annotation: Annotation<'a>) -> Snippet<'a, Annotation<'a>> { self.markers.push(annotation); self } + /// Highlight and describe spans of text within the [`source`][Self::source] pub fn annotations(mut self, annotation: impl IntoIterator<Item = Annotation<'a>>) -> Self { self.markers.extend(annotation); self @@ -209,17 +242,22 @@ impl<'a> Snippet<'a, Annotation<'a>> { } impl<'a> Snippet<'a, Patch<'a>> { + /// Suggest to the user an edit to the [`source`][Self::source] pub fn patch(mut self, patch: Patch<'a>) -> Snippet<'a, Patch<'a>> { self.markers.push(patch); self } + /// Suggest to the user edits to the [`source`][Self::source] pub fn patches(mut self, patches: impl IntoIterator<Item = Patch<'a>>) -> Self { self.markers.extend(patches); self } } +/// Highlighted and describe a span of text within a [`Snippet`] +/// +/// See [`AnnotationKind`] to create an annotation. #[derive(Clone, Debug)] pub struct Annotation<'a> { pub(crate) span: Range<usize>, @@ -229,20 +267,30 @@ pub struct Annotation<'a> { } impl<'a> Annotation<'a> { + /// Describe the reason the span is highlighted + /// + /// This will be styled according to the [`AnnotationKind`] + /// + /// <div class="warning"> + /// /// Text passed to this function is considered "untrusted input", as such /// all text is passed through a normalization function. Pre-styled text is /// not allowed to be passed to this function. + /// + /// </div> pub fn label(mut self, label: &'a str) -> Self { self.label = Some(label); self } + /// Style the source according to the [`AnnotationKind`] pub fn highlight_source(mut self, highlight_source: bool) -> Self { self.highlight_source = highlight_source; self } } +/// The category of the [`Annotation`] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum AnnotationKind { /// Color to [`Message`]'s [`Level`] @@ -266,6 +314,7 @@ impl AnnotationKind { } } +/// Suggested edit to the [`Snippet`] #[derive(Clone, Debug)] pub struct Patch<'a> { pub(crate) span: Range<usize>, @@ -273,9 +322,15 @@ pub struct Patch<'a> { } impl<'a> Patch<'a> { + /// Splice `replacement` into the [`Snippet`] at the `span` + /// + /// <div class="warning"> + /// /// Text passed to this function is considered "untrusted input", as such /// all text is passed through a normalization function. Pre-styled text is /// not allowed to be passed to this function. + /// + /// </div> pub fn new(span: Range<usize>, replacement: &'a str) -> Self { Self { span, replacement } } @@ -328,6 +383,7 @@ impl<'a> Patch<'a> { } } +/// The location of the [`Snippet`] (e.g. a path) #[derive(Clone, Debug)] pub struct Origin<'a> { pub(crate) origin: &'a str, @@ -338,9 +394,13 @@ pub struct Origin<'a> { } impl<'a> Origin<'a> { + /// <div class="warning"> + /// /// Text passed to this function is considered "untrusted input", as such /// all text is passed through a normalization function. Pre-styled text is /// not allowed to be passed to this function. + /// + /// </div> pub fn new(origin: &'a str) -> Self { Self { origin, @@ -351,11 +411,17 @@ impl<'a> Origin<'a> { } } + /// Set the default line number to display + /// + /// Otherwise this will be inferred from the primary [`Annotation`] pub fn line(mut self, line: usize) -> Self { self.line = Some(line); self } + /// Set the default column to display + /// + /// Otherwise this will be inferred from the primary [`Annotation`] pub fn char_column(mut self, char_column: usize) -> Self { self.char_column = Some(char_column); self @@ -366,9 +432,15 @@ impl<'a> Origin<'a> { self } + /// Like [`Annotation::label`], but when there is no source + /// + /// <div class="warning"> + /// /// Text passed to this function is considered "untrusted input", as such /// all text is passed through a normalization function. Pre-styled text is /// not allowed to be passed to this function. + /// + /// </div> pub fn label(mut self, label: &'a str) -> Self { self.label = Some(label); self From 17eb40e020e119d8f9bf20987050015d761d54c7 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Sat, 26 Apr 2025 06:29:03 -0600 Subject: [PATCH 339/455] test(fixtures): Move to source code based color tests --- Cargo.lock | 327 +----------------- Cargo.toml | 6 - tests/color/ann_eof.rs | 18 + .../ann_eof.svg => color/ann_eof.term.svg} | 0 tests/color/ann_insertion.rs | 18 + .../ann_insertion.term.svg} | 0 tests/color/ann_multiline.rs | 31 ++ .../ann_multiline.term.svg} | 0 tests/color/ann_multiline2.rs | 31 ++ .../ann_multiline2.term.svg} | 0 tests/color/ann_removed_nl.rs | 18 + .../ann_removed_nl.term.svg} | 0 tests/color/ensure_emoji_highlight_width.rs | 24 ++ .../ensure_emoji_highlight_width.term.svg} | 0 tests/color/fold_ann_multiline.rs | 50 +++ .../fold_ann_multiline.term.svg} | 0 tests/color/fold_bad_origin_line.rs | 24 ++ .../fold_bad_origin_line.term.svg} | 0 tests/color/fold_leading.rs | 35 ++ .../fold_leading.term.svg} | 0 tests/color/fold_trailing.rs | 34 ++ .../fold_trailing.term.svg} | 0 tests/color/issue_9.rs | 31 ++ .../issue_9.svg => color/issue_9.term.svg} | 0 tests/color/main.rs | 16 + tests/color/multiple_annotations.rs | 42 +++ .../multiple_annotations.term.svg} | 0 tests/color/simple.rs | 34 ++ .../simple.svg => color/simple.term.svg} | 0 tests/color/strip_line.rs | 24 ++ .../strip_line.term.svg} | 0 tests/color/strip_line_char.rs | 24 ++ .../strip_line_char.term.svg} | 0 tests/color/strip_line_non_ws.rs | 30 ++ .../strip_line_non_ws.term.svg} | 0 tests/fixtures/color/ann_eof.toml | 15 - tests/fixtures/color/ann_insertion.toml | 15 - tests/fixtures/color/ann_multiline.toml | 21 -- tests/fixtures/color/ann_multiline2.toml | 21 -- tests/fixtures/color/ann_removed_nl.toml | 15 - .../color/ensure-emoji-highlight-width.toml | 18 - tests/fixtures/color/fold_ann_multiline.toml | 41 --- .../fixtures/color/fold_bad_origin_line.toml | 20 -- tests/fixtures/color/fold_leading.toml | 29 -- tests/fixtures/color/fold_trailing.toml | 28 -- tests/fixtures/color/issue_9.toml | 34 -- .../fixtures/color/multiple_annotations.toml | 33 -- tests/fixtures/color/simple.toml | 23 -- tests/fixtures/color/strip_line.toml | 19 - tests/fixtures/color/strip_line_char.toml | 19 - tests/fixtures/color/strip_line_non_ws.toml | 27 -- tests/fixtures/deserialize.rs | 217 ------------ tests/fixtures/main.rs | 42 --- 53 files changed, 489 insertions(+), 965 deletions(-) create mode 100644 tests/color/ann_eof.rs rename tests/{fixtures/color/ann_eof.svg => color/ann_eof.term.svg} (100%) create mode 100644 tests/color/ann_insertion.rs rename tests/{fixtures/color/ann_insertion.svg => color/ann_insertion.term.svg} (100%) create mode 100644 tests/color/ann_multiline.rs rename tests/{fixtures/color/ann_multiline.svg => color/ann_multiline.term.svg} (100%) create mode 100644 tests/color/ann_multiline2.rs rename tests/{fixtures/color/ann_multiline2.svg => color/ann_multiline2.term.svg} (100%) create mode 100644 tests/color/ann_removed_nl.rs rename tests/{fixtures/color/ann_removed_nl.svg => color/ann_removed_nl.term.svg} (100%) create mode 100644 tests/color/ensure_emoji_highlight_width.rs rename tests/{fixtures/color/ensure-emoji-highlight-width.svg => color/ensure_emoji_highlight_width.term.svg} (100%) create mode 100644 tests/color/fold_ann_multiline.rs rename tests/{fixtures/color/fold_ann_multiline.svg => color/fold_ann_multiline.term.svg} (100%) create mode 100644 tests/color/fold_bad_origin_line.rs rename tests/{fixtures/color/fold_bad_origin_line.svg => color/fold_bad_origin_line.term.svg} (100%) create mode 100644 tests/color/fold_leading.rs rename tests/{fixtures/color/fold_leading.svg => color/fold_leading.term.svg} (100%) create mode 100644 tests/color/fold_trailing.rs rename tests/{fixtures/color/fold_trailing.svg => color/fold_trailing.term.svg} (100%) create mode 100644 tests/color/issue_9.rs rename tests/{fixtures/color/issue_9.svg => color/issue_9.term.svg} (100%) create mode 100644 tests/color/main.rs create mode 100644 tests/color/multiple_annotations.rs rename tests/{fixtures/color/multiple_annotations.svg => color/multiple_annotations.term.svg} (100%) create mode 100644 tests/color/simple.rs rename tests/{fixtures/color/simple.svg => color/simple.term.svg} (100%) create mode 100644 tests/color/strip_line.rs rename tests/{fixtures/color/strip_line.svg => color/strip_line.term.svg} (100%) create mode 100644 tests/color/strip_line_char.rs rename tests/{fixtures/color/strip_line_char.svg => color/strip_line_char.term.svg} (100%) create mode 100644 tests/color/strip_line_non_ws.rs rename tests/{fixtures/color/strip_line_non_ws.svg => color/strip_line_non_ws.term.svg} (100%) delete mode 100644 tests/fixtures/color/ann_eof.toml delete mode 100644 tests/fixtures/color/ann_insertion.toml delete mode 100644 tests/fixtures/color/ann_multiline.toml delete mode 100644 tests/fixtures/color/ann_multiline2.toml delete mode 100644 tests/fixtures/color/ann_removed_nl.toml delete mode 100644 tests/fixtures/color/ensure-emoji-highlight-width.toml delete mode 100644 tests/fixtures/color/fold_ann_multiline.toml delete mode 100644 tests/fixtures/color/fold_bad_origin_line.toml delete mode 100644 tests/fixtures/color/fold_leading.toml delete mode 100644 tests/fixtures/color/fold_trailing.toml delete mode 100644 tests/fixtures/color/issue_9.toml delete mode 100644 tests/fixtures/color/multiple_annotations.toml delete mode 100644 tests/fixtures/color/simple.toml delete mode 100644 tests/fixtures/color/strip_line.toml delete mode 100644 tests/fixtures/color/strip_line_char.toml delete mode 100644 tests/fixtures/color/strip_line_non_ws.toml delete mode 100644 tests/fixtures/deserialize.rs delete mode 100644 tests/fixtures/main.rs diff --git a/Cargo.lock b/Cargo.lock index 8838e31a..dd4fd20b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,21 +2,12 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - [[package]] name = "annotate-snippets" version = "0.11.5" dependencies = [ "annotate-snippets", - "anstream 0.6.18", + "anstream", "anstyle", "difference", "divan", @@ -24,26 +15,9 @@ dependencies = [ "memchr", "serde", "snapbox", - "toml", - "tryfn", "unicode-width 0.2.0", ] -[[package]] -name = "anstream" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon 1.0.2", - "colorchoice", - "is-terminal", - "utf8parse", -] - [[package]] name = "anstream" version = "0.6.18" @@ -53,7 +27,7 @@ dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", - "anstyle-wincon 3.0.6", + "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", @@ -98,23 +72,13 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbf0bf947d663010f0b4132f28ca08da9151f3b9035fa7578a38de521c1d1aa" dependencies = [ - "anstream 0.6.18", + "anstream", "anstyle", "anstyle-lossy", "html-escape", "unicode-width 0.1.13", ] -[[package]] -name = "anstyle-wincon" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" -dependencies = [ - "anstyle", - "windows-sys 0.48.0", -] - [[package]] name = "anstyle-wincon" version = "3.0.6" @@ -131,16 +95,6 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bstr" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "cfg-if" version = "1.0.0" @@ -154,8 +108,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb690e81c7840c0d7aade59f242ea3b41b9bc27bcd5997890e7702ae4b32e487" dependencies = [ "clap_builder", - "clap_derive", - "once_cell", ] [[package]] @@ -164,25 +116,11 @@ version = "4.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ed2e96bc16d8d740f6f48d663eddf4b8a0983e79210fd55479b7bcd0a69860e" dependencies = [ - "anstream 0.3.2", "anstyle", "clap_lex", - "strsim", "terminal_size", ] -[[package]] -name = "clap_derive" -version = "4.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "clap_lex" version = "0.5.0" @@ -232,12 +170,6 @@ dependencies = [ "syn", ] -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - [[package]] name = "errno" version = "0.3.9" @@ -248,15 +180,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "escape8259" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4911e3666fcd7826997b4745c8224295a6f3072f1418c3067b97a67557ee" -dependencies = [ - "rustversion", -] - [[package]] name = "escargot" version = "0.5.13" @@ -275,31 +198,6 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" -[[package]] -name = "globset" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" -dependencies = [ - "aho-corasick", - "bstr", - "log", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "hashbrown" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "hermit-abi" version = "0.3.9" @@ -315,33 +213,6 @@ dependencies = [ "utf8-width", ] -[[package]] -name = "ignore" -version = "0.4.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" -dependencies = [ - "globset", - "lazy_static", - "log", - "memchr", - "regex", - "same-file", - "thread_local", - "walkdir", - "winapi-util", -] - -[[package]] -name = "indexmap" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" -dependencies = [ - "equivalent", - "hashbrown", -] - [[package]] name = "io-lifetimes" version = "1.0.11" @@ -379,30 +250,12 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - [[package]] name = "libc" version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" -[[package]] -name = "libtest-mimic" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc0bda45ed5b3a2904262c1bb91e526127aa70e7ef3758aba2ef93cf896b9b58" -dependencies = [ - "clap", - "escape8259", - "termcolor", - "threadpool", -] - [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -427,16 +280,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "once_cell" version = "1.19.0" @@ -471,41 +314,12 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "regex" -version = "1.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - [[package]] name = "regex-lite" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" -[[package]] -name = "regex-syntax" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" - [[package]] name = "rustix" version = "0.37.27" @@ -520,27 +334,12 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "rustversion" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" - [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - [[package]] name = "serde" version = "1.0.219" @@ -572,15 +371,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_spanned" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" -dependencies = [ - "serde", -] - [[package]] name = "similar" version = "2.5.0" @@ -593,7 +383,7 @@ version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96dcfc4581e3355d70ac2ee14cfdf81dce3d85c85f1ed9e2c1d3013f53b3436b" dependencies = [ - "anstream 0.6.18", + "anstream", "anstyle", "anstyle-svg", "escargot", @@ -613,15 +403,9 @@ version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16569f53ca23a41bb6f62e0a5084aa1661f4814a67fa33696a79073e03a664af" dependencies = [ - "anstream 0.6.18", + "anstream", ] -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "syn" version = "2.0.86" @@ -633,15 +417,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - [[package]] name = "terminal_size" version = "0.2.6" @@ -652,70 +427,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "thread_local" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" -dependencies = [ - "cfg-if", - "once_cell", -] - -[[package]] -name = "threadpool" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" -dependencies = [ - "num_cpus", -] - -[[package]] -name = "toml" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.22.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", -] - -[[package]] -name = "tryfn" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fe242ee9e646acec9ab73a5c540e8543ed1b107f0ce42be831e0775d423c396" -dependencies = [ - "ignore", - "libtest-mimic", - "snapbox", -] - [[package]] name = "unicode-ident" version = "1.0.12" @@ -755,25 +466,6 @@ dependencies = [ "libc", ] -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "winapi-util" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" -dependencies = [ - "windows-sys 0.52.0", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -921,12 +613,3 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "winnow" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" -dependencies = [ - "memchr", -] diff --git a/Cargo.toml b/Cargo.toml index f30c64f9..4a8adf00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -128,17 +128,11 @@ divan = "0.1.14" glob = "0.3.1" serde = { version = "1.0.199", features = ["derive"] } snapbox = { version = "0.6.0", features = ["diff", "term-svg", "cmd", "examples"] } -toml = "0.8.0" -tryfn = "0.2.1" [[bench]] name = "bench" harness = false -[[test]] -name = "fixtures" -harness = false - [features] default = [] simd = ["memchr"] diff --git a/tests/color/ann_eof.rs b/tests/color/ann_eof.rs new file mode 100644 index 00000000..00e34b16 --- /dev/null +++ b/tests/color/ann_eof.rs @@ -0,0 +1,18 @@ +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; + +use snapbox::{assert_data_eq, file}; + +#[test] +fn case() { + let input = Level::ERROR.header("expected `.`, `=`").group( + Group::new().element( + Snippet::source("asdf") + .origin("Cargo.toml") + .line_start(1) + .annotation(AnnotationKind::Primary.span(4..4).label("")), + ), + ); + let expected = file!["ann_eof.term.svg"]; + let renderer = Renderer::styled(); + assert_data_eq!(renderer.render(input), expected); +} diff --git a/tests/fixtures/color/ann_eof.svg b/tests/color/ann_eof.term.svg similarity index 100% rename from tests/fixtures/color/ann_eof.svg rename to tests/color/ann_eof.term.svg diff --git a/tests/color/ann_insertion.rs b/tests/color/ann_insertion.rs new file mode 100644 index 00000000..802a0c78 --- /dev/null +++ b/tests/color/ann_insertion.rs @@ -0,0 +1,18 @@ +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; + +use snapbox::{assert_data_eq, file}; + +#[test] +fn case() { + let input = Level::ERROR.header("expected `.`, `=`").group( + Group::new().element( + Snippet::source("asf") + .origin("Cargo.toml") + .line_start(1) + .annotation(AnnotationKind::Primary.span(2..2).label("'d' belongs here")), + ), + ); + let expected = file!["ann_insertion.term.svg"]; + let renderer = Renderer::styled(); + assert_data_eq!(renderer.render(input), expected); +} diff --git a/tests/fixtures/color/ann_insertion.svg b/tests/color/ann_insertion.term.svg similarity index 100% rename from tests/fixtures/color/ann_insertion.svg rename to tests/color/ann_insertion.term.svg diff --git a/tests/color/ann_multiline.rs b/tests/color/ann_multiline.rs new file mode 100644 index 00000000..4b561ed3 --- /dev/null +++ b/tests/color/ann_multiline.rs @@ -0,0 +1,31 @@ +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; + +use snapbox::{assert_data_eq, file}; + +#[test] +fn case() { + let source = r#" if let DisplayLine::Source { + ref mut inline_marks, + } = body[body_idx] +"#; + + let input = Level::ERROR + .header("pattern does not mention fields `lineno`, `content`") + .id("E0027") + .group( + Group::new().element( + Snippet::source(source) + .origin("src/display_list.rs") + .line_start(139) + .fold(false) + .annotation( + AnnotationKind::Primary + .span(31..128) + .label("missing fields `lineno`, `content`"), + ), + ), + ); + let expected = file!["ann_multiline.term.svg"]; + let renderer = Renderer::styled(); + assert_data_eq!(renderer.render(input), expected); +} diff --git a/tests/fixtures/color/ann_multiline.svg b/tests/color/ann_multiline.term.svg similarity index 100% rename from tests/fixtures/color/ann_multiline.svg rename to tests/color/ann_multiline.term.svg diff --git a/tests/color/ann_multiline2.rs b/tests/color/ann_multiline2.rs new file mode 100644 index 00000000..9996fa97 --- /dev/null +++ b/tests/color/ann_multiline2.rs @@ -0,0 +1,31 @@ +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; + +use snapbox::{assert_data_eq, file}; + +#[test] +fn case() { + let source = r#"This is an example +of an edge case of an annotation overflowing +to exactly one character on next line. +"#; + + let input = Level::ERROR + .header("spacing error found") + .id("E####") + .group( + Group::new().element( + Snippet::source(source) + .origin("foo.txt") + .line_start(26) + .fold(false) + .annotation( + AnnotationKind::Primary + .span(11..19) + .label("this should not be on separate lines"), + ), + ), + ); + let expected = file!["ann_multiline2.term.svg"]; + let renderer = Renderer::styled(); + assert_data_eq!(renderer.render(input), expected); +} diff --git a/tests/fixtures/color/ann_multiline2.svg b/tests/color/ann_multiline2.term.svg similarity index 100% rename from tests/fixtures/color/ann_multiline2.svg rename to tests/color/ann_multiline2.term.svg diff --git a/tests/color/ann_removed_nl.rs b/tests/color/ann_removed_nl.rs new file mode 100644 index 00000000..45a64626 --- /dev/null +++ b/tests/color/ann_removed_nl.rs @@ -0,0 +1,18 @@ +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; + +use snapbox::{assert_data_eq, file}; + +#[test] +fn case() { + let input = Level::ERROR.header("expected `.`, `=`").group( + Group::new().element( + Snippet::source("asdf") + .origin("Cargo.toml") + .line_start(1) + .annotation(AnnotationKind::Primary.span(4..5).label("")), + ), + ); + let expected = file!["ann_removed_nl.term.svg"]; + let renderer = Renderer::styled(); + assert_data_eq!(renderer.render(input), expected); +} diff --git a/tests/fixtures/color/ann_removed_nl.svg b/tests/color/ann_removed_nl.term.svg similarity index 100% rename from tests/fixtures/color/ann_removed_nl.svg rename to tests/color/ann_removed_nl.term.svg diff --git a/tests/color/ensure_emoji_highlight_width.rs b/tests/color/ensure_emoji_highlight_width.rs new file mode 100644 index 00000000..b2397845 --- /dev/null +++ b/tests/color/ensure_emoji_highlight_width.rs @@ -0,0 +1,24 @@ +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; + +use snapbox::{assert_data_eq, file}; + +#[test] +fn case() { + let source = r#""haha this isn't a valid name 🐛" = { package = "libc", version = "0.1" } +"#; + + let input = Level::ERROR.header("invalid character ` ` in package name: `haha this isn't a valid name 🐛`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)") + .group( + Group::new() + .element( + Snippet::source(source) + .origin("<file>") + .line_start(7) + .annotation(AnnotationKind::Primary.span(0..35).label("")) + ) + ) +; + let expected = file!["ensure_emoji_highlight_width.term.svg"]; + let renderer = Renderer::styled(); + assert_data_eq!(renderer.render(input), expected); +} diff --git a/tests/fixtures/color/ensure-emoji-highlight-width.svg b/tests/color/ensure_emoji_highlight_width.term.svg similarity index 100% rename from tests/fixtures/color/ensure-emoji-highlight-width.svg rename to tests/color/ensure_emoji_highlight_width.term.svg diff --git a/tests/color/fold_ann_multiline.rs b/tests/color/fold_ann_multiline.rs new file mode 100644 index 00000000..3995b686 --- /dev/null +++ b/tests/color/fold_ann_multiline.rs @@ -0,0 +1,50 @@ +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; + +use snapbox::{assert_data_eq, file}; + +#[test] +fn case() { + let source = r#") -> Option<String> { + for ann in annotations { + match (ann.range.0, ann.range.1) { + (None, None) => continue, + (Some(start), Some(end)) if start > end_index || end < start_index => continue, + (Some(start), Some(end)) if start >= start_index && end <= end_index => { + let label = if let Some(ref label) = ann.label { + format!(" {}", label) + } else { + String::from("") + }; + + return Some(format!( + "{}{}{}", + " ".repeat(start - start_index), + "^".repeat(end - start), + label + )); + } + _ => continue, + } + } +"#; + + let input = Level::ERROR.header("mismatched types").id("E0308").group( + Group::new().element( + Snippet::source(source) + .origin("src/format.rs") + .line_start(51) + .fold(true) + .annotation(AnnotationKind::Context.span(5..19).label( + "expected `std::option::Option<std::string::String>` because of return type", + )) + .annotation( + AnnotationKind::Primary + .span(22..766) + .label("expected enum `std::option::Option`, found ()"), + ), + ), + ); + let expected = file!["fold_ann_multiline.term.svg"]; + let renderer = Renderer::styled(); + assert_data_eq!(renderer.render(input), expected); +} diff --git a/tests/fixtures/color/fold_ann_multiline.svg b/tests/color/fold_ann_multiline.term.svg similarity index 100% rename from tests/fixtures/color/fold_ann_multiline.svg rename to tests/color/fold_ann_multiline.term.svg diff --git a/tests/color/fold_bad_origin_line.rs b/tests/color/fold_bad_origin_line.rs new file mode 100644 index 00000000..1a21a5ef --- /dev/null +++ b/tests/color/fold_bad_origin_line.rs @@ -0,0 +1,24 @@ +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; + +use snapbox::{assert_data_eq, file}; + +#[test] +fn case() { + let source = r#" + +invalid syntax +"#; + + let input = Level::ERROR.header("").group( + Group::new().element( + Snippet::source(source) + .origin("path/to/error.rs") + .line_start(1) + .fold(true) + .annotation(AnnotationKind::Context.span(2..16).label("error here")), + ), + ); + let expected = file!["fold_bad_origin_line.term.svg"]; + let renderer = Renderer::styled(); + assert_data_eq!(renderer.render(input), expected); +} diff --git a/tests/fixtures/color/fold_bad_origin_line.svg b/tests/color/fold_bad_origin_line.term.svg similarity index 100% rename from tests/fixtures/color/fold_bad_origin_line.svg rename to tests/color/fold_bad_origin_line.term.svg diff --git a/tests/color/fold_leading.rs b/tests/color/fold_leading.rs new file mode 100644 index 00000000..93ba4992 --- /dev/null +++ b/tests/color/fold_leading.rs @@ -0,0 +1,35 @@ +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; + +use snapbox::{assert_data_eq, file}; + +#[test] +fn case() { + let source = r#"[workspace] + +[package] +name = "hello" +version = "1.0.0" +license = "MIT" +rust-version = "1.70" +edition = "2021" + +[lints] +workspace = 20 +"#; + + let input = Level::ERROR + .header("invalid type: integer `20`, expected a bool") + .id("E0308") + .group( + Group::new().element( + Snippet::source(source) + .origin("Cargo.toml") + .line_start(1) + .fold(true) + .annotation(AnnotationKind::Primary.span(132..134).label("")), + ), + ); + let expected = file!["fold_leading.term.svg"]; + let renderer = Renderer::styled(); + assert_data_eq!(renderer.render(input), expected); +} diff --git a/tests/fixtures/color/fold_leading.svg b/tests/color/fold_leading.term.svg similarity index 100% rename from tests/fixtures/color/fold_leading.svg rename to tests/color/fold_leading.term.svg diff --git a/tests/color/fold_trailing.rs b/tests/color/fold_trailing.rs new file mode 100644 index 00000000..f86ade78 --- /dev/null +++ b/tests/color/fold_trailing.rs @@ -0,0 +1,34 @@ +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; + +use snapbox::{assert_data_eq, file}; + +#[test] +fn case() { + let source = r#"lints = 20 + +[workspace] + +[package] +name = "hello" +version = "1.0.0" +license = "MIT" +rust-version = "1.70" +edition = "2021" +"#; + + let input = Level::ERROR + .header("invalid type: integer `20`, expected a lints table") + .id("E0308") + .group( + Group::new().element( + Snippet::source(source) + .origin("Cargo.toml") + .line_start(1) + .fold(true) + .annotation(AnnotationKind::Primary.span(8..10).label("")), + ), + ); + let expected = file!["fold_trailing.term.svg"]; + let renderer = Renderer::styled(); + assert_data_eq!(renderer.render(input), expected); +} diff --git a/tests/fixtures/color/fold_trailing.svg b/tests/color/fold_trailing.term.svg similarity index 100% rename from tests/fixtures/color/fold_trailing.svg rename to tests/color/fold_trailing.term.svg diff --git a/tests/color/issue_9.rs b/tests/color/issue_9.rs new file mode 100644 index 00000000..2accd2f2 --- /dev/null +++ b/tests/color/issue_9.rs @@ -0,0 +1,31 @@ +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; + +use snapbox::{assert_data_eq, file}; + +#[test] +fn case() { + let input = Level::ERROR.header("expected one of `.`, `;`, `?`, or an operator, found `for`") + .group( + Group::new() + .element( + Snippet::source("let x = vec![1];") + .origin("/code/rust/src/test/ui/annotate-snippet/suggestion.rs") + .line_start(4) + .annotation(AnnotationKind::Context.span(4..5).label("move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait")) + ) + .element( + Snippet::source("let y = x;") + .line_start(7) + .annotation(AnnotationKind::Context.span(8..9).label("value moved here")) + ) + .element( + Snippet::source("x;") + .line_start(9) + .annotation(AnnotationKind::Primary.span(0..1).label("value used here after move")) + ) + ) +; + let expected = file!["issue_9.term.svg"]; + let renderer = Renderer::styled(); + assert_data_eq!(renderer.render(input), expected); +} diff --git a/tests/fixtures/color/issue_9.svg b/tests/color/issue_9.term.svg similarity index 100% rename from tests/fixtures/color/issue_9.svg rename to tests/color/issue_9.term.svg diff --git a/tests/color/main.rs b/tests/color/main.rs new file mode 100644 index 00000000..f954bb7a --- /dev/null +++ b/tests/color/main.rs @@ -0,0 +1,16 @@ +mod ann_eof; +mod ann_insertion; +mod ann_multiline; +mod ann_multiline2; +mod ann_removed_nl; +mod ensure_emoji_highlight_width; +mod fold_ann_multiline; +mod fold_bad_origin_line; +mod fold_leading; +mod fold_trailing; +mod issue_9; +mod multiple_annotations; +mod simple; +mod strip_line; +mod strip_line_char; +mod strip_line_non_ws; diff --git a/tests/color/multiple_annotations.rs b/tests/color/multiple_annotations.rs new file mode 100644 index 00000000..b568b919 --- /dev/null +++ b/tests/color/multiple_annotations.rs @@ -0,0 +1,42 @@ +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; + +use snapbox::{assert_data_eq, file}; + +#[test] +fn case() { + let source = r#"fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>) { + if let Some(annotation) = main_annotation { + result.push(format_title_line( + &annotation.annotation_type, + None, + &annotation.label, + )); + } +} +"#; + + let input = Level::ERROR.header("").group( + Group::new().element( + Snippet::source(source) + .line_start(96) + .annotation( + AnnotationKind::Primary + .span(100..110) + .label("Variable defined here"), + ) + .annotation( + AnnotationKind::Primary + .span(184..194) + .label("Referenced here"), + ) + .annotation( + AnnotationKind::Primary + .span(243..253) + .label("Referenced again here"), + ), + ), + ); + let expected = file!["multiple_annotations.term.svg"]; + let renderer = Renderer::styled(); + assert_data_eq!(renderer.render(input), expected); +} diff --git a/tests/fixtures/color/multiple_annotations.svg b/tests/color/multiple_annotations.term.svg similarity index 100% rename from tests/fixtures/color/multiple_annotations.svg rename to tests/color/multiple_annotations.term.svg diff --git a/tests/color/simple.rs b/tests/color/simple.rs new file mode 100644 index 00000000..35e83d38 --- /dev/null +++ b/tests/color/simple.rs @@ -0,0 +1,34 @@ +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; + +use snapbox::{assert_data_eq, file}; + +#[test] +fn case() { + let source = r#" }) + + for line in &self.body { +"#; + + let input = Level::ERROR + .header("expected one of `.`, `;`, `?`, or an operator, found `for`") + .group( + Group::new().element( + Snippet::source(source) + .origin("src/format_color.rs") + .line_start(169) + .annotation( + AnnotationKind::Primary + .span(20..23) + .label("unexpected token"), + ) + .annotation( + AnnotationKind::Context + .span(10..11) + .label("expected one of `.`, `;`, `?`, or an operator here"), + ), + ), + ); + let expected = file!["simple.term.svg"]; + let renderer = Renderer::styled(); + assert_data_eq!(renderer.render(input), expected); +} diff --git a/tests/fixtures/color/simple.svg b/tests/color/simple.term.svg similarity index 100% rename from tests/fixtures/color/simple.svg rename to tests/color/simple.term.svg diff --git a/tests/color/strip_line.rs b/tests/color/strip_line.rs new file mode 100644 index 00000000..fd1ba588 --- /dev/null +++ b/tests/color/strip_line.rs @@ -0,0 +1,24 @@ +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; + +use snapbox::{assert_data_eq, file}; + +#[test] +fn case() { + let source = r#" let _: () = 42;"#; + + let input = Level::ERROR.header("mismatched types").id("E0308").group( + Group::new().element( + Snippet::source(source) + .origin("$DIR/whitespace-trimming.rs") + .line_start(4) + .annotation( + AnnotationKind::Primary + .span(192..194) + .label("expected (), found integer"), + ), + ), + ); + let expected = file!["strip_line.term.svg"]; + let renderer = Renderer::styled().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected); +} diff --git a/tests/fixtures/color/strip_line.svg b/tests/color/strip_line.term.svg similarity index 100% rename from tests/fixtures/color/strip_line.svg rename to tests/color/strip_line.term.svg diff --git a/tests/color/strip_line_char.rs b/tests/color/strip_line_char.rs new file mode 100644 index 00000000..df609e2f --- /dev/null +++ b/tests/color/strip_line_char.rs @@ -0,0 +1,24 @@ +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; + +use snapbox::{assert_data_eq, file}; + +#[test] +fn case() { + let source = r#" let _: () = 42ñ"#; + + let input = Level::ERROR.header("mismatched types").id("E0308").group( + Group::new().element( + Snippet::source(source) + .origin("$DIR/whitespace-trimming.rs") + .line_start(4) + .annotation( + AnnotationKind::Primary + .span(192..194) + .label("expected (), found integer"), + ), + ), + ); + let expected = file!["strip_line_char.term.svg"]; + let renderer = Renderer::styled().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected); +} diff --git a/tests/fixtures/color/strip_line_char.svg b/tests/color/strip_line_char.term.svg similarity index 100% rename from tests/fixtures/color/strip_line_char.svg rename to tests/color/strip_line_char.term.svg diff --git a/tests/color/strip_line_non_ws.rs b/tests/color/strip_line_non_ws.rs new file mode 100644 index 00000000..f82d369b --- /dev/null +++ b/tests/color/strip_line_non_ws.rs @@ -0,0 +1,30 @@ +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; + +use snapbox::{assert_data_eq, file}; + +#[test] +fn case() { + let source = r#" let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); +"#; + + let input = Level::ERROR.header("mismatched types").id("E0308").group( + Group::new().element( + Snippet::source(source) + .origin("$DIR/non-whitespace-trimming.rs") + .line_start(4) + .annotation( + AnnotationKind::Primary + .span(237..239) + .label("expected `()`, found integer"), + ) + .annotation( + AnnotationKind::Primary + .span(232..234) + .label("expected due to this"), + ), + ), + ); + let expected = file!["strip_line_non_ws.term.svg"]; + let renderer = Renderer::styled().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected); +} diff --git a/tests/fixtures/color/strip_line_non_ws.svg b/tests/color/strip_line_non_ws.term.svg similarity index 100% rename from tests/fixtures/color/strip_line_non_ws.svg rename to tests/color/strip_line_non_ws.term.svg diff --git a/tests/fixtures/color/ann_eof.toml b/tests/fixtures/color/ann_eof.toml deleted file mode 100644 index ef711dee..00000000 --- a/tests/fixtures/color/ann_eof.toml +++ /dev/null @@ -1,15 +0,0 @@ -[message] -level = "Error" -header = "expected `.`, `=`" - -[[message.sections]] -type = "Cause" -source = "asdf" -line_start = 1 -origin = "Cargo.toml" -annotations = [ - { label = "", kind = "Primary", range = [4, 4] }, -] - -[renderer] -color = true diff --git a/tests/fixtures/color/ann_insertion.toml b/tests/fixtures/color/ann_insertion.toml deleted file mode 100644 index 30af1bfb..00000000 --- a/tests/fixtures/color/ann_insertion.toml +++ /dev/null @@ -1,15 +0,0 @@ -[message] -level = "Error" -header = "expected `.`, `=`" - -[[message.sections]] -type = "Cause" -source = "asf" -line_start = 1 -origin = "Cargo.toml" -annotations = [ - { label = "'d' belongs here", kind = "Primary", range = [2, 2] } -] - -[renderer] -color = true diff --git a/tests/fixtures/color/ann_multiline.toml b/tests/fixtures/color/ann_multiline.toml deleted file mode 100644 index 2a5f206b..00000000 --- a/tests/fixtures/color/ann_multiline.toml +++ /dev/null @@ -1,21 +0,0 @@ -[message] -level = "Error" -id = "E0027" -header = "pattern does not mention fields `lineno`, `content`" - -[[message.sections]] -type = "Cause" -source = """ - if let DisplayLine::Source { - ref mut inline_marks, - } = body[body_idx] -""" -line_start = 139 -origin = "src/display_list.rs" -fold = false -annotations = [ - { label = "missing fields `lineno`, `content`", kind = "Primary", range = [31, 128] } -] - -[renderer] -color = true diff --git a/tests/fixtures/color/ann_multiline2.toml b/tests/fixtures/color/ann_multiline2.toml deleted file mode 100644 index 854b38a7..00000000 --- a/tests/fixtures/color/ann_multiline2.toml +++ /dev/null @@ -1,21 +0,0 @@ -[message] -level = "Error" -id = "E####" -header = "spacing error found" - -[[message.sections]] -type = "Cause" -source = """ -This is an example -of an edge case of an annotation overflowing -to exactly one character on next line. -""" -line_start = 26 -origin = "foo.txt" -fold = false -annotations = [ - { label = "this should not be on separate lines", kind = "Primary", range = [11, 19] }, -] - -[renderer] -color = true diff --git a/tests/fixtures/color/ann_removed_nl.toml b/tests/fixtures/color/ann_removed_nl.toml deleted file mode 100644 index 6ffeb7a0..00000000 --- a/tests/fixtures/color/ann_removed_nl.toml +++ /dev/null @@ -1,15 +0,0 @@ -[message] -level = "Error" -header = "expected `.`, `=`" - -[[message.sections]] -type = "Cause" -source = "asdf" -line_start = 1 -origin = "Cargo.toml" -annotations = [ - { label = "", kind = "Primary", range = [4, 5] }, -] - -[renderer] -color = true diff --git a/tests/fixtures/color/ensure-emoji-highlight-width.toml b/tests/fixtures/color/ensure-emoji-highlight-width.toml deleted file mode 100644 index 669959f6..00000000 --- a/tests/fixtures/color/ensure-emoji-highlight-width.toml +++ /dev/null @@ -1,18 +0,0 @@ -[message] -header = "invalid character ` ` in package name: `haha this isn't a valid name 🐛`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)" -level = "Error" - - -[[message.sections]] -type = "Cause" -source = """ -"haha this isn't a valid name 🐛" = { package = "libc", version = "0.1" } -""" -line_start = 7 -origin = "<file>" -annotations = [ - { label = "", kind = "Primary", range = [0, 35] }, -] - -[renderer] -color = true diff --git a/tests/fixtures/color/fold_ann_multiline.toml b/tests/fixtures/color/fold_ann_multiline.toml deleted file mode 100644 index 2cee27d6..00000000 --- a/tests/fixtures/color/fold_ann_multiline.toml +++ /dev/null @@ -1,41 +0,0 @@ -[message] -level = "Error" -id = "E0308" -header = "mismatched types" - -[[message.sections]] -type = "Cause" -source = """ -) -> Option<String> { - for ann in annotations { - match (ann.range.0, ann.range.1) { - (None, None) => continue, - (Some(start), Some(end)) if start > end_index || end < start_index => continue, - (Some(start), Some(end)) if start >= start_index && end <= end_index => { - let label = if let Some(ref label) = ann.label { - format!(" {}", label) - } else { - String::from("") - }; - - return Some(format!( - "{}{}{}", - " ".repeat(start - start_index), - "^".repeat(end - start), - label - )); - } - _ => continue, - } - } -""" -line_start = 51 -origin = "src/format.rs" -fold = true -annotations = [ - { label = "expected `std::option::Option<std::string::String>` because of return type", kind = "Context", range = [5, 19] }, - { label = "expected enum `std::option::Option`, found ()", kind = "Primary", range = [22, 766] }, -] - -[renderer] -color = true diff --git a/tests/fixtures/color/fold_bad_origin_line.toml b/tests/fixtures/color/fold_bad_origin_line.toml deleted file mode 100644 index 2fab2d64..00000000 --- a/tests/fixtures/color/fold_bad_origin_line.toml +++ /dev/null @@ -1,20 +0,0 @@ -[message] -level = "Error" -header = "" - -[[message.sections]] -type = "Cause" -source = """ - - -invalid syntax -""" -line_start = 1 -origin = "path/to/error.rs" -fold = true -annotations = [ - { label = "error here", kind = "Context", range = [2,16] }, -] - -[renderer] -color = true diff --git a/tests/fixtures/color/fold_leading.toml b/tests/fixtures/color/fold_leading.toml deleted file mode 100644 index 0ef043c9..00000000 --- a/tests/fixtures/color/fold_leading.toml +++ /dev/null @@ -1,29 +0,0 @@ -[message] -level = "Error" -id = "E0308" -header = "invalid type: integer `20`, expected a bool" - -[[message.sections]] -type = "Cause" -source = """ -[workspace] - -[package] -name = "hello" -version = "1.0.0" -license = "MIT" -rust-version = "1.70" -edition = "2021" - -[lints] -workspace = 20 -""" -line_start = 1 -origin = "Cargo.toml" -fold = true -annotations = [ - { label = "", kind = "Primary", range = [132, 134] }, -] - -[renderer] -color = true diff --git a/tests/fixtures/color/fold_trailing.toml b/tests/fixtures/color/fold_trailing.toml deleted file mode 100644 index 91e4ab4f..00000000 --- a/tests/fixtures/color/fold_trailing.toml +++ /dev/null @@ -1,28 +0,0 @@ -[message] -level = "Error" -id = "E0308" -header = "invalid type: integer `20`, expected a lints table" - -[[message.sections]] -type = "Cause" -source = """ -lints = 20 - -[workspace] - -[package] -name = "hello" -version = "1.0.0" -license = "MIT" -rust-version = "1.70" -edition = "2021" -""" -line_start = 1 -origin = "Cargo.toml" -fold = true -annotations = [ - { label = "", kind = "Primary", range = [8, 10] }, -] - -[renderer] -color = true diff --git a/tests/fixtures/color/issue_9.toml b/tests/fixtures/color/issue_9.toml deleted file mode 100644 index f4239154..00000000 --- a/tests/fixtures/color/issue_9.toml +++ /dev/null @@ -1,34 +0,0 @@ -[message] -level = "Error" -header = "expected one of `.`, `;`, `?`, or an operator, found `for`" - -[[message.sections]] -type = "Cause" -source = "let x = vec![1];" -line_start = 4 -origin = "/code/rust/src/test/ui/annotate-snippet/suggestion.rs" -[[message.sections.annotations]] -label = "move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait" -kind = "Context" -range = [4, 5] - -[[message.sections]] -type = "Cause" -source = "let y = x;" -line_start = 7 -[[message.sections.annotations]] -label = "value moved here" -kind = "Context" -range = [8, 9] - -[[message.sections]] -type = "Cause" -source = "x;" -line_start = 9 -[[message.sections.annotations]] -label = "value used here after move" -kind = "Primary" -range = [0, 1] - -[renderer] -color = true diff --git a/tests/fixtures/color/multiple_annotations.toml b/tests/fixtures/color/multiple_annotations.toml deleted file mode 100644 index 367c53ee..00000000 --- a/tests/fixtures/color/multiple_annotations.toml +++ /dev/null @@ -1,33 +0,0 @@ -[message] -level = "Error" -header = "" - -[[message.sections]] -type = "Cause" -source = """ -fn add_title_line(result: &mut Vec<String>, main_annotation: Option<&Annotation>) { - if let Some(annotation) = main_annotation { - result.push(format_title_line( - &annotation.annotation_type, - None, - &annotation.label, - )); - } -} -""" -line_start = 96 -[[message.sections.annotations]] -label = "Variable defined here" -kind = "Primary" -range = [100, 110] -[[message.sections.annotations]] -label = "Referenced here" -kind = "Primary" -range = [184, 194] -[[message.sections.annotations]] -label = "Referenced again here" -kind = "Primary" -range = [243, 253] - -[renderer] -color = true diff --git a/tests/fixtures/color/simple.toml b/tests/fixtures/color/simple.toml deleted file mode 100644 index d5a36474..00000000 --- a/tests/fixtures/color/simple.toml +++ /dev/null @@ -1,23 +0,0 @@ -[message] -level = "Error" -header = "expected one of `.`, `;`, `?`, or an operator, found `for`" - -[[message.sections]] -type = "Cause" -source = """ - }) - - for line in &self.body {""" -line_start = 169 -origin = "src/format_color.rs" -[[message.sections.annotations]] -label = "unexpected token" -kind = "Primary" -range = [20, 23] -[[message.sections.annotations]] -label = "expected one of `.`, `;`, `?`, or an operator here" -kind = "Context" -range = [10, 11] - -[renderer] -color = true diff --git a/tests/fixtures/color/strip_line.toml b/tests/fixtures/color/strip_line.toml deleted file mode 100644 index 18a7805d..00000000 --- a/tests/fixtures/color/strip_line.toml +++ /dev/null @@ -1,19 +0,0 @@ -[message] -level = "Error" -id = "E0308" -header = "mismatched types" - -[[message.sections]] -type = "Cause" -source = " let _: () = 42;" -line_start = 4 -origin = "$DIR/whitespace-trimming.rs" - -[[message.sections.annotations]] -label = "expected (), found integer" -kind = "Primary" -range = [192, 194] - -[renderer] -color = true -anonymized_line_numbers = true diff --git a/tests/fixtures/color/strip_line_char.toml b/tests/fixtures/color/strip_line_char.toml deleted file mode 100644 index 3174cedc..00000000 --- a/tests/fixtures/color/strip_line_char.toml +++ /dev/null @@ -1,19 +0,0 @@ -[message] -level = "Error" -id = "E0308" -header = "mismatched types" - -[[message.sections]] -type = "Cause" -source = " let _: () = 42ñ" -line_start = 4 -origin = "$DIR/whitespace-trimming.rs" - -[[message.sections.annotations]] -label = "expected (), found integer" -kind = "Primary" -range = [192, 194] - -[renderer] -color = true -anonymized_line_numbers = true diff --git a/tests/fixtures/color/strip_line_non_ws.toml b/tests/fixtures/color/strip_line_non_ws.toml deleted file mode 100644 index b7844ec2..00000000 --- a/tests/fixtures/color/strip_line_non_ws.toml +++ /dev/null @@ -1,27 +0,0 @@ -[message] -level = "Error" -id = "E0308" -header = "mismatched types" - -[[message.sections]] -type = "Cause" -source = """ - let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); -""" -line_start = 4 -origin = "$DIR/non-whitespace-trimming.rs" - -[[message.sections.annotations]] -label = "expected `()`, found integer" -kind = "Primary" -range = [237, 239] - -[[message.sections.annotations]] -label = "expected due to this" -kind = "Primary" -range = [232, 234] - - -[renderer] -anonymized_line_numbers = true -color = true diff --git a/tests/fixtures/deserialize.rs b/tests/fixtures/deserialize.rs deleted file mode 100644 index 20429665..00000000 --- a/tests/fixtures/deserialize.rs +++ /dev/null @@ -1,217 +0,0 @@ -use serde::Deserialize; -use std::ops::Range; - -use annotate_snippets::renderer::DEFAULT_TERM_WIDTH; -use annotate_snippets::{ - Annotation, AnnotationKind, Element, Group, Level, Message, Patch, Renderer, Snippet, -}; - -#[derive(Deserialize)] -pub(crate) struct Fixture { - #[serde(default)] - pub(crate) renderer: RendererDef, - pub(crate) message: MessageDef, -} - -#[derive(Deserialize)] -pub struct MessageDef { - pub level: LevelDef, - pub header: String, - #[serde(default)] - pub id: Option<String>, - #[serde(default)] - pub sections: Vec<ElementDef>, -} - -impl<'a> From<&'a MessageDef> for Message<'a> { - fn from(val: &'a MessageDef) -> Self { - let MessageDef { - level, - header, - id, - sections, - } = val; - let mut message = Level::from(level).header(header); - if let Some(id) = id { - message = message.id(id); - } - - message = message.group(Group::new().elements(sections.iter().map(|s| match s { - ElementDef::Title(title) => { - Element::Title(Level::from(&title.level).title(&title.title)) - } - ElementDef::Cause(cause) => Element::Cause(Snippet::from(cause)), - ElementDef::Suggestion(suggestion) => Element::Suggestion(Snippet::from(suggestion)), - }))); - message - } -} - -#[derive(Deserialize)] -#[serde(tag = "type")] -pub enum ElementDef { - Title(TitleDef), - Cause(SnippetAnnotationDef), - Suggestion(SnippetPatchDef), -} - -impl<'a> From<&'a ElementDef> for Element<'a> { - fn from(val: &'a ElementDef) -> Self { - match val { - ElementDef::Title(title) => { - Element::Title(Level::from(&title.level).title(&title.title)) - } - ElementDef::Cause(cause) => Element::Cause(Snippet::from(cause)), - ElementDef::Suggestion(suggestion) => Element::Suggestion(Snippet::from(suggestion)), - } - } -} - -#[derive(Deserialize)] -pub struct TitleDef { - pub title: String, - pub level: LevelDef, -} - -#[derive(Deserialize)] -pub struct SnippetAnnotationDef { - pub(crate) origin: Option<String>, - pub(crate) line_start: usize, - pub(crate) source: String, - pub(crate) annotations: Vec<AnnotationDef>, - #[serde(default)] - pub(crate) fold: bool, -} - -impl<'a> From<&'a SnippetAnnotationDef> for Snippet<'a, Annotation<'a>> { - fn from(val: &'a SnippetAnnotationDef) -> Self { - let SnippetAnnotationDef { - origin, - line_start, - source, - annotations, - fold, - } = val; - let mut snippet = Snippet::source(source).line_start(*line_start).fold(*fold); - if let Some(origin) = origin { - snippet = snippet.origin(origin); - } - snippet = snippet.annotations(annotations.iter().map(Into::into)); - snippet - } -} - -#[derive(Deserialize)] -pub struct AnnotationDef { - pub range: Range<usize>, - pub label: String, - #[serde(with = "AnnotationKindDef")] - pub kind: AnnotationKind, -} - -impl<'a> From<&'a AnnotationDef> for Annotation<'a> { - fn from(val: &'a AnnotationDef) -> Self { - let AnnotationDef { range, label, kind } = val; - kind.span(range.start..range.end).label(label) - } -} - -#[allow(dead_code)] -#[derive(Deserialize)] -#[serde(remote = "AnnotationKind")] -enum AnnotationKindDef { - Primary, - Context, -} - -#[derive(Deserialize)] -pub struct SnippetPatchDef { - pub(crate) origin: Option<String>, - pub(crate) line_start: usize, - pub(crate) source: String, - pub(crate) patches: Vec<PatchDef>, - #[serde(default)] - pub(crate) fold: bool, -} - -impl<'a> From<&'a SnippetPatchDef> for Snippet<'a, Patch<'a>> { - fn from(val: &'a SnippetPatchDef) -> Self { - let SnippetPatchDef { - origin, - line_start, - source, - patches, - fold, - } = val; - let mut snippet = Snippet::source(source).line_start(*line_start).fold(*fold); - if let Some(origin) = origin { - snippet = snippet.origin(origin); - } - snippet = snippet.patches(patches.iter().map(Into::into)); - snippet - } -} - -#[derive(Deserialize)] -pub struct PatchDef { - pub range: Range<usize>, - pub replacement: String, -} - -impl<'a> From<&'a PatchDef> for Patch<'a> { - fn from(val: &'a PatchDef) -> Self { - let PatchDef { range, replacement } = val; - Patch::new(range.start..range.end, replacement) - } -} - -#[allow(dead_code)] -#[derive(Clone, Copy, Deserialize)] -pub enum LevelDef { - Error, - Warning, - Info, - Note, - Help, -} - -impl<'a> From<&'a LevelDef> for Level<'a> { - fn from(val: &'a LevelDef) -> Self { - match val { - LevelDef::Error => Level::ERROR, - LevelDef::Warning => Level::WARNING, - LevelDef::Info => Level::INFO, - LevelDef::Note => Level::NOTE, - LevelDef::Help => Level::HELP, - } - } -} - -#[derive(Default, Deserialize)] -pub struct RendererDef { - #[serde(default)] - anonymized_line_numbers: bool, - #[serde(default)] - term_width: Option<usize>, - #[serde(default)] - color: bool, -} - -impl From<RendererDef> for Renderer { - fn from(val: RendererDef) -> Self { - let RendererDef { - anonymized_line_numbers, - term_width, - color, - } = val; - - let renderer = if color { - Renderer::styled() - } else { - Renderer::plain() - }; - renderer - .anonymized_line_numbers(anonymized_line_numbers) - .term_width(term_width.unwrap_or(DEFAULT_TERM_WIDTH)) - } -} diff --git a/tests/fixtures/main.rs b/tests/fixtures/main.rs deleted file mode 100644 index 27082622..00000000 --- a/tests/fixtures/main.rs +++ /dev/null @@ -1,42 +0,0 @@ -mod deserialize; - -use crate::deserialize::Fixture; -use annotate_snippets::{Message, Renderer}; -use snapbox::data::DataFormat; -use snapbox::Data; -use std::error::Error; - -fn main() { - #[cfg(not(windows))] - tryfn::Harness::new("tests/fixtures/", setup, test) - .select(["*/*.toml"]) - .test(); -} - -fn setup(input_path: std::path::PathBuf) -> tryfn::Case { - let parent = input_path - .parent() - .unwrap() - .file_name() - .unwrap() - .to_str() - .unwrap(); - let file_name = input_path.file_name().unwrap().to_str().unwrap(); - let name = format!("{parent}/{file_name}"); - let expected = Data::read_from(&input_path.with_extension("svg"), None); - tryfn::Case { - name, - fixture: input_path, - expected, - } -} - -fn test(input_path: &std::path::Path) -> Result<Data, Box<dyn Error>> { - let src = std::fs::read_to_string(input_path)?; - let fixture: Fixture = toml::from_str(&src)?; - let renderer: Renderer = fixture.renderer.into(); - let message: Message<'_> = (&fixture.message).into(); - - let actual = renderer.render(message); - Ok(Data::from(actual).coerce_to(DataFormat::TermSvg)) -} From df05eb77a29bc9baa56764f6041b318f4e55ba4d Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Sat, 26 Apr 2025 08:23:27 -0600 Subject: [PATCH 340/455] chore: Remove unused dev-dependencies --- Cargo.lock | 15 --------------- Cargo.toml | 3 --- 2 files changed, 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dd4fd20b..e6431344 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,11 +9,8 @@ dependencies = [ "annotate-snippets", "anstream", "anstyle", - "difference", "divan", - "glob", "memchr", - "serde", "snapbox", "unicode-width 0.2.0", ] @@ -139,12 +136,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf0a07a401f374238ab8e2f11a104d2851bf9ce711ec69804834de8af45c7af" -[[package]] -name = "difference" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" - [[package]] name = "divan" version = "0.1.17" @@ -192,12 +183,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "glob" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" - [[package]] name = "hermit-abi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 4a8adf00..84345673 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -123,10 +123,7 @@ unicode-width = "0.2.0" [dev-dependencies] annotate-snippets = { path = ".", features = ["testing-colors"] } anstream = "0.6.13" -difference = "2.0.0" divan = "0.1.14" -glob = "0.3.1" -serde = { version = "1.0.199", features = ["derive"] } snapbox = { version = "0.6.0", features = ["diff", "term-svg", "cmd", "examples"] } [[bench]] From 97755b3c011d292774d5a3199327bfb1dc29e6a6 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 30 Apr 2025 20:07:18 -0500 Subject: [PATCH 341/455] chore(ci): Improve perf at the cost of coverage --- .github/workflows/ci.yml | 4 ++-- .github/workflows/rust-next.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7bcbd419..baf17232 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,7 +50,7 @@ jobs: - name: Build run: cargo test --workspace --no-run - name: Test - run: cargo hack test --feature-powerset --workspace + run: cargo hack test --each-feature --workspace msrv: name: "Check MSRV" runs-on: ubuntu-latest @@ -64,7 +64,7 @@ jobs: - uses: Swatinem/rust-cache@v2 - uses: taiki-e/install-action@cargo-hack - name: Default features - run: cargo hack check --feature-powerset --locked --rust-version --ignore-private --workspace --all-targets --keep-going + run: cargo hack check --each-feature --locked --rust-version --ignore-private --workspace --all-targets --keep-going minimal-versions: name: Minimal versions runs-on: ubuntu-latest diff --git a/.github/workflows/rust-next.yml b/.github/workflows/rust-next.yml index e98386c4..be8b2dbe 100644 --- a/.github/workflows/rust-next.yml +++ b/.github/workflows/rust-next.yml @@ -40,7 +40,7 @@ jobs: - name: Build run: cargo test --workspace --no-run - name: Test - run: cargo hack test --feature-powerset --workspace + run: cargo hack test --each-feature --workspace latest: name: "Check latest dependencies" runs-on: ubuntu-latest @@ -58,4 +58,4 @@ jobs: - name: Build run: cargo test --workspace --no-run - name: Test - run: cargo hack test --feature-powerset --workspace + run: cargo hack test --each-feature --workspace From 9c5ba46e1cd041df9e4a62a2936130c1f5c8ceea Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 1 May 2025 03:41:39 +0000 Subject: [PATCH 342/455] chore(deps): Update Rust crate divan to v0.1.21 (#202) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e6431344..532bb57a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -138,9 +138,9 @@ checksum = "baf0a07a401f374238ab8e2f11a104d2851bf9ce711ec69804834de8af45c7af" [[package]] name = "divan" -version = "0.1.17" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0583193020b29b03682d8d33bb53a5b0f50df6daacece12ca99b904cfdcb8c4" +checksum = "a405457ec78b8fe08b0e32b4a3570ab5dff6dd16eb9e76a5ee0a9d9cbd898933" dependencies = [ "cfg-if", "clap", @@ -152,9 +152,9 @@ dependencies = [ [[package]] name = "divan-macros" -version = "0.1.17" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dc51d98e636f5e3b0759a39257458b22619cac7e96d932da6eeb052891bb67c" +checksum = "9556bc800956545d6420a640173e5ba7dfa82f38d3ea5a167eb555bc69ac3323" dependencies = [ "proc-macro2", "quote", From d1a562756a21d6ed9626ffcbb157af5841c4a1b5 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 7 May 2025 08:42:54 -0500 Subject: [PATCH 343/455] chore: Update RenovateBot --- .github/renovate.json5 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 7ab13b9f..cf3c8fe7 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -9,7 +9,7 @@ customManagers: [ { customType: 'regex', - fileMatch: [ + managerFilePatterns: [ '^rust-toolchain\\.toml$', 'Cargo.toml$', 'clippy.toml$', From 8c5c6ad69f2da2abaf2f8254df40ee1d52478837 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 7 May 2025 14:06:57 -0500 Subject: [PATCH 344/455] chore: Fix regex for renovatebot --- .github/renovate.json5 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index cf3c8fe7..27749d4b 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -10,12 +10,12 @@ { customType: 'regex', managerFilePatterns: [ - '^rust-toolchain\\.toml$', - 'Cargo.toml$', - 'clippy.toml$', - '\\.clippy.toml$', - '^\\.github/workflows/ci.yml$', - '^\\.github/workflows/rust-next.yml$', + '/^rust-toolchain\\.toml$/', + '/Cargo.toml$/', + '/clippy.toml$/', + '/\\.clippy.toml$/', + '/^\\.github/workflows/ci.yml$/', + '/^\\.github/workflows/rust-next.yml$/', ], matchStrings: [ 'STABLE.*?(?<currentValue>\\d+\\.\\d+(\\.\\d+)?)', From 1c62b61c44c8384b7c891b352b0c303d0b6a992d Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 14 May 2025 12:32:39 +0200 Subject: [PATCH 345/455] chore: Address clippy lints --- src/renderer/mod.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index be2abab4..de1b77dd 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -2537,11 +2537,7 @@ impl LineAnnotation<'_> { /// Length of this annotation as displayed in the stderr output pub(crate) fn len(&self) -> usize { // Account for usize underflows - if self.end.display > self.start.display { - self.end.display - self.start.display - } else { - self.start.display - self.end.display - } + self.end.display.abs_diff(self.start.display) } pub(crate) fn has_label(&self) -> bool { From c47d7508c93c2471e240ded7bcfc2bcff7280f47 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 14 May 2025 12:30:34 +0200 Subject: [PATCH 346/455] chore: Add Clone to Message --- src/snippet.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/snippet.rs b/src/snippet.rs index 03bfe3dd..7226783d 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -11,7 +11,7 @@ pub(crate) const NOTE_TXT: &str = "note"; pub(crate) const WARNING_TXT: &str = "warning"; /// Top-level user message -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Message<'a> { pub(crate) id: Option<&'a str>, // for "correctness", could be sloppy and be on Title pub(crate) groups: Vec<Group<'a>>, @@ -76,7 +76,7 @@ impl<'a> Message<'a> { } /// An [`Element`] container -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Group<'a> { pub(crate) elements: Vec<Element<'a>>, } @@ -108,7 +108,7 @@ impl<'a> Group<'a> { } /// A section of content within a [`Group`] -#[derive(Debug)] +#[derive(Clone, Debug)] #[non_exhaustive] pub enum Element<'a> { Title(Title<'a>), @@ -149,13 +149,13 @@ impl From<Padding> for Element<'_> { } /// A whitespace [`Element`] in a [`Group`] -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Padding; /// A text [`Element`] in a [`Group`] /// /// See [`Level::title`] to create this. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Title<'a> { pub(crate) level: Level<'a>, pub(crate) title: &'a str, @@ -170,7 +170,7 @@ impl Title<'_> { } /// A source view [`Element`] in a [`Group`] -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Snippet<'a, T> { pub(crate) origin: Option<&'a str>, pub(crate) line_start: usize, From 962407d6e2239ec2419c7f6f614a9d7068091234 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 14 May 2025 12:26:30 +0200 Subject: [PATCH 347/455] test: Add unicode varients for some tests --- tests/formatter.rs | 137 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 114 insertions(+), 23 deletions(-) diff --git a/tests/formatter.rs b/tests/formatter.rs index 75cf8532..4c536329 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -2129,7 +2129,7 @@ fn unicode_cut_handling() { .annotation(AnnotationKind::Primary.span(85..228).label("annotation")), ), ); - let expected = str![[r#" + let expected_ascii = str![[r#" error: title | 1 | version = "0.1.0" @@ -2140,8 +2140,22 @@ error: title 5 | | ] | |_^ annotation "#]]; - let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input), expected); + let renderer_ascii = Renderer::plain(); + assert_data_eq!(renderer_ascii.render(input.clone()), expected_ascii); + + let expected_unicode = str![[r#" +error: title + │ +1 │ version = "0.1.0" +2 │ # Ensure that the spans from toml handle utf-8 correctly +3 │ authors = [ + │ ┏━━━━━━━━━━━┛ +4 │ ┃ { name = "Z͑ͫ̓ͪ̂ͫ̽͏̴̙̤̞͉͚̯̞̠͍A̴̵̜̰͔ͫ͗͢L̠ͨͧͩ͘G̴̻͈͍͔̹̑͗̎̅͛́Ǫ̵̹̻̝̳͂̌̌͘", email = 1 } +5 │ ┃ ] + ╰╴┗━┛ annotation +"#]]; + let renderer_unicode = renderer_ascii.theme(OutputTheme::Unicode); + assert_data_eq!(renderer_unicode.render(input), expected_unicode); } #[test] @@ -2159,7 +2173,7 @@ fn unicode_cut_handling2() { ) ); - let expected = str![[r#" + let expected_ascii = str![[r#" error: expected item, found `?` | 1 | ...的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/? @@ -2167,8 +2181,18 @@ error: expected item, found `?` = note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html> "#]]; - let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input), expected); + let renderer_ascii = Renderer::plain(); + assert_data_eq!(renderer_ascii.render(input.clone()), expected_ascii); + + let expected_unicode = str![[r#" +error: expected item, found `?` + │ +1 │ …宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/? + │ ━ expected item + ╰ note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html> +"#]]; + let renderer_unicode = renderer_ascii.theme(OutputTheme::Unicode); + assert_data_eq!(renderer_unicode.render(input), expected_unicode); } #[test] @@ -2186,7 +2210,7 @@ fn unicode_cut_handling3() { ) ); - let expected = str![[r#" + let expected_ascii = str![[r#" error: expected item, found `?` | 1 | ...。这是宽的。这是宽的。这是宽的... @@ -2194,8 +2218,18 @@ error: expected item, found `?` = note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html> "#]]; - let renderer = Renderer::plain().term_width(43); - assert_data_eq!(renderer.render(input), expected); + let renderer_ascii = Renderer::plain().term_width(43); + assert_data_eq!(renderer_ascii.render(input.clone()), expected_ascii); + + let expected_unicode = str![[r#" +error: expected item, found `?` + │ +1 │ …的。这是宽的。这是宽的。这是宽的。… + │ ━━ expected item + ╰ note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html> +"#]]; + let renderer_unicode = renderer_ascii.theme(OutputTheme::Unicode); + assert_data_eq!(renderer_unicode.render(input), expected_unicode); } #[test] @@ -2213,7 +2247,7 @@ fn unicode_cut_handling4() { ) ); - let expected = str![[r#" + let expected_ascii = str![[r#" error: expected item, found `?` | 1 | ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/? @@ -2221,8 +2255,18 @@ error: expected item, found `?` = note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html> "#]]; - let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input), expected); + let renderer_ascii = Renderer::plain(); + assert_data_eq!(renderer_ascii.render(input.clone()), expected_ascii); + + let expected_unicode = str![[r#" +error: expected item, found `?` + │ +1 │ …aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/? + │ ━ expected item + ╰ note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html> +"#]]; + let renderer_unicode = renderer_ascii.theme(OutputTheme::Unicode); + assert_data_eq!(renderer_unicode.render(input), expected_unicode); } #[test] @@ -2252,7 +2296,7 @@ fn main() { ), ); - let expected = str![[r#" + let expected_ascii = str![[r#" error[E0308]: mismatched types --> $DIR/non-whitespace-trimming-unicode.rs:4:415 | @@ -2262,8 +2306,20 @@ LL | ...♧♨♩♪♫♬♭♮♯♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾ | expected due to this "#]]; - let renderer = Renderer::plain().anonymized_line_numbers(true); - assert_data_eq!(renderer.render(input), expected); + let renderer_ascii = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer_ascii.render(input.clone()), expected_ascii); + + let expected_unicode = str![[r#" +error[E0308]: mismatched types + ╭▸ $DIR/non-whitespace-trimming-unicode.rs:4:415 + │ +LL │ …♥♦♧♨♩♪♫♬♭♮♯♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾♿⚀⚁⚂⚃⚄⚅⚆⚈⚉4"; let _: () = 42; let _: &str = "🦀☀☁☂☃☄★☆☇☈☉☊☋☌☍☎☏☐☑☒☓ ☖☗☘☙☚☛☜☝☞☟☠☡☢☣☤☥☦☧☨☩☪☫☬☭☮☯☰☱☲☳☴☵☶☷☸☹… + │ ┬─ ━━ expected `()`, found integer + │ │ + ╰╴ expected due to this +"#]]; + let renderer_unicode = renderer_ascii.theme(OutputTheme::Unicode); + assert_data_eq!(renderer_unicode.render(input), expected_unicode); } #[test] @@ -2311,7 +2367,27 @@ fn main() { ), ); - let expected = str![[r#" + let expected_ascii = str![[r#" +error[E0369]: cannot add `&str` to `&str` + --> $DIR/non-1-width-unicode-multiline-label.rs:7:260 + | +LL | ...࿉࿊࿋࿌࿎࿏࿐࿑࿒࿓࿔࿕࿖࿗࿘࿙࿚"; let _a = unicode_is_fun + " really fun!";; + | -------------- ^ -------------- &str + | | | + | | `+` cannot be used to concatenate two `&str` strings + | &str + | + = note: string concatenation requires an owned `String` on the left +help: create an owned `String` from a string reference + | +LL | let _ = "ༀ༁༂༃༄༅༆༇༈༉༊་༌།༎༏༐༑༒༓༔༕༖༗༘༙༚༛༜༝༞༟༠༡༢༣༤༥༦༧༨༩༪༫༬༭༮༯༰༱༲༳༴༵༶༷༸༹༺༻༼༽༾༿ཀཁགགྷངཅཆཇཉཊཋཌཌྷཎཏཐདདྷནཔཕབབྷམཙཚཛཛྷཝཞཟའཡརལཤཥསཧཨཀྵཪཫཬཱཱཱིིུུྲྀཷླྀཹེཻོཽཾཿ྄ཱྀྀྂྃ྅྆྇ྈྉྊྋྌྍྎྏྐྑྒྒྷྔྕྖྗྙྚྛྜྜྷྞྟྠྡྡྷྣྤྥྦྦྷྨྩྪྫྫྷྭྮྯྰྱྲླྴྵྶྷྸྐྵྺྻྼ྾྿࿀࿁࿂࿃࿄࿅࿆࿇࿈࿉࿊࿋࿌࿎࿏࿐࿑࿒࿓࿔࿕࿖࿗࿘࿙࿚"; let _a = unicode_is_fun.to_owned() + " really fun!"; + | +++++++++++ +"#]]; + + let renderer_ascii = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer_ascii.render(input.clone()), expected_ascii); + + let expected_unicode = str![[r#" error[E0369]: cannot add `&str` to `&str` ╭▸ $DIR/non-1-width-unicode-multiline-label.rs:7:260 │ @@ -2328,10 +2404,8 @@ LL │ let _ = "ༀ༁༂༃༄༅༆༇༈༉༊་༌།༎༏༐༑༒༓ ╰╴ +++++++++++ "#]]; - let renderer = Renderer::plain() - .anonymized_line_numbers(true) - .theme(OutputTheme::Unicode); - assert_data_eq!(renderer.render(input), expected); + let renderer_unicode = renderer_ascii.theme(OutputTheme::Unicode); + assert_data_eq!(renderer_unicode.render(input), expected_unicode); } #[test] @@ -2367,7 +2441,7 @@ fn foo() { .element(Level::NOTE.title("this error originates in the macro `include` (in Nightly builds, run with -Z macro-backtrace for more info)")), ); - let expected = str![[r#" + let expected_ascii = str![[r#" error: couldn't read `$DIR/not-utf8.bin`: stream did not contain valid UTF-8 --> $DIR/not-utf8.rs:6:5 | @@ -2382,6 +2456,23 @@ LL | �|�␂!5�cc␕␂�Ӻi��WWj�ȥ�'�}�␒�J�ȉ��W = note: this error originates in the macro `include` (in Nightly builds, run with -Z macro-backtrace for more info) "#]]; - let renderer = Renderer::plain().anonymized_line_numbers(true); - assert_data_eq!(renderer.render(input), expected); + let renderer_ascii = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer_ascii.render(input.clone()), expected_ascii); + + let expected_unicode = str![[r#" +error: couldn't read `$DIR/not-utf8.bin`: stream did not contain valid UTF-8 + ╭▸ $DIR/not-utf8.rs:6:5 + │ +LL │ include!("not-utf8.bin"); + │ ━━━━━━━━━━━━━━━━━━━━━━━━ + ╰╴ +note: byte `193` is not valid utf-8 + ╭▸ $DIR/not-utf8.bin:1:1 + │ +LL │ �|�␂!5�cc␕␂�Ӻi��WWj�ȥ�'�}�␒�J�ȉ��W�␞O�@����␜w�V���LO����␔[ ␃_�'���SQ�~ذ��ų&��- ��lN~��!@␌ _#���kQ��h�␝�:�␜␇� + │ ━ + ╰ note: this error originates in the macro `include` (in Nightly builds, run with -Z macro-backtrace for more info) +"#]]; + let renderer_unicode = renderer_ascii.theme(OutputTheme::Unicode); + assert_data_eq!(renderer_unicode.render(input), expected_unicode); } From 42c12f36980d5171ae5d82823ae31f8ee4bb3d34 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 14 May 2025 13:10:42 +0200 Subject: [PATCH 348/455] fix: Don't output some source chars twice --- src/renderer/mod.rs | 1 - tests/formatter.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index de1b77dd..81f08512 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -2048,7 +2048,6 @@ impl Renderer { }) .collect(); - buffer.puts(line_offset, code_offset, &code, ElementStyle::Quotation); let placeholder = self.margin(); let padding = str_width(placeholder); let (width_taken, bytes_taken) = if margin.was_cut_left() { diff --git a/tests/formatter.rs b/tests/formatter.rs index 4c536329..a0e4df87 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -2371,7 +2371,7 @@ fn main() { error[E0369]: cannot add `&str` to `&str` --> $DIR/non-1-width-unicode-multiline-label.rs:7:260 | -LL | ...࿉࿊࿋࿌࿎࿏࿐࿑࿒࿓࿔࿕࿖࿗࿘࿙࿚"; let _a = unicode_is_fun + " really fun!";; +LL | ...࿉࿊࿋࿌࿎࿏࿐࿑࿒࿓࿔࿕࿖࿗࿘࿙࿚"; let _a = unicode_is_fun + " really fun!"; | -------------- ^ -------------- &str | | | | | `+` cannot be used to concatenate two `&str` strings From 722a5a66f993fd31bb5a66c8b711b907a50536f7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 15 May 2025 22:39:11 +0000 Subject: [PATCH 349/455] chore(deps): Update Rust Stable to v1.87 (#206) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aa6d8bbd..e9e3450a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -104,7 +104,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.86" # STABLE + toolchain: "1.87" # STABLE - uses: Swatinem/rust-cache@v2 - name: Check documentation env: @@ -119,7 +119,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.86" # STABLE + toolchain: "1.87" # STABLE components: rustfmt - uses: Swatinem/rust-cache@v2 - name: Check formatting @@ -135,7 +135,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.86" # STABLE + toolchain: "1.87" # STABLE components: clippy - uses: Swatinem/rust-cache@v2 - name: Install SARIF tools From cf166e316a92ce71afd467e4c10affe29b7b3395 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Tue, 20 May 2025 14:58:07 -0500 Subject: [PATCH 350/455] chore(pre-commit): Update default stages --- .pre-commit-config.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 68db968e..4acd1787 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,22 +3,22 @@ repos: rev: v4.5.0 hooks: - id: check-yaml - stages: [commit] + stages: [pre-commit] - id: check-json - stages: [commit] + stages: [pre-commit] - id: check-toml - stages: [commit] + stages: [pre-commit] - id: check-merge-conflict - stages: [commit] + stages: [pre-commit] - id: check-case-conflict - stages: [commit] + stages: [pre-commit] - id: detect-private-key - stages: [commit] + stages: [pre-commit] - repo: https://github.com/crate-ci/typos rev: v1.16.20 hooks: - id: typos - stages: [commit] + stages: [pre-commit] - repo: https://github.com/crate-ci/committed rev: v1.0.20 hooks: From a7bfa220ca56e7c6b0c9785a46e88d125bd8797b Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Tue, 20 May 2025 15:00:06 -0500 Subject: [PATCH 351/455] chore(pre-commit): Use default stages --- .pre-commit-config.yaml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4acd1787..8f7afc59 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,24 +3,16 @@ repos: rev: v4.5.0 hooks: - id: check-yaml - stages: [pre-commit] - id: check-json - stages: [pre-commit] - id: check-toml - stages: [pre-commit] - id: check-merge-conflict - stages: [pre-commit] - id: check-case-conflict - stages: [pre-commit] - id: detect-private-key - stages: [pre-commit] - repo: https://github.com/crate-ci/typos rev: v1.16.20 hooks: - id: typos - stages: [pre-commit] - repo: https://github.com/crate-ci/committed rev: v1.0.20 hooks: - id: committed - stages: [commit-msg] From 65fdcf65ba58357ac3c2f85fe551627dbd22046f Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Tue, 20 May 2025 15:03:11 -0500 Subject: [PATCH 352/455] chore(pre-commit): Update hooks --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8f7afc59..dbaa86ce 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v5.0.0 hooks: - id: check-yaml - id: check-json @@ -9,10 +9,10 @@ repos: - id: check-case-conflict - id: detect-private-key - repo: https://github.com/crate-ci/typos - rev: v1.16.20 + rev: v1.32.0 hooks: - id: typos - repo: https://github.com/crate-ci/committed - rev: v1.0.20 + rev: v1.1.7 hooks: - id: committed From 7a72bd0e0dcfee16d8a59cb1c89042968e3d01f4 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Tue, 20 May 2025 15:04:41 -0500 Subject: [PATCH 353/455] chore(pre-commit): Ensure commit-msg hook is installed --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dbaa86ce..656c68ec 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,4 @@ +default_install_hook_types: ["pre-commit", "commit-msg"] repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 From 5e85d6859df1d28dc8208b5be9d8bd03756957b3 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Tue, 27 May 2025 09:01:17 -0500 Subject: [PATCH 354/455] chore: Strip benches on publish --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 92d8817d..71ae7cf8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,6 @@ include = [ "Cargo.lock", "LICENSE*", "README.md", - "benches/**/*", "examples/**/*" ] From f9842b3b3f920ef64c5fc06298b4762018d88809 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 2 Jun 2025 12:27:10 -0500 Subject: [PATCH 355/455] chore: Avoid MSRV problems out of the box --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 71ae7cf8..0de8e135 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,7 +93,7 @@ panic = "abort" panic = "abort" codegen-units = 1 lto = true -debug = "line-tables-only" +# debug = "line-tables-only" # requires Cargo 1.71 [package] name = "PROJECT" From 66a7d10ab2f97e2bebdafbb746227d392e4b4c5e Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 2 Jun 2025 12:14:03 -0500 Subject: [PATCH 356/455] docs(readme): Specify code fence --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 21f95c4b..97599d36 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,10 @@ which may look like this: Local Development ----------------- - cargo build - cargo test +```console +$ cargo build +$ cargo test +``` When submitting a PR please use [`cargo fmt`][] (nightly). From a56d445744e31f01c634060151274d3a55f5ffd9 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 10 Jun 2025 03:38:14 -0600 Subject: [PATCH 357/455] chore: Address clippy::needless_doctest_main --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 92584f63..bf5a720e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ //! # Example //! //! ```rust +//! # #[allow(clippy::needless_doctest_main)] #![doc = include_str!("../examples/expected_type.rs")] //! ``` //! From d4300058bd506fb83683bb8f4ca1275760bf03a8 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 11 Jun 2025 09:39:53 -0600 Subject: [PATCH 358/455] chore: Fix clippy warning --- src/renderer/source_map.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/renderer/source_map.rs b/src/renderer/source_map.rs index d014bb01..d42dccce 100644 --- a/src/renderer/source_map.rs +++ b/src/renderer/source_map.rs @@ -298,9 +298,9 @@ impl<'a> SourceMap<'a> { annotated_line_infos.retain(|l| !l.annotations.is_empty()); } - annotated_line_infos - .iter_mut() - .for_each(|l| l.annotations.sort_by(|a, b| a.start.cmp(&b.start))); + for l in annotated_line_infos.iter_mut() { + l.annotations.sort_by(|a, b| a.start.cmp(&b.start)); + } (max_depth, annotated_line_infos) } From 86cc0e44a468e52ecb6934c1a5a2cb601428f247 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 11 Jun 2025 04:52:30 -0600 Subject: [PATCH 359/455] test: Update highlight_title to match rustc --- examples/highlight_title.rs | 56 ++++++++++++++++++++++-------------- examples/highlight_title.svg | 29 +++++++++++++++---- 2 files changed, 58 insertions(+), 27 deletions(-) diff --git a/examples/highlight_title.rs b/examples/highlight_title.rs index 12c106a6..218e414f 100644 --- a/examples/highlight_title.rs +++ b/examples/highlight_title.rs @@ -5,7 +5,6 @@ fn main() { let source = r#"// Make sure "highlighted" code is colored purple //@ compile-flags: --error-format=human --color=always -//@ error-pattern:[35mfor<'a> [0m //@ edition:2018 use core::pin::Pin; @@ -24,8 +23,7 @@ fn wrapped_fn<'a>(_: Box<(dyn Any + Send)>) -> Pin<Box<( fn main() { query(wrapped_fn); -} -"#; +}"#; let magenta = annotate_snippets::renderer::AnsiColor::Magenta .on_default() @@ -43,25 +41,39 @@ fn main() { magenta.render_reset() ); - let message = Level::ERROR.header("mismatched types").id("E0308").group( - Group::new() - .element( - Snippet::source(source) - .fold(true) - .origin("$DIR/highlighting.rs") - .annotation( - AnnotationKind::Primary - .span(589..599) - .label("one type is more general than the other"), - ) - .annotation( - AnnotationKind::Context - .span(583..588) - .label("arguments to this function are incorrect"), - ), - ) - .element(Level::NOTE.title(&title)), - ); + let message = Level::ERROR + .header("mismatched types") + .id("E0308") + .group( + Group::new() + .element( + Snippet::source(source) + .fold(true) + .origin("$DIR/highlighting.rs") + .annotation( + AnnotationKind::Primary + .span(553..563) + .label("one type is more general than the other"), + ) + .annotation( + AnnotationKind::Context + .span(547..552) + .label("arguments to this function are incorrect"), + ), + ) + .element(Level::NOTE.title(&title)), + ) + .group( + Group::new() + .element(Level::NOTE.title("function defined here")) + .element( + Snippet::source(source) + .fold(true) + .origin("$DIR/highlighting.rs") + .annotation(AnnotationKind::Context.span(200..333).label("")) + .annotation(AnnotationKind::Primary.span(194..199)), + ), + ); let renderer = Renderer::styled().anonymized_line_numbers(true); anstream::println!("{}", renderer.render(message)); diff --git a/examples/highlight_title.svg b/examples/highlight_title.svg index ad358761..a42d14f3 100644 --- a/examples/highlight_title.svg +++ b/examples/highlight_title.svg @@ -1,8 +1,9 @@ -<svg width="785px" height="200px" xmlns="http://www.w3.org/2000/svg"> +<svg width="785px" height="362px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } .fg-bright-blue { fill: #5555FF } + .fg-bright-green { fill: #55FF55 } .fg-bright-red { fill: #FF5555 } .fg-magenta { fill: #AA00AA } .container { @@ -22,7 +23,7 @@ <text xml:space="preserve" class="container fg"> <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan> </tspan> - <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>$DIR/highlighting.rs:22:11</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>$DIR/highlighting.rs:21:11</tspan> </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> @@ -34,11 +35,29 @@ </tspan> <tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">arguments to this function are incorrect</tspan> </tspan> - <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">= </tspan><tspan class="bold">note</tspan><tspan>: expected fn pointer `</tspan><tspan class="fg-magenta bold">for<'a></tspan><tspan> fn(Box<</tspan><tspan class="fg-magenta bold">(dyn Any + Send + 'a)</tspan><tspan>>) -> Pin<_>`</tspan> + <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="172px"><tspan> found fn item `fn(Box<</tspan><tspan class="fg-magenta bold">(dyn Any + Send + 'static)</tspan><tspan>>) -> Pin<_> </tspan><tspan class="fg-magenta bold">{wrapped_fn}</tspan><tspan>`</tspan> + <tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">= </tspan><tspan class="bold">note</tspan><tspan>: expected fn pointer `</tspan><tspan class="fg-magenta bold">for<'a></tspan><tspan> fn(Box<</tspan><tspan class="fg-magenta bold">(dyn Any + Send + 'a)</tspan><tspan>>) -> Pin<_>`</tspan> </tspan> - <tspan x="10px" y="190px"> + <tspan x="10px" y="190px"><tspan> found fn item `fn(Box<</tspan><tspan class="fg-magenta bold">(dyn Any + Send + 'static)</tspan><tspan>>) -> Pin<_> </tspan><tspan class="fg-magenta bold">{wrapped_fn}</tspan><tspan>`</tspan> +</tspan> + <tspan x="10px" y="208px"><tspan class="fg-bright-green bold">note</tspan><tspan class="bold">: function defined here</tspan> +</tspan> + <tspan x="10px" y="226px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>$DIR/highlighting.rs:10:4</tspan> +</tspan> + <tspan x="10px" y="244px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> +</tspan> + <tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">LL</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> fn query(_: fn(Box<(dyn Any + Send + '_)>) -> Pin<Box<(</tspan> +</tspan> + <tspan x="10px" y="280px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold"> ____</tspan><tspan class="fg-bright-red bold">^^^^^</tspan><tspan class="fg-bright-blue bold">_-</tspan> +</tspan> + <tspan x="10px" y="298px"><tspan class="fg-bright-blue bold">LL</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> dyn Future<Output = Result<Box<(dyn Any + 'static)>, String>> + Send + 'static</tspan> +</tspan> + <tspan x="10px" y="316px"><tspan class="fg-bright-blue bold">LL</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> )>>) {}</tspan> +</tspan> + <tspan x="10px" y="334px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|___-</tspan> +</tspan> + <tspan x="10px" y="352px"> </tspan> </text> From 33f44ae288123c03689b5dd39fc019d602b247ea Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Fri, 30 May 2025 22:42:22 -0600 Subject: [PATCH 360/455] fix: Color Primary annotation by their group level --- examples/highlight_title.svg | 2 +- src/renderer/mod.rs | 63 +++++++++++++++++++++-------------- src/renderer/styled_buffer.rs | 6 ++-- 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/examples/highlight_title.svg b/examples/highlight_title.svg index a42d14f3..24f1b364 100644 --- a/examples/highlight_title.svg +++ b/examples/highlight_title.svg @@ -49,7 +49,7 @@ </tspan> <tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">LL</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> fn query(_: fn(Box<(dyn Any + Send + '_)>) -> Pin<Box<(</tspan> </tspan> - <tspan x="10px" y="280px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold"> ____</tspan><tspan class="fg-bright-red bold">^^^^^</tspan><tspan class="fg-bright-blue bold">_-</tspan> + <tspan x="10px" y="280px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold"> ____</tspan><tspan class="fg-bright-green bold">^^^^^</tspan><tspan class="fg-bright-blue bold">_-</tspan> </tspan> <tspan x="10px" y="298px"><tspan class="fg-bright-blue bold">LL</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> dyn Future<Output = Result<Box<(dyn Any + 'static)>, String>> + Send + 'static</tspan> </tspan> diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 81f08512..24455add 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -52,6 +52,7 @@ use margin::Margin; use std::borrow::Cow; use std::cmp::{max, min, Ordering, Reverse}; use std::collections::{HashMap, VecDeque}; +use std::fmt; use std::ops::Range; use stylesheet::Stylesheet; @@ -198,7 +199,6 @@ impl Renderer { impl Renderer { pub fn render(&self, mut message: Message<'_>) -> String { - let mut buffer = StyledBuffer::new(); let max_line_num_len = if self.anonymized_line_numbers { ANONYMIZED_LINE_NUM.len() } else { @@ -206,27 +206,21 @@ impl Renderer { num_decimal_digits(n) }; let title = message.groups.remove(0).elements.remove(0); - let level = if let Element::Title(title) = &title { - title.level.clone() - } else { - panic!("Expected a title as the first element of the message") - }; if let Some(first) = message.groups.first_mut() { first.elements.insert(0, title); } else { message.groups.push(Group::new().element(title)); } - self.render_message(&mut buffer, message, max_line_num_len); - - buffer.render(level, &self.stylesheet).unwrap() + self.render_message(message, max_line_num_len).unwrap() } fn render_message( &self, - buffer: &mut StyledBuffer, message: Message<'_>, max_line_num_len: usize, - ) { + ) -> Result<String, fmt::Error> { + let mut out_string = String::new(); + let og_primary_origin = message .groups .iter() @@ -264,6 +258,7 @@ impl Renderer { ); let group_len = message.groups.len(); for (g, group) in message.groups.into_iter().enumerate() { + let mut buffer = StyledBuffer::new(); let primary_origin = group .elements .iter() @@ -295,6 +290,14 @@ impl Renderer { }) .unwrap_or_default(), ); + let level = group + .elements + .iter() + .find_map(|s| match &s { + Element::Title(title) => Some(title.level.clone()), + _ => None, + }) + .unwrap_or(Level::ERROR); let mut source_map_annotated_lines = VecDeque::new(); let mut max_depth = 0; for e in &group.elements { @@ -313,7 +316,7 @@ impl Renderer { match §ion { Element::Title(title) => { self.render_title( - buffer, + &mut buffer, title, peek, max_line_num_len, @@ -334,7 +337,7 @@ impl Renderer { source_map_annotated_lines.pop_front() { self.render_snippet_annotations( - buffer, + &mut buffer, max_line_num_len, cause, primary_origin, @@ -345,19 +348,20 @@ impl Renderer { ); if g == 0 && group_len > 1 { + let current_line = buffer.num_lines(); if matches!(peek, Some(Element::Title(level)) if level.level.name != Some(None)) { self.draw_col_separator_no_space( - buffer, - buffer.num_lines(), + &mut buffer, + current_line, max_line_num_len + 1, ); // We want to draw the separator when it is // requested, or when it is the last element } else if peek.is_none() { self.draw_col_separator_end( - buffer, - buffer.num_lines(), + &mut buffer, + current_line, max_line_num_len + 1, ); } @@ -369,7 +373,7 @@ impl Renderer { Element::Suggestion(suggestion) => { let source_map = SourceMap::new(suggestion.source, suggestion.line_start); self.emit_suggestion_default( - buffer, + &mut buffer, suggestion, max_line_num_len, &source_map, @@ -380,13 +384,14 @@ impl Renderer { } Element::Origin(origin) => { - self.render_origin(buffer, max_line_num_len, origin); + self.render_origin(&mut buffer, max_line_num_len, origin); last_was_suggestion = false; } Element::Padding(_) => { + let current_line = buffer.num_lines(); self.draw_col_separator_no_space( - buffer, - buffer.num_lines(), + &mut buffer, + current_line, max_line_num_len + 1, ); } @@ -396,23 +401,31 @@ impl Renderer { || (matches!(section, Element::Title(_)) && i == 0) || matches!(section, Element::Title(level) if level.level.name == Some(None))) { + let current_line = buffer.num_lines(); if peek.is_none() && group_len > 1 { self.draw_col_separator_end( - buffer, - buffer.num_lines(), + &mut buffer, + current_line, max_line_num_len + 1, ); } else if matches!(peek, Some(Element::Title(level)) if level.level.name != Some(None)) { self.draw_col_separator_no_space( - buffer, - buffer.num_lines(), + &mut buffer, + current_line, max_line_num_len + 1, ); } } } + buffer.render(level, &self.stylesheet, &mut out_string)?; + if g != group_len - 1 { + use std::fmt::Write; + + writeln!(out_string)?; + } } + Ok(out_string) } #[allow(clippy::too_many_arguments)] diff --git a/src/renderer/styled_buffer.rs b/src/renderer/styled_buffer.rs index c9b805a0..aa2a7a2a 100644 --- a/src/renderer/styled_buffer.rs +++ b/src/renderer/styled_buffer.rs @@ -43,8 +43,8 @@ impl StyledBuffer { &self, level: Level<'_>, stylesheet: &Stylesheet, - ) -> Result<String, fmt::Error> { - let mut str = String::new(); + str: &mut String, + ) -> Result<(), fmt::Error> { for (i, line) in self.lines.iter().enumerate() { let mut current_style = stylesheet.none; for StyledChar { ch, style } in line { @@ -63,7 +63,7 @@ impl StyledBuffer { writeln!(str)?; } } - Ok(str) + Ok(()) } /// Sets `chr` with `style` for given `line`, `col`. From f64b07d3038ffefaff11441a198194945e66bb2a Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Fri, 30 May 2025 19:50:40 -0600 Subject: [PATCH 361/455] fix: Only bold the first title of a message --- examples/custom_level.svg | 2 +- examples/footer.svg | 4 +-- examples/highlight_title.svg | 2 +- src/level.rs | 7 +----- src/renderer/mod.rs | 48 ++++++++++++++++++++---------------- src/snippet.rs | 8 ------ tests/formatter.rs | 2 ++ 7 files changed, 34 insertions(+), 39 deletions(-) diff --git a/examples/custom_level.svg b/examples/custom_level.svg index eebff280..62dded57 100644 --- a/examples/custom_level.svg +++ b/examples/custom_level.svg @@ -41,7 +41,7 @@ </tspan> <tspan x="10px" y="190px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan> </tspan> - <tspan x="10px" y="208px"><tspan class="fg-bright-cyan bold">suggestion</tspan><tspan class="bold">: use `break` on its own without a value inside this `while` loop</tspan> + <tspan x="10px" y="208px"><tspan class="fg-bright-cyan bold">suggestion</tspan><tspan>: use `break` on its own without a value inside this `while` loop</tspan> </tspan> <tspan x="10px" y="226px"><tspan> </tspan><tspan class="fg-bright-blue bold">╭╴</tspan> </tspan> diff --git a/examples/footer.svg b/examples/footer.svg index e24ba5f5..e55ee041 100644 --- a/examples/footer.svg +++ b/examples/footer.svg @@ -32,9 +32,9 @@ </tspan> <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="136px"><tspan class="fg-bright-green bold">note</tspan><tspan class="bold">: expected type: `snippet::Annotation`</tspan> + <tspan x="10px" y="136px"><tspan class="fg-bright-green bold">note</tspan><tspan>: expected type: `snippet::Annotation`</tspan> </tspan> - <tspan x="10px" y="154px"><tspan class="bold"> found type: `__&__snippet::Annotation`</tspan> + <tspan x="10px" y="154px"><tspan> found type: `__&__snippet::Annotation`</tspan> </tspan> <tspan x="10px" y="172px"> </tspan> diff --git a/examples/highlight_title.svg b/examples/highlight_title.svg index 24f1b364..c748a1de 100644 --- a/examples/highlight_title.svg +++ b/examples/highlight_title.svg @@ -41,7 +41,7 @@ </tspan> <tspan x="10px" y="190px"><tspan> found fn item `fn(Box<</tspan><tspan class="fg-magenta bold">(dyn Any + Send + 'static)</tspan><tspan>>) -> Pin<_> </tspan><tspan class="fg-magenta bold">{wrapped_fn}</tspan><tspan>`</tspan> </tspan> - <tspan x="10px" y="208px"><tspan class="fg-bright-green bold">note</tspan><tspan class="bold">: function defined here</tspan> + <tspan x="10px" y="208px"><tspan class="fg-bright-green bold">note</tspan><tspan>: function defined here</tspan> </tspan> <tspan x="10px" y="226px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>$DIR/highlighting.rs:10:4</tspan> </tspan> diff --git a/src/level.rs b/src/level.rs index 87d1a9f6..eaa95600 100644 --- a/src/level.rs +++ b/src/level.rs @@ -78,7 +78,6 @@ impl<'a> Level<'a> { groups: vec![Group::new().element(Element::Title(Title { level: self, title: header, - primary: true, }))], } } @@ -92,11 +91,7 @@ impl<'a> Level<'a> { /// /// </div> pub fn title(self, title: &'a str) -> Title<'a> { - Title { - level: self, - title, - primary: false, - } + Title { level: self, title } } pub(crate) fn as_str(&self) -> &'a str { diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 24455add..6afe3183 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -315,12 +315,16 @@ impl Renderer { let peek = message_iter.peek().map(|(_, s)| s).copied(); match §ion { Element::Title(title) => { + let title_style = match (i == 0, g == 0) { + (true, true) => TitleStyle::MainHeader, + (true, false) => TitleStyle::Header, + (false, _) => TitleStyle::Secondary, + }; self.render_title( &mut buffer, title, - peek, max_line_num_len, - if i == 0 { false } else { !title.primary }, + title_style, message.id.as_ref().and_then(|id| { if g == 0 && i == 0 { Some(id) @@ -433,26 +437,14 @@ impl Renderer { &self, buffer: &mut StyledBuffer, title: &Title<'_>, - next_section: Option<&Element<'_>>, max_line_num_len: usize, - is_secondary: bool, + title_style: TitleStyle, id: Option<&&str>, is_cont: bool, ) { let line_offset = buffer.num_lines(); - let (has_primary_spans, has_span_labels) = - next_section.map_or((false, false), |s| match s { - Element::Title(_) | Element::Padding(_) => (false, false), - Element::Cause(cause) => ( - cause.markers.iter().any(|m| m.kind.is_primary()), - cause.markers.iter().any(|m| m.label.is_some()), - ), - Element::Suggestion(_) => (true, false), - Element::Origin(_) => (false, true), - }); - - if !has_primary_spans && !has_span_labels && is_secondary { + if title_style == TitleStyle::Secondary { // This is a secondary message with no span info for _ in 0..max_line_num_len { buffer.prepend(line_offset, " ", ElementStyle::NoStyle); @@ -503,10 +495,10 @@ impl Renderer { buffer.append(line_offset, "]", ElementStyle::Level(title.level.level)); label_width += 2 + id.len(); } - let header_style = if is_secondary { - ElementStyle::HeaderMsg - } else { - ElementStyle::MainHeaderMsg + let header_style = match title_style { + TitleStyle::MainHeader => ElementStyle::MainHeaderMsg, + TitleStyle::Header => ElementStyle::HeaderMsg, + TitleStyle::Secondary => unreachable!(), }; if title.level.name != Some(None) { buffer.append(line_offset, ": ", header_style); @@ -661,7 +653,14 @@ impl Renderer { max_line_num_len + 1, ); let title = Level::NOTE.title(label); - self.render_title(buffer, &title, None, max_line_num_len, true, None, false); + self.render_title( + buffer, + &title, + max_line_num_len, + TitleStyle::Secondary, + None, + false, + ); } } @@ -2717,6 +2716,13 @@ pub enum OutputTheme { Unicode, } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum TitleStyle { + MainHeader, + Header, + Secondary, +} + #[cfg(test)] mod test { use super::OUTPUT_REPLACEMENTS; diff --git a/src/snippet.rs b/src/snippet.rs index 7226783d..d3f50104 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -159,14 +159,6 @@ pub struct Padding; pub struct Title<'a> { pub(crate) level: Level<'a>, pub(crate) title: &'a str, - pub(crate) primary: bool, -} - -impl Title<'_> { - pub fn primary(mut self, primary: bool) -> Self { - self.primary = primary; - self - } } /// A source view [`Element`] in a [`Group`] diff --git a/tests/formatter.rs b/tests/formatter.rs index a0e4df87..4fd96ff2 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -1663,6 +1663,8 @@ zappy let input_new = Level::ERROR .header("the size for values of type `T` cannot be known at compilation time") .id("E0277") + // We need an empty group here to ensure the HELP line is rendered correctly + .group(Group::new()) .group( Group::new() .element(Level::HELP.title( From c5dc85b7461b532196d38f6c542298254e89c660 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Fri, 30 May 2025 19:50:40 -0600 Subject: [PATCH 362/455] fix: Better account for unicode chars when trimming --- src/renderer/mod.rs | 29 +++++++++++------------------ tests/formatter.rs | 16 ++++++++-------- 2 files changed, 19 insertions(+), 26 deletions(-) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 6afe3183..fc5d2673 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -967,21 +967,7 @@ impl Renderer { let line_offset = buffer.num_lines(); - // Left trim - let left = margin.left(str_width(&source_string)); - - // FIXME: This looks fishy. See #132860. - // Account for unicode characters of width !=0 that were removed. - let mut taken = 0; - source_string.chars().for_each(|ch| { - let next = char_width(ch); - if taken + next <= left { - taken += next; - } - }); - - let left = taken; - self.draw_line( + let left = self.draw_line( buffer, &source_string, line_info.line_index, @@ -2036,12 +2022,12 @@ impl Renderer { code_offset: usize, max_line_num_len: usize, margin: Margin, - ) { + ) -> usize { // Tabs are assumed to have been replaced by spaces in calling code. debug_assert!(!source_string.contains('\t')); let line_len = str_width(source_string); // Create the source line we will highlight. - let left = margin.left(line_len); + let mut left = margin.left(line_len); let right = margin.right(line_len); // FIXME: The following code looks fishy. See #132860. // On long lines, we strip the source line, accounting for unicode. @@ -2074,10 +2060,15 @@ impl Renderer { break; } } + + if width_taken > padding { + left -= width_taken - padding; + } + buffer.puts( line_offset, code_offset, - &format!("{placeholder:>width_taken$}"), + placeholder, ElementStyle::LineNumber, ); (width_taken, bytes_taken) @@ -2121,6 +2112,8 @@ impl Renderer { ); self.draw_col_separator_no_space(buffer, line_offset, width_offset - 2); + + left } fn draw_range( diff --git a/tests/formatter.rs b/tests/formatter.rs index 4fd96ff2..664bd78a 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -2178,8 +2178,8 @@ fn unicode_cut_handling2() { let expected_ascii = str![[r#" error: expected item, found `?` | -1 | ...的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/? - | ^ expected item +1 | ... 的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/? + | ^ expected item = note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html> "#]]; @@ -2189,8 +2189,8 @@ error: expected item, found `?` let expected_unicode = str![[r#" error: expected item, found `?` │ -1 │ …宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/? - │ ━ expected item +1 │ … 宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/? + │ ━ expected item ╰ note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html> "#]]; let renderer_unicode = renderer_ascii.theme(OutputTheme::Unicode); @@ -2215,8 +2215,8 @@ fn unicode_cut_handling3() { let expected_ascii = str![[r#" error: expected item, found `?` | -1 | ...。这是宽的。这是宽的。这是宽的... - | ^^ expected item +1 | ... 。这是宽的。这是宽的。这是宽的... + | ^^ expected item = note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html> "#]]; @@ -2226,8 +2226,8 @@ error: expected item, found `?` let expected_unicode = str![[r#" error: expected item, found `?` │ -1 │ …的。这是宽的。这是宽的。这是宽的。… - │ ━━ expected item +1 │ … 的。这是宽的。这是宽的。这是宽的。… + │ ━━ expected item ╰ note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html> "#]]; let renderer_unicode = renderer_ascii.theme(OutputTheme::Unicode); From 8b5e6a3c77c16214251ce8b4f0df2be3558d40f4 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 29 May 2025 21:49:14 -0600 Subject: [PATCH 363/455] test: Add middle folding tests --- tests/rustc_tests.rs | 139 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index 69986292..9f5b1411 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -4,6 +4,7 @@ use annotate_snippets::{AnnotationKind, Group, Level, Origin, Renderer, Snippet}; +use annotate_snippets::renderer::OutputTheme; use snapbox::{assert_data_eq, str}; #[test] @@ -1881,3 +1882,141 @@ LL | trait EqAlias = Eq; let renderer = Renderer::plain().anonymized_line_numbers(true); assert_data_eq!(renderer.render(input), expected); } + +#[test] +fn long_span_shortest() { + // tests/ui/diagnostic-width/long-span.rs + let source = r#" +const C: u8 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + +fn main() {} +"#; + let input = Level::ERROR.header("mismatched types").id("E0038").group( + Group::new().element( + Snippet::source(source) + .origin("$DIR/long-span.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(15..5055) + .label("expected `u8`, found `[{integer}; 1680]`"), + ), + ), + ); + let expected = str![[r#" +error[E0038]: mismatched types + --> $DIR/long-span.rs:2:15 + | +LL | ... = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `u8`, found `[{integer}; 1680]` +"#]]; + + let renderer = Renderer::plain() + .anonymized_line_numbers(true) + .term_width(8); + assert_data_eq!(renderer.render(input), expected); +} + +#[test] +fn long_span_short() { + // tests/ui/diagnostic-width/long-span.rs + let source = r#" +const C: u8 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + +fn main() {} +"#; + let input = Level::ERROR.header("mismatched types").id("E0038").group( + Group::new().element( + Snippet::source(source) + .origin("$DIR/long-span.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(15..5055) + .label("expected `u8`, found `[{integer}; 1680]`"), + ), + ), + ); + let expected = str![[r#" +error[E0038]: mismatched types + ╭▸ $DIR/long-span.rs:2:15 + │ +LL │ …u8 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]… + ╰╴ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ expected `u8`, found `[{integer}; 1680]` +"#]]; + + let renderer = Renderer::plain() + .anonymized_line_numbers(true) + .term_width(12) + .theme(OutputTheme::Unicode); + assert_data_eq!(renderer.render(input), expected); +} + +#[test] +fn long_span_long() { + // tests/ui/diagnostic-width/long-span.rs + let source = r#" +const C: u8 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + +fn main() {} +"#; + let input = Level::ERROR.header("mismatched types").id("E0038").group( + Group::new().element( + Snippet::source(source) + .origin("$DIR/long-span.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(15..5055) + .label("expected `u8`, found `[{integer}; 1680]`"), + ), + ), + ); + let expected = str![[r#" +error[E0038]: mismatched types + ╭▸ $DIR/long-span.rs:2:15 + │ +LL │ …u8 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]… + ╰╴ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ expected `u8`, found `[{integer}; 1680]` +"#]]; + + let renderer = Renderer::plain() + .anonymized_line_numbers(true) + .term_width(80) + .theme(OutputTheme::Unicode); + assert_data_eq!(renderer.render(input), expected); +} + +#[test] +fn long_span_longest() { + // tests/ui/diagnostic-width/long-span.rs + let source = r#" +const C: u8 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + +fn main() {} +"#; + let input = Level::ERROR.header("mismatched types").id("E0038").group( + Group::new().element( + Snippet::source(source) + .origin("$DIR/long-span.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(15..5055) + .label("expected `u8`, found `[{integer}; 1680]`"), + ), + ), + ); + let expected = str![[r#" +error[E0038]: mismatched types + --> $DIR/long-span.rs:2:15 + | +LL | ... = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `u8`, found `[{integer}; 1680]` +"#]]; + + let renderer = Renderer::plain() + .anonymized_line_numbers(true) + .term_width(120); + assert_data_eq!(renderer.render(input), expected); +} From 6819166f36e98ce01772621154a6472f50ed4eb4 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 29 May 2025 21:50:03 -0600 Subject: [PATCH 364/455] fix: Don't add margin when span is near line end --- src/renderer/margin.rs | 12 ------------ src/renderer/mod.rs | 2 +- tests/rustc_tests.rs | 8 ++++---- 3 files changed, 5 insertions(+), 17 deletions(-) diff --git a/src/renderer/margin.rs b/src/renderer/margin.rs index c4844166..59bd5507 100644 --- a/src/renderer/margin.rs +++ b/src/renderer/margin.rs @@ -58,18 +58,6 @@ impl Margin { self.computed_left > 0 } - pub(crate) fn was_cut_right(&self, line_len: usize) -> bool { - let right = - if self.computed_right == self.span_right || self.computed_right == self.label_right { - // Account for the "..." padding given above. Otherwise we end up with code lines that - // do fit but end in "..." as if they were trimmed. - self.computed_right - ELLIPSIS_PASSING - } else { - self.computed_right - }; - right < line_len && self.computed_left + self.term_width < line_len - } - fn compute(&mut self, max_line_len: usize) { // When there's a lot of whitespace (>20), we want to trim it as it is useless. self.computed_left = if self.whitespace_left > LONG_WHITESPACE { diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index fc5d2673..77411a19 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -2083,7 +2083,7 @@ impl Renderer { ElementStyle::Quotation, ); - if margin.was_cut_right(line_len) { + if line_len > right { // We have stripped some code/whitespace from the beginning, make it clear. let mut char_taken = 0; let mut width_taken_inner = 0; diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index 9f5b1411..dd7dceb9 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -1907,7 +1907,7 @@ fn main() {} error[E0038]: mismatched types --> $DIR/long-span.rs:2:15 | -LL | ... = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... +LL | ... = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `u8`, found `[{integer}; 1680]` "#]]; @@ -1941,7 +1941,7 @@ fn main() {} error[E0038]: mismatched types ╭▸ $DIR/long-span.rs:2:15 │ -LL │ …u8 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]… +LL │ …u8 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; ╰╴ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ expected `u8`, found `[{integer}; 1680]` "#]]; @@ -1976,7 +1976,7 @@ fn main() {} error[E0038]: mismatched types ╭▸ $DIR/long-span.rs:2:15 │ -LL │ …u8 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]… +LL │ …u8 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; ╰╴ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ expected `u8`, found `[{integer}; 1680]` "#]]; @@ -2011,7 +2011,7 @@ fn main() {} error[E0038]: mismatched types --> $DIR/long-span.rs:2:15 | -LL | ... = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... +LL | ... = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `u8`, found `[{integer}; 1680]` "#]]; From 76f8220673be3a3ea44f733ab2c0863f054f19e1 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Fri, 30 May 2025 13:42:35 -0600 Subject: [PATCH 365/455] feat: Trim the middle of long spans --- src/renderer/margin.rs | 2 +- src/renderer/mod.rs | 38 +++++++++++++++++++++++++++++++++++ src/renderer/styled_buffer.rs | 10 +++++++++ tests/rustc_tests.rs | 16 +++++++-------- 4 files changed, 57 insertions(+), 9 deletions(-) diff --git a/src/renderer/margin.rs b/src/renderer/margin.rs index 59bd5507..6d3989be 100644 --- a/src/renderer/margin.rs +++ b/src/renderer/margin.rs @@ -17,7 +17,7 @@ pub(crate) struct Margin { /// The end of the line to be displayed. computed_right: usize, /// The current width of the terminal. 140 by default and in tests. - term_width: usize, + pub(crate) term_width: usize, /// The end column of a span label, including the span. Doesn't account for labels not in the /// same line as the span. label_right: usize, diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 77411a19..04dacdb3 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -1122,11 +1122,16 @@ impl Renderer { // | x_span // <EMPTY LINE> // + let mut overlap = vec![false; annotations.len()]; let mut annotations_position = vec![]; let mut line_len: usize = 0; let mut p = 0; for (i, annotation) in annotations.iter().enumerate() { for (j, next) in annotations.iter().enumerate() { + if overlaps(next, annotation, 0) && j > 1 { + overlap[i] = true; + overlap[j] = true; + } if overlaps(next, annotation, 0) // This label overlaps with another one and both && annotation.has_label() // take space (they have text and are not && j > i // multiline lines). @@ -1474,6 +1479,39 @@ impl Renderer { ); } } + + // We look for individual *long* spans, and we trim the *middle*, so that we render + // LL | ...= [0, 0, 0, ..., 0, 0]; + // | ^^^^^^^^^^...^^^^^^^ expected `&[u8]`, found `[{integer}; 1680]` + for (i, (_pos, annotation)) in annotations_position.iter().enumerate() { + // Skip cases where multiple spans overlap eachother. + if overlap[i] { + continue; + }; + let LineAnnotationType::Singleline = annotation.annotation_type else { + continue; + }; + let width = annotation.end.display - annotation.start.display; + if width > margin.term_width * 2 && width > 10 { + // If the terminal is *too* small, we keep at least a tiny bit of the span for + // display. + let pad = max(margin.term_width / 3, 5); + // Code line + buffer.replace( + line_offset, + annotation.start.display + pad, + annotation.end.display - pad, + self.margin(), + ); + // Underline line + buffer.replace( + line_offset + 1, + annotation.start.display + pad, + annotation.end.display - pad, + self.margin(), + ); + } + } annotations_position .iter() .filter_map(|&(_, annotation)| match annotation.annotation_type { diff --git a/src/renderer/styled_buffer.rs b/src/renderer/styled_buffer.rs index aa2a7a2a..925bb446 100644 --- a/src/renderer/styled_buffer.rs +++ b/src/renderer/styled_buffer.rs @@ -99,6 +99,16 @@ impl StyledBuffer { } } + pub(crate) fn replace(&mut self, line: usize, start: usize, end: usize, string: &str) { + if start == end { + return; + } + let _ = self.lines[line].drain(start..(end - string.chars().count())); + for (i, c) in string.chars().enumerate() { + self.lines[line][start + i] = StyledChar::new(c, ElementStyle::LineNumber); + } + } + /// For given `line` inserts `string` with `style` before old content of that line, /// adding lines if needed pub(crate) fn prepend(&mut self, line: usize, string: &str, style: ElementStyle) { diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index dd7dceb9..642c84ee 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -1907,8 +1907,8 @@ fn main() {} error[E0038]: mismatched types --> $DIR/long-span.rs:2:15 | -LL | ... = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `u8`, found `[{integer}; 1680]` +LL | ... = [0, 0, 0...0]; + | ^^^^^^^^...^^ expected `u8`, found `[{integer}; 1680]` "#]]; let renderer = Renderer::plain() @@ -1941,8 +1941,8 @@ fn main() {} error[E0038]: mismatched types ╭▸ $DIR/long-span.rs:2:15 │ -LL │ …u8 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - ╰╴ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ expected `u8`, found `[{integer}; 1680]` +LL │ …u8 = [0, 0, 0…0]; + ╰╴ ━━━━━━━━…━━ expected `u8`, found `[{integer}; 1680]` "#]]; let renderer = Renderer::plain() @@ -1976,8 +1976,8 @@ fn main() {} error[E0038]: mismatched types ╭▸ $DIR/long-span.rs:2:15 │ -LL │ …u8 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - ╰╴ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ expected `u8`, found `[{integer}; 1680]` +LL │ …u8 = [0, 0, 0, 0, 0, 0, 0, 0, 0, …, 0, 0, 0, 0, 0, 0, 0]; + ╰╴ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━…━━━━━━━━━━━━━━━━━━━━━━ expected `u8`, found `[{integer}; 1680]` "#]]; let renderer = Renderer::plain() @@ -2011,8 +2011,8 @@ fn main() {} error[E0038]: mismatched types --> $DIR/long-span.rs:2:15 | -LL | ... = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `u8`, found `[{integer}; 1680]` +LL | ... = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0...0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^...^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `u8`, found `[{integer}; 1680]` "#]]; let renderer = Renderer::plain() From 14fb674cd6561a4631341fcd7f748cdff33de385 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Sat, 31 May 2025 00:09:21 -0600 Subject: [PATCH 366/455] fix: Match rustc's first group trailing line --- examples/highlight_source.svg | 8 +++++--- src/renderer/mod.rs | 28 +++++++++++++++------------- tests/formatter.rs | 7 +++++++ 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/examples/highlight_source.svg b/examples/highlight_source.svg index 391a097d..14014944 100644 --- a/examples/highlight_source.svg +++ b/examples/highlight_source.svg @@ -1,4 +1,4 @@ -<svg width="961px" height="146px" xmlns="http://www.w3.org/2000/svg"> +<svg width="961px" height="164px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -29,9 +29,11 @@ </tspan> <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">allocation not allowed in constants</tspan> </tspan> - <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">= </tspan><tspan class="bold">note</tspan><tspan>: The runtime heap is not yet available at compile-time, so no runtime heap allocations can be created.</tspan> + <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="136px"> + <tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">= </tspan><tspan class="bold">note</tspan><tspan>: The runtime heap is not yet available at compile-time, so no runtime heap allocations can be created.</tspan> +</tspan> + <tspan x="10px" y="154px"> </tspan> </text> diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 04dacdb3..ab791edd 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -351,23 +351,25 @@ impl Renderer { peek.is_some() || (g == 0 && group_len > 1), ); - if g == 0 && group_len > 1 { + if g == 0 { let current_line = buffer.num_lines(); - if matches!(peek, Some(Element::Title(level)) if level.level.name != Some(None)) - { - self.draw_col_separator_no_space( - &mut buffer, - current_line, - max_line_num_len + 1, - ); - // We want to draw the separator when it is - // requested, or when it is the last element - } else if peek.is_none() { - self.draw_col_separator_end( + match peek { + Some(Element::Title(level)) + if level.level.name != Some(None) => + { + self.draw_col_separator_no_space( + &mut buffer, + current_line, + max_line_num_len + 1, + ); + } + + None if group_len > 1 => self.draw_col_separator_end( &mut buffer, current_line, max_line_num_len + 1, - ); + ), + _ => {} } } } diff --git a/tests/formatter.rs b/tests/formatter.rs index 664bd78a..7e25a011 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -2021,6 +2021,7 @@ LL │ ┃ Ok("") LL │ ┃ )))))))))))))))))))))))))))))) LL │ ┃ )))))))))))))))))))))))))))))); │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ expected `Atype<Btype<Ctype<..., i32>, i32>, i32>`, found `Result<Result<Result<..., _>, _>, _>` + │ ├ note: expected struct `Atype<Btype<..., i32>, i32>` │ found enum `Result<Result<..., _>, _>` ├ note: the full name for the type has been written to '$TEST_BUILD_DIR/$FILE.long-type-hash.txt' @@ -2180,6 +2181,7 @@ error: expected item, found `?` | 1 | ... 的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/? | ^ expected item + | = note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html> "#]]; @@ -2191,6 +2193,7 @@ error: expected item, found `?` │ 1 │ … 宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/? │ ━ expected item + │ ╰ note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html> "#]]; let renderer_unicode = renderer_ascii.theme(OutputTheme::Unicode); @@ -2217,6 +2220,7 @@ error: expected item, found `?` | 1 | ... 。这是宽的。这是宽的。这是宽的... | ^^ expected item + | = note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html> "#]]; @@ -2228,6 +2232,7 @@ error: expected item, found `?` │ 1 │ … 的。这是宽的。这是宽的。这是宽的。… │ ━━ expected item + │ ╰ note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html> "#]]; let renderer_unicode = renderer_ascii.theme(OutputTheme::Unicode); @@ -2254,6 +2259,7 @@ error: expected item, found `?` | 1 | ...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/? | ^ expected item + | = note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html> "#]]; @@ -2265,6 +2271,7 @@ error: expected item, found `?` │ 1 │ …aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/? │ ━ expected item + │ ╰ note: for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html> "#]]; let renderer_unicode = renderer_ascii.theme(OutputTheme::Unicode); From a4c20fb6fecf5bfc54dd5204f29e4d9b5d1dd90e Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Mon, 2 Jun 2025 19:40:48 -0600 Subject: [PATCH 367/455] test: Add another rustc multiline test --- tests/rustc_tests.rs | 93 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index 642c84ee..9649c12b 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -2,7 +2,7 @@ //! //! [parser-tests]: https://github.com/rust-lang/rust/blob/894f7a4ba6554d3797404bbf550d9919df060b97/compiler/rustc_parse/src/parser/tests.rs -use annotate_snippets::{AnnotationKind, Group, Level, Origin, Renderer, Snippet}; +use annotate_snippets::{AnnotationKind, Group, Level, Origin, Patch, Renderer, Snippet}; use annotate_snippets::renderer::OutputTheme; use snapbox::{assert_data_eq, str}; @@ -2020,3 +2020,94 @@ LL | ... = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0...0, 0, 0, 0, 0, 0, 0, 0, 0 .term_width(120); assert_data_eq!(renderer.render(input), expected); } + +#[test] +fn lint_map_unit_fn() { + // tests/ui/lint/lint_map_unit_fn.rs + let source = r#"#![deny(map_unit_fn)] + +fn foo(items: &mut Vec<u8>) { + items.sort(); +} + +fn main() { + let mut x: Vec<Vec<u8>> = vec![vec![0, 2, 1], vec![5, 4, 3]]; + x.iter_mut().map(foo); + //~^ ERROR `Iterator::map` call that discard the iterator's values + x.iter_mut().map(|items| { + //~^ ERROR `Iterator::map` call that discard the iterator's values + items.sort(); + }); + let f = |items: &mut Vec<u8>| { + items.sort(); + }; + x.iter_mut().map(f); + //~^ ERROR `Iterator::map` call that discard the iterator's values +} +"#; + + let input = Level::ERROR + .header("`Iterator::map` call that discard the iterator's values") + .group( + Group::new() + .element( + Snippet::source(source) + .origin("$DIR/lint_map_unit_fn.rs") + .fold(true) + .annotation(AnnotationKind::Context.span(271..278).label( + "this function returns `()`, which is likely not what you wanted", + )) + .annotation( + AnnotationKind::Context + .span(271..379) + .label("called `Iterator::map` with callable that returns `()`"), + ) + .annotation( + AnnotationKind::Context + .span(267..380) + .label("after this call to map, the resulting iterator is `impl Iterator<Item = ()>`, which means the only information carried by the iterator is the number of items") + ) + .annotation(AnnotationKind::Primary.span(267..380)), + ) + .element( + Level::NOTE.title("`Iterator::map`, like many of the methods on `Iterator`, gets executed lazily, meaning that its effects won't be visible until it is iterated")), + ) + .group( + Group::new() + .element(Level::HELP.title("you might have meant to use `Iterator::for_each`")) + .element( + Snippet::source(source) + .origin("$DIR/lint_map_unit_fn.rs") + .fold(true) + .patch(Patch::new(267..270, r#"for_each"#)), + ), + ); + + let expected = str![[r#" +error: `Iterator::map` call that discard the iterator's values + --> $DIR/lint_map_unit_fn.rs:11:18 + | +LL | x.iter_mut().map(|items| { + | ^ ------- + | | | + | __________________|___this function returns `()`, which is likely not what you wanted + | |__________________| + | || +LL | || //~^ ERROR `Iterator::map` call that discard the iterator's values +LL | || items.sort(); +LL | || }); + | || -^ after this call to map, the resulting iterator is `impl Iterator<Item = ()>`, which means the only information carried by the iterator is the number of items + | ||_____|| + | |_____| + | called `Iterator::map` with callable that returns `()` + | + = note: `Iterator::map`, like many of the methods on `Iterator`, gets executed lazily, meaning that its effects won't be visible until it is iterated +help: you might have meant to use `Iterator::for_each` + | +LL - x.iter_mut().map(|items| { +LL + x.iter_mut().for_each(|items| { + | +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected); +} From c8041fe43246091b2e7a0158c8ab89146568321c Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Sat, 31 May 2025 00:09:21 -0600 Subject: [PATCH 368/455] fix: Match rustc's multiline reordering --- src/renderer/source_map.rs | 3 +-- tests/formatter.rs | 16 ++++++++-------- tests/rustc_tests.rs | 26 +++++++++++++------------- 3 files changed, 22 insertions(+), 23 deletions(-) diff --git a/src/renderer/source_map.rs b/src/renderer/source_map.rs index d42dccce..eca057ee 100644 --- a/src/renderer/source_map.rs +++ b/src/renderer/source_map.rs @@ -194,8 +194,7 @@ impl<'a> SourceMap<'a> { let mut primary_spans = vec![]; // Find overlapping multiline annotations, put them at different depths - multiline_annotations - .sort_by_key(|ml| (ml.start.line, usize::MAX - ml.end.line, ml.start.byte)); + multiline_annotations.sort_by_key(|ml| (ml.start.line, usize::MAX - ml.end.line)); for ann in multiline_annotations.clone() { if ann.kind.is_primary() { primary_spans.push((ann.start, ann.end)); diff --git a/tests/formatter.rs b/tests/formatter.rs index 7e25a011..bf998539 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -887,15 +887,15 @@ bar = { version = "0.1.0", optional = true } error: unused optional dependency | 4 | bar = { version = "0.1.0", optional = true } - | _________^__________________--------------^ - | | | | - | |_________| This should also be long but not too long + | __________^__________________--------------^ + | | | | + | | _________| This should also be long but not too long | || 5 | || this is another line 6 | || so is this 7 | || bar = { version = "0.1.0", optional = true } | ||_________________________^________________^ I need this to be really long so I can test overlaps - | |__________________________| + | |_________________________| | I need this to be really long so I can test overlaps "#]]; let renderer = Renderer::plain(); @@ -940,16 +940,16 @@ this is another line error: unused optional dependency | 4 | bar = { version = "0.1.0", optional = true } - | __________^__________________--------------^ - | | | | - | |__________| This should also be long but not too long + | ___________^__________________--------------^ + | | | | + | | __________| This should also be long but not too long | || 5 | || this is another line | || ____^ 6 | ||| so is this 7 | ||| bar = { version = "0.1.0", optional = true } | |||_________________________^________________^ I need this to be really long so I can test overlaps - | |_|_________________________| + | ||_________________________| | | I need this to be really long so I can test overlaps 8 | | this is another line | |____^ I need this to be really long so I can test overlaps diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index 9649c12b..cf33969a 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -2087,19 +2087,19 @@ fn main() { error: `Iterator::map` call that discard the iterator's values --> $DIR/lint_map_unit_fn.rs:11:18 | -LL | x.iter_mut().map(|items| { - | ^ ------- - | | | - | __________________|___this function returns `()`, which is likely not what you wanted - | |__________________| - | || -LL | || //~^ ERROR `Iterator::map` call that discard the iterator's values -LL | || items.sort(); -LL | || }); - | || -^ after this call to map, the resulting iterator is `impl Iterator<Item = ()>`, which means the only information carried by the iterator is the number of items - | ||_____|| - | |_____| - | called `Iterator::map` with callable that returns `()` +LL | x.iter_mut().map(|items| { + | ^ ------- + | | | + | ____________________|___this function returns `()`, which is likely not what you wanted + | | __________________| + | | | +LL | | | //~^ ERROR `Iterator::map` call that discard the iterator's values +LL | | | items.sort(); +LL | | | }); + | | | -^ after this call to map, the resulting iterator is `impl Iterator<Item = ()>`, which means the only information carried by the iterator is the number of items + | | |_____|| + | |_______| + | called `Iterator::map` with callable that returns `()` | = note: `Iterator::map`, like many of the methods on `Iterator`, gets executed lazily, meaning that its effects won't be visible until it is iterated help: you might have meant to use `Iterator::for_each` From 5cc5a3c8905e8a29f5a7c59408057f7484ccb324 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 3 Jun 2025 09:45:06 -0600 Subject: [PATCH 369/455] test: Add rustc tests for pointing at end of line --- tests/rustc_tests.rs | 352 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 352 insertions(+) diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index cf33969a..d013aa33 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -2111,3 +2111,355 @@ LL + x.iter_mut().for_each(|items| { let renderer = Renderer::plain().anonymized_line_numbers(true); assert_data_eq!(renderer.render(input), expected); } + +#[test] +fn bad_char_literals() { + // tests/ui/parser/bad-char-literals.rs + + let source = r#"// ignore-tidy-cr +// ignore-tidy-tab + +fn main() { + // these literals are just silly. + '''; + //~^ ERROR: character constant must be escaped: `'` + + // note that this is a literal "\n" byte + ' +'; + //~^^ ERROR: character constant must be escaped: `\n` + + // note that this is a literal "\r" byte +; //~ ERROR: character constant must be escaped: `\r` + + // note that this is a literal NULL + '--'; //~ ERROR: character literal may only contain one codepoint + + // note that this is a literal tab character here + ' '; + //~^ ERROR: character constant must be escaped: `\t` +} +"#; + + let input = Level::ERROR + .header("character constant must be escaped: `\\n`") + .group( + Group::new().element( + Snippet::source(source) + .origin("$DIR/bad-char-literals.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(204..205)), + ), + ) + .group( + Group::new() + .element(Level::HELP.title("escape the character")) + .element( + Snippet::source(source) + .origin("$DIR/bad-char-literals.rs") + .line_start(1) + .fold(true) + .patch(Patch::new(204..205, r#"\n"#)), + ), + ); + let expected = str![[r#" +error: character constant must be escaped: `/n` + --> $DIR/bad-char-literals.rs:10:6 + | +LL | ' + | ^ + | +help: escape the character + | +LL | '/n + | ++ +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected); +} + +#[test] +fn unclosed_1() { + // tests/ui/frontmatter/unclosed-1.rs + + let source = r#"----cargo +//~^ ERROR: unclosed frontmatter + +// This test checks that the #! characters can help us recover a frontmatter +// close. There should not be a "missing `main` function" error as the rest +// are properly parsed. + +#![feature(frontmatter)] + +fn main() {} +"#; + + let input = Level::ERROR + .header("unclosed frontmatter") + .group( + Group::new().element( + Snippet::source(source) + .origin("$DIR/unclosed-1.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(0..221)), + ), + ) + .group( + Group::new() + .element(Level::NOTE.title("frontmatter opening here was not closed")) + .element( + Snippet::source(source) + .origin("$DIR/unclosed-1.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(0..4)), + ), + ); + let expected = str![[r#" +error: unclosed frontmatter + --> $DIR/unclosed-1.rs:1:1 + | +LL | / ----cargo +... | +LL | | // are properly parsed. + | |________________________^ + | +note: frontmatter opening here was not closed + --> $DIR/unclosed-1.rs:1:1 + | +LL | ----cargo + | ^^^^ +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected); +} + +#[test] +fn unclosed_2() { + // tests/ui/frontmatter/unclosed-2.rs + + let source = r#"----cargo +//~^ ERROR: unclosed frontmatter +//~| ERROR: frontmatters are experimental + +//@ compile-flags: --crate-type lib + +// Leading whitespace on the feature line prevents recovery. However +// the dashes quoted will not be used for recovery and the entire file +// should be treated as within the frontmatter block. + + #![feature(frontmatter)] + +fn foo() -> &str { + "----" +} +"#; + + let input = Level::ERROR + .header("unclosed frontmatter") + .group( + Group::new().element( + Snippet::source(source) + .origin("$DIR/unclosed-2.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(0..377)), + ), + ) + .group( + Group::new() + .element(Level::NOTE.title("frontmatter opening here was not closed")) + .element( + Snippet::source(source) + .origin("$DIR/unclosed-2.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(0..4)), + ), + ); + let expected = str![[r#" +error: unclosed frontmatter + --> $DIR/unclosed-2.rs:1:1 + | +LL | / ----cargo +... | +LL | | "----" +LL | | } + | |__^ + | +note: frontmatter opening here was not closed + --> $DIR/unclosed-2.rs:1:1 + | +LL | ----cargo + | ^^^^ +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected); +} + +#[test] +fn unclosed_3() { + // tests/ui/frontmatter/unclosed-3.rs + + let source = r#"----cargo +//~^ ERROR: frontmatter close does not match the opening + +//@ compile-flags: --crate-type lib + +// Unfortunate recovery situation. Not really preventable with improving the +// recovery strategy, but this type of code is rare enough already. + + #![feature(frontmatter)] + +fn foo(x: i32) -> i32 { + ---x + //~^ ERROR: invalid preceding whitespace for frontmatter close + //~| ERROR: extra characters after frontmatter close are not allowed +} +//~^ ERROR: unexpected closing delimiter: `}` +"#; + + let input = Level::ERROR + .header("invalid preceding whitespace for frontmatter close") + .group( + Group::new().element( + Snippet::source(source) + .origin("$DIR/unclosed-3.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(302..310)), + ), + ) + .group( + Group::new() + .element( + Level::NOTE.title("frontmatter close should not be preceded by whitespace"), + ) + .element( + Snippet::source(source) + .origin("$DIR/unclosed-3.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(302..306)), + ), + ); + let expected = str![[r#" +error: invalid preceding whitespace for frontmatter close + --> $DIR/unclosed-3.rs:12:1 + | +LL | ---x + | ^^^^^^^^ + | +note: frontmatter close should not be preceded by whitespace + --> $DIR/unclosed-3.rs:12:1 + | +LL | ---x + | ^^^^ +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected); +} + +#[test] +fn unclosed_4() { + // tests/ui/frontmatter/unclosed-4.rs + + let source = r#"----cargo +//~^ ERROR: unclosed frontmatter + +//! Similarly, a module-level content should allow for recovery as well (as +//! per unclosed-1.rs) + +#![feature(frontmatter)] + +fn main() {} +"#; + + let input = Level::ERROR + .header("unclosed frontmatter") + .group( + Group::new().element( + Snippet::source(source) + .origin("$DIR/unclosed-4.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(0..43)), + ), + ) + .group( + Group::new() + .element(Level::NOTE.title("frontmatter opening here was not closed")) + .element( + Snippet::source(source) + .origin("$DIR/unclosed-4.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(0..4)), + ), + ); + let expected = str![[r#" +error: unclosed frontmatter + --> $DIR/unclosed-4.rs:1:1 + | +LL | / ----cargo +LL | | //~^ ERROR: unclosed frontmatter + | |_________________________________^ + | +note: frontmatter opening here was not closed + --> $DIR/unclosed-4.rs:1:1 + | +LL | ----cargo + | ^^^^ +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected); +} + +#[test] +fn unclosed_5() { + // tests/ui/frontmatter/unclosed-5.rs + + let source = r#"----cargo +//~^ ERROR: unclosed frontmatter +//~| ERROR: frontmatters are experimental + +// Similarly, a use statement should allow for recovery as well (as +// per unclosed-1.rs) + +use std::env; + +fn main() {} +"#; + + let input = Level::ERROR + .header("unclosed frontmatter") + .group( + Group::new().element( + Snippet::source(source) + .origin("$DIR/unclosed-5.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(0..176)), + ), + ) + .group( + Group::new() + .element(Level::NOTE.title("frontmatter opening here was not closed")) + .element( + Snippet::source(source) + .origin("$DIR/unclosed-5.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(0..4)), + ), + ); + + let expected = str![[r#" +error: unclosed frontmatter + --> $DIR/unclosed-5.rs:1:1 + | +LL | / ----cargo +... | +LL | | // per unclosed-1.rs) + | |______________________^ + | +note: frontmatter opening here was not closed + --> $DIR/unclosed-5.rs:1:1 + | +LL | ----cargo + | ^^^^ +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected); +} From 47e7ec6a8c10ab9766e6812a744c7ae8b7cff972 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 3 Jun 2025 09:45:06 -0600 Subject: [PATCH 370/455] fix: Match how rustc handles annotating newlines --- src/renderer/source_map.rs | 8 ++--- tests/color/ann_multiline2.term.svg | 12 ++++--- tests/color/simple.term.svg | 14 ++++---- tests/formatter.rs | 55 +++++++++++++++++------------ tests/rustc_tests.rs | 19 +++++----- 5 files changed, 60 insertions(+), 48 deletions(-) diff --git a/src/renderer/source_map.rs b/src/renderer/source_map.rs index eca057ee..403c1c63 100644 --- a/src/renderer/source_map.rs +++ b/src/renderer/source_map.rs @@ -73,9 +73,9 @@ impl<'a> SourceMap<'a> { let end_info = self .lines .iter() - .find(|info| info.end_byte > span.end.saturating_sub(1)) + .find(|info| span.end >= info.start_byte && span.end < info.end_byte) .unwrap_or(self.lines.last().unwrap()); - let (mut end_char_pos, end_display_pos) = end_info.line + let (end_char_pos, end_display_pos) = end_info.line [0..(span.end - end_info.start_byte).min(end_info.line.len())] .chars() .fold((0, 0), |(char_pos, byte_pos), c| { @@ -83,10 +83,6 @@ impl<'a> SourceMap<'a> { (char_pos + 1, byte_pos + display) }); - // correct the char pos if we are highlighting the end of a line - if (span.end - end_info.start_byte).saturating_sub(end_info.line.len()) > 0 { - end_char_pos += 1; - } let mut end = Loc { line: end_info.line_index, char: end_char_pos, diff --git a/tests/color/ann_multiline2.term.svg b/tests/color/ann_multiline2.term.svg index 24827f66..2f7eb902 100644 --- a/tests/color/ann_multiline2.term.svg +++ b/tests/color/ann_multiline2.term.svg @@ -1,4 +1,4 @@ -<svg width="740px" height="146px" xmlns="http://www.w3.org/2000/svg"> +<svg width="740px" height="164px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -25,13 +25,15 @@ </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">26</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> This is an example</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">26</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> This is an example</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">this should not be on separate lines</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold"> ____________^</tspan> </tspan> - <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">27</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> of an edge case of an annotation overflowing</tspan> + <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">27</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|</tspan><tspan> of an edge case of an annotation overflowing</tspan> </tspan> - <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">28</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> to exactly one character on next line.</tspan> + <tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">|_^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">this should not be on separate lines</tspan> +</tspan> + <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">28</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> to exactly one character on next line.</tspan> </tspan> </text> diff --git a/tests/color/simple.term.svg b/tests/color/simple.term.svg index b849cf46..76aa393e 100644 --- a/tests/color/simple.term.svg +++ b/tests/color/simple.term.svg @@ -1,4 +1,4 @@ -<svg width="740px" height="164px" xmlns="http://www.w3.org/2000/svg"> +<svg width="740px" height="182px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -25,15 +25,17 @@ </tspan> <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">169</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> })</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">169</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> })</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">-</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">expected one of `.`, `;`, `?`, or an operator here</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold"> ___________-</tspan> </tspan> - <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">170</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> + <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">170</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">171</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> for line in &self.body {</tspan> + <tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|_-</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">expected one of `.`, `;`, `?`, or an operator here</tspan> </tspan> - <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">unexpected token</tspan> + <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">171</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> for line in &self.body {</tspan> +</tspan> + <tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">unexpected token</tspan> </tspan> </text> diff --git a/tests/formatter.rs b/tests/formatter.rs index bf998539..e5647b8c 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -415,9 +415,9 @@ fn char_eol_annotate_char() { error: --> file/path:3:1 | -3 | a - | ^ -4 | b +3 | / a +4 | | b + | |_^ "#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); assert_data_eq!(renderer.render(input), expected); @@ -437,10 +437,11 @@ fn char_eol_annotate_char_double_width() { error: --> <current file>:1:2 | -1 | こん - | ^^ -2 | にちは -3 | 世界 +1 | こん + | ___^ +2 | | にちは + | |_^ +3 | 世界 "#]]; let renderer = Renderer::plain(); @@ -485,9 +486,10 @@ fn annotate_eol2() { error: --> file/path:3:2 | -3 | a - | ^ -4 | b +3 | a + | __^ +4 | | b + | |_^ "#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); assert_data_eq!(renderer.render(input), expected); @@ -508,9 +510,10 @@ fn annotate_eol3() { error: --> file/path:3:3 | -3 | a - | ^ -4 | b +3 | a + | __^ +4 | | b + | |_^ "#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); assert_data_eq!(renderer.render(input), expected); @@ -553,10 +556,11 @@ fn annotate_eol_double_width() { error: --> <current file>:1:4 | -1 | こん - | ^ -2 | にちは -3 | 世界 +1 | こん + | _____^ +2 | | にちは + | |_^ +3 | 世界 "#]]; let renderer = Renderer::plain(); @@ -678,8 +682,8 @@ error: 3 | a | __^ 4 | | b - | |__^ -5 | c +5 | | c + | |_^ "#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); assert_data_eq!(renderer.render(input), expected); @@ -728,8 +732,8 @@ error: 3 | a | __^ 4 | | b - | |__^ -5 | c +5 | | c + | |_^ "#]]; let renderer = Renderer::plain().anonymized_line_numbers(false); assert_data_eq!(renderer.render(input), expected); @@ -1532,6 +1536,7 @@ LL - T LL - : LL - ? LL - Sized +LL + { | "#]]; let renderer = Renderer::plain().anonymized_line_numbers(true); @@ -1640,8 +1645,12 @@ LL | struct Wrapper<T>(T); | this could be changed to `T: ?Sized`... help: consider removing the `?Sized` bound to make the type parameter `Sized` | -LL ~ and -LL ~ + Send{ +LL - and where +LL - T +LL - : +LL - ? +LL - Sized +LL + and + Send{ | "#]]; let renderer = Renderer::plain().anonymized_line_numbers(true); diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index d013aa33..4979e77a 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -2166,12 +2166,14 @@ fn main() { error: character constant must be escaped: `/n` --> $DIR/bad-char-literals.rs:10:6 | -LL | ' - | ^ +LL | ' + | ______^ +LL | | '; + | |_^ | help: escape the character | -LL | '/n +LL | '/n'; | ++ "#]]; let renderer = Renderer::plain().anonymized_line_numbers(true); @@ -2220,8 +2222,8 @@ error: unclosed frontmatter | LL | / ----cargo ... | -LL | | // are properly parsed. - | |________________________^ +LL | | + | |_^ | note: frontmatter opening here was not closed --> $DIR/unclosed-1.rs:1:1 @@ -2396,7 +2398,8 @@ error: unclosed frontmatter | LL | / ----cargo LL | | //~^ ERROR: unclosed frontmatter - | |_________________________________^ +LL | | + | |_^ | note: frontmatter opening here was not closed --> $DIR/unclosed-4.rs:1:1 @@ -2451,8 +2454,8 @@ error: unclosed frontmatter | LL | / ----cargo ... | -LL | | // per unclosed-1.rs) - | |______________________^ +LL | | + | |_^ | note: frontmatter opening here was not closed --> $DIR/unclosed-5.rs:1:1 From 7a77fce612eecced766a905074ed5bd0c33e6c49 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 3 Jun 2025 09:45:06 -0600 Subject: [PATCH 371/455] test: Add rustc tests for annotation sorting --- tests/rustc_tests.rs | 212 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 212 insertions(+) diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index 4979e77a..ec8ee7f6 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -2466,3 +2466,215 @@ LL | ----cargo let renderer = Renderer::plain().anonymized_line_numbers(true); assert_data_eq!(renderer.render(input), expected); } + +#[test] +fn pat_tuple_field_count_cross() { + // tests/ui/pattern/pat-tuple-field-count-cross.stderr + + let source = r#"//@ aux-build:declarations-for-tuple-field-count-errors.rs + +extern crate declarations_for_tuple_field_count_errors; + +use declarations_for_tuple_field_count_errors::*; + +fn main() { + match Z0 { + Z0() => {} //~ ERROR expected tuple struct or tuple variant, found unit struct `Z0` + Z0(x) => {} //~ ERROR expected tuple struct or tuple variant, found unit struct `Z0` + } + match Z1() { + Z1 => {} //~ ERROR match bindings cannot shadow tuple structs + Z1(x) => {} //~ ERROR this pattern has 1 field, but the corresponding tuple struct has 0 fields + } + + match S(1, 2, 3) { + S() => {} //~ ERROR this pattern has 0 fields, but the corresponding tuple struct has 3 fields + S(1) => {} //~ ERROR this pattern has 1 field, but the corresponding tuple struct has 3 fields + S(xyz, abc) => {} //~ ERROR this pattern has 2 fields, but the corresponding tuple struct has 3 fields + S(1, 2, 3, 4) => {} //~ ERROR this pattern has 4 fields, but the corresponding tuple struct has 3 fields + } + match M(1, 2, 3) { + M() => {} //~ ERROR this pattern has 0 fields, but the corresponding tuple struct has 3 fields + M(1) => {} //~ ERROR this pattern has 1 field, but the corresponding tuple struct has 3 fields + M(xyz, abc) => {} //~ ERROR this pattern has 2 fields, but the corresponding tuple struct has 3 fields + M(1, 2, 3, 4) => {} //~ ERROR this pattern has 4 fields, but the corresponding tuple struct has 3 fields + } + + match E1::Z0 { + E1::Z0() => {} //~ ERROR expected tuple struct or tuple variant, found unit variant `E1::Z0` + E1::Z0(x) => {} //~ ERROR expected tuple struct or tuple variant, found unit variant `E1::Z0` + } + match E1::Z1() { + E1::Z1 => {} //~ ERROR expected unit struct, unit variant or constant, found tuple variant `E1::Z1` + E1::Z1(x) => {} //~ ERROR this pattern has 1 field, but the corresponding tuple variant has 0 fields + } + match E1::S(1, 2, 3) { + E1::S() => {} //~ ERROR this pattern has 0 fields, but the corresponding tuple variant has 3 fields + E1::S(1) => {} //~ ERROR this pattern has 1 field, but the corresponding tuple variant has 3 fields + E1::S(xyz, abc) => {} //~ ERROR this pattern has 2 fields, but the corresponding tuple variant has 3 fields + E1::S(1, 2, 3, 4) => {} //~ ERROR this pattern has 4 fields, but the corresponding tuple variant has 3 fields + } + + match E2::S(1, 2, 3) { + E2::S() => {} //~ ERROR this pattern has 0 fields, but the corresponding tuple variant has 3 fields + E2::S(1) => {} //~ ERROR this pattern has 1 field, but the corresponding tuple variant has 3 fields + E2::S(xyz, abc) => {} //~ ERROR this pattern has 2 fields, but the corresponding tuple variant has 3 fields + E2::S(1, 2, 3, 4) => {} //~ ERROR this pattern has 4 fields, but the corresponding tuple variant has 3 fields + } + match E2::M(1, 2, 3) { + E2::M() => {} //~ ERROR this pattern has 0 fields, but the corresponding tuple variant has 3 fields + E2::M(1) => {} //~ ERROR this pattern has 1 field, but the corresponding tuple variant has 3 fields + E2::M(xyz, abc) => {} //~ ERROR this pattern has 2 fields, but the corresponding tuple variant has 3 fields + E2::M(1, 2, 3, 4) => {} //~ ERROR this pattern has 4 fields, but the corresponding tuple variant has 3 fields + } +} +"#; + let source1 = r#"pub struct Z0; +pub struct Z1(); + +pub struct S(pub u8, pub u8, pub u8); +pub struct M( + pub u8, + pub u8, + pub u8, +); + +pub enum E1 { Z0, Z1(), S(u8, u8, u8) } + +pub enum E2 { + S(u8, u8, u8), + M( + u8, + u8, + u8, + ), +} +"#; + + let input = Level::ERROR + .header("expected unit struct, unit variant or constant, found tuple variant `E1::Z1`") + .id(r#"E0532"#) + .group( + Group::new() + .element( + Snippet::source(source) + .origin("$DIR/pat-tuple-field-count-cross.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(1760..1766)), + ) + .element( + Snippet::source(source1) + .origin("$DIR/auxiliary/declarations-for-tuple-field-count-errors.rs") + .fold(true) + .annotation( + AnnotationKind::Context + .span(143..145) + .label("`E1::Z1` defined here"), + ) + .annotation( + AnnotationKind::Context + .span(139..141) + .label("similarly named unit variant `Z0` defined here"), + ), + ), + ) + .group( + Group::new() + .element(Level::HELP.title("use the tuple variant pattern syntax instead")) + .element( + Snippet::source(source) + .origin("$DIR/pat-tuple-field-count-cross.rs") + .fold(true) + .patch(Patch::new(1760..1766, r#"E1::Z1()"#)), + ), + ) + .group( + Group::new() + .element(Level::HELP.title("a unit variant with a similar name exists")) + .element( + Snippet::source(source) + .origin("$DIR/pat-tuple-field-count-cross.rs") + .fold(true) + .patch(Patch::new(1764..1766, r#"Z0"#)), + ), + ); + let expected = str![[r#" +error[E0532]: expected unit struct, unit variant or constant, found tuple variant `E1::Z1` + --> $DIR/pat-tuple-field-count-cross.rs:35:9 + | +LL | E1::Z1 => {} //~ ERROR expected unit struct, unit variant or constant, found tuple variant `E1::Z1` + | ^^^^^^ + | + ::: $DIR/auxiliary/declarations-for-tuple-field-count-errors.rs:11:15 + | +LL | pub enum E1 { Z0, Z1(), S(u8, u8, u8) } + | -- -- `E1::Z1` defined here + | | + | similarly named unit variant `Z0` defined here + | +help: use the tuple variant pattern syntax instead + | +LL | E1::Z1() => {} //~ ERROR expected unit struct, unit variant or constant, found tuple variant `E1::Z1` + | ++ +help: a unit variant with a similar name exists + | +LL - E1::Z1 => {} //~ ERROR expected unit struct, unit variant or constant, found tuple variant `E1::Z1` +LL + E1::Z0 => {} //~ ERROR expected unit struct, unit variant or constant, found tuple variant `E1::Z1` + | +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected); +} + +#[test] +fn unterminated_nested_comment() { + // tests/ui/lexer/unterminated-nested-comment.rs + + let source = r#"/* //~ ERROR E0758 +/* */ +/* +*/ +"#; + + let input = Level::ERROR.header("unterminated block comment").id("E0758").group( + Group::new().element( + Snippet::source(source) + .origin("$DIR/unterminated-nested-comment.rs") + .fold(true) + .annotation( + AnnotationKind::Context + .span(0..2) + .label("unterminated block comment"), + ) + .annotation(AnnotationKind::Context.span(25..27).label( + "...as last nested comment starts here, maybe you want to close this instead?", + )) + .annotation( + AnnotationKind::Context + .span(28..30) + .label("...and last nested comment terminates here."), + ) + .annotation(AnnotationKind::Primary.span(0..31)), + ), + ); + + let expected = str![[r#" +error[E0758]: unterminated block comment + --> $DIR/unterminated-nested-comment.rs:1:1 + | +LL | /* //~ ERROR E0758 + | ^- + | | + | _unterminated block comment + | | +LL | | /* */ +LL | | /* + | | -- ...as last nested comment starts here, maybe you want to close this instead? +LL | | */ + | |_--^ + | | + | ...and last nested comment terminates here. +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected); +} From 0c8bb3717a24627fd6576acd56757996074dc098 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 3 Jun 2025 09:45:06 -0600 Subject: [PATCH 372/455] fix: Remove unneeded annotation sorting --- src/renderer/mod.rs | 3 +-- src/renderer/source_map.rs | 4 ---- tests/rustc_tests.rs | 6 ++++-- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index ab791edd..590c83d9 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -695,8 +695,7 @@ impl Renderer { if let Some(first_annotation) = primary_line .annotations .iter() - .find(|a| a.is_primary()) - .or(primary_line.annotations.first()) + .min_by_key(|a| (Reverse(a.is_primary()), a.start.char)) { origin.char_column = Some(first_annotation.start.char + 1); } diff --git a/src/renderer/source_map.rs b/src/renderer/source_map.rs index 403c1c63..d5f91a1c 100644 --- a/src/renderer/source_map.rs +++ b/src/renderer/source_map.rs @@ -293,10 +293,6 @@ impl<'a> SourceMap<'a> { annotated_line_infos.retain(|l| !l.annotations.is_empty()); } - for l in annotated_line_infos.iter_mut() { - l.annotations.sort_by(|a, b| a.start.cmp(&b.start)); - } - (max_depth, annotated_line_infos) } diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index ec8ee7f6..eec19e56 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -2605,7 +2605,7 @@ error[E0532]: expected unit struct, unit variant or constant, found tuple varian LL | E1::Z1 => {} //~ ERROR expected unit struct, unit variant or constant, found tuple variant `E1::Z1` | ^^^^^^ | - ::: $DIR/auxiliary/declarations-for-tuple-field-count-errors.rs:11:15 + ::: $DIR/auxiliary/declarations-for-tuple-field-count-errors.rs:11:19 | LL | pub enum E1 { Z0, Z1(), S(u8, u8, u8) } | -- -- `E1::Z1` defined here @@ -2669,7 +2669,9 @@ LL | /* //~ ERROR E0758 | | LL | | /* */ LL | | /* - | | -- ...as last nested comment starts here, maybe you want to close this instead? + | | -- + | | | + | | ...as last nested comment starts here, maybe you want to close this instead? LL | | */ | |_--^ | | From 857817778442f1662ecabaa54e10275ea0abe2b9 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 4 Jun 2025 15:16:07 -0600 Subject: [PATCH 373/455] fix!: Remove of Origin::label --- src/renderer/mod.rs | 17 ----------------- src/snippet.rs | 16 ---------------- tests/rustc_tests.rs | 6 +++--- 3 files changed, 3 insertions(+), 36 deletions(-) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 590c83d9..07abf539 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -647,23 +647,6 @@ impl Renderer { for _ in 0..max_line_num_len { buffer.prepend(buffer_msg_line_offset, " ", ElementStyle::NoStyle); } - - if let Some(label) = &origin.label { - self.draw_col_separator_no_space( - buffer, - buffer_msg_line_offset + 1, - max_line_num_len + 1, - ); - let title = Level::NOTE.title(label); - self.render_title( - buffer, - &title, - max_line_num_len, - TitleStyle::Secondary, - None, - false, - ); - } } #[allow(clippy::too_many_arguments)] diff --git a/src/snippet.rs b/src/snippet.rs index d3f50104..16f337ad 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -382,7 +382,6 @@ pub struct Origin<'a> { pub(crate) line: Option<usize>, pub(crate) char_column: Option<usize>, pub(crate) primary: bool, - pub(crate) label: Option<&'a str>, } impl<'a> Origin<'a> { @@ -399,7 +398,6 @@ impl<'a> Origin<'a> { line: None, char_column: None, primary: false, - label: None, } } @@ -423,20 +421,6 @@ impl<'a> Origin<'a> { self.primary = primary; self } - - /// Like [`Annotation::label`], but when there is no source - /// - /// <div class="warning"> - /// - /// Text passed to this function is considered "untrusted input", as such - /// all text is passed through a normalization function. Pre-styled text is - /// not allowed to be passed to this function. - /// - /// </div> - pub fn label(mut self, label: &'a str) -> Self { - self.label = Some(label); - self - } } fn newline_count(body: &str) -> usize { diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index eec19e56..0a1c5317 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -2,7 +2,7 @@ //! //! [parser-tests]: https://github.com/rust-lang/rust/blob/894f7a4ba6554d3797404bbf550d9919df060b97/compiler/rustc_parse/src/parser/tests.rs -use annotate_snippets::{AnnotationKind, Group, Level, Origin, Patch, Renderer, Snippet}; +use annotate_snippets::{AnnotationKind, Group, Level, Origin, Padding, Patch, Renderer, Snippet}; use annotate_snippets::renderer::OutputTheme; use snapbox::{assert_data_eq, str}; @@ -1845,9 +1845,9 @@ fn main() { .line(334) .char_column(14) .primary(true) - .label("...because it uses `Self` as a type parameter") - ) + .element(Padding) + .element(Level::NOTE.title("...because it uses `Self` as a type parameter")) .element( Snippet::source(source) .line_start(1) From f41055266537a3dfa9ea22970df73403e618e963 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 10 Jun 2025 04:00:02 -0600 Subject: [PATCH 374/455] test: Add rustc test with empty source --- tests/rustc_tests.rs | 114 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index 0a1c5317..13924c8e 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -2680,3 +2680,117 @@ LL | | */ let renderer = Renderer::plain().anonymized_line_numbers(true); assert_data_eq!(renderer.render(input), expected); } + +#[test] +#[should_panic(expected = "called `Option::unwrap()` on a `None` value")] +fn mismatched_types1() { + // tests/ui/include-macros/mismatched-types.rs + + let file_txt_source = r#""#; + + let rust_source = r#"fn main() { + let b: &[u8] = include_str!("file.txt"); //~ ERROR mismatched types + let s: &str = include_bytes!("file.txt"); //~ ERROR mismatched types +}"#; + + let input = Level::ERROR.header("mismatched types").id("E0308").group( + Group::new() + .element( + Snippet::source(file_txt_source) + .fold(true) + .line_start(3) + .origin("$DIR/file.txt") + .annotation( + AnnotationKind::Primary + .span(0..0) + .label("expected `&[u8]`, found `&str`"), + ), + ) + .element( + Snippet::source(rust_source) + .origin("$DIR/mismatched-types.rs") + .fold(true) + .annotation( + AnnotationKind::Context + .span(23..28) + .label("expected due to this"), + ) + .annotation( + AnnotationKind::Context + .span(31..55) + .label("in this macro invocation"), + ), + ) + .element( + Level::NOTE.title("expected reference `&[u8]`\n found reference `&'static str`"), + ), + ); + + let expected = str![[r#" +error[E0308]: mismatched types + --> $DIR/file.txt:3:1 + | +LL | + | ^ expected `&[u8]`, found `&str` + | + ::: $DIR/mismatched-types.rs:2:12 + | +LL | let b: &[u8] = include_str!("file.txt"); //~ ERROR mismatched types + | ----- ------------------------ in this macro invocation + | | + | expected due to this + | + = note: expected reference `&[u8]` + found reference `&'static str` +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected); +} + +#[test] +fn mismatched_types2() { + // tests/ui/include-macros/mismatched-types.rs + + let source = r#"fn main() { + let b: &[u8] = include_str!("file.txt"); //~ ERROR mismatched types + let s: &str = include_bytes!("file.txt"); //~ ERROR mismatched types +}"#; + + let input = Level::ERROR.header("mismatched types").id("E0308").group( + Group::new() + .element( + Snippet::source(source) + .origin("$DIR/mismatched-types.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(105..131) + .label("expected `&str`, found `&[u8; 0]`"), + ) + .annotation( + AnnotationKind::Context + .span(98..102) + .label("expected due to this"), + ), + ) + .element( + Level::NOTE + .title("expected reference `&str`\n found reference `&'static [u8; 0]`"), + ), + ); + + let expected = str![[r#" +error[E0308]: mismatched types + --> $DIR/mismatched-types.rs:3:19 + | +LL | let s: &str = include_bytes!("file.txt"); //~ ERROR mismatched types + | ---- ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `&str`, found `&[u8; 0]` + | | + | expected due to this + | + = note: expected reference `&str` + found reference `&'static [u8; 0]` +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected); +} From 23242144f51cc7902d6bcc5d69420600f9426459 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 4 Jun 2025 15:16:07 -0600 Subject: [PATCH 375/455] fix: Ensure empty sources have one "line" --- src/renderer/source_map.rs | 15 +++++++++++++++ tests/formatter.rs | 1 + tests/rustc_tests.rs | 1 - 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/renderer/source_map.rs b/src/renderer/source_map.rs index d5f91a1c..7401fb96 100644 --- a/src/renderer/source_map.rs +++ b/src/renderer/source_map.rs @@ -11,6 +11,21 @@ pub(crate) struct SourceMap<'a> { impl<'a> SourceMap<'a> { pub(crate) fn new(source: &'a str, line_start: usize) -> Self { + // Empty sources do have a "line", but it is empty, so we need to add + // a line with an empty string to the source map. + if source.is_empty() { + return Self { + lines: vec![LineInfo { + line: "", + line_index: line_start, + start_byte: 0, + end_byte: 0, + end_line_size: 0, + }], + source, + }; + } + let mut current_index = 0; let mut mapping = vec![]; diff --git a/tests/formatter.rs b/tests/formatter.rs index e5647b8c..61277ecc 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -303,6 +303,7 @@ fn test_only_source() { error: --> file.rs | +1 | "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input), expected); diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index 13924c8e..b1505d72 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -2682,7 +2682,6 @@ LL | | */ } #[test] -#[should_panic(expected = "called `Option::unwrap()` on a `None` value")] fn mismatched_types1() { // tests/ui/include-macros/mismatched-types.rs From 51b2678794203e915a7ae1595e2c16a4d072543c Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 10 Jun 2025 02:55:42 -0600 Subject: [PATCH 376/455] test: Add rustc short error format test --- tests/rustc_tests.rs | 108 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index b1505d72..736a069e 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -2793,3 +2793,111 @@ LL | let s: &str = include_bytes!("file.txt"); //~ ERROR mismatched types let renderer = Renderer::plain().anonymized_line_numbers(true); assert_data_eq!(renderer.render(input), expected); } + +#[test] +fn short_error_format1() { + // tests/ui/short-error-format.rs + + let source = r#"//@ compile-flags: --error-format=short + +fn foo(_: u32) {} + +fn main() { + foo("Bonjour".to_owned()); + let x = 0u32; + x.salut(); +} +"#; + + let input = Level::ERROR + .header("mismatched types") + .id("E0308") + .group( + Group::new().element( + Snippet::source(source) + .origin("$DIR/short-error-format.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(80..100) + .label("expected `u32`, found `String`"), + ) + .annotation( + AnnotationKind::Context + .span(76..79) + .label("arguments to this function are incorrect"), + ), + ), + ) + .group( + Group::new() + .element(Level::NOTE.title("function defined here")) + .element( + Snippet::source(source) + .origin("$DIR/short-error-format.rs") + .fold(true) + .annotation(AnnotationKind::Context.span(48..54).label("")) + .annotation(AnnotationKind::Primary.span(44..47)), + ), + ); + + let expected = str![[r#" +error[E0308]: mismatched types + --> $DIR/short-error-format.rs:6:9 + | +LL | foo("Bonjour".to_owned()); + | --- ^^^^^^^^^^^^^^^^^^^^ expected `u32`, found `String` + | | + | arguments to this function are incorrect + | +note: function defined here + --> $DIR/short-error-format.rs:3:4 + | +LL | fn foo(_: u32) {} + | ^^^ ------ +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected); +} + +#[test] +fn short_error_format2() { + // tests/ui/short-error-format.rs + + let source = r#"//@ compile-flags: --error-format=short + +fn foo(_: u32) {} + +fn main() { + foo("Bonjour".to_owned()); + let x = 0u32; + x.salut(); +} +"#; + + let input = Level::ERROR + .header("no method named `salut` found for type `u32` in the current scope") + .id("E0599") + .group( + Group::new().element( + Snippet::source(source) + .origin("$DIR/short-error-format.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(127..132) + .label("method not found in `u32`"), + ), + ), + ); + + let expected = str![[r#" +error[E0599]: no method named `salut` found for type `u32` in the current scope + --> $DIR/short-error-format.rs:8:7 + | +LL | x.salut(); + | ^^^^^ method not found in `u32` +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected); +} From 7a776963b6116c48de7c5987fba44bbb5fbe92dc Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 4 Jun 2025 15:16:07 -0600 Subject: [PATCH 377/455] feat: Add support for "short message" --- src/renderer/mod.rs | 194 ++++++++++++++++++++++++++++++++++++------- tests/rustc_tests.rs | 28 ++----- 2 files changed, 170 insertions(+), 52 deletions(-) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 07abf539..f6af1e3b 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -66,6 +66,7 @@ pub struct Renderer { term_width: usize, theme: OutputTheme, stylesheet: Stylesheet, + short_message: bool, } impl Renderer { @@ -76,6 +77,7 @@ impl Renderer { term_width: DEFAULT_TERM_WIDTH, theme: OutputTheme::Ascii, stylesheet: Stylesheet::plain(), + short_message: false, } } @@ -137,6 +139,11 @@ impl Renderer { self } + pub const fn short_message(mut self, short_message: bool) -> Self { + self.short_message = short_message; + self + } + // Set the terminal width pub const fn term_width(mut self, term_width: usize) -> Self { self.term_width = term_width; @@ -199,19 +206,23 @@ impl Renderer { impl Renderer { pub fn render(&self, mut message: Message<'_>) -> String { - let max_line_num_len = if self.anonymized_line_numbers { - ANONYMIZED_LINE_NUM.len() + if self.short_message { + self.render_short_message(message).unwrap() } else { - let n = message.max_line_number(); - num_decimal_digits(n) - }; - let title = message.groups.remove(0).elements.remove(0); - if let Some(first) = message.groups.first_mut() { - first.elements.insert(0, title); - } else { - message.groups.push(Group::new().element(title)); + let max_line_num_len = if self.anonymized_line_numbers { + ANONYMIZED_LINE_NUM.len() + } else { + let n = message.max_line_number(); + num_decimal_digits(n) + }; + let title = message.groups.remove(0).elements.remove(0); + if let Some(first) = message.groups.first_mut() { + first.elements.insert(0, title); + } else { + message.groups.push(Group::new().element(title)); + } + self.render_message(message, max_line_num_len).unwrap() } - self.render_message(message, max_line_num_len).unwrap() } fn render_message( @@ -320,6 +331,7 @@ impl Renderer { (true, false) => TitleStyle::Header, (false, _) => TitleStyle::Secondary, }; + let buffer_msg_line_offset = buffer.num_lines(); self.render_title( &mut buffer, title, @@ -333,6 +345,7 @@ impl Renderer { } }), matches!(peek, Some(Element::Title(_))), + buffer_msg_line_offset, ); last_was_suggestion = false; } @@ -390,7 +403,13 @@ impl Renderer { } Element::Origin(origin) => { - self.render_origin(&mut buffer, max_line_num_len, origin); + let buffer_msg_line_offset = buffer.num_lines(); + self.render_origin( + &mut buffer, + max_line_num_len, + origin, + buffer_msg_line_offset, + ); last_was_suggestion = false; } Element::Padding(_) => { @@ -434,6 +453,91 @@ impl Renderer { Ok(out_string) } + fn render_short_message(&self, mut message: Message<'_>) -> Result<String, fmt::Error> { + let mut buffer = StyledBuffer::new(); + + let Element::Title(title) = message.groups.remove(0).elements.remove(0) else { + panic!( + "Expected first element to be a Title, got: {:?}", + message.groups + ); + }; + + let mut labels = None; + + if let Some(Element::Cause(cause)) = message.groups.first().and_then(|group| { + group + .elements + .iter() + .find(|e| matches!(e, Element::Cause(_))) + }) { + let labels_inner = cause + .markers + .iter() + .filter_map(|ann| match ann.label { + Some(msg) if ann.kind.is_primary() => { + if !msg.trim().is_empty() { + Some(msg.to_owned()) + } else { + None + } + } + _ => None, + }) + .collect::<Vec<_>>() + .join(", "); + if !labels_inner.is_empty() { + labels = Some(labels_inner); + } + + if let Some(origin) = cause.origin { + let mut origin = Origin::new(origin); + origin.primary = true; + + let source_map = SourceMap::new(cause.source, cause.line_start); + let (_depth, annotated_lines) = + source_map.annotated_lines(cause.markers.clone(), cause.fold); + + if let Some(primary_line) = annotated_lines + .iter() + .find(|l| l.annotations.iter().any(LineAnnotation::is_primary)) + .or(annotated_lines.iter().find(|l| !l.annotations.is_empty())) + { + origin.line = Some(primary_line.line_index); + if let Some(first_annotation) = primary_line + .annotations + .iter() + .min_by_key(|a| (Reverse(a.is_primary()), a.start.char)) + { + origin.char_column = Some(first_annotation.start.char + 1); + } + } + + self.render_origin(&mut buffer, 0, &origin, 0); + buffer.append(0, ": ", ElementStyle::LineAndColumn); + } + } + + self.render_title( + &mut buffer, + &title, + 0, // No line numbers in short messages + TitleStyle::MainHeader, + message.id.as_ref(), + false, + 0, + ); + + if let Some(labels) = labels { + buffer.append(0, &format!(": {labels}"), ElementStyle::NoStyle); + } + + let mut out_string = String::new(); + buffer.render(title.level, &self.stylesheet, &mut out_string)?; + + Ok(out_string) + } + #[allow(clippy::too_many_arguments)] fn render_title( &self, @@ -443,23 +547,27 @@ impl Renderer { title_style: TitleStyle, id: Option<&&str>, is_cont: bool, + buffer_msg_line_offset: usize, ) { - let line_offset = buffer.num_lines(); - if title_style == TitleStyle::Secondary { // This is a secondary message with no span info for _ in 0..max_line_num_len { - buffer.prepend(line_offset, " ", ElementStyle::NoStyle); + buffer.prepend(buffer_msg_line_offset, " ", ElementStyle::NoStyle); } if title.level.name != Some(None) { - self.draw_note_separator(buffer, line_offset, max_line_num_len + 1, is_cont); + self.draw_note_separator( + buffer, + buffer_msg_line_offset, + max_line_num_len + 1, + is_cont, + ); buffer.append( - line_offset, + buffer_msg_line_offset, title.level.as_str(), ElementStyle::MainHeaderMsg, ); - buffer.append(line_offset, ": ", ElementStyle::NoStyle); + buffer.append(buffer_msg_line_offset, ": ", ElementStyle::NoStyle); } let printed_lines = @@ -476,7 +584,7 @@ impl Renderer { // │ bar // ╰ note: foo // bar - for i in line_offset + 1..=printed_lines { + for i in buffer_msg_line_offset + 1..=printed_lines { self.draw_col_separator_no_space(buffer, i, max_line_num_len + 1); } } @@ -485,31 +593,49 @@ impl Renderer { if title.level.name != Some(None) { buffer.append( - line_offset, + buffer_msg_line_offset, title.level.as_str(), ElementStyle::Level(title.level.level), ); } label_width += title.level.as_str().len(); if let Some(id) = id { - buffer.append(line_offset, "[", ElementStyle::Level(title.level.level)); - buffer.append(line_offset, id, ElementStyle::Level(title.level.level)); - buffer.append(line_offset, "]", ElementStyle::Level(title.level.level)); + buffer.append( + buffer_msg_line_offset, + "[", + ElementStyle::Level(title.level.level), + ); + buffer.append( + buffer_msg_line_offset, + id, + ElementStyle::Level(title.level.level), + ); + buffer.append( + buffer_msg_line_offset, + "]", + ElementStyle::Level(title.level.level), + ); label_width += 2 + id.len(); } let header_style = match title_style { - TitleStyle::MainHeader => ElementStyle::MainHeaderMsg, + TitleStyle::MainHeader => { + if self.short_message { + ElementStyle::NoStyle + } else { + ElementStyle::MainHeaderMsg + } + } TitleStyle::Header => ElementStyle::HeaderMsg, TitleStyle::Secondary => unreachable!(), }; if title.level.name != Some(None) { - buffer.append(line_offset, ": ", header_style); + buffer.append(buffer_msg_line_offset, ": ", header_style); label_width += 2; } if !title.title.is_empty() { for (line, text) in normalize_whitespace(title.title).lines().enumerate() { buffer.append( - line_offset + line, + buffer_msg_line_offset + line, &format!( "{}{}", if line == 0 { @@ -600,15 +726,15 @@ impl Renderer { buffer: &mut StyledBuffer, max_line_num_len: usize, origin: &Origin<'_>, + buffer_msg_line_offset: usize, ) { - let buffer_msg_line_offset = buffer.num_lines(); - if origin.primary { + if origin.primary && !self.short_message { buffer.prepend( buffer_msg_line_offset, self.file_start(), ElementStyle::LineNumber, ); - } else { + } else if !self.short_message { // if !origin.standalone { // // Add spacing line, as shown: // // --> $DIR/file:54:15 @@ -643,9 +769,12 @@ impl Renderer { (Some(line), None) => format!("{}:{}", origin.origin, line), _ => origin.origin.to_owned(), }; + buffer.append(buffer_msg_line_offset, &str, ElementStyle::LineAndColumn); - for _ in 0..max_line_num_len { - buffer.prepend(buffer_msg_line_offset, " ", ElementStyle::NoStyle); + if !self.short_message { + for _ in 0..max_line_num_len { + buffer.prepend(buffer_msg_line_offset, " ", ElementStyle::NoStyle); + } } } @@ -707,7 +836,8 @@ impl Renderer { } } } - self.render_origin(buffer, max_line_num_len, &origin); + let buffer_msg_line_offset = buffer.num_lines(); + self.render_origin(buffer, max_line_num_len, &origin, buffer_msg_line_offset); } // Put in the spacer between the location and annotated source diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index 736a069e..bdffe38b 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -2842,21 +2842,11 @@ fn main() { ); let expected = str![[r#" -error[E0308]: mismatched types - --> $DIR/short-error-format.rs:6:9 - | -LL | foo("Bonjour".to_owned()); - | --- ^^^^^^^^^^^^^^^^^^^^ expected `u32`, found `String` - | | - | arguments to this function are incorrect - | -note: function defined here - --> $DIR/short-error-format.rs:3:4 - | -LL | fn foo(_: u32) {} - | ^^^ ------ +$DIR/short-error-format.rs:6:9: error[E0308]: mismatched types: expected `u32`, found `String` "#]]; - let renderer = Renderer::plain().anonymized_line_numbers(true); + let renderer = Renderer::plain() + .short_message(true) + .anonymized_line_numbers(true); assert_data_eq!(renderer.render(input), expected); } @@ -2892,12 +2882,10 @@ fn main() { ); let expected = str![[r#" -error[E0599]: no method named `salut` found for type `u32` in the current scope - --> $DIR/short-error-format.rs:8:7 - | -LL | x.salut(); - | ^^^^^ method not found in `u32` +$DIR/short-error-format.rs:8:7: error[E0599]: no method named `salut` found for type `u32` in the current scope: method not found in `u32` "#]]; - let renderer = Renderer::plain().anonymized_line_numbers(true); + let renderer = Renderer::plain() + .short_message(true) + .anonymized_line_numbers(true); assert_data_eq!(renderer.render(input), expected); } From a0d26c6aaf7e880dc6c37830ecd983e5537c97ce Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 10 Jun 2025 06:06:08 -0600 Subject: [PATCH 378/455] test: Ensure all examples have a test --- tests/examples.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/examples.rs b/tests/examples.rs index 66dd94be..02e961c8 100644 --- a/tests/examples.rs +++ b/tests/examples.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeSet; + #[test] fn custom_error() { let target = "custom_error"; @@ -63,3 +65,40 @@ fn assert_example(target: &str, expected: snapbox::Data) { .success() .stdout_eq(expected.raw()); } + +#[test] +fn ensure_all_examples_have_tests() { + let path = snapbox::utils::current_rs!(); + let actual = std::fs::read_to_string(&path).unwrap(); + let actual = actual + .lines() + .filter_map(|l| { + if l.starts_with("fn ") + && !l.starts_with("fn all_examples_have_tests") + && !l.starts_with("fn assert_example") + { + Some(l[3..l.len() - 4].to_string()) + } else { + None + } + }) + .collect::<BTreeSet<_>>(); + + let expected = std::fs::read_dir("examples") + .unwrap() + .map(|res| res.map(|e| e.path().file_stem().unwrap().display().to_string())) + .collect::<Result<BTreeSet<_>, std::io::Error>>() + .unwrap(); + + let mut diff = expected.difference(&actual).collect::<Vec<_>>(); + diff.sort(); + + let mut need_added = String::new(); + for name in &diff { + need_added.push_str(&format!("{name}\n")); + } + assert!( + diff.is_empty(), + "\n`Please add a test for the following examples to `tests/examples.rs`:\n{need_added}", + ); +} From 2ef8888285326bdb76457e698b3a3fae48dfd725 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Mon, 2 Jun 2025 18:28:50 -0600 Subject: [PATCH 379/455] test: Add a test for out of bounds replacement --- tests/rustc_tests.rs | 76 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index bdffe38b..d5c24af2 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -2889,3 +2889,79 @@ $DIR/short-error-format.rs:8:7: error[E0599]: no method named `salut` found for .anonymized_line_numbers(true); assert_data_eq!(renderer.render(input), expected); } + +#[test] +#[should_panic(expected = "range end index 47 out of range for slice of length 26")] +fn rustdoc_ui_diagnostic_width() { + // tests/rustdoc-ui/diagnostic-width.rs + + let source_0 = r#"//@ compile-flags: --diagnostic-width=10 +#![deny(rustdoc::bare_urls)] + +/// This is a long line that contains a http://link.com +pub struct Foo; //~^ ERROR +"#; + let source_1 = r#"/// This is a long line that contains a http://link.com +"#; + + let input = Level::ERROR + .header("this URL is not a hyperlink") + .group( + Group::new() + .element( + Snippet::source(source_0) + .origin("$DIR/diagnostic-width.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(111..126)), + ) + .element( + Level::NOTE + .title("bare URLs are not automatically turned into clickable links"), + ), + ) + .group( + Group::new() + .element(Level::NOTE.title("the lint level is defined here")) + .element( + Snippet::source(source_0) + .origin("$DIR/diagnostic-width.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(49..67)), + ), + ) + .group( + Group::new() + .element(Level::HELP.title("use an automatic link instead")) + .element( + Snippet::source(source_1) + .origin("$DIR/diagnostic-width.rs") + .line_start(4) + .fold(true) + .patch(Patch::new(40..40, "<")) + .patch(Patch::new(55..55, ">")), + ), + ); + + let expected = str![[r#" +error: this URL is not a hyperlink + --> $DIR/diagnostic-width.rs:4:41 + | +LL | ... a http://link.com + | ^^^^^^^^^^^^^^^ + | + = note: bare URLs are not automatically turned into clickable links +note: the lint level is defined here + --> $DIR/diagnostic-width.rs:2:9 + | +LL | ...ny(ru...are_urls)] + | ^^...^^^^^^^^ +help: use an automatic link instead + | +LL | /// This is a long line that contains a <http://link.com> + | + + +"#]]; + let renderer = Renderer::plain() + .anonymized_line_numbers(true) + .term_width(10); + assert_data_eq!(renderer.render(input), expected); +} From 22e1e546822c640cf351a97d41a9733ab627499e Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Mon, 2 Jun 2025 18:28:50 -0600 Subject: [PATCH 380/455] fix: Don't attempt out of bounds buffer replacments --- src/renderer/styled_buffer.rs | 5 +++++ tests/rustc_tests.rs | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/renderer/styled_buffer.rs b/src/renderer/styled_buffer.rs index 925bb446..f72c58c6 100644 --- a/src/renderer/styled_buffer.rs +++ b/src/renderer/styled_buffer.rs @@ -103,6 +103,11 @@ impl StyledBuffer { if start == end { return; } + // If the replacement range would be out of bounds, do nothing, as we + // can't replace things that don't exist. + if start > self.lines[line].len() || end > self.lines[line].len() { + return; + } let _ = self.lines[line].drain(start..(end - string.chars().count())); for (i, c) in string.chars().enumerate() { self.lines[line][start + i] = StyledChar::new(c, ElementStyle::LineNumber); diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index d5c24af2..13306590 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -2891,7 +2891,6 @@ $DIR/short-error-format.rs:8:7: error[E0599]: no method named `salut` found for } #[test] -#[should_panic(expected = "range end index 47 out of range for slice of length 26")] fn rustdoc_ui_diagnostic_width() { // tests/rustdoc-ui/diagnostic-width.rs From eb800f6fd9f4810335ff44c6dc7dc5bb230aa1aa Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 25 Jun 2025 15:37:11 -0600 Subject: [PATCH 381/455] test: Adjust spans to match original test spans --- tests/formatter.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/formatter.rs b/tests/formatter.rs index 61277ecc..b48d8f52 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -1519,7 +1519,7 @@ fn main() {}"#; .element( Snippet::source(source) .fold(true) - .patch(Patch::new(52..86, "")), + .patch(Patch::new(52..85, "")), ), ); let expected = str![[r#" @@ -1537,7 +1537,6 @@ LL - T LL - : LL - ? LL - Sized -LL + { | "#]]; let renderer = Renderer::plain().anonymized_line_numbers(true); @@ -1619,8 +1618,8 @@ fn main() {}"#; ).element( Snippet::source(source) .fold(true) - .patch(Patch::new(56..90, "")) - .patch(Patch::new(90..90, "+ Send")) + .patch(Patch::new(56..89, "")) + .patch(Patch::new(89..89, "+ Send")) , )); let expected = str![[r#" @@ -1651,7 +1650,7 @@ LL - T LL - : LL - ? LL - Sized -LL + and + Send{ +LL + and + Send | "#]]; let renderer = Renderer::plain().anonymized_line_numbers(true); From e8ac4395014fce3f2578b0bd5232196d0166c356 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Fri, 30 May 2025 15:00:25 -0600 Subject: [PATCH 382/455] test: Add test for Suggestion with range 0..0 --- tests/rustc_tests.rs | 80 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index 13306590..9360b0af 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -2964,3 +2964,83 @@ LL | /// This is a long line that contains a <http://link.com> .term_width(10); assert_data_eq!(renderer.render(input), expected); } + +#[test] +#[should_panic = "attempt to subtract with overflow"] +fn array_into_iter() { + let source1 = r#"#![allow(unused)] +fn main() { +[1, 2, 3].into_iter().for_each(|n| { *n; }); +} +"#; + let source2 = r#"[1, 2, 3].into_iter().for_each(|n| { *n; }); +"#; + + let long_title1 ="this method call resolves to `<&[T; N] as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<[T; N] as IntoIterator>::into_iter` in Rust 2021"; + let long_title2 = "for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2021/IntoIterator-for-arrays.html>"; + let long_title3 = "or use `IntoIterator::into_iter(..)` instead of `.into_iter()` to explicitly iterate by value"; + + let input = Level::WARNING + .header(long_title1) + .group( + Group::new() + .element( + Snippet::source(source1) + .origin("lint_example.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(40..49)), + ) + .element(Level::WARNING.title("this changes meaning in Rust 2021")) + .element(Level::NOTE.title(long_title2)) + .element(Level::NOTE.title("`#[warn(array_into_iter)]` on by default")), + ) + .group( + Group::new() + .element( + Level::HELP.title("use `.iter()` instead of `.into_iter()` to avoid ambiguity"), + ) + .element( + Snippet::source(source2) + .origin("lint_example.rs") + .line_start(3) + .fold(true) + .patch(Patch::new(10..19, "iter")), + ), + ) + .group( + Group::new() + .element(Level::HELP.title(long_title3)) + .element( + Snippet::source(source2) + .origin("lint_example.rs") + .line_start(3) + .fold(true) + .patch(Patch::new(0..0, "IntoIterator::into_iter(")) + .patch(Patch::new(9..21, ")")), + ), + ); + + let expected = str![[r#" +warning: this method call resolves to `<&[T; N] as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<[T; N] as IntoIterator>::into_iter` in Rust 2021 + --> lint_example.rs:3:11 + | +3 | [1, 2, 3].into_iter().for_each(|n| { *n; }); + | ^^^^^^^^^ + | + = warning: this changes meaning in Rust 2021 + = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2021/IntoIterator-for-arrays.html> + = note: `#[warn(array_into_iter)]` on by default +help: use `.iter()` instead of `.into_iter()` to avoid ambiguity + | +3 - [1, 2, 3].into_iter().for_each(|n| { *n; }); +3 + [1, 2, 3].iter().for_each(|n| { *n; }); + | +help: or use `IntoIterator::into_iter(..)` instead of `.into_iter()` to explicitly iterate by value + | +3 - [1, 2, 3].into_iter().for_each(|n| { *n; }); +3 + IntoIterator::into_iter([1, 2, 3]).for_each(|n| { *n; }); + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input), expected); +} From 3d4d913682c99f0de9ab204a0ef7611d392b3f3d Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Fri, 30 May 2025 15:00:25 -0600 Subject: [PATCH 383/455] fix: Make span_to_lines return at least one line --- src/renderer/source_map.rs | 2 +- tests/rustc_tests.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/renderer/source_map.rs b/src/renderer/source_map.rs index 7401fb96..026b2a42 100644 --- a/src/renderer/source_map.rs +++ b/src/renderer/source_map.rs @@ -124,7 +124,7 @@ impl<'a> SourceMap<'a> { if start >= line_info.end_byte { continue; } - if end <= line_info.start_byte { + if end < line_info.start_byte { break; } lines.push(line_info); diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index 9360b0af..23c178bf 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -2966,7 +2966,6 @@ LL | /// This is a long line that contains a <http://link.com> } #[test] -#[should_panic = "attempt to subtract with overflow"] fn array_into_iter() { let source1 = r#"#![allow(unused)] fn main() { From e284907061eef3c38a2403bb702c85060080857f Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 10 Jun 2025 05:12:11 -0600 Subject: [PATCH 384/455] test: Add rustc multiline-removal-suggestion --- tests/color/main.rs | 1 + tests/color/multiline_removal_suggestion.rs | 113 ++++++++++++++++++ .../multiline_removal_suggestion.term.svg | 68 +++++++++++ 3 files changed, 182 insertions(+) create mode 100644 tests/color/multiline_removal_suggestion.rs create mode 100644 tests/color/multiline_removal_suggestion.term.svg diff --git a/tests/color/main.rs b/tests/color/main.rs index f954bb7a..a9885a0b 100644 --- a/tests/color/main.rs +++ b/tests/color/main.rs @@ -9,6 +9,7 @@ mod fold_bad_origin_line; mod fold_leading; mod fold_trailing; mod issue_9; +mod multiline_removal_suggestion; mod multiple_annotations; mod simple; mod strip_line; diff --git a/tests/color/multiline_removal_suggestion.rs b/tests/color/multiline_removal_suggestion.rs new file mode 100644 index 00000000..fbc47540 --- /dev/null +++ b/tests/color/multiline_removal_suggestion.rs @@ -0,0 +1,113 @@ +use annotate_snippets::{AnnotationKind, Group, Level, Origin, Patch, Renderer, Snippet}; + +use snapbox::{assert_data_eq, file}; + +#[test] +fn case() { + let source = r#"// Make sure suggestion for removal of a span that covers multiple lines is properly highlighted. +//@ compile-flags: --error-format=human --color=always +//@ edition:2018 +//@ only-linux +// ignore-tidy-tab +// We use `\t` instead of spaces for indentation to ensure that the highlighting logic properly +// accounts for replaced characters (like we do for `\t` with ` `). The naïve way of highlighting +// could be counting chars of the original code, instead of operating on the code as it is being +// displayed. +use std::collections::{HashMap, HashSet}; +fn foo() -> Vec<(bool, HashSet<u8>)> { + let mut hm = HashMap::<bool, Vec<HashSet<u8>>>::new(); + hm.into_iter() + .map(|(is_true, ts)| { + ts.into_iter() + .map(|t| { + ( + is_true, + t, + ) + }).flatten() + }) + .flatten() + .collect() +} +fn bar() -> Vec<(bool, HashSet<u8>)> { + let mut hm = HashMap::<bool, Vec<HashSet<u8>>>::new(); + hm.into_iter() + .map(|(is_true, ts)| { + ts.into_iter() + .map(|t| (is_true, t)) + .flatten() + }) + .flatten() + .collect() +} +fn baz() -> Vec<(bool, HashSet<u8>)> { + let mut hm = HashMap::<bool, Vec<HashSet<u8>>>::new(); + hm.into_iter() + .map(|(is_true, ts)| { + ts.into_iter().map(|t| { + (is_true, t) + }).flatten() + }) + .flatten() + .collect() +} +fn bay() -> Vec<(bool, HashSet<u8>)> { + let mut hm = HashMap::<bool, Vec<HashSet<u8>>>::new(); + hm.into_iter() + .map(|(is_true, ts)| { + ts.into_iter() + .map(|t| (is_true, t)).flatten() + }) + .flatten() + .collect() +} +fn main() {} +"#; + + let input = Level::ERROR + .header("`(bool, HashSet<u8>)` is not an iterator") + .id("E0277") + .group( + Group::new() + .element( + Snippet::source(source) + .origin("$DIR/multiline-removal-suggestion.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(769..776) + .label("`(bool, HashSet<u8>)` is not an iterator"), + ), + ) + .element( + Level::HELP + .title("the trait `Iterator` is not implemented for `(bool, HashSet<u8>)`"), + ) + .element( + Level::NOTE + .title("required for `(bool, HashSet<u8>)` to implement `IntoIterator`"), + ), + ) + .group( + Group::new() + .element(Level::NOTE.title("required by a bound in `flatten`")) + .element( + Origin::new("/rustc/FAKE_PREFIX/library/core/src/iter/traits/iterator.rs") + .line(1556) + .char_column(4), + ), + ) + .group( + Group::new() + .element(Level::HELP.title("consider removing this method call, as the receiver has type `std::vec::IntoIter<HashSet<u8>>` and `std::vec::IntoIter<HashSet<u8>>: Iterator` trivially holds")) + .element( + Snippet::source(source) + .origin("$DIR/multiline-removal-suggestion.rs") + .fold(true) + .patch(Patch::new(708..768, "")), + ), + ); + let expected = file!["multiline_removal_suggestion.term.svg"]; + let renderer = Renderer::styled(); + assert_data_eq!(renderer.render(input), expected); +} diff --git a/tests/color/multiline_removal_suggestion.term.svg b/tests/color/multiline_removal_suggestion.term.svg new file mode 100644 index 00000000..bef4b25a --- /dev/null +++ b/tests/color/multiline_removal_suggestion.term.svg @@ -0,0 +1,68 @@ +<svg width="1398px" height="398px" xmlns="http://www.w3.org/2000/svg"> + <style> + .fg { fill: #AAAAAA } + .bg { background: #000000 } + .fg-bright-blue { fill: #5555FF } + .fg-bright-cyan { fill: #55FFFF } + .fg-bright-green { fill: #55FF55 } + .fg-bright-red { fill: #FF5555 } + .container { + padding: 0 10px; + line-height: 18px; + } + .bold { font-weight: bold; } + tspan { + font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + white-space: pre; + line-height: 18px; + } + </style> + + <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> + + <text xml:space="preserve" class="container fg"> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0277]</tspan><tspan class="bold">: `(bool, HashSet<u8>)` is not an iterator</tspan> +</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold">--> </tspan><tspan>$DIR/multiline-removal-suggestion.rs:21:8</tspan> +</tspan> + <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> +</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">21</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> }).flatten()</tspan> +</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^^^^^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">`(bool, HashSet<u8>)` is not an iterator</tspan> +</tspan> + <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> +</tspan> + <tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">= </tspan><tspan class="bold">help</tspan><tspan>: the trait `Iterator` is not implemented for `(bool, HashSet<u8>)`</tspan> +</tspan> + <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">= </tspan><tspan class="bold">note</tspan><tspan>: required for `(bool, HashSet<u8>)` to implement `IntoIterator`</tspan> +</tspan> + <tspan x="10px" y="172px"><tspan class="fg-bright-green bold">note</tspan><tspan>: required by a bound in `flatten`</tspan> +</tspan> + <tspan x="10px" y="190px"><tspan> </tspan><tspan class="fg-bright-blue bold">::: </tspan><tspan>/rustc/FAKE_PREFIX/library/core/src/iter/traits/iterator.rs:1556:4</tspan> +</tspan> + <tspan x="10px" y="208px"><tspan class="fg-bright-cyan bold">help</tspan><tspan>: consider removing this method call, as the receiver has type `std::vec::IntoIter<HashSet<u8>>` and `std::vec::IntoIter<HashSet<u8>>: Iterator` trivially holds</tspan> +</tspan> + <tspan x="10px" y="226px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> +</tspan> + <tspan x="10px" y="244px"><tspan class="fg-bright-blue bold">15</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan> ts.into_iter()</tspan> +</tspan> + <tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">16</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan> .map(|t| {</tspan> +</tspan> + <tspan x="10px" y="280px"><tspan class="fg-bright-blue bold">17</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan> (</tspan> +</tspan> + <tspan x="10px" y="298px"><tspan class="fg-bright-blue bold">18</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan> is_true,</tspan> +</tspan> + <tspan x="10px" y="316px"><tspan class="fg-bright-blue bold">19</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan> t,</tspan> +</tspan> + <tspan x="10px" y="334px"><tspan class="fg-bright-blue bold">20</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan> )</tspan> +</tspan> + <tspan x="10px" y="352px"><tspan class="fg-bright-blue bold">21</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan> }).flatten()</tspan> +</tspan> + <tspan x="10px" y="370px"><tspan class="fg-bright-blue bold">15</tspan><tspan> </tspan><tspan class="fg-bright-green">+ </tspan><tspan> ts.into_iter().flatten()</tspan> +</tspan> + <tspan x="10px" y="388px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> +</tspan> + </text> + +</svg> From 01b8da75479b6b4f64bad39a06c8cffe0c8c17ad Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 3 Jun 2025 09:45:06 -0600 Subject: [PATCH 385/455] fix: Color suggestion removal diff --- examples/custom_level.svg | 6 +- src/renderer/mod.rs | 88 +++++++++++++++++-- .../multiline_removal_suggestion.term.svg | 12 +-- 3 files changed, 90 insertions(+), 16 deletions(-) diff --git a/examples/custom_level.svg b/examples/custom_level.svg index 62dded57..46d3165c 100644 --- a/examples/custom_level.svg +++ b/examples/custom_level.svg @@ -45,11 +45,11 @@ </tspan> <tspan x="10px" y="226px"><tspan> </tspan><tspan class="fg-bright-blue bold">╭╴</tspan> </tspan> - <tspan x="10px" y="244px"><tspan class="fg-bright-blue bold">22</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan> break (|| { //~ ERROR `break` with value from a `while` loop</tspan> + <tspan x="10px" y="244px"><tspan class="fg-bright-blue bold">22</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan> </tspan><tspan class="fg-bright-red">break (|| { //~ ERROR `break` with value from a `while` loop</tspan> </tspan> - <tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">23</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan> let local = 9;</tspan> + <tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">23</tspan><tspan> </tspan><tspan class="fg-bright-red">- let local = 9;</tspan> </tspan> - <tspan x="10px" y="280px"><tspan class="fg-bright-blue bold">24</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan> </tspan><tspan class="fg-bright-red">})</tspan><tspan>;</tspan> + <tspan x="10px" y="280px"><tspan class="fg-bright-blue bold">24</tspan><tspan> </tspan><tspan class="fg-bright-red">- })</tspan><tspan>;</tspan> </tspan> <tspan x="10px" y="298px"><tspan class="fg-bright-blue bold">22</tspan><tspan> </tspan><tspan class="fg-bright-green">+ </tspan><tspan> </tspan><tspan class="fg-bright-green">break</tspan><tspan>;</tspan> </tspan> diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index f6af1e3b..fadf7805 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -1874,6 +1874,7 @@ impl Renderer { show_code_change { for part in parts { + let snippet = sm.span_to_snippet(part.span.clone()).unwrap_or_default(); let (span_start, span_end) = sm.span_to_locations(part.span.clone()); let span_start_pos = span_start.display; let span_end_pos = span_end.display; @@ -1932,13 +1933,86 @@ impl Renderer { } if let DisplaySuggestion::Diff = show_code_change { // Colorize removal with red in diff format. - buffer.set_style_range( - row_num - 2, - (padding as isize + span_start_pos as isize) as usize, - (padding as isize + span_end_pos as isize) as usize, - ElementStyle::Removal, - true, - ); + + // Below, there's some tricky buffer indexing going on. `row_num` at this + // point corresponds to: + // + // | + // LL | CODE + // | ++++ <- `row_num` + // + // in the buffer. When we have a diff format output, we end up with + // + // | + // LL - OLDER <- row_num - 2 + // LL + NEWER + // | <- row_num + // + // The `row_num - 2` is to select the buffer line that has the "old version + // of the diff" at that point. When the removal is a single line, `i` is + // `0`, `newlines` is `1` so `(newlines - i - 1)` ends up being `0`, so row + // points at `LL - OLDER`. When the removal corresponds to multiple lines, + // we end up with `newlines > 1` and `i` being `0..newlines - 1`. + // + // | + // LL - OLDER <- row_num - 2 - (newlines - last_i - 1) + // LL - CODE + // LL - BEING + // LL - REMOVED <- row_num - 2 - (newlines - first_i - 1) + // LL + NEWER + // | <- row_num + + let newlines = snippet.lines().count(); + if newlines > 0 && row_num > newlines { + // Account for removals where the part being removed spans multiple + // lines. + // FIXME: We check the number of rows because in some cases, like in + // `tests/ui/lint/invalid-nan-comparison-suggestion.rs`, the rendered + // suggestion will only show the first line of code being replaced. The + // proper way of doing this would be to change the suggestion rendering + // logic to show the whole prior snippet, but the current output is not + // too bad to begin with, so we side-step that issue here. + for (i, line) in snippet.lines().enumerate() { + let line = normalize_whitespace(line); + let row = row_num - 2 - (newlines - i - 1); + // On the first line, we highlight between the start of the part + // span, and the end of that line. + // On the last line, we highlight between the start of the line, and + // the column of the part span end. + // On all others, we highlight the whole line. + let start = if i == 0 { + (padding as isize + span_start_pos as isize) as usize + } else { + padding + }; + let end = if i == 0 { + (padding as isize + + span_start_pos as isize + + line.len() as isize) + as usize + } else if i == newlines - 1 { + (padding as isize + span_end_pos as isize) as usize + } else { + (padding as isize + line.len() as isize) as usize + }; + buffer.set_style_range( + row, + start, + end, + ElementStyle::Removal, + true, + ); + } + } else { + // The removed code fits all in one line. + buffer.set_style_range( + row_num - 2, + (padding as isize + span_start_pos as isize) as usize, + (padding as isize + span_end_pos as isize) as usize, + ElementStyle::Removal, + true, + ); + } } // length of the code after substitution diff --git a/tests/color/multiline_removal_suggestion.term.svg b/tests/color/multiline_removal_suggestion.term.svg index bef4b25a..ed203237 100644 --- a/tests/color/multiline_removal_suggestion.term.svg +++ b/tests/color/multiline_removal_suggestion.term.svg @@ -47,17 +47,17 @@ </tspan> <tspan x="10px" y="244px"><tspan class="fg-bright-blue bold">15</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan> ts.into_iter()</tspan> </tspan> - <tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">16</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan> .map(|t| {</tspan> + <tspan x="10px" y="262px"><tspan class="fg-bright-blue bold">16</tspan><tspan> </tspan><tspan class="fg-bright-red">- .map(|t| {</tspan> </tspan> - <tspan x="10px" y="280px"><tspan class="fg-bright-blue bold">17</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan> (</tspan> + <tspan x="10px" y="280px"><tspan class="fg-bright-blue bold">17</tspan><tspan> </tspan><tspan class="fg-bright-red">- (</tspan> </tspan> - <tspan x="10px" y="298px"><tspan class="fg-bright-blue bold">18</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan> is_true,</tspan> + <tspan x="10px" y="298px"><tspan class="fg-bright-blue bold">18</tspan><tspan> </tspan><tspan class="fg-bright-red">- is_true,</tspan> </tspan> - <tspan x="10px" y="316px"><tspan class="fg-bright-blue bold">19</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan> t,</tspan> + <tspan x="10px" y="316px"><tspan class="fg-bright-blue bold">19</tspan><tspan> </tspan><tspan class="fg-bright-red">- t,</tspan> </tspan> - <tspan x="10px" y="334px"><tspan class="fg-bright-blue bold">20</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan> )</tspan> + <tspan x="10px" y="334px"><tspan class="fg-bright-blue bold">20</tspan><tspan> </tspan><tspan class="fg-bright-red">- )</tspan> </tspan> - <tspan x="10px" y="352px"><tspan class="fg-bright-blue bold">21</tspan><tspan> </tspan><tspan class="fg-bright-red">- </tspan><tspan> }).flatten()</tspan> + <tspan x="10px" y="352px"><tspan class="fg-bright-blue bold">21</tspan><tspan> </tspan><tspan class="fg-bright-red">- })</tspan><tspan>.flatten()</tspan> </tspan> <tspan x="10px" y="370px"><tspan class="fg-bright-blue bold">15</tspan><tspan> </tspan><tspan class="fg-bright-green">+ </tspan><tspan> ts.into_iter().flatten()</tspan> </tspan> From 7305271c52251ef46d67e8c2476be0806e82ad19 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 26 Jun 2025 14:34:06 -0600 Subject: [PATCH 386/455] refactor!: Rename origin fields to path --- benches/bench.rs | 4 +- examples/custom_error.rs | 2 +- examples/custom_level.rs | 4 +- examples/expected_type.rs | 2 +- examples/footer.rs | 2 +- examples/format.rs | 2 +- examples/highlight_source.rs | 2 +- examples/highlight_title.rs | 4 +- examples/multislice.rs | 4 +- src/renderer/mod.rs | 52 +++---- src/snippet.rs | 14 +- tests/color/ann_eof.rs | 2 +- tests/color/ann_insertion.rs | 2 +- tests/color/ann_multiline.rs | 2 +- tests/color/ann_multiline2.rs | 2 +- tests/color/ann_removed_nl.rs | 2 +- tests/color/ensure_emoji_highlight_width.rs | 2 +- tests/color/fold_ann_multiline.rs | 2 +- tests/color/fold_bad_origin_line.rs | 2 +- tests/color/fold_leading.rs | 2 +- tests/color/fold_trailing.rs | 2 +- tests/color/issue_9.rs | 2 +- tests/color/multiline_removal_suggestion.rs | 4 +- tests/color/simple.rs | 2 +- tests/color/strip_line.rs | 2 +- tests/color/strip_line_char.rs | 2 +- tests/color/strip_line_non_ws.rs | 2 +- tests/formatter.rs | 90 ++++++------ tests/rustc_tests.rs | 150 ++++++++++---------- 29 files changed, 183 insertions(+), 183 deletions(-) diff --git a/benches/bench.rs b/benches/bench.rs index c3799fbd..32b67c3f 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -28,7 +28,7 @@ fn simple() -> String { Group::new().element( Snippet::source(source) .line_start(51) - .origin("src/format.rs") + .path("src/format.rs") .annotation( AnnotationKind::Context .span(5..19) @@ -73,7 +73,7 @@ fn fold(bencher: divan::Bencher<'_, '_>, context: usize) { Group::new().element( Snippet::source(&input) .fold(true) - .origin("src/format.rs") + .path("src/format.rs") .annotation( AnnotationKind::Context .span(span) diff --git a/examples/custom_error.rs b/examples/custom_error.rs index b9e27b31..dfb8fc6c 100644 --- a/examples/custom_error.rs +++ b/examples/custom_error.rs @@ -22,7 +22,7 @@ pub static C: u32 = 0 - 1; .group( Group::new().element( Snippet::source(source) - .origin("$DIR/err.rs") + .path("$DIR/err.rs") .fold(true) .annotation( AnnotationKind::Primary diff --git a/examples/custom_level.rs b/examples/custom_level.rs index b2af361a..57f2fb5a 100644 --- a/examples/custom_level.rs +++ b/examples/custom_level.rs @@ -36,7 +36,7 @@ fn main() { Group::new().element( Snippet::source(source) .line_start(1) - .origin("$DIR/issue-114529-illegal-break-with-value.rs") + .path("$DIR/issue-114529-illegal-break-with-value.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -60,7 +60,7 @@ fn main() { .element( Snippet::source(source) .line_start(1) - .origin("$DIR/issue-114529-illegal-break-with-value.rs") + .path("$DIR/issue-114529-illegal-break-with-value.rs") .fold(true) .patch(Patch::new(483..581, "break")), ), diff --git a/examples/expected_type.rs b/examples/expected_type.rs index 02abdecf..440c64c3 100644 --- a/examples/expected_type.rs +++ b/examples/expected_type.rs @@ -10,7 +10,7 @@ fn main() { Group::new().element( Snippet::source(source) .line_start(26) - .origin("examples/footer.rs") + .path("examples/footer.rs") .fold(true) .annotation(AnnotationKind::Primary.span(193..195).label( "expected struct `annotate_snippets::snippet::Slice`, found reference", diff --git a/examples/footer.rs b/examples/footer.rs index ca6f1dc7..36173350 100644 --- a/examples/footer.rs +++ b/examples/footer.rs @@ -8,7 +8,7 @@ fn main() { Group::new().element( Snippet::source(" slices: vec![\"A\",") .line_start(13) - .origin("src/multislice.rs") + .path("src/multislice.rs") .annotation(AnnotationKind::Primary.span(21..24).label( "expected struct `annotate_snippets::snippet::Slice`, found reference", )), diff --git a/examples/format.rs b/examples/format.rs index 4b688d4b..4268e315 100644 --- a/examples/format.rs +++ b/examples/format.rs @@ -27,7 +27,7 @@ fn main() { Group::new().element( Snippet::source(source) .line_start(51) - .origin("src/format.rs") + .path("src/format.rs") .annotation( AnnotationKind::Context .span(5..19) diff --git a/examples/highlight_source.rs b/examples/highlight_source.rs index 92d8114f..f5871453 100644 --- a/examples/highlight_source.rs +++ b/examples/highlight_source.rs @@ -17,7 +17,7 @@ fn main() {} .element( Snippet::source(source) .fold(true) - .origin("$DIR/E0010-teach.rs") + .path("$DIR/E0010-teach.rs") .annotation( AnnotationKind::Primary .span(72..85) diff --git a/examples/highlight_title.rs b/examples/highlight_title.rs index 218e414f..2f74a37b 100644 --- a/examples/highlight_title.rs +++ b/examples/highlight_title.rs @@ -49,7 +49,7 @@ fn main() { .element( Snippet::source(source) .fold(true) - .origin("$DIR/highlighting.rs") + .path("$DIR/highlighting.rs") .annotation( AnnotationKind::Primary .span(553..563) @@ -69,7 +69,7 @@ fn main() { .element( Snippet::source(source) .fold(true) - .origin("$DIR/highlighting.rs") + .path("$DIR/highlighting.rs") .annotation(AnnotationKind::Context.span(200..333).label("")) .annotation(AnnotationKind::Primary.span(194..199)), ), diff --git a/examples/multislice.rs b/examples/multislice.rs index a7d340ad..b8b4ac74 100644 --- a/examples/multislice.rs +++ b/examples/multislice.rs @@ -6,12 +6,12 @@ fn main() { .element( Snippet::<Annotation<'_>>::source("Foo") .line_start(51) - .origin("src/format.rs"), + .path("src/format.rs"), ) .element( Snippet::<Annotation<'_>>::source("Faa") .line_start(129) - .origin("src/display.rs"), + .path("src/display.rs"), ), ); diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index fadf7805..93996075 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -24,7 +24,7 @@ //! .group( //! Group::new().element( //! Snippet::source(source) -//! .origin("temp.rs") +//! .path("temp.rs") //! .line_start(1) //! .fold(true) //! .annotation( @@ -232,21 +232,21 @@ impl Renderer { ) -> Result<String, fmt::Error> { let mut out_string = String::new(); - let og_primary_origin = message + let og_primary_path = message .groups .iter() .find_map(|group| { group.elements.iter().find_map(|s| match &s { Element::Cause(cause) => { if cause.markers.iter().any(|m| m.kind.is_primary()) { - Some(cause.origin) + Some(cause.path) } else { None } } Element::Origin(origin) => { if origin.primary { - Some(Some(origin.origin)) + Some(Some(origin.path)) } else { None } @@ -260,8 +260,8 @@ impl Renderer { .iter() .find_map(|group| { group.elements.iter().find_map(|s| match &s { - Element::Cause(cause) => Some(cause.origin), - Element::Origin(origin) => Some(Some(origin.origin)), + Element::Cause(cause) => Some(cause.path), + Element::Origin(origin) => Some(Some(origin.path)), _ => None, }) }) @@ -270,20 +270,20 @@ impl Renderer { let group_len = message.groups.len(); for (g, group) in message.groups.into_iter().enumerate() { let mut buffer = StyledBuffer::new(); - let primary_origin = group + let primary_path = group .elements .iter() .find_map(|s| match &s { Element::Cause(cause) => { if cause.markers.iter().any(|m| m.kind.is_primary()) { - Some(cause.origin) + Some(cause.path) } else { None } } Element::Origin(origin) => { if origin.primary { - Some(Some(origin.origin)) + Some(Some(origin.path)) } else { None } @@ -295,8 +295,8 @@ impl Renderer { .elements .iter() .find_map(|s| match &s { - Element::Cause(cause) => Some(cause.origin), - Element::Origin(origin) => Some(Some(origin.origin)), + Element::Cause(cause) => Some(cause.path), + Element::Origin(origin) => Some(Some(origin.path)), _ => None, }) .unwrap_or_default(), @@ -357,7 +357,7 @@ impl Renderer { &mut buffer, max_line_num_len, cause, - primary_origin, + primary_path, &source_map, &annotated_lines, max_depth, @@ -396,7 +396,7 @@ impl Renderer { suggestion, max_line_num_len, &source_map, - primary_origin.or(og_primary_origin), + primary_path.or(og_primary_path), last_was_suggestion, ); last_was_suggestion = true; @@ -490,8 +490,8 @@ impl Renderer { labels = Some(labels_inner); } - if let Some(origin) = cause.origin { - let mut origin = Origin::new(origin); + if let Some(path) = cause.path { + let mut origin = Origin::new(path); origin.primary = true; let source_map = SourceMap::new(cause.source, cause.line_start); @@ -764,10 +764,10 @@ impl Renderer { let str = match (&origin.line, &origin.char_column) { (Some(line), Some(col)) => { - format!("{}:{}:{}", origin.origin, line, col) + format!("{}:{}:{}", origin.path, line, col) } - (Some(line), None) => format!("{}:{}", origin.origin, line), - _ => origin.origin.to_owned(), + (Some(line), None) => format!("{}:{}", origin.path, line), + _ => origin.path.to_owned(), }; buffer.append(buffer_msg_line_offset, &str, ElementStyle::LineAndColumn); @@ -784,17 +784,17 @@ impl Renderer { buffer: &mut StyledBuffer, max_line_num_len: usize, snippet: &Snippet<'_, Annotation<'_>>, - primary_origin: Option<&str>, + primary_path: Option<&str>, sm: &SourceMap<'_>, annotated_lines: &[AnnotatedLineInfo<'_>], multiline_depth: usize, is_cont: bool, ) { - if let Some(origin) = snippet.origin { - let mut origin = Origin::new(origin); + if let Some(path) = snippet.path { + let mut origin = Origin::new(path); // print out the span location and spacer before we print the annotated source // to do this, we need to know if this span will be primary - let is_primary = primary_origin == Some(origin.origin); + let is_primary = primary_path == Some(origin.path); if is_primary { origin.primary = true; @@ -1648,7 +1648,7 @@ impl Renderer { suggestion: &Snippet<'_, Patch<'_>>, max_line_num_len: usize, sm: &SourceMap<'_>, - primary_origin: Option<&str>, + primary_path: Option<&str>, is_cont: bool, ) { let suggestions = sm.splice_lines(suggestion.markers.clone()); @@ -1671,14 +1671,14 @@ impl Renderer { ElementStyle::LineNumber, ); } - if suggestion.origin != primary_origin { - if let Some(origin) = suggestion.origin { + if suggestion.path != primary_path { + if let Some(path) = suggestion.path { let (loc, _) = sm.span_to_locations(parts[0].span.clone()); // --> file.rs:line:col // | let arrow = self.file_start(); buffer.puts(row_num - 1, 0, arrow, ElementStyle::LineNumber); - let message = format!("{}:{}:{}", origin, loc.line, loc.char + 1); + let message = format!("{}:{}:{}", path, loc.line, loc.char + 1); if is_cont { buffer.append(row_num - 1, &message, ElementStyle::LineAndColumn); } else { diff --git a/src/snippet.rs b/src/snippet.rs index 16f337ad..abdab118 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -164,7 +164,7 @@ pub struct Title<'a> { /// A source view [`Element`] in a [`Group`] #[derive(Clone, Debug)] pub struct Snippet<'a, T> { - pub(crate) origin: Option<&'a str>, + pub(crate) path: Option<&'a str>, pub(crate) line_start: usize, pub(crate) source: &'a str, pub(crate) markers: Vec<T>, @@ -183,7 +183,7 @@ impl<'a, T: Clone> Snippet<'a, T> { /// </div> pub fn source(source: &'a str) -> Self { Self { - origin: None, + path: None, line_start: 1, source, markers: vec![], @@ -207,8 +207,8 @@ impl<'a, T: Clone> Snippet<'a, T> { /// not allowed to be passed to this function. /// /// </div> - pub fn origin(mut self, origin: &'a str) -> Self { - self.origin = Some(origin); + pub fn path(mut self, path: &'a str) -> Self { + self.path = Some(path); self } @@ -378,7 +378,7 @@ impl<'a> Patch<'a> { /// The location of the [`Snippet`] (e.g. a path) #[derive(Clone, Debug)] pub struct Origin<'a> { - pub(crate) origin: &'a str, + pub(crate) path: &'a str, pub(crate) line: Option<usize>, pub(crate) char_column: Option<usize>, pub(crate) primary: bool, @@ -392,9 +392,9 @@ impl<'a> Origin<'a> { /// not allowed to be passed to this function. /// /// </div> - pub fn new(origin: &'a str) -> Self { + pub fn new(path: &'a str) -> Self { Self { - origin, + path, line: None, char_column: None, primary: false, diff --git a/tests/color/ann_eof.rs b/tests/color/ann_eof.rs index 00e34b16..ea0c95b8 100644 --- a/tests/color/ann_eof.rs +++ b/tests/color/ann_eof.rs @@ -7,7 +7,7 @@ fn case() { let input = Level::ERROR.header("expected `.`, `=`").group( Group::new().element( Snippet::source("asdf") - .origin("Cargo.toml") + .path("Cargo.toml") .line_start(1) .annotation(AnnotationKind::Primary.span(4..4).label("")), ), diff --git a/tests/color/ann_insertion.rs b/tests/color/ann_insertion.rs index 802a0c78..a0c538b8 100644 --- a/tests/color/ann_insertion.rs +++ b/tests/color/ann_insertion.rs @@ -7,7 +7,7 @@ fn case() { let input = Level::ERROR.header("expected `.`, `=`").group( Group::new().element( Snippet::source("asf") - .origin("Cargo.toml") + .path("Cargo.toml") .line_start(1) .annotation(AnnotationKind::Primary.span(2..2).label("'d' belongs here")), ), diff --git a/tests/color/ann_multiline.rs b/tests/color/ann_multiline.rs index 4b561ed3..127c462e 100644 --- a/tests/color/ann_multiline.rs +++ b/tests/color/ann_multiline.rs @@ -15,7 +15,7 @@ fn case() { .group( Group::new().element( Snippet::source(source) - .origin("src/display_list.rs") + .path("src/display_list.rs") .line_start(139) .fold(false) .annotation( diff --git a/tests/color/ann_multiline2.rs b/tests/color/ann_multiline2.rs index 9996fa97..b8e36197 100644 --- a/tests/color/ann_multiline2.rs +++ b/tests/color/ann_multiline2.rs @@ -15,7 +15,7 @@ to exactly one character on next line. .group( Group::new().element( Snippet::source(source) - .origin("foo.txt") + .path("foo.txt") .line_start(26) .fold(false) .annotation( diff --git a/tests/color/ann_removed_nl.rs b/tests/color/ann_removed_nl.rs index 45a64626..b4398c47 100644 --- a/tests/color/ann_removed_nl.rs +++ b/tests/color/ann_removed_nl.rs @@ -7,7 +7,7 @@ fn case() { let input = Level::ERROR.header("expected `.`, `=`").group( Group::new().element( Snippet::source("asdf") - .origin("Cargo.toml") + .path("Cargo.toml") .line_start(1) .annotation(AnnotationKind::Primary.span(4..5).label("")), ), diff --git a/tests/color/ensure_emoji_highlight_width.rs b/tests/color/ensure_emoji_highlight_width.rs index b2397845..59dcdaa2 100644 --- a/tests/color/ensure_emoji_highlight_width.rs +++ b/tests/color/ensure_emoji_highlight_width.rs @@ -12,7 +12,7 @@ fn case() { Group::new() .element( Snippet::source(source) - .origin("<file>") + .path("<file>") .line_start(7) .annotation(AnnotationKind::Primary.span(0..35).label("")) ) diff --git a/tests/color/fold_ann_multiline.rs b/tests/color/fold_ann_multiline.rs index 3995b686..b0ccdd55 100644 --- a/tests/color/fold_ann_multiline.rs +++ b/tests/color/fold_ann_multiline.rs @@ -31,7 +31,7 @@ fn case() { let input = Level::ERROR.header("mismatched types").id("E0308").group( Group::new().element( Snippet::source(source) - .origin("src/format.rs") + .path("src/format.rs") .line_start(51) .fold(true) .annotation(AnnotationKind::Context.span(5..19).label( diff --git a/tests/color/fold_bad_origin_line.rs b/tests/color/fold_bad_origin_line.rs index 1a21a5ef..852f9b54 100644 --- a/tests/color/fold_bad_origin_line.rs +++ b/tests/color/fold_bad_origin_line.rs @@ -12,7 +12,7 @@ invalid syntax let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) - .origin("path/to/error.rs") + .path("path/to/error.rs") .line_start(1) .fold(true) .annotation(AnnotationKind::Context.span(2..16).label("error here")), diff --git a/tests/color/fold_leading.rs b/tests/color/fold_leading.rs index 93ba4992..965741d5 100644 --- a/tests/color/fold_leading.rs +++ b/tests/color/fold_leading.rs @@ -23,7 +23,7 @@ workspace = 20 .group( Group::new().element( Snippet::source(source) - .origin("Cargo.toml") + .path("Cargo.toml") .line_start(1) .fold(true) .annotation(AnnotationKind::Primary.span(132..134).label("")), diff --git a/tests/color/fold_trailing.rs b/tests/color/fold_trailing.rs index f86ade78..bbcf5d80 100644 --- a/tests/color/fold_trailing.rs +++ b/tests/color/fold_trailing.rs @@ -22,7 +22,7 @@ edition = "2021" .group( Group::new().element( Snippet::source(source) - .origin("Cargo.toml") + .path("Cargo.toml") .line_start(1) .fold(true) .annotation(AnnotationKind::Primary.span(8..10).label("")), diff --git a/tests/color/issue_9.rs b/tests/color/issue_9.rs index 2accd2f2..36d13f44 100644 --- a/tests/color/issue_9.rs +++ b/tests/color/issue_9.rs @@ -9,7 +9,7 @@ fn case() { Group::new() .element( Snippet::source("let x = vec![1];") - .origin("/code/rust/src/test/ui/annotate-snippet/suggestion.rs") + .path("/code/rust/src/test/ui/annotate-snippet/suggestion.rs") .line_start(4) .annotation(AnnotationKind::Context.span(4..5).label("move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait")) ) diff --git a/tests/color/multiline_removal_suggestion.rs b/tests/color/multiline_removal_suggestion.rs index fbc47540..ced5e09d 100644 --- a/tests/color/multiline_removal_suggestion.rs +++ b/tests/color/multiline_removal_suggestion.rs @@ -71,7 +71,7 @@ fn main() {} Group::new() .element( Snippet::source(source) - .origin("$DIR/multiline-removal-suggestion.rs") + .path("$DIR/multiline-removal-suggestion.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -102,7 +102,7 @@ fn main() {} .element(Level::HELP.title("consider removing this method call, as the receiver has type `std::vec::IntoIter<HashSet<u8>>` and `std::vec::IntoIter<HashSet<u8>>: Iterator` trivially holds")) .element( Snippet::source(source) - .origin("$DIR/multiline-removal-suggestion.rs") + .path("$DIR/multiline-removal-suggestion.rs") .fold(true) .patch(Patch::new(708..768, "")), ), diff --git a/tests/color/simple.rs b/tests/color/simple.rs index 35e83d38..83834295 100644 --- a/tests/color/simple.rs +++ b/tests/color/simple.rs @@ -14,7 +14,7 @@ fn case() { .group( Group::new().element( Snippet::source(source) - .origin("src/format_color.rs") + .path("src/format_color.rs") .line_start(169) .annotation( AnnotationKind::Primary diff --git a/tests/color/strip_line.rs b/tests/color/strip_line.rs index fd1ba588..4b21f9a1 100644 --- a/tests/color/strip_line.rs +++ b/tests/color/strip_line.rs @@ -9,7 +9,7 @@ fn case() { let input = Level::ERROR.header("mismatched types").id("E0308").group( Group::new().element( Snippet::source(source) - .origin("$DIR/whitespace-trimming.rs") + .path("$DIR/whitespace-trimming.rs") .line_start(4) .annotation( AnnotationKind::Primary diff --git a/tests/color/strip_line_char.rs b/tests/color/strip_line_char.rs index df609e2f..f30d5e90 100644 --- a/tests/color/strip_line_char.rs +++ b/tests/color/strip_line_char.rs @@ -9,7 +9,7 @@ fn case() { let input = Level::ERROR.header("mismatched types").id("E0308").group( Group::new().element( Snippet::source(source) - .origin("$DIR/whitespace-trimming.rs") + .path("$DIR/whitespace-trimming.rs") .line_start(4) .annotation( AnnotationKind::Primary diff --git a/tests/color/strip_line_non_ws.rs b/tests/color/strip_line_non_ws.rs index f82d369b..a67d70d1 100644 --- a/tests/color/strip_line_non_ws.rs +++ b/tests/color/strip_line_non_ws.rs @@ -10,7 +10,7 @@ fn case() { let input = Level::ERROR.header("mismatched types").id("E0308").group( Group::new().element( Snippet::source(source) - .origin("$DIR/non-whitespace-trimming.rs") + .path("$DIR/non-whitespace-trimming.rs") .line_start(4) .annotation( AnnotationKind::Primary diff --git a/tests/formatter.rs b/tests/formatter.rs index b48d8f52..42886762 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -8,7 +8,7 @@ fn test_i_29() { let snippets = Level::ERROR.header("oops").group( Group::new().element( Snippet::source("First line\r\nSecond oops line") - .origin("<current file>") + .path("<current file>") .annotation(AnnotationKind::Primary.span(19..23).label("oops")) .fold(true), ), @@ -30,7 +30,7 @@ fn test_point_to_double_width_characters() { let snippets = Level::ERROR.header("").group( Group::new().element( Snippet::source("こんにちは、世界") - .origin("<current file>") + .path("<current file>") .annotation(AnnotationKind::Primary.span(18..24).label("world")), ), ); @@ -52,7 +52,7 @@ fn test_point_to_double_width_characters_across_lines() { let snippets = Level::ERROR.header("").group( Group::new().element( Snippet::source("おはよう\nございます") - .origin("<current file>") + .path("<current file>") .annotation(AnnotationKind::Primary.span(6..22).label("Good morning")), ), ); @@ -76,7 +76,7 @@ fn test_point_to_double_width_characters_multiple() { let snippets = Level::ERROR.header("").group( Group::new().element( Snippet::source("お寿司\n食べたい🍣") - .origin("<current file>") + .path("<current file>") .annotation(AnnotationKind::Primary.span(0..9).label("Sushi1")) .annotation(AnnotationKind::Context.span(16..22).label("Sushi2")), ), @@ -101,7 +101,7 @@ fn test_point_to_double_width_characters_mixed() { let snippets = Level::ERROR.header("").group( Group::new().element( Snippet::source("こんにちは、新しいWorld!") - .origin("<current file>") + .path("<current file>") .annotation(AnnotationKind::Primary.span(18..32).label("New world")), ), ); @@ -153,12 +153,12 @@ fn test_format_snippets_continuation() { .element( Snippet::<Annotation<'_>>::source(src_0) .line_start(5402) - .origin("file1.rs"), + .path("file1.rs"), ) .element( Snippet::<Annotation<'_>>::source(src_1) .line_start(2) - .origin("file2.rs"), + .path("file2.rs"), ), ); let expected = str![[r#" @@ -298,7 +298,7 @@ error: fn test_only_source() { let input = Level::ERROR .header("") - .group(Group::new().element(Snippet::<Annotation<'_>>::source("").origin("file.rs"))); + .group(Group::new().element(Snippet::<Annotation<'_>>::source("").path("file.rs"))); let expected = str![[r#" error: --> file.rs @@ -332,7 +332,7 @@ fn issue_130() { let input = Level::ERROR.header("dummy").group( Group::new().element( Snippet::source("foo\nbar\nbaz") - .origin("file/path") + .path("file/path") .line_start(3) .fold(true) .annotation(AnnotationKind::Primary.span(4..11)), @@ -360,7 +360,7 @@ a\" let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) - .origin("file/path") + .path("file/path") .line_start(3) .fold(true) .annotation(AnnotationKind::Primary.span(0..10)), @@ -384,7 +384,7 @@ fn char_and_nl_annotate_char() { let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) - .origin("file/path") + .path("file/path") .line_start(3) .annotation(AnnotationKind::Primary.span(0..2)), ), // a\r @@ -407,7 +407,7 @@ fn char_eol_annotate_char() { let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) - .origin("file/path") + .path("file/path") .line_start(3) .annotation(AnnotationKind::Primary.span(0..3)), ), // a\r\n @@ -429,7 +429,7 @@ fn char_eol_annotate_char_double_width() { let snippets = Level::ERROR.header("").group( Group::new().element( Snippet::source("こん\r\nにちは\r\n世界") - .origin("<current file>") + .path("<current file>") .annotation(AnnotationKind::Primary.span(3..8)), ), // ん\r\n ); @@ -455,7 +455,7 @@ fn annotate_eol() { let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) - .origin("file/path") + .path("file/path") .line_start(3) .annotation(AnnotationKind::Primary.span(1..2)), ), // \r @@ -478,7 +478,7 @@ fn annotate_eol2() { let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) - .origin("file/path") + .path("file/path") .line_start(3) .annotation(AnnotationKind::Primary.span(1..3)), ), // \r\n @@ -502,7 +502,7 @@ fn annotate_eol3() { let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) - .origin("file/path") + .path("file/path") .line_start(3) .annotation(AnnotationKind::Primary.span(2..3)), ), // \n @@ -526,7 +526,7 @@ fn annotate_eol4() { let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) - .origin("file/path") + .path("file/path") .line_start(3) .annotation(AnnotationKind::Primary.span(2..2)), ), // \n @@ -548,7 +548,7 @@ fn annotate_eol_double_width() { let snippets = Level::ERROR.header("").group( Group::new().element( Snippet::source("こん\r\nにちは\r\n世界") - .origin("<current file>") + .path("<current file>") .annotation(AnnotationKind::Primary.span(7..8)), ), // \n ); @@ -574,7 +574,7 @@ fn multiline_eol_start() { let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) - .origin("file/path") + .path("file/path") .line_start(3) .annotation(AnnotationKind::Primary.span(1..4)), ), // \r\nb @@ -598,7 +598,7 @@ fn multiline_eol_start2() { let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) - .origin("file/path") + .path("file/path") .line_start(3) .annotation(AnnotationKind::Primary.span(2..4)), ), // \nb @@ -622,7 +622,7 @@ fn multiline_eol_start3() { let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) - .origin("file/path") + .path("file/path") .line_start(3) .annotation(AnnotationKind::Primary.span(1..3)), ), // \nb @@ -645,7 +645,7 @@ fn multiline_eol_start_double_width() { let snippets = Level::ERROR.header("").group( Group::new().element( Snippet::source("こん\r\nにちは\r\n世界") - .origin("<current file>") + .path("<current file>") .annotation(AnnotationKind::Primary.span(7..11)), ), // \r\nに ); @@ -671,7 +671,7 @@ fn multiline_eol_start_eol_end() { let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) - .origin("file/path") + .path("file/path") .line_start(3) .annotation(AnnotationKind::Primary.span(1..4)), ), // \nb\n @@ -696,7 +696,7 @@ fn multiline_eol_start_eol_end2() { let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) - .origin("file/path") + .path("file/path") .line_start(3) .annotation(AnnotationKind::Primary.span(2..5)), ), // \nb\r @@ -721,7 +721,7 @@ fn multiline_eol_start_eol_end3() { let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) - .origin("file/path") + .path("file/path") .line_start(3) .annotation(AnnotationKind::Primary.span(2..6)), ), // \nb\r\n @@ -746,7 +746,7 @@ fn multiline_eol_start_eof_end() { let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) - .origin("file/path") + .path("file/path") .line_start(3) .annotation(AnnotationKind::Primary.span(1..5)), ), // \r\nb(EOF) @@ -770,7 +770,7 @@ fn multiline_eol_start_eof_end_double_width() { let input = Level::ERROR.header("").group( Group::new().element( Snippet::source(source) - .origin("file/path") + .path("file/path") .line_start(3) .annotation(AnnotationKind::Primary.span(3..9)), ), // \r\nに(EOF) @@ -794,7 +794,7 @@ fn two_single_line_same_line() { let input = Level::ERROR.header("unused optional dependency").group( Group::new().element( Snippet::source(source) - .origin("Cargo.toml") + .path("Cargo.toml") .line_start(4) .annotation( AnnotationKind::Primary @@ -969,7 +969,7 @@ fn origin_correct_start_line() { let input = Level::ERROR.header("title").group( Group::new().element( Snippet::source(source) - .origin("origin.txt") + .path("origin.txt") .fold(false) .annotation(AnnotationKind::Primary.span(8..8 + 3).label("annotation")), ), @@ -995,7 +995,7 @@ fn origin_correct_mid_line() { let input = Level::ERROR.header("title").group( Group::new().element( Snippet::source(source) - .origin("origin.txt") + .path("origin.txt") .fold(false) .annotation( AnnotationKind::Primary @@ -1565,7 +1565,7 @@ fn main() {}"#; .id("E0277") .group(Group::new().element(Snippet::source(source) .line_start(1) - .origin("$DIR/removal-of-multiline-trait-bound-in-where-clause.rs") + .path("$DIR/removal-of-multiline-trait-bound-in-where-clause.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -1583,7 +1583,7 @@ fn main() {}"#; ).element( Snippet::source(source) .line_start(1) - .origin("$DIR/removal-of-multiline-trait-bound-in-where-clause.rs") + .path("$DIR/removal-of-multiline-trait-bound-in-where-clause.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -1598,7 +1598,7 @@ fn main() {}"#; .element( Snippet::source(source) .line_start(1) - .origin("$DIR/removal-of-multiline-trait-bound-in-where-clause.rs") + .path("$DIR/removal-of-multiline-trait-bound-in-where-clause.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -1744,7 +1744,7 @@ fn main() { .id("E0271") .group(Group::new().element(Snippet::source(source) .line_start(4) - .origin("$DIR/E0271.rs") + .path("$DIR/E0271.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -1756,7 +1756,7 @@ fn main() { ).element( Snippet::source(source) .line_start(4) - .origin("$DIR/E0271.rs") + .path("$DIR/E0271.rs") .fold(true) .annotation(AnnotationKind::Primary.span(89..90)) ).element( @@ -1832,7 +1832,7 @@ fn main() { .id("E0271") .group(Group::new().element(Snippet::source(source) .line_start(4) - .origin("$DIR/E0271.rs") + .path("$DIR/E0271.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -1844,7 +1844,7 @@ fn main() { ).element( Snippet::source(source) .line_start(4) - .origin("$DIR/E0271.rs") + .path("$DIR/E0271.rs") .fold(true) .annotation(AnnotationKind::Primary.span(89..90)) ).element( @@ -1986,7 +1986,7 @@ fn main() { .group(Group::new().element( Snippet::source(source) .line_start(7) - .origin("$DIR/long-E0308.rs") + .path("$DIR/long-E0308.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -2071,7 +2071,7 @@ fn main() { .group(Group::new().element( Snippet::source(source) .line_start(7) - .origin("$DIR/unicode-output.rs") + .path("$DIR/unicode-output.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -2093,7 +2093,7 @@ fn main() { ).element( Snippet::source(source) .line_start(7) - .origin("$DIR/unicode-output.rs") + .path("$DIR/unicode-output.rs") .fold(true) .annotation(AnnotationKind::Primary.span(77..210)) .annotation(AnnotationKind::Context.span(71..76)), @@ -2299,7 +2299,7 @@ fn main() { let input = Level::ERROR.header("mismatched types").id("E0308").group( Group::new().element( Snippet::source(source) - .origin("$DIR/non-whitespace-trimming-unicode.rs") + .path("$DIR/non-whitespace-trimming-unicode.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -2359,7 +2359,7 @@ fn main() { Group::new() .element( Snippet::source(source) - .origin("$DIR/non-1-width-unicode-multiline-label.rs") + .path("$DIR/non-1-width-unicode-multiline-label.rs") .fold(true) .annotation(AnnotationKind::Context.span(970..984).label("&str")) .annotation(AnnotationKind::Context.span(987..1001).label("&str")) @@ -2379,7 +2379,7 @@ fn main() { .element(Level::HELP.title("create an owned `String` from a string reference")) .element( Snippet::source(source) - .origin("$DIR/non-1-width-unicode-multiline-label.rs") + .path("$DIR/non-1-width-unicode-multiline-label.rs") .fold(true) .patch(Patch::new(984..984, ".to_owned()")), ), @@ -2442,7 +2442,7 @@ fn foo() { .group( Group::new().element( Snippet::source(source) - .origin("$DIR/not-utf8.rs") + .path("$DIR/not-utf8.rs") .fold(true) .annotation(AnnotationKind::Primary.span(136..160)), ), @@ -2452,7 +2452,7 @@ fn foo() { .element(Level::NOTE.title("byte `193` is not valid utf-8")) .element( Snippet::source(bin_source) - .origin("$DIR/not-utf8.bin") + .path("$DIR/not-utf8.bin") .fold(true) .annotation(AnnotationKind::Primary.span(0..0)), ) diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index 23c178bf..c5e0602f 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -17,7 +17,7 @@ fn foo() { Group::new().element( Snippet::source(source) .line_start(1) - .origin("test.rs") + .path("test.rs") .fold(true) .annotation(AnnotationKind::Primary.span(10..13).label("test")), ), @@ -47,7 +47,7 @@ fn foo() { Group::new().element( Snippet::source(source) .line_start(1) - .origin("test.rs") + .path("test.rs") .fold(true) .annotation(AnnotationKind::Primary.span(10..17).label("test")), ), @@ -79,7 +79,7 @@ fn foo() { Group::new().element( Snippet::source(source) .line_start(1) - .origin("test.rs") + .path("test.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -122,7 +122,7 @@ fn foo() { Group::new().element( Snippet::source(source) .line_start(1) - .origin("test.rs") + .path("test.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -166,7 +166,7 @@ fn foo() { Group::new().element( Snippet::source(source) .line_start(1) - .origin("test.rs") + .path("test.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -210,7 +210,7 @@ fn foo() { Group::new().element( Snippet::source(source) .line_start(1) - .origin("test.rs") + .path("test.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -257,7 +257,7 @@ fn foo() { Group::new().element( Snippet::source(source) .line_start(1) - .origin("test.rs") + .path("test.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -305,7 +305,7 @@ fn foo() { Group::new().element( Snippet::source(source) .line_start(1) - .origin("test.rs") + .path("test.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -355,7 +355,7 @@ fn foo() { Group::new().element( Snippet::source(source) .line_start(1) - .origin("test.rs") + .path("test.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -399,7 +399,7 @@ fn foo() { Group::new().element( Snippet::source(source) .line_start(1) - .origin("test.rs") + .path("test.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -442,7 +442,7 @@ fn foo() { Group::new().element( Snippet::source(source) .line_start(1) - .origin("test.rs") + .path("test.rs") .fold(true) .annotation(AnnotationKind::Primary.span(18..25).label("")) .annotation( @@ -475,7 +475,7 @@ fn foo() { Group::new().element( Snippet::source(source) .line_start(1) - .origin("test.rs") + .path("test.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -507,7 +507,7 @@ fn foo() { Group::new().element( Snippet::source(source) .line_start(1) - .origin("test.rs") + .path("test.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -542,7 +542,7 @@ fn foo() { Group::new().element( Snippet::source(source) .line_start(1) - .origin("test.rs") + .path("test.rs") .fold(true) .annotation(AnnotationKind::Primary.span(14..27).label("")) .annotation( @@ -576,7 +576,7 @@ fn foo() { Group::new().element( Snippet::source(source) .line_start(1) - .origin("test.rs") + .path("test.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -610,7 +610,7 @@ fn foo() { Group::new().element( Snippet::source(source) .line_start(1) - .origin("test.rs") + .path("test.rs") .fold(true) .annotation(AnnotationKind::Primary.span(14..27).label("")) .annotation(AnnotationKind::Context.span(18..25).label("")), @@ -638,7 +638,7 @@ fn foo() { Group::new().element( Snippet::source(source) .line_start(1) - .origin("test.rs") + .path("test.rs") .fold(true) .annotation(AnnotationKind::Primary.span(18..25).label("")) .annotation(AnnotationKind::Context.span(14..27).label("")) @@ -667,7 +667,7 @@ fn foo() { Group::new().element( Snippet::source(source) .line_start(1) - .origin("test.rs") + .path("test.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -706,7 +706,7 @@ fn foo() { Group::new().element( Snippet::source(source) .line_start(1) - .origin("test.rs") + .path("test.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -737,7 +737,7 @@ fn foo() { Group::new().element( Snippet::source(source) .line_start(1) - .origin("test.rs") + .path("test.rs") .fold(true) .annotation(AnnotationKind::Primary.span(14..27).label("")), ), @@ -777,7 +777,7 @@ fn foo() { Group::new().element( Snippet::source(source) .line_start(1) - .origin("test.rs") + .path("test.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -837,7 +837,7 @@ fn foo() { Group::new().element( Snippet::source(source) .line_start(1) - .origin("test.rs") + .path("test.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -893,7 +893,7 @@ fn f(){||yield(((){), Group::new().element( Snippet::source(source) .line_start(1) - .origin("$DIR/issue-91334.rs") + .path("$DIR/issue-91334.rs") .fold(true) .annotation( AnnotationKind::Context @@ -965,7 +965,7 @@ fn main() { Group::new().element( Snippet::source(source) .line_start(1) - .origin("$DIR/issue-114529-illegal-break-with-value.rs") + .path("$DIR/issue-114529-illegal-break-with-value.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -988,7 +988,7 @@ fn main() { .element( Snippet::source(source) .line_start(1) - .origin("$DIR/issue-114529-illegal-break-with-value.rs") + .path("$DIR/issue-114529-illegal-break-with-value.rs") .fold(true) .annotation(AnnotationKind::Context.span(483..581).label("break")), ), @@ -1175,7 +1175,7 @@ fn nsize() { Group::new().element( Snippet::source(source) .line_start(1) - .origin("$DIR/primitive_reprs_should_have_correct_length.rs") + .path("$DIR/primitive_reprs_should_have_correct_length.rs") .fold(true) .annotation(AnnotationKind::Primary.span(4375..4381).label( "the size of `V0usize` is smaller than the size of `[usize; 2]`", @@ -1188,7 +1188,7 @@ fn nsize() { .element( Snippet::source(source) .line_start(1) - .origin("$DIR/primitive_reprs_should_have_correct_length.rs") + .path("$DIR/primitive_reprs_should_have_correct_length.rs") .fold(true) .annotation( AnnotationKind::Context @@ -1262,7 +1262,7 @@ fn main() { Snippet::source(source) .line_start(1) .fold(true) - .origin("$DIR/align-fail.rs") + .path("$DIR/align-fail.rs") .annotation( AnnotationKind::Primary .span(442..459) @@ -1330,7 +1330,7 @@ fn main() {} Group::new().element( Snippet::source(source) .line_start(1) - .origin("$DIR/missing-semicolon.rs") + .path("$DIR/missing-semicolon.rs") .fold(true) .annotation( AnnotationKind::Context @@ -1421,7 +1421,7 @@ outer_macro!(FirstStruct, FirstAttrStruct); .element( Snippet::source(aux_source) .line_start(1) - .origin("$DIR/auxiliary/nested-macro-rules.rs") + .path("$DIR/auxiliary/nested-macro-rules.rs") .fold(true) .annotation( AnnotationKind::Context @@ -1433,7 +1433,7 @@ outer_macro!(FirstStruct, FirstAttrStruct); .element( Snippet::source(source) .line_start(1) - .origin("$DIR/nested-macro-rules.rs") + .path("$DIR/nested-macro-rules.rs") .fold(true) .annotation( AnnotationKind::Context @@ -1456,7 +1456,7 @@ outer_macro!(FirstStruct, FirstAttrStruct); .element( Snippet::source(source) .line_start(1) - .origin("$DIR/nested-macro-rules.rs") + .path("$DIR/nested-macro-rules.rs") .fold(true) .annotation(AnnotationKind::Primary.span(224..245)), ), @@ -1554,7 +1554,7 @@ macro_rules! inline { Group::new().element( Snippet::source(source) .line_start(1) - .origin("$DIR/method-on-ambiguous-numeric-type.rs") + .path("$DIR/method-on-ambiguous-numeric-type.rs") .fold(true) .annotation(AnnotationKind::Primary.span(916..919)), ), @@ -1565,7 +1565,7 @@ macro_rules! inline { .element( Snippet::source(aux_source) .line_start(1) - .origin("$DIR/auxiliary/macro-in-other-crate.rs") + .path("$DIR/auxiliary/macro-in-other-crate.rs") .fold(true) .annotation(AnnotationKind::Context.span(69..69).label(": i32")), ), @@ -1618,7 +1618,7 @@ fn main() {} Group::new().element( Snippet::source(source) .line_start(1) - .origin("$DIR/issue-42234-unknown-receiver-type.rs") + .path("$DIR/issue-42234-unknown-receiver-type.rs") .fold(true) .annotation(AnnotationKind::Primary.span(536..539).label( "cannot infer type of the type parameter `S` declared on the method `sum`", @@ -1726,7 +1726,7 @@ fn main() {} Group::new().element( Snippet::source(source) .line_start(1) - .origin("$DIR/empty-match.rs") + .path("$DIR/empty-match.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -1741,7 +1741,7 @@ fn main() {} .element( Snippet::source(source) .line_start(1) - .origin("$DIR/empty-match.rs") + .path("$DIR/empty-match.rs") .fold(true) .annotation(AnnotationKind::Primary.span(818..831)) .annotation(AnnotationKind::Context.span(842..844).label("not covered")) @@ -1762,7 +1762,7 @@ fn main() {} .element( Snippet::source(source) .line_start(1) - .origin("$DIR/empty-match.rs") + .path("$DIR/empty-match.rs") .fold(true) .annotation(AnnotationKind::Context.span(485..485).label(",\n _ => todo!()")) ) @@ -1826,7 +1826,7 @@ fn main() { Group::new().element( Snippet::source(source) .line_start(1) - .origin("$DIR/object-fail.rs") + .path("$DIR/object-fail.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -1851,7 +1851,7 @@ fn main() { .element( Snippet::source(source) .line_start(1) - .origin("$DIR/object-fail.rs") + .path("$DIR/object-fail.rs") .fold(true) .annotation( AnnotationKind::Context @@ -1894,7 +1894,7 @@ fn main() {} let input = Level::ERROR.header("mismatched types").id("E0038").group( Group::new().element( Snippet::source(source) - .origin("$DIR/long-span.rs") + .path("$DIR/long-span.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -1928,7 +1928,7 @@ fn main() {} let input = Level::ERROR.header("mismatched types").id("E0038").group( Group::new().element( Snippet::source(source) - .origin("$DIR/long-span.rs") + .path("$DIR/long-span.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -1963,7 +1963,7 @@ fn main() {} let input = Level::ERROR.header("mismatched types").id("E0038").group( Group::new().element( Snippet::source(source) - .origin("$DIR/long-span.rs") + .path("$DIR/long-span.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -1998,7 +1998,7 @@ fn main() {} let input = Level::ERROR.header("mismatched types").id("E0038").group( Group::new().element( Snippet::source(source) - .origin("$DIR/long-span.rs") + .path("$DIR/long-span.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -2052,7 +2052,7 @@ fn main() { Group::new() .element( Snippet::source(source) - .origin("$DIR/lint_map_unit_fn.rs") + .path("$DIR/lint_map_unit_fn.rs") .fold(true) .annotation(AnnotationKind::Context.span(271..278).label( "this function returns `()`, which is likely not what you wanted", @@ -2077,7 +2077,7 @@ fn main() { .element(Level::HELP.title("you might have meant to use `Iterator::for_each`")) .element( Snippet::source(source) - .origin("$DIR/lint_map_unit_fn.rs") + .path("$DIR/lint_map_unit_fn.rs") .fold(true) .patch(Patch::new(267..270, r#"for_each"#)), ), @@ -2146,7 +2146,7 @@ fn main() { .group( Group::new().element( Snippet::source(source) - .origin("$DIR/bad-char-literals.rs") + .path("$DIR/bad-char-literals.rs") .fold(true) .annotation(AnnotationKind::Primary.span(204..205)), ), @@ -2156,7 +2156,7 @@ fn main() { .element(Level::HELP.title("escape the character")) .element( Snippet::source(source) - .origin("$DIR/bad-char-literals.rs") + .path("$DIR/bad-char-literals.rs") .line_start(1) .fold(true) .patch(Patch::new(204..205, r#"\n"#)), @@ -2201,7 +2201,7 @@ fn main() {} .group( Group::new().element( Snippet::source(source) - .origin("$DIR/unclosed-1.rs") + .path("$DIR/unclosed-1.rs") .fold(true) .annotation(AnnotationKind::Primary.span(0..221)), ), @@ -2211,7 +2211,7 @@ fn main() {} .element(Level::NOTE.title("frontmatter opening here was not closed")) .element( Snippet::source(source) - .origin("$DIR/unclosed-1.rs") + .path("$DIR/unclosed-1.rs") .fold(true) .annotation(AnnotationKind::Primary.span(0..4)), ), @@ -2261,7 +2261,7 @@ fn foo() -> &str { .group( Group::new().element( Snippet::source(source) - .origin("$DIR/unclosed-2.rs") + .path("$DIR/unclosed-2.rs") .fold(true) .annotation(AnnotationKind::Primary.span(0..377)), ), @@ -2271,7 +2271,7 @@ fn foo() -> &str { .element(Level::NOTE.title("frontmatter opening here was not closed")) .element( Snippet::source(source) - .origin("$DIR/unclosed-2.rs") + .path("$DIR/unclosed-2.rs") .fold(true) .annotation(AnnotationKind::Primary.span(0..4)), ), @@ -2323,7 +2323,7 @@ fn foo(x: i32) -> i32 { .group( Group::new().element( Snippet::source(source) - .origin("$DIR/unclosed-3.rs") + .path("$DIR/unclosed-3.rs") .fold(true) .annotation(AnnotationKind::Primary.span(302..310)), ), @@ -2335,7 +2335,7 @@ fn foo(x: i32) -> i32 { ) .element( Snippet::source(source) - .origin("$DIR/unclosed-3.rs") + .path("$DIR/unclosed-3.rs") .fold(true) .annotation(AnnotationKind::Primary.span(302..306)), ), @@ -2377,7 +2377,7 @@ fn main() {} .group( Group::new().element( Snippet::source(source) - .origin("$DIR/unclosed-4.rs") + .path("$DIR/unclosed-4.rs") .fold(true) .annotation(AnnotationKind::Primary.span(0..43)), ), @@ -2387,7 +2387,7 @@ fn main() {} .element(Level::NOTE.title("frontmatter opening here was not closed")) .element( Snippet::source(source) - .origin("$DIR/unclosed-4.rs") + .path("$DIR/unclosed-4.rs") .fold(true) .annotation(AnnotationKind::Primary.span(0..4)), ), @@ -2432,7 +2432,7 @@ fn main() {} .group( Group::new().element( Snippet::source(source) - .origin("$DIR/unclosed-5.rs") + .path("$DIR/unclosed-5.rs") .fold(true) .annotation(AnnotationKind::Primary.span(0..176)), ), @@ -2442,7 +2442,7 @@ fn main() {} .element(Level::NOTE.title("frontmatter opening here was not closed")) .element( Snippet::source(source) - .origin("$DIR/unclosed-5.rs") + .path("$DIR/unclosed-5.rs") .fold(true) .annotation(AnnotationKind::Primary.span(0..4)), ), @@ -2558,13 +2558,13 @@ pub enum E2 { Group::new() .element( Snippet::source(source) - .origin("$DIR/pat-tuple-field-count-cross.rs") + .path("$DIR/pat-tuple-field-count-cross.rs") .fold(true) .annotation(AnnotationKind::Primary.span(1760..1766)), ) .element( Snippet::source(source1) - .origin("$DIR/auxiliary/declarations-for-tuple-field-count-errors.rs") + .path("$DIR/auxiliary/declarations-for-tuple-field-count-errors.rs") .fold(true) .annotation( AnnotationKind::Context @@ -2583,7 +2583,7 @@ pub enum E2 { .element(Level::HELP.title("use the tuple variant pattern syntax instead")) .element( Snippet::source(source) - .origin("$DIR/pat-tuple-field-count-cross.rs") + .path("$DIR/pat-tuple-field-count-cross.rs") .fold(true) .patch(Patch::new(1760..1766, r#"E1::Z1()"#)), ), @@ -2593,7 +2593,7 @@ pub enum E2 { .element(Level::HELP.title("a unit variant with a similar name exists")) .element( Snippet::source(source) - .origin("$DIR/pat-tuple-field-count-cross.rs") + .path("$DIR/pat-tuple-field-count-cross.rs") .fold(true) .patch(Patch::new(1764..1766, r#"Z0"#)), ), @@ -2639,7 +2639,7 @@ fn unterminated_nested_comment() { let input = Level::ERROR.header("unterminated block comment").id("E0758").group( Group::new().element( Snippet::source(source) - .origin("$DIR/unterminated-nested-comment.rs") + .path("$DIR/unterminated-nested-comment.rs") .fold(true) .annotation( AnnotationKind::Context @@ -2698,7 +2698,7 @@ fn mismatched_types1() { Snippet::source(file_txt_source) .fold(true) .line_start(3) - .origin("$DIR/file.txt") + .path("$DIR/file.txt") .annotation( AnnotationKind::Primary .span(0..0) @@ -2707,7 +2707,7 @@ fn mismatched_types1() { ) .element( Snippet::source(rust_source) - .origin("$DIR/mismatched-types.rs") + .path("$DIR/mismatched-types.rs") .fold(true) .annotation( AnnotationKind::Context @@ -2759,7 +2759,7 @@ fn mismatched_types2() { Group::new() .element( Snippet::source(source) - .origin("$DIR/mismatched-types.rs") + .path("$DIR/mismatched-types.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -2815,7 +2815,7 @@ fn main() { .group( Group::new().element( Snippet::source(source) - .origin("$DIR/short-error-format.rs") + .path("$DIR/short-error-format.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -2834,7 +2834,7 @@ fn main() { .element(Level::NOTE.title("function defined here")) .element( Snippet::source(source) - .origin("$DIR/short-error-format.rs") + .path("$DIR/short-error-format.rs") .fold(true) .annotation(AnnotationKind::Context.span(48..54).label("")) .annotation(AnnotationKind::Primary.span(44..47)), @@ -2871,7 +2871,7 @@ fn main() { .group( Group::new().element( Snippet::source(source) - .origin("$DIR/short-error-format.rs") + .path("$DIR/short-error-format.rs") .fold(true) .annotation( AnnotationKind::Primary @@ -2909,7 +2909,7 @@ pub struct Foo; //~^ ERROR Group::new() .element( Snippet::source(source_0) - .origin("$DIR/diagnostic-width.rs") + .path("$DIR/diagnostic-width.rs") .fold(true) .annotation(AnnotationKind::Primary.span(111..126)), ) @@ -2923,7 +2923,7 @@ pub struct Foo; //~^ ERROR .element(Level::NOTE.title("the lint level is defined here")) .element( Snippet::source(source_0) - .origin("$DIR/diagnostic-width.rs") + .path("$DIR/diagnostic-width.rs") .fold(true) .annotation(AnnotationKind::Primary.span(49..67)), ), @@ -2933,7 +2933,7 @@ pub struct Foo; //~^ ERROR .element(Level::HELP.title("use an automatic link instead")) .element( Snippet::source(source_1) - .origin("$DIR/diagnostic-width.rs") + .path("$DIR/diagnostic-width.rs") .line_start(4) .fold(true) .patch(Patch::new(40..40, "<")) @@ -2985,7 +2985,7 @@ fn main() { Group::new() .element( Snippet::source(source1) - .origin("lint_example.rs") + .path("lint_example.rs") .fold(true) .annotation(AnnotationKind::Primary.span(40..49)), ) @@ -3000,7 +3000,7 @@ fn main() { ) .element( Snippet::source(source2) - .origin("lint_example.rs") + .path("lint_example.rs") .line_start(3) .fold(true) .patch(Patch::new(10..19, "iter")), @@ -3011,7 +3011,7 @@ fn main() { .element(Level::HELP.title(long_title3)) .element( Snippet::source(source2) - .origin("lint_example.rs") + .path("lint_example.rs") .line_start(3) .fold(true) .patch(Patch::new(0..0, "IntoIterator::into_iter(")) From 8df4d37c138d0fcc2e9127876d89bfbd1ba7a4cc Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 26 Jun 2025 14:34:06 -0600 Subject: [PATCH 387/455] chore: Improve docs arount Origin and Snippet --- src/snippet.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/snippet.rs b/src/snippet.rs index abdab118..f3c517bb 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -162,6 +162,8 @@ pub struct Title<'a> { } /// A source view [`Element`] in a [`Group`] +/// +/// If you do not have [source][Snippet::source] available, see instead [`Origin`] #[derive(Clone, Debug)] pub struct Snippet<'a, T> { pub(crate) path: Option<&'a str>, @@ -375,7 +377,9 @@ impl<'a> Patch<'a> { } } -/// The location of the [`Snippet`] (e.g. a path) +/// The referenced location (e.g. a path) +/// +/// If you have source available, see instead [`Snippet`] #[derive(Clone, Debug)] pub struct Origin<'a> { pub(crate) path: &'a str, @@ -412,6 +416,12 @@ impl<'a> Origin<'a> { /// Set the default column to display /// /// Otherwise this will be inferred from the primary [`Annotation`] + /// + /// <div class="warning"> + /// + /// `char_column` is only be respected if [`Origin::line`] is also set. + /// + /// </div> pub fn char_column(mut self, char_column: usize) -> Self { self.char_column = Some(char_column); self From 3f45b4d5fd89205054e112b97b4fa7940f1ca6d5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 26 Jun 2025 22:00:45 +0000 Subject: [PATCH 388/455] chore(deps): Update Rust Stable to v1.88 (#228) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d88caceb..b1f01275 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -104,7 +104,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.87" # STABLE + toolchain: "1.88" # STABLE - uses: Swatinem/rust-cache@v2 - name: Check documentation env: @@ -119,7 +119,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.87" # STABLE + toolchain: "1.88" # STABLE components: rustfmt - uses: Swatinem/rust-cache@v2 - name: Check formatting @@ -135,7 +135,7 @@ jobs: - name: Install Rust uses: dtolnay/rust-toolchain@stable with: - toolchain: "1.87" # STABLE + toolchain: "1.88" # STABLE components: clippy - uses: Swatinem/rust-cache@v2 - name: Install SARIF tools From a2595e2b381271ebfbb75f48787d150e9410e50b Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 12 Jun 2025 10:21:23 -0600 Subject: [PATCH 389/455] test: Secondary title alignment --- tests/formatter.rs | 93 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/tests/formatter.rs b/tests/formatter.rs index 42886762..edae0a62 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -2494,3 +2494,96 @@ LL │ �|�␂!5�cc␕␂�Ӻi��WWj�ȥ�'�}�␒�J�ȉ��W let renderer_unicode = renderer_ascii.theme(OutputTheme::Unicode); assert_data_eq!(renderer_unicode.render(input), expected_unicode); } + +#[test] +fn secondary_title_no_level_text() { + let source = r#"fn main() { + let b: &[u8] = include_str!("file.txt"); //~ ERROR mismatched types + let s: &str = include_bytes!("file.txt"); //~ ERROR mismatched types +}"#; + + let input = Level::ERROR.header("mismatched types").id("E0308").group( + Group::new() + .element( + Snippet::source(source) + .path("$DIR/mismatched-types.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(105..131) + .label("expected `&str`, found `&[u8; 0]`"), + ) + .annotation( + AnnotationKind::Context + .span(98..102) + .label("expected due to this"), + ), + ) + .element( + Level::NOTE + .text(None) + .title("expected reference `&str`\nfound reference `&'static [u8; 0]`"), + ), + ); + + let expected = str![[r#" +error[E0308]: mismatched types + --> $DIR/mismatched-types.rs:3:19 + | +LL | let s: &str = include_bytes!("file.txt"); //~ ERROR mismatched types + | ---- ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `&str`, found `&[u8; 0]` + | | + | expected due to this + expected reference `&str` + found reference `&'static [u8; 0]` +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected); +} + +#[test] +fn secondary_title_custom_level_text() { + let source = r#"fn main() { + let b: &[u8] = include_str!("file.txt"); //~ ERROR mismatched types + let s: &str = include_bytes!("file.txt"); //~ ERROR mismatched types +}"#; + + let input = Level::ERROR.header("mismatched types").id("E0308").group( + Group::new() + .element( + Snippet::source(source) + .path("$DIR/mismatched-types.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(105..131) + .label("expected `&str`, found `&[u8; 0]`"), + ) + .annotation( + AnnotationKind::Context + .span(98..102) + .label("expected due to this"), + ), + ) + .element( + Level::NOTE + .text(Some("custom")) + .title("expected reference `&str`\nfound reference `&'static [u8; 0]`"), + ), + ); + + let expected = str![[r#" +error[E0308]: mismatched types + --> $DIR/mismatched-types.rs:3:19 + | +LL | let s: &str = include_bytes!("file.txt"); //~ ERROR mismatched types + | ---- ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `&str`, found `&[u8; 0]` + | | + | expected due to this + | + = custom: expected reference `&str` + found reference `&'static [u8; 0]` +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected); +} From 2af175c609a42d914aaa084ed279f32883547d79 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 12 Jun 2025 10:27:56 -0600 Subject: [PATCH 390/455] fix: Add '=' before all secondary titles --- src/renderer/mod.rs | 13 +++++++------ tests/formatter.rs | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 93996075..732e5a86 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -555,13 +555,14 @@ impl Renderer { buffer.prepend(buffer_msg_line_offset, " ", ElementStyle::NoStyle); } + self.draw_note_separator( + buffer, + buffer_msg_line_offset, + max_line_num_len + 1, + is_cont, + ); + if title.level.name != Some(None) { - self.draw_note_separator( - buffer, - buffer_msg_line_offset, - max_line_num_len + 1, - is_cont, - ); buffer.append( buffer_msg_line_offset, title.level.as_str(), diff --git a/tests/formatter.rs b/tests/formatter.rs index edae0a62..f763f663 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -2534,7 +2534,7 @@ LL | let s: &str = include_bytes!("file.txt"); //~ ERROR mismatched types | ---- ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `&str`, found `&[u8; 0]` | | | expected due to this - expected reference `&str` + = expected reference `&str` found reference `&'static [u8; 0]` "#]]; let renderer = Renderer::plain().anonymized_line_numbers(true); From e7c3b6bc0b09170a50307190f4c3049c28dc5179 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 12 Jun 2025 10:27:56 -0600 Subject: [PATCH 391/455] fix: Align multi-line secondary titles by level text --- src/renderer/mod.rs | 47 ++++++++++++++++++++++++--------------------- tests/formatter.rs | 4 ++-- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 732e5a86..39c805b7 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -562,17 +562,37 @@ impl Renderer { is_cont, ); - if title.level.name != Some(None) { + let label_width = if title.level.name != Some(None) { buffer.append( buffer_msg_line_offset, title.level.as_str(), ElementStyle::MainHeaderMsg, ); buffer.append(buffer_msg_line_offset, ": ", ElementStyle::NoStyle); - } + title.level.as_str().len() + 2 + } else { + 0 + }; + // The extra 3 ` ` is padding that's always needed to align to the + // label i.e. `note: `: + // + // error: message + // --> file.rs:13:20 + // | + // 13 | <CODE> + // | ^^^^ + // | + // = note: multiline + // message + // ++^^^------ + // | | | + // | | | + // | | width of label + // | magic `3` + // `max_line_num_len` + let padding = max_line_num_len + 3 + label_width; - let printed_lines = - self.msgs_to_buffer(buffer, title.title, max_line_num_len, "note", None); + let printed_lines = self.msgs_to_buffer(buffer, title.title, padding, None); if is_cont && matches!(self.theme, OutputTheme::Unicode) { // There's another note after this one, associated to the subwindow above. // We write additional vertical lines to join them: @@ -660,26 +680,9 @@ impl Renderer { buffer: &mut StyledBuffer, title: &str, padding: usize, - label: &str, override_style: Option<ElementStyle>, ) -> usize { - // The extra 5 ` ` is padding that's always needed to align to the `note: `: - // - // error: message - // --> file.rs:13:20 - // | - // 13 | <CODE> - // | ^^^^ - // | - // = note: multiline - // message - // ++^^^----xx - // | | | | - // | | | magic `2` - // | | length of label - // | magic `3` - // `max_line_num_len` - let padding = " ".repeat(padding + label.len() + 5); + let padding = " ".repeat(padding); let mut line_number = buffer.num_lines().saturating_sub(1); diff --git a/tests/formatter.rs b/tests/formatter.rs index f763f663..fb671dfd 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -2535,7 +2535,7 @@ LL | let s: &str = include_bytes!("file.txt"); //~ ERROR mismatched types | | | expected due to this = expected reference `&str` - found reference `&'static [u8; 0]` + found reference `&'static [u8; 0]` "#]]; let renderer = Renderer::plain().anonymized_line_numbers(true); assert_data_eq!(renderer.render(input), expected); @@ -2582,7 +2582,7 @@ LL | let s: &str = include_bytes!("file.txt"); //~ ERROR mismatched types | expected due to this | = custom: expected reference `&str` - found reference `&'static [u8; 0]` + found reference `&'static [u8; 0]` "#]]; let renderer = Renderer::plain().anonymized_line_numbers(true); assert_data_eq!(renderer.render(input), expected); From 359a6ed10e5f2f065f8cd51a5f5f4164e6291d0b Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 10 Jun 2025 04:20:52 -0600 Subject: [PATCH 392/455] test: Add hyperlink test --- examples/id_hyperlink.rs | 32 +++++++++++++++++++++++++++++++ examples/id_hyperlink.svg | 40 +++++++++++++++++++++++++++++++++++++++ tests/examples.rs | 7 +++++++ 3 files changed, 79 insertions(+) create mode 100644 examples/id_hyperlink.rs create mode 100644 examples/id_hyperlink.svg diff --git a/examples/id_hyperlink.rs b/examples/id_hyperlink.rs new file mode 100644 index 00000000..7c3ace1e --- /dev/null +++ b/examples/id_hyperlink.rs @@ -0,0 +1,32 @@ +use annotate_snippets::renderer::OutputTheme; +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; + +fn main() { + let source = r#"//@ compile-flags: -Zterminal-urls=yes +fn main() { + let () = 4; //~ ERROR +} +"#; + + let message = Level::ERROR.header("mismatched types").id("E0308").group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .path("$DIR/terminal_urls.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(59..61) + .label("expected integer, found `()`"), + ) + .annotation( + AnnotationKind::Context + .span(64..65) + .label("this expression has type `{integer}`"), + ), + ), + ); + + let renderer = Renderer::styled().theme(OutputTheme::Unicode); + anstream::println!("{}", renderer.render(message)); +} diff --git a/examples/id_hyperlink.svg b/examples/id_hyperlink.svg new file mode 100644 index 00000000..64dbfe18 --- /dev/null +++ b/examples/id_hyperlink.svg @@ -0,0 +1,40 @@ +<svg width="740px" height="164px" xmlns="http://www.w3.org/2000/svg"> + <style> + .fg { fill: #AAAAAA } + .bg { background: #000000 } + .fg-bright-blue { fill: #5555FF } + .fg-bright-red { fill: #FF5555 } + .container { + padding: 0 10px; + line-height: 18px; + } + .bold { font-weight: bold; } + tspan { + font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + white-space: pre; + line-height: 18px; + } + </style> + + <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> + + <text xml:space="preserve" class="container fg"> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan> +</tspan> + <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>$DIR/terminal_urls.rs:3:9</tspan> +</tspan> + <tspan x="10px" y="64px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan> +</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">3</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> let () = 4; //~ ERROR</tspan> +</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">┯━</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">─</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">this expression has type `{integer}`</tspan> +</tspan> + <tspan x="10px" y="118px"><tspan> </tspan><tspan class="fg-bright-blue bold">│</tspan><tspan> </tspan><tspan class="fg-bright-red bold">│</tspan> +</tspan> + <tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">╰╴</tspan><tspan> </tspan><tspan class="fg-bright-red bold">expected integer, found `()`</tspan> +</tspan> + <tspan x="10px" y="154px"> +</tspan> + </text> + +</svg> diff --git a/tests/examples.rs b/tests/examples.rs index 02e961c8..ec2643e9 100644 --- a/tests/examples.rs +++ b/tests/examples.rs @@ -49,6 +49,13 @@ fn highlight_title() { assert_example(target, expected); } +#[test] +fn id_hyperlink() { + let target = "id_hyperlink"; + let expected = snapbox::file!["../examples/id_hyperlink.svg": TermSvg]; + assert_example(target, expected); +} + #[test] fn multislice() { let target = "multislice"; From bff9dd5f88bcba80e8c4aa7ecc60a8f178e46137 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 5 Jun 2025 20:14:19 -0600 Subject: [PATCH 393/455] feat: Add support for ID hyperlinks --- Cargo.lock | 20 +++++++------------ examples/id_hyperlink.rs | 41 +++++++++++++++++++++------------------ examples/id_hyperlink.svg | 2 +- src/renderer/mod.rs | 19 ++++++++++++++++-- src/snippet.rs | 20 +++++++++++++++++-- 5 files changed, 65 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 532bb57a..8cfa6638 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,7 +12,7 @@ dependencies = [ "divan", "memchr", "snapbox", - "unicode-width 0.2.0", + "unicode-width", ] [[package]] @@ -47,9 +47,9 @@ dependencies = [ [[package]] name = "anstyle-parse" -version = "0.2.4" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] @@ -65,15 +65,15 @@ dependencies = [ [[package]] name = "anstyle-svg" -version = "0.1.4" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbf0bf947d663010f0b4132f28ca08da9151f3b9035fa7578a38de521c1d1aa" +checksum = "0a43964079ef399480603125d5afae2b219aceffb77478956e25f17b9bc3435c" dependencies = [ - "anstream", "anstyle", "anstyle-lossy", + "anstyle-parse", "html-escape", - "unicode-width 0.1.13", + "unicode-width", ] [[package]] @@ -418,12 +418,6 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" -[[package]] -name = "unicode-width" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" - [[package]] name = "unicode-width" version = "0.2.0" diff --git a/examples/id_hyperlink.rs b/examples/id_hyperlink.rs index 7c3ace1e..209fc15b 100644 --- a/examples/id_hyperlink.rs +++ b/examples/id_hyperlink.rs @@ -7,25 +7,28 @@ fn main() { let () = 4; //~ ERROR } "#; - - let message = Level::ERROR.header("mismatched types").id("E0308").group( - Group::new().element( - Snippet::source(source) - .line_start(1) - .path("$DIR/terminal_urls.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(59..61) - .label("expected integer, found `()`"), - ) - .annotation( - AnnotationKind::Context - .span(64..65) - .label("this expression has type `{integer}`"), - ), - ), - ); + let message = Level::ERROR + .header("mismatched types") + .id("E0308") + .id_url("https://doc.rust-lang.org/error_codes/E0308.html") + .group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .path("$DIR/terminal_urls.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(59..61) + .label("expected integer, found `()`"), + ) + .annotation( + AnnotationKind::Context + .span(64..65) + .label("this expression has type `{integer}`"), + ), + ), + ); let renderer = Renderer::styled().theme(OutputTheme::Unicode); anstream::println!("{}", renderer.render(message)); diff --git a/examples/id_hyperlink.svg b/examples/id_hyperlink.svg index 64dbfe18..5caa4114 100644 --- a/examples/id_hyperlink.svg +++ b/examples/id_hyperlink.svg @@ -19,7 +19,7 @@ <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> <text xml:space="preserve" class="container fg"> - <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[E0308]</tspan><tspan class="bold">: mismatched types</tspan> + <tspan x="10px" y="28px"><tspan class="fg-bright-red bold">error[</tspan><tspan class="fg-bright-red bold"><a href="https://doc.rust-lang.org/error_codes/E0308.html">E0308</a></tspan><tspan class="fg-bright-red bold">]</tspan><tspan class="bold">: mismatched types</tspan> </tspan> <tspan x="10px" y="46px"><tspan> </tspan><tspan class="fg-bright-blue bold"> ╭▸ </tspan><tspan>$DIR/terminal_urls.rs:3:9</tspan> </tspan> diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 39c805b7..a5459734 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -46,6 +46,7 @@ use crate::renderer::source_map::{ AnnotatedLineInfo, LineInfo, Loc, SourceMap, SubstitutionHighlight, }; use crate::renderer::styled_buffer::StyledBuffer; +use crate::snippet::Id; use crate::{Annotation, AnnotationKind, Element, Group, Message, Origin, Patch, Snippet, Title}; pub use anstyle::*; use margin::Margin; @@ -545,7 +546,7 @@ impl Renderer { title: &Title<'_>, max_line_num_len: usize, title_style: TitleStyle, - id: Option<&&str>, + id: Option<&Id<'_>>, is_cont: bool, buffer_msg_line_offset: usize, ) { @@ -620,17 +621,31 @@ impl Renderer { ); } label_width += title.level.as_str().len(); - if let Some(id) = id { + if let Some(Id { id: Some(id), url }) = id { buffer.append( buffer_msg_line_offset, "[", ElementStyle::Level(title.level.level), ); + if let Some(url) = url.as_ref() { + buffer.append( + buffer_msg_line_offset, + &format!("\x1B]8;;{url}\x1B\\"), + ElementStyle::Level(title.level.level), + ); + } buffer.append( buffer_msg_line_offset, id, ElementStyle::Level(title.level.level), ); + if url.is_some() { + buffer.append( + buffer_msg_line_offset, + "\x1B]8;;\x1B\\", + ElementStyle::Level(title.level.level), + ); + } buffer.append( buffer_msg_line_offset, "]", diff --git a/src/snippet.rs b/src/snippet.rs index f3c517bb..2d686a5e 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -13,7 +13,7 @@ pub(crate) const WARNING_TXT: &str = "warning"; /// Top-level user message #[derive(Clone, Debug)] pub struct Message<'a> { - pub(crate) id: Option<&'a str>, // for "correctness", could be sloppy and be on Title + pub(crate) id: Option<Id<'a>>, // for "correctness", could be sloppy and be on Title pub(crate) groups: Vec<Group<'a>>, } @@ -26,7 +26,17 @@ impl<'a> Message<'a> { /// /// </div> pub fn id(mut self, id: &'a str) -> Self { - self.id = Some(id); + self.id.get_or_insert(Id::default()).id = Some(id); + self + } + + /// <div class="warning"> + /// + /// This is only relevant if the `id` present + /// + /// </div> + pub fn id_url(mut self, url: &'a str) -> Self { + self.id.get_or_insert(Id::default()).url = Some(url); self } @@ -75,6 +85,12 @@ impl<'a> Message<'a> { } } +#[derive(Clone, Debug, Default)] +pub(crate) struct Id<'a> { + pub(crate) id: Option<&'a str>, + pub(crate) url: Option<&'a str>, +} + /// An [`Element`] container #[derive(Clone, Debug)] pub struct Group<'a> { From 0adeff8f8deb0914b0a0d7b089db32bab0c02ca0 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 12 Jun 2025 11:59:37 -0600 Subject: [PATCH 394/455] fix: Make pre-styled titles use their own fn --- examples/highlight_title.rs | 2 +- src/level.rs | 24 ++++++++++++++++++++++-- src/renderer/mod.rs | 22 +++++++++++++++++----- src/snippet.rs | 1 + 4 files changed, 41 insertions(+), 8 deletions(-) diff --git a/examples/highlight_title.rs b/examples/highlight_title.rs index 2f74a37b..f4e24627 100644 --- a/examples/highlight_title.rs +++ b/examples/highlight_title.rs @@ -61,7 +61,7 @@ fn main() { .label("arguments to this function are incorrect"), ), ) - .element(Level::NOTE.title(&title)), + .element(Level::NOTE.pre_styled_title(&title)), ) .group( Group::new() diff --git a/src/level.rs b/src/level.rs index eaa95600..fe746eca 100644 --- a/src/level.rs +++ b/src/level.rs @@ -78,10 +78,26 @@ impl<'a> Level<'a> { groups: vec![Group::new().element(Element::Title(Title { level: self, title: header, + is_pre_styled: false, }))], } } + /// <div class="warning"> + /// + /// Text passed to this function is considered "untrusted input", as such + /// all text is passed through a normalization function. Pre-styled text is + /// not allowed to be passed to this function. + /// + /// </div> + pub fn title(self, title: &'a str) -> Title<'a> { + Title { + level: self, + title, + is_pre_styled: false, + } + } + /// <div class="warning"> /// /// Text passed to this function is allowed to be pre-styled, as such all @@ -90,8 +106,12 @@ impl<'a> Level<'a> { /// used to normalize untrusted text before it is passed to this function. /// /// </div> - pub fn title(self, title: &'a str) -> Title<'a> { - Title { level: self, title } + pub fn pre_styled_title(self, title: &'a str) -> Title<'a> { + Title { + level: self, + title, + is_pre_styled: true, + } } pub(crate) fn as_str(&self) -> &'a str { diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index a5459734..8f279c62 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -593,7 +593,8 @@ impl Renderer { // `max_line_num_len` let padding = max_line_num_len + 3 + label_width; - let printed_lines = self.msgs_to_buffer(buffer, title.title, padding, None); + let printed_lines = + self.msgs_to_buffer(buffer, title.title, padding, None, title.is_pre_styled); if is_cont && matches!(self.theme, OutputTheme::Unicode) { // There's another note after this one, associated to the subwindow above. // We write additional vertical lines to join them: @@ -669,7 +670,12 @@ impl Renderer { label_width += 2; } if !title.title.is_empty() { - for (line, text) in normalize_whitespace(title.title).lines().enumerate() { + let (title_str, style) = if title.is_pre_styled { + (title.title.to_owned(), ElementStyle::NoStyle) + } else { + (normalize_whitespace(title.title), header_style) + }; + for (line, text) in title_str.lines().enumerate() { buffer.append( buffer_msg_line_offset + line, &format!( @@ -681,7 +687,7 @@ impl Renderer { }, text ), - header_style, + style, ); } } @@ -696,6 +702,7 @@ impl Renderer { title: &str, padding: usize, override_style: Option<ElementStyle>, + is_pre_styled: bool, ) -> usize { let padding = " ".repeat(padding); @@ -725,7 +732,12 @@ impl Renderer { } else { ElementStyle::NoStyle }; - let lines = title.split('\n').collect::<Vec<_>>(); + let title_str = if is_pre_styled { + title.to_owned() + } else { + normalize_whitespace(title) + }; + let lines = title_str.split('\n').collect::<Vec<_>>(); if lines.len() > 1 { for (i, line) in lines.iter().enumerate() { if i != 0 { @@ -735,7 +747,7 @@ impl Renderer { buffer.append(line_number, line, style); } } else { - buffer.append(line_number, title, style); + buffer.append(line_number, &title_str, style); } line_number } diff --git a/src/snippet.rs b/src/snippet.rs index 2d686a5e..5f35e582 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -175,6 +175,7 @@ pub struct Padding; pub struct Title<'a> { pub(crate) level: Level<'a>, pub(crate) title: &'a str, + pub(crate) is_pre_styled: bool, } /// A source view [`Element`] in a [`Group`] From e72952b5436e9af52db0f6dfa3822894797831d6 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 12 Jun 2025 10:50:33 -0600 Subject: [PATCH 395/455] refactor: Unify render_title logic --- src/renderer/mod.rs | 253 ++++++++++++++------------------------------ 1 file changed, 80 insertions(+), 173 deletions(-) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 8f279c62..148c2c56 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -550,30 +550,59 @@ impl Renderer { is_cont: bool, buffer_msg_line_offset: usize, ) { - if title_style == TitleStyle::Secondary { - // This is a secondary message with no span info - for _ in 0..max_line_num_len { - buffer.prepend(buffer_msg_line_offset, " ", ElementStyle::NoStyle); - } - - self.draw_note_separator( - buffer, - buffer_msg_line_offset, - max_line_num_len + 1, - is_cont, - ); + let (label_style, title_element_style) = match title_style { + TitleStyle::MainHeader => ( + ElementStyle::Level(title.level.level), + if self.short_message { + ElementStyle::NoStyle + } else { + ElementStyle::MainHeaderMsg + }, + ), + TitleStyle::Header => ( + ElementStyle::Level(title.level.level), + ElementStyle::HeaderMsg, + ), + TitleStyle::Secondary => { + for _ in 0..max_line_num_len { + buffer.prepend(buffer_msg_line_offset, " ", ElementStyle::NoStyle); + } - let label_width = if title.level.name != Some(None) { - buffer.append( + self.draw_note_separator( + buffer, buffer_msg_line_offset, - title.level.as_str(), - ElementStyle::MainHeaderMsg, + max_line_num_len + 1, + is_cont, ); - buffer.append(buffer_msg_line_offset, ": ", ElementStyle::NoStyle); - title.level.as_str().len() + 2 - } else { - 0 - }; + (ElementStyle::MainHeaderMsg, ElementStyle::NoStyle) + } + }; + let mut label_width = 0; + + if title.level.name != Some(None) { + buffer.append(buffer_msg_line_offset, title.level.as_str(), label_style); + label_width += title.level.as_str().len(); + if let Some(Id { id: Some(id), url }) = id { + buffer.append(buffer_msg_line_offset, "[", label_style); + if let Some(url) = url.as_ref() { + buffer.append( + buffer_msg_line_offset, + &format!("\x1B]8;;{url}\x1B\\"), + label_style, + ); + } + buffer.append(buffer_msg_line_offset, id, label_style); + if url.is_some() { + buffer.append(buffer_msg_line_offset, "\x1B]8;;\x1B\\", label_style); + } + buffer.append(buffer_msg_line_offset, "]", label_style); + label_width += 2 + id.len(); + } + buffer.append(buffer_msg_line_offset, ": ", title_element_style); + label_width += 2; + } + + let padding = " ".repeat(if title_style == TitleStyle::Secondary { // The extra 3 ` ` is padding that's always needed to align to the // label i.e. `note: `: // @@ -591,165 +620,43 @@ impl Renderer { // | | width of label // | magic `3` // `max_line_num_len` - let padding = max_line_num_len + 3 + label_width; - - let printed_lines = - self.msgs_to_buffer(buffer, title.title, padding, None, title.is_pre_styled); - if is_cont && matches!(self.theme, OutputTheme::Unicode) { - // There's another note after this one, associated to the subwindow above. - // We write additional vertical lines to join them: - // ╭▸ test.rs:3:3 - // │ - // 3 │ code - // │ ━━━━ - // │ - // ├ note: foo - // │ bar - // ╰ note: foo - // bar - for i in buffer_msg_line_offset + 1..=printed_lines { - self.draw_col_separator_no_space(buffer, i, max_line_num_len + 1); - } - } + max_line_num_len + 3 + label_width } else { - let mut label_width = 0; - - if title.level.name != Some(None) { - buffer.append( - buffer_msg_line_offset, - title.level.as_str(), - ElementStyle::Level(title.level.level), - ); - } - label_width += title.level.as_str().len(); - if let Some(Id { id: Some(id), url }) = id { - buffer.append( - buffer_msg_line_offset, - "[", - ElementStyle::Level(title.level.level), - ); - if let Some(url) = url.as_ref() { - buffer.append( - buffer_msg_line_offset, - &format!("\x1B]8;;{url}\x1B\\"), - ElementStyle::Level(title.level.level), - ); - } - buffer.append( - buffer_msg_line_offset, - id, - ElementStyle::Level(title.level.level), - ); - if url.is_some() { - buffer.append( - buffer_msg_line_offset, - "\x1B]8;;\x1B\\", - ElementStyle::Level(title.level.level), - ); - } - buffer.append( - buffer_msg_line_offset, - "]", - ElementStyle::Level(title.level.level), - ); - label_width += 2 + id.len(); - } - let header_style = match title_style { - TitleStyle::MainHeader => { - if self.short_message { - ElementStyle::NoStyle - } else { - ElementStyle::MainHeaderMsg - } - } - TitleStyle::Header => ElementStyle::HeaderMsg, - TitleStyle::Secondary => unreachable!(), - }; - if title.level.name != Some(None) { - buffer.append(buffer_msg_line_offset, ": ", header_style); - label_width += 2; - } - if !title.title.is_empty() { - let (title_str, style) = if title.is_pre_styled { - (title.title.to_owned(), ElementStyle::NoStyle) - } else { - (normalize_whitespace(title.title), header_style) - }; - for (line, text) in title_str.lines().enumerate() { - buffer.append( - buffer_msg_line_offset + line, - &format!( - "{}{}", - if line == 0 { - String::new() - } else { - " ".repeat(label_width) - }, - text - ), - style, - ); - } - } - } - } - - /// Adds a left margin to every line but the first, given a padding length and the label being - /// displayed, keeping the provided highlighting. - fn msgs_to_buffer( - &self, - buffer: &mut StyledBuffer, - title: &str, - padding: usize, - override_style: Option<ElementStyle>, - is_pre_styled: bool, - ) -> usize { - let padding = " ".repeat(padding); - - let mut line_number = buffer.num_lines().saturating_sub(1); + label_width + }); - // Provided the following diagnostic message: - // - // let msgs = vec![ - // (" - // ("highlighted multiline\nstring to\nsee how it ", Style::NoStyle), - // ("looks", Style::Highlight), - // ("with\nvery ", Style::NoStyle), - // ("weird", Style::Highlight), - // (" formats\n", Style::NoStyle), - // ("see?", Style::Highlight), - // ]; - // - // the expected output on a note is (* surround the highlighted text) - // - // = note: highlighted multiline - // string to - // see how it *looks* with - // very *weird* formats - // see? - let style = if let Some(override_style) = override_style { - override_style - } else { - ElementStyle::NoStyle - }; - let title_str = if is_pre_styled { - title.to_owned() + let (title_str, style) = if title.is_pre_styled { + (title.title.to_owned(), ElementStyle::NoStyle) } else { - normalize_whitespace(title) + (normalize_whitespace(title.title), title_element_style) }; - let lines = title_str.split('\n').collect::<Vec<_>>(); - if lines.len() > 1 { - for (i, line) in lines.iter().enumerate() { - if i != 0 { - line_number += 1; - buffer.append(line_number, &padding, ElementStyle::NoStyle); + for (i, text) in title_str.lines().enumerate() { + if i != 0 { + buffer.append(buffer_msg_line_offset + i, &padding, ElementStyle::NoStyle); + if title_style == TitleStyle::Secondary + && is_cont + && matches!(self.theme, OutputTheme::Unicode) + { + // There's another note after this one, associated to the subwindow above. + // We write additional vertical lines to join them: + // ╭▸ test.rs:3:3 + // │ + // 3 │ code + // │ ━━━━ + // │ + // ├ note: foo + // │ bar + // ╰ note: foo + // bar + self.draw_col_separator_no_space( + buffer, + buffer_msg_line_offset + i, + max_line_num_len + 1, + ); } - buffer.append(line_number, line, style); } - } else { - buffer.append(line_number, &title_str, style); + buffer.append(buffer_msg_line_offset + i, text, style); } - line_number } fn render_origin( From 5cee9d38993d049b986ed1223c197e74dbc64cfc Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 12 Jun 2025 08:44:03 -0600 Subject: [PATCH 396/455] test: Add a test for id on any Title --- tests/formatter.rs | 112 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) diff --git a/tests/formatter.rs b/tests/formatter.rs index fb671dfd..daa03e4b 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -2587,3 +2587,115 @@ LL | let s: &str = include_bytes!("file.txt"); //~ ERROR mismatched types let renderer = Renderer::plain().anonymized_line_numbers(true); assert_data_eq!(renderer.render(input), expected); } + +#[test] +fn id_on_title() { + let source = r#"// Regression test for issue #114529 +// Tests that we do not ICE during const eval for a +// break-with-value in contexts where it is illegal + +#[allow(while_true)] +fn main() { + [(); { + while true { + break 9; //~ ERROR `break` with value from a `while` loop + }; + 51 + }]; + + [(); { + while let Some(v) = Some(9) { + break v; //~ ERROR `break` with value from a `while` loop + }; + 51 + }]; + + while true { + break (|| { //~ ERROR `break` with value from a `while` loop + let local = 9; + }); + } +} +"#; + let input = Level::ERROR + .header("`break` with value from a `while` loop") + .id("E0571") + .group( + Group::new().element( + Snippet::source(source) + .line_start(1) + .path("$DIR/issue-114529-illegal-break-with-value.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(483..581) + .label("can only break with a value inside `loop` or breakable block"), + ) + .annotation( + AnnotationKind::Context + .span(462..472) + .label("you can't `break` with a value in a `while` loop"), + ), + ), + ) + .group( + Group::new() + .element( + Level::HELP + .text(Some("suggestion[S0123]")) + .title("use `break` on its own without a value inside this `while` loop"), + ) + .element( + Snippet::source(source) + .line_start(1) + .path("$DIR/issue-114529-illegal-break-with-value.rs") + .fold(true) + .patch(Patch::new(483..581, "break")), + ), + ); + + let expected_ascii = str![[r#" +error[E0571]: `break` with value from a `while` loop + --> $DIR/issue-114529-illegal-break-with-value.rs:22:9 + | +LL | while true { + | ---------- you can't `break` with a value in a `while` loop +LL | / break (|| { //~ ERROR `break` with value from a `while` loop +LL | | let local = 9; +LL | | }); + | |__________^ can only break with a value inside `loop` or breakable block + | +suggestion[S0123]: use `break` on its own without a value inside this `while` loop + | +LL - break (|| { //~ ERROR `break` with value from a `while` loop +LL - let local = 9; +LL - }); +LL + break; + | +"#]]; + + let renderer_ascii = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer_ascii.render(input.clone()), expected_ascii); + + let expected_unicode = str![[r#" +error[E0571]: `break` with value from a `while` loop + ╭▸ $DIR/issue-114529-illegal-break-with-value.rs:22:9 + │ +LL │ while true { + │ ────────── you can't `break` with a value in a `while` loop +LL │ ┏ break (|| { //~ ERROR `break` with value from a `while` loop +LL │ ┃ let local = 9; +LL │ ┃ }); + │ ┗━━━━━━━━━━┛ can only break with a value inside `loop` or breakable block + ╰╴ +suggestion[S0123]: use `break` on its own without a value inside this `while` loop + ╭╴ +LL - break (|| { //~ ERROR `break` with value from a `while` loop +LL - let local = 9; +LL - }); +LL + break; + ╰╴ +"#]]; + let renderer_unicode = renderer_ascii.theme(OutputTheme::Unicode); + assert_data_eq!(renderer_unicode.render(input), expected_unicode); +} From 9024488ea0fdad67d09b016b7608cb64c1e41bdb Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 12 Jun 2025 09:42:56 -0600 Subject: [PATCH 397/455] feat: Allow all titles to have IDs --- src/level.rs | 4 +++- src/renderer/mod.rs | 11 +--------- src/snippet.rs | 52 ++++++++++++++++++++++++++++++++++++++++++--- tests/formatter.rs | 5 +++-- 4 files changed, 56 insertions(+), 16 deletions(-) diff --git a/src/level.rs b/src/level.rs index fe746eca..4a8e8347 100644 --- a/src/level.rs +++ b/src/level.rs @@ -74,9 +74,9 @@ impl<'a> Level<'a> { /// </div> pub fn header(self, header: &'a str) -> Message<'a> { Message { - id: None, groups: vec![Group::new().element(Element::Title(Title { level: self, + id: None, title: header, is_pre_styled: false, }))], @@ -93,6 +93,7 @@ impl<'a> Level<'a> { pub fn title(self, title: &'a str) -> Title<'a> { Title { level: self, + id: None, title, is_pre_styled: false, } @@ -109,6 +110,7 @@ impl<'a> Level<'a> { pub fn pre_styled_title(self, title: &'a str) -> Title<'a> { Title { level: self, + id: None, title, is_pre_styled: true, } diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 148c2c56..5084b631 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -338,13 +338,6 @@ impl Renderer { title, max_line_num_len, title_style, - message.id.as_ref().and_then(|id| { - if g == 0 && i == 0 { - Some(id) - } else { - None - } - }), matches!(peek, Some(Element::Title(_))), buffer_msg_line_offset, ); @@ -524,7 +517,6 @@ impl Renderer { &title, 0, // No line numbers in short messages TitleStyle::MainHeader, - message.id.as_ref(), false, 0, ); @@ -546,7 +538,6 @@ impl Renderer { title: &Title<'_>, max_line_num_len: usize, title_style: TitleStyle, - id: Option<&Id<'_>>, is_cont: bool, buffer_msg_line_offset: usize, ) { @@ -582,7 +573,7 @@ impl Renderer { if title.level.name != Some(None) { buffer.append(buffer_msg_line_offset, title.level.as_str(), label_style); label_width += title.level.as_str().len(); - if let Some(Id { id: Some(id), url }) = id { + if let Some(Id { id: Some(id), url }) = title.id { buffer.append(buffer_msg_line_offset, "[", label_style); if let Some(url) = url.as_ref() { buffer.append( diff --git a/src/snippet.rs b/src/snippet.rs index 5f35e582..e5080a4c 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -13,7 +13,6 @@ pub(crate) const WARNING_TXT: &str = "warning"; /// Top-level user message #[derive(Clone, Debug)] pub struct Message<'a> { - pub(crate) id: Option<Id<'a>>, // for "correctness", could be sloppy and be on Title pub(crate) groups: Vec<Group<'a>>, } @@ -26,7 +25,15 @@ impl<'a> Message<'a> { /// /// </div> pub fn id(mut self, id: &'a str) -> Self { - self.id.get_or_insert(Id::default()).id = Some(id); + let Some(Element::Title(title)) = + self.groups.get_mut(0).and_then(|g| g.elements.first_mut()) + else { + panic!( + "Expected first element to be a Title, got: {:?}", + self.groups + ); + }; + title.id.get_or_insert(Id::default()).id = Some(id); self } @@ -36,7 +43,15 @@ impl<'a> Message<'a> { /// /// </div> pub fn id_url(mut self, url: &'a str) -> Self { - self.id.get_or_insert(Id::default()).url = Some(url); + let Some(Element::Title(title)) = + self.groups.get_mut(0).and_then(|g| g.elements.first_mut()) + else { + panic!( + "Expected first element to be a Title, got: {:?}", + self.groups + ); + }; + title.id.get_or_insert(Id::default()).url = Some(url); self } @@ -174,10 +189,41 @@ pub struct Padding; #[derive(Clone, Debug)] pub struct Title<'a> { pub(crate) level: Level<'a>, + pub(crate) id: Option<Id<'a>>, pub(crate) title: &'a str, pub(crate) is_pre_styled: bool, } +impl<'a> Title<'a> { + /// <div class="warning"> + /// + /// This is only relevant if the title is the first element of a group. + /// + /// </div> + /// <div class="warning"> + /// + /// Text passed to this function is considered "untrusted input", as such + /// all text is passed through a normalization function. Pre-styled text is + /// not allowed to be passed to this function. + /// + /// </div> + pub fn id(mut self, id: &'a str) -> Self { + self.id.get_or_insert(Id::default()).id = Some(id); + self + } + + /// <div class="warning"> + /// + /// This is only relevant if the title is the first element of a group and + /// `id` present + /// + /// </div> + pub fn id_url(mut self, url: &'a str) -> Self { + self.id.get_or_insert(Id::default()).url = Some(url); + self + } +} + /// A source view [`Element`] in a [`Group`] /// /// If you do not have [source][Snippet::source] available, see instead [`Origin`] diff --git a/tests/formatter.rs b/tests/formatter.rs index daa03e4b..7e431215 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -2642,8 +2642,9 @@ fn main() { Group::new() .element( Level::HELP - .text(Some("suggestion[S0123]")) - .title("use `break` on its own without a value inside this `while` loop"), + .text(Some("suggestion")) + .title("use `break` on its own without a value inside this `while` loop") + .id("S0123"), ) .element( Snippet::source(source) From 99b68cbb8dc55422c3a2fadc453085cabab0588a Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 12 Jun 2025 11:51:22 -0600 Subject: [PATCH 398/455] fix!: Remove Message in favor of Group --- benches/bench.rs | 16 +- examples/custom_error.rs | 33 +- examples/custom_level.rs | 43 +- examples/expected_type.rs | 8 +- examples/footer.rs | 15 +- examples/format.rs | 8 +- examples/highlight_source.rs | 35 +- examples/highlight_title.rs | 62 +- examples/id_hyperlink.rs | 45 +- examples/multislice.rs | 25 +- src/level.rs | 22 +- src/lib.rs | 18 - src/renderer/mod.rs | 487 ++++--- src/renderer/styled_buffer.rs | 4 +- src/snippet.rs | 106 +- tests/color/ann_eof.rs | 8 +- tests/color/ann_insertion.rs | 8 +- tests/color/ann_multiline.rs | 33 +- tests/color/ann_multiline2.rs | 29 +- tests/color/ann_removed_nl.rs | 8 +- tests/color/ensure_emoji_highlight_width.rs | 19 +- tests/color/fold_ann_multiline.rs | 8 +- tests/color/fold_bad_origin_line.rs | 16 +- tests/color/fold_leading.rs | 25 +- tests/color/fold_trailing.rs | 25 +- tests/color/issue_9.rs | 39 +- tests/color/multiline_removal_suggestion.rs | 81 +- tests/color/multiple_annotations.rs | 40 +- tests/color/simple.rs | 36 +- tests/color/strip_line.rs | 8 +- tests/color/strip_line_char.rs | 8 +- tests/color/strip_line_non_ws.rs | 8 +- tests/formatter.rs | 1164 +++++++-------- tests/rustc_tests.rs | 1451 +++++++++---------- 34 files changed, 1806 insertions(+), 2135 deletions(-) diff --git a/benches/bench.rs b/benches/bench.rs index 32b67c3f..6390628f 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -24,8 +24,9 @@ fn simple() -> String { _ => continue, } }"#; - let message = Level::ERROR.header("mismatched types").id("E0308").group( - Group::new().element( + let message = &[Group::new() + .element(Level::ERROR.title("mismatched types").id("E0308")) + .element( Snippet::source(source) .line_start(51) .path("src/format.rs") @@ -39,8 +40,7 @@ fn simple() -> String { .span(26..724) .label("expected enum `std::option::Option`"), ), - ), - ); + )]; let renderer = Renderer::plain(); let rendered = renderer.render(message); @@ -69,8 +69,9 @@ fn fold(bencher: divan::Bencher<'_, '_>, context: usize) { (input, span) }) .bench_values(|(input, span)| { - let message = Level::ERROR.header("mismatched types").id("E0308").group( - Group::new().element( + let message = &[Group::new() + .element(Level::ERROR.title("mismatched types").id("E0308")) + .element( Snippet::source(&input) .fold(true) .path("src/format.rs") @@ -79,8 +80,7 @@ fn fold(bencher: divan::Bencher<'_, '_>, context: usize) { .span(span) .label("expected `Option<String>` because of return type"), ), - ), - ); + )]; let renderer = Renderer::plain(); let rendered = renderer.render(message); diff --git a/examples/custom_error.rs b/examples/custom_error.rs index dfb8fc6c..e80b1466 100644 --- a/examples/custom_error.rs +++ b/examples/custom_error.rs @@ -15,22 +15,23 @@ fn main() { pub static C: u32 = 0 - 1; //~^ ERROR could not evaluate static initializer "#; - let message = Level::ERROR - .text(Some("error: internal compiler error")) - .header("could not evaluate static initializer") - .id("E0080") - .group( - Group::new().element( - Snippet::source(source) - .path("$DIR/err.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(386..391) - .label("attempt to compute `0_u32 - 1_u32`, which would overflow"), - ), - ), - ); + let message = &[Group::new() + .element( + Level::ERROR + .text(Some("error: internal compiler error")) + .title("could not evaluate static initializer") + .id("E0080"), + ) + .element( + Snippet::source(source) + .path("$DIR/err.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(386..391) + .label("attempt to compute `0_u32 - 1_u32`, which would overflow"), + ), + )]; let renderer = Renderer::styled().theme(OutputTheme::Unicode); anstream::println!("{}", renderer.render(message)); diff --git a/examples/custom_level.rs b/examples/custom_level.rs index 57f2fb5a..b500c063 100644 --- a/examples/custom_level.rs +++ b/examples/custom_level.rs @@ -29,11 +29,14 @@ fn main() { } } "#; - let message = Level::ERROR - .header("`break` with value from a `while` loop") - .id("E0571") - .group( - Group::new().element( + let message = &[ + Group::new() + .element( + Level::ERROR + .title("`break` with value from a `while` loop") + .id("E0571"), + ) + .element( Snippet::source(source) .line_start(1) .path("$DIR/issue-114529-illegal-break-with-value.rs") @@ -49,22 +52,20 @@ fn main() { .label("you can't `break` with a value in a `while` loop"), ), ), - ) - .group( - Group::new() - .element( - Level::HELP - .text(Some("suggestion")) - .title("use `break` on its own without a value inside this `while` loop"), - ) - .element( - Snippet::source(source) - .line_start(1) - .path("$DIR/issue-114529-illegal-break-with-value.rs") - .fold(true) - .patch(Patch::new(483..581, "break")), - ), - ); + Group::new() + .element( + Level::HELP + .text(Some("suggestion")) + .title("use `break` on its own without a value inside this `while` loop"), + ) + .element( + Snippet::source(source) + .line_start(1) + .path("$DIR/issue-114529-illegal-break-with-value.rs") + .fold(true) + .patch(Patch::new(483..581, "break")), + ), + ]; let renderer = Renderer::styled().theme(OutputTheme::Unicode); anstream::println!("{}", renderer.render(message)); diff --git a/examples/expected_type.rs b/examples/expected_type.rs index 440c64c3..0fce9938 100644 --- a/examples/expected_type.rs +++ b/examples/expected_type.rs @@ -6,8 +6,9 @@ fn main() { , range: <22, 25>,"#; let message = - Level::ERROR.header("expected type, found `22`").group( - Group::new().element( + &[Group::new() + .element(Level::ERROR.title("expected type, found `22`")) + .element( Snippet::source(source) .line_start(26) .path("examples/footer.rs") @@ -20,8 +21,7 @@ fn main() { .span(34..50) .label("while parsing this struct"), ), - ), - ); + )]; let renderer = Renderer::styled(); anstream::println!("{}", renderer.render(message)); diff --git a/examples/footer.rs b/examples/footer.rs index 36173350..aa9b784f 100644 --- a/examples/footer.rs +++ b/examples/footer.rs @@ -1,11 +1,10 @@ use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; fn main() { - let message = Level::ERROR - .header("mismatched types") - .id("E0308") - .group( - Group::new().element( + let message = &[ + Group::new() + .element(Level::ERROR.title("mismatched types").id("E0308")) + .element( Snippet::source(" slices: vec![\"A\",") .line_start(13) .path("src/multislice.rs") @@ -13,10 +12,10 @@ fn main() { "expected struct `annotate_snippets::snippet::Slice`, found reference", )), ), - ) - .group(Group::new().element(Level::NOTE.title( + Group::new().element(Level::NOTE.title( "expected type: `snippet::Annotation`\n found type: `__&__snippet::Annotation`", - ))); + )), + ]; let renderer = Renderer::styled(); anstream::println!("{}", renderer.render(message)); diff --git a/examples/format.rs b/examples/format.rs index 4268e315..ae603259 100644 --- a/examples/format.rs +++ b/examples/format.rs @@ -23,8 +23,9 @@ fn main() { _ => continue, } }"#; - let message = Level::ERROR.header("mismatched types").id("E0308").group( - Group::new().element( + let message = &[Group::new() + .element(Level::ERROR.title("mismatched types").id("E0308")) + .element( Snippet::source(source) .line_start(51) .path("src/format.rs") @@ -38,8 +39,7 @@ fn main() { .span(26..724) .label("expected enum `std::option::Option`"), ), - ), - ); + )]; let renderer = Renderer::styled(); anstream::println!("{}", renderer.render(message)); diff --git a/examples/highlight_source.rs b/examples/highlight_source.rs index f5871453..f897a3f5 100644 --- a/examples/highlight_source.rs +++ b/examples/highlight_source.rs @@ -9,26 +9,23 @@ const CON: Vec<i32> = vec![1, 2, 3]; //~ ERROR E0010 //~| ERROR cannot call non-const method fn main() {} "#; - let message = Level::ERROR - .header("allocations are not allowed in constants") - .id("E0010") - .group( - Group::new() - .element( - Snippet::source(source) - .fold(true) - .path("$DIR/E0010-teach.rs") - .annotation( - AnnotationKind::Primary - .span(72..85) - .label("allocation not allowed in constants") - .highlight_source(true), - ), - ) - .element( - Level::NOTE.title("The runtime heap is not yet available at compile-time, so no runtime heap allocations can be created."), + let message = &[Group::new().element(Level::ERROR.title("allocations are not allowed in constants") + .id("E0010")) + .element( + Snippet::source(source) + .fold(true) + .path("$DIR/E0010-teach.rs") + .annotation( + AnnotationKind::Primary + .span(72..85) + .label("allocation not allowed in constants") + .highlight_source(true), ), - ); + ) + .element( + Level::NOTE.title("The runtime heap is not yet available at compile-time, so no runtime heap allocations can be created."), + + )]; let renderer = Renderer::styled().anonymized_line_numbers(true); anstream::println!("{}", renderer.render(message)); diff --git a/examples/highlight_title.rs b/examples/highlight_title.rs index f4e24627..6ed3817f 100644 --- a/examples/highlight_title.rs +++ b/examples/highlight_title.rs @@ -41,39 +41,35 @@ fn main() { magenta.render_reset() ); - let message = Level::ERROR - .header("mismatched types") - .id("E0308") - .group( - Group::new() - .element( - Snippet::source(source) - .fold(true) - .path("$DIR/highlighting.rs") - .annotation( - AnnotationKind::Primary - .span(553..563) - .label("one type is more general than the other"), - ) - .annotation( - AnnotationKind::Context - .span(547..552) - .label("arguments to this function are incorrect"), - ), - ) - .element(Level::NOTE.pre_styled_title(&title)), - ) - .group( - Group::new() - .element(Level::NOTE.title("function defined here")) - .element( - Snippet::source(source) - .fold(true) - .path("$DIR/highlighting.rs") - .annotation(AnnotationKind::Context.span(200..333).label("")) - .annotation(AnnotationKind::Primary.span(194..199)), - ), - ); + let message = &[ + Group::new() + .element(Level::ERROR.title("mismatched types").id("E0308")) + .element( + Snippet::source(source) + .fold(true) + .path("$DIR/highlighting.rs") + .annotation( + AnnotationKind::Primary + .span(553..563) + .label("one type is more general than the other"), + ) + .annotation( + AnnotationKind::Context + .span(547..552) + .label("arguments to this function are incorrect"), + ), + ) + .element(Level::NOTE.pre_styled_title(&title)), + Group::new() + .element(Level::NOTE.title("function defined here")) + .element( + Snippet::source(source) + .fold(true) + .path("$DIR/highlighting.rs") + .annotation(AnnotationKind::Context.span(200..333).label("")) + .annotation(AnnotationKind::Primary.span(194..199)), + ), + ]; let renderer = Renderer::styled().anonymized_line_numbers(true); anstream::println!("{}", renderer.render(message)); diff --git a/examples/id_hyperlink.rs b/examples/id_hyperlink.rs index 209fc15b..2d49b275 100644 --- a/examples/id_hyperlink.rs +++ b/examples/id_hyperlink.rs @@ -7,28 +7,29 @@ fn main() { let () = 4; //~ ERROR } "#; - let message = Level::ERROR - .header("mismatched types") - .id("E0308") - .id_url("https://doc.rust-lang.org/error_codes/E0308.html") - .group( - Group::new().element( - Snippet::source(source) - .line_start(1) - .path("$DIR/terminal_urls.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(59..61) - .label("expected integer, found `()`"), - ) - .annotation( - AnnotationKind::Context - .span(64..65) - .label("this expression has type `{integer}`"), - ), - ), - ); + let message = &[Group::new() + .element( + Level::ERROR + .title("mismatched types") + .id("E0308") + .id_url("https://doc.rust-lang.org/error_codes/E0308.html"), + ) + .element( + Snippet::source(source) + .line_start(1) + .path("$DIR/terminal_urls.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(59..61) + .label("expected integer, found `()`"), + ) + .annotation( + AnnotationKind::Context + .span(64..65) + .label("this expression has type `{integer}`"), + ), + )]; let renderer = Renderer::styled().theme(OutputTheme::Unicode); anstream::println!("{}", renderer.render(message)); diff --git a/examples/multislice.rs b/examples/multislice.rs index b8b4ac74..9393aadb 100644 --- a/examples/multislice.rs +++ b/examples/multislice.rs @@ -1,19 +1,18 @@ use annotate_snippets::{Annotation, Group, Level, Renderer, Snippet}; fn main() { - let message = Level::ERROR.header("mismatched types").group( - Group::new() - .element( - Snippet::<Annotation<'_>>::source("Foo") - .line_start(51) - .path("src/format.rs"), - ) - .element( - Snippet::<Annotation<'_>>::source("Faa") - .line_start(129) - .path("src/display.rs"), - ), - ); + let message = &[Group::new() + .element(Level::ERROR.title("mismatched types")) + .element( + Snippet::<Annotation<'_>>::source("Foo") + .line_start(51) + .path("src/format.rs"), + ) + .element( + Snippet::<Annotation<'_>>::source("Faa") + .line_start(129) + .path("src/display.rs"), + )]; let renderer = Renderer::styled(); anstream::println!("{}", renderer.render(message)); diff --git a/src/level.rs b/src/level.rs index 4a8e8347..d3db1814 100644 --- a/src/level.rs +++ b/src/level.rs @@ -2,7 +2,7 @@ use crate::renderer::stylesheet::Stylesheet; use crate::snippet::{ERROR_TXT, HELP_TXT, INFO_TXT, NOTE_TXT, WARNING_TXT}; -use crate::{Element, Group, Message, Title}; +use crate::Title; use anstyle::Style; /// Default `error:` [`Level`] @@ -35,7 +35,7 @@ pub const HELP: Level<'_> = Level { level: LevelInner::Help, }; -/// [`Message`] or [`Title`] severity level +/// [`Title`] severity level #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct Level<'a> { pub(crate) name: Option<Option<&'a str>>, @@ -65,24 +65,6 @@ impl<'a> Level<'a> { } impl<'a> Level<'a> { - /// <div class="warning"> - /// - /// Text passed to this function is considered "untrusted input", as such - /// all text is passed through a normalization function. Pre-styled text is - /// not allowed to be passed to this function. - /// - /// </div> - pub fn header(self, header: &'a str) -> Message<'a> { - Message { - groups: vec![Group::new().element(Element::Title(Title { - level: self, - id: None, - title: header, - is_pre_styled: false, - }))], - } - } - /// <div class="warning"> /// /// Text passed to this function is considered "untrusted input", as such diff --git a/src/lib.rs b/src/lib.rs index bf5a720e..74f116aa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,24 +12,6 @@ //! #![doc = include_str!("../examples/expected_type.svg")] //! -//! The crate uses a three stage process with two conversions between states: -//! -//! ```text -//! Message --> Renderer --> impl Display -//! ``` -//! -//! The input type - [Message] is a structure designed -//! to align with likely output from any parser whose code snippet is to be -//! annotated. -//! -//! The middle structure - [Renderer] is a structure designed -//! to convert a snippet into an internal structure that is designed to store -//! the snippet data in a way that is easy to format. -//! [Renderer] also handles the user-configurable formatting -//! options, such as color, or margins. -//! -//! Finally, `impl Display` into a final `String` output. -//! //! # features //! - `testing-colors` - Makes [Renderer::styled] colors OS independent, which //! allows for easier testing when testing colored output. It should be added as diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 5084b631..12139c38 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -1,6 +1,6 @@ // Most of this file is adapted from https://github.com/rust-lang/rust/blob/160905b6253f42967ed4aef4b98002944c7df24c/compiler/rustc_errors/src/emitter.rs -//! The renderer for [`Message`]s +//! The renderer for [`Group`]s //! //! # Example //! ``` @@ -18,21 +18,24 @@ //! bar(); //! } //! "#; -//! Level::ERROR -//! .header("unresolved import `baz::zed`") -//! .id("E0432") -//! .group( -//! Group::new().element( -//! Snippet::source(source) -//! .path("temp.rs") -//! .line_start(1) -//! .fold(true) -//! .annotation( -//! AnnotationKind::Primary -//! .span(10..13) -//! .label("could not find `zed` in `baz`"), -//! ) -//! ) +//! +//! +//! Group::new() +//! .element( +//! Level::ERROR +//! .title("unresolved import `baz::zed`") +//! .id("E0432") +//! ) +//! .element( +//! Snippet::source(source) +//! .path("temp.rs") +//! .line_start(1) +//! .fold(true) +//! .annotation( +//! AnnotationKind::Primary +//! .span(10..13) +//! .label("could not find `zed` in `baz`"), +//! ) //! ); //! ``` @@ -47,7 +50,7 @@ use crate::renderer::source_map::{ }; use crate::renderer::styled_buffer::StyledBuffer; use crate::snippet::Id; -use crate::{Annotation, AnnotationKind, Element, Group, Message, Origin, Patch, Snippet, Title}; +use crate::{Annotation, AnnotationKind, Element, Group, Origin, Patch, Snippet, Title}; pub use anstyle::*; use margin::Margin; use std::borrow::Cow; @@ -60,7 +63,7 @@ use stylesheet::Stylesheet; const ANONYMIZED_LINE_NUM: &str = "LL"; pub const DEFAULT_TERM_WIDTH: usize = 140; -/// A renderer for [`Message`]s +/// A renderer for [`Group`]s #[derive(Clone, Debug)] pub struct Renderer { anonymized_line_numbers: bool, @@ -206,265 +209,220 @@ impl Renderer { } impl Renderer { - pub fn render(&self, mut message: Message<'_>) -> String { + pub fn render(&self, groups: &[Group<'_>]) -> String { if self.short_message { - self.render_short_message(message).unwrap() + self.render_short_message(groups).unwrap() } else { let max_line_num_len = if self.anonymized_line_numbers { ANONYMIZED_LINE_NUM.len() } else { - let n = message.max_line_number(); - num_decimal_digits(n) + num_decimal_digits(max_line_number(groups)) }; - let title = message.groups.remove(0).elements.remove(0); - if let Some(first) = message.groups.first_mut() { - first.elements.insert(0, title); - } else { - message.groups.push(Group::new().element(title)); - } - self.render_message(message, max_line_num_len).unwrap() - } - } - - fn render_message( - &self, - message: Message<'_>, - max_line_num_len: usize, - ) -> Result<String, fmt::Error> { - let mut out_string = String::new(); - - let og_primary_path = message - .groups - .iter() - .find_map(|group| { - group.elements.iter().find_map(|s| match &s { - Element::Cause(cause) => { - if cause.markers.iter().any(|m| m.kind.is_primary()) { - Some(cause.path) - } else { - None + let mut out_string = String::new(); + let group_len = groups.len(); + let mut og_primary_path = None; + for (g, group) in groups.iter().enumerate() { + let mut buffer = StyledBuffer::new(); + let primary_path = group + .elements + .iter() + .find_map(|s| match &s { + Element::Cause(cause) => { + if cause.markers.iter().any(|m| m.kind.is_primary()) { + Some(cause.path) + } else { + None + } } - } - Element::Origin(origin) => { - if origin.primary { - Some(Some(origin.path)) - } else { - None + Element::Origin(origin) => { + if origin.primary { + Some(Some(origin.path)) + } else { + None + } } - } - _ => None, - }) - }) - .unwrap_or( - message - .groups + _ => None, + }) + .unwrap_or( + group + .elements + .iter() + .find_map(|s| match &s { + Element::Cause(cause) => Some(cause.path), + Element::Origin(origin) => Some(Some(origin.path)), + _ => None, + }) + .unwrap_or_default(), + ); + if og_primary_path.is_none() && primary_path.is_some() { + og_primary_path = primary_path; + } + let level = group + .elements .iter() - .find_map(|group| { - group.elements.iter().find_map(|s| match &s { - Element::Cause(cause) => Some(cause.path), - Element::Origin(origin) => Some(Some(origin.path)), - _ => None, - }) + .find_map(|s| match &s { + Element::Title(title) => Some(title.level.clone()), + _ => None, }) - .unwrap_or_default(), - ); - let group_len = message.groups.len(); - for (g, group) in message.groups.into_iter().enumerate() { - let mut buffer = StyledBuffer::new(); - let primary_path = group - .elements - .iter() - .find_map(|s| match &s { - Element::Cause(cause) => { - if cause.markers.iter().any(|m| m.kind.is_primary()) { - Some(cause.path) - } else { - None - } + .unwrap_or(Level::ERROR); + let mut source_map_annotated_lines = VecDeque::new(); + let mut max_depth = 0; + for e in &group.elements { + if let Element::Cause(cause) = e { + let source_map = SourceMap::new(cause.source, cause.line_start); + let (depth, annotated_lines) = + source_map.annotated_lines(cause.markers.clone(), cause.fold); + max_depth = max(max_depth, depth); + source_map_annotated_lines.push_back((source_map, annotated_lines)); } - Element::Origin(origin) => { - if origin.primary { - Some(Some(origin.path)) - } else { - None - } - } - _ => None, - }) - .unwrap_or( - group - .elements - .iter() - .find_map(|s| match &s { - Element::Cause(cause) => Some(cause.path), - Element::Origin(origin) => Some(Some(origin.path)), - _ => None, - }) - .unwrap_or_default(), - ); - let level = group - .elements - .iter() - .find_map(|s| match &s { - Element::Title(title) => Some(title.level.clone()), - _ => None, - }) - .unwrap_or(Level::ERROR); - let mut source_map_annotated_lines = VecDeque::new(); - let mut max_depth = 0; - for e in &group.elements { - if let Element::Cause(cause) = e { - let source_map = SourceMap::new(cause.source, cause.line_start); - let (depth, annotated_lines) = - source_map.annotated_lines(cause.markers.clone(), cause.fold); - max_depth = max(max_depth, depth); - source_map_annotated_lines.push_back((source_map, annotated_lines)); } - } - let mut message_iter = group.elements.iter().enumerate().peekable(); - let mut last_was_suggestion = false; - while let Some((i, section)) = message_iter.next() { - let peek = message_iter.peek().map(|(_, s)| s).copied(); - match §ion { - Element::Title(title) => { - let title_style = match (i == 0, g == 0) { - (true, true) => TitleStyle::MainHeader, - (true, false) => TitleStyle::Header, - (false, _) => TitleStyle::Secondary, - }; - let buffer_msg_line_offset = buffer.num_lines(); - self.render_title( - &mut buffer, - title, - max_line_num_len, - title_style, - matches!(peek, Some(Element::Title(_))), - buffer_msg_line_offset, - ); - last_was_suggestion = false; - } - Element::Cause(cause) => { - if let Some((source_map, annotated_lines)) = - source_map_annotated_lines.pop_front() - { - self.render_snippet_annotations( + let mut message_iter = group.elements.iter().enumerate().peekable(); + let mut last_was_suggestion = false; + while let Some((i, section)) = message_iter.next() { + let peek = message_iter.peek().map(|(_, s)| s).copied(); + match §ion { + Element::Title(title) => { + let title_style = match (i == 0, g == 0) { + (true, true) => TitleStyle::MainHeader, + (true, false) => TitleStyle::Header, + (false, _) => TitleStyle::Secondary, + }; + let buffer_msg_line_offset = buffer.num_lines(); + self.render_title( &mut buffer, + title, max_line_num_len, - cause, - primary_path, - &source_map, - &annotated_lines, - max_depth, - peek.is_some() || (g == 0 && group_len > 1), + title_style, + matches!(peek, Some(Element::Title(_))), + buffer_msg_line_offset, ); + last_was_suggestion = false; + } + Element::Cause(cause) => { + if let Some((source_map, annotated_lines)) = + source_map_annotated_lines.pop_front() + { + self.render_snippet_annotations( + &mut buffer, + max_line_num_len, + cause, + primary_path, + &source_map, + &annotated_lines, + max_depth, + peek.is_some() || (g == 0 && group_len > 1), + ); - if g == 0 { - let current_line = buffer.num_lines(); - match peek { - Some(Element::Title(level)) - if level.level.name != Some(None) => - { - self.draw_col_separator_no_space( + if g == 0 { + let current_line = buffer.num_lines(); + match peek { + Some(Element::Title(level)) + if level.level.name != Some(None) => + { + self.draw_col_separator_no_space( + &mut buffer, + current_line, + max_line_num_len + 1, + ); + } + + None if group_len > 1 => self.draw_col_separator_end( &mut buffer, current_line, max_line_num_len + 1, - ); + ), + _ => {} } - - None if group_len > 1 => self.draw_col_separator_end( - &mut buffer, - current_line, - max_line_num_len + 1, - ), - _ => {} } } - } - last_was_suggestion = false; - } - Element::Suggestion(suggestion) => { - let source_map = SourceMap::new(suggestion.source, suggestion.line_start); - self.emit_suggestion_default( - &mut buffer, - suggestion, - max_line_num_len, - &source_map, - primary_path.or(og_primary_path), - last_was_suggestion, - ); - last_was_suggestion = true; - } + last_was_suggestion = false; + } + Element::Suggestion(suggestion) => { + let source_map = + SourceMap::new(suggestion.source, suggestion.line_start); + self.emit_suggestion_default( + &mut buffer, + suggestion, + max_line_num_len, + &source_map, + primary_path.or(og_primary_path), + last_was_suggestion, + ); + last_was_suggestion = true; + } - Element::Origin(origin) => { - let buffer_msg_line_offset = buffer.num_lines(); - self.render_origin( - &mut buffer, - max_line_num_len, - origin, - buffer_msg_line_offset, - ); - last_was_suggestion = false; - } - Element::Padding(_) => { - let current_line = buffer.num_lines(); - self.draw_col_separator_no_space( - &mut buffer, - current_line, - max_line_num_len + 1, - ); + Element::Origin(origin) => { + let buffer_msg_line_offset = buffer.num_lines(); + self.render_origin( + &mut buffer, + max_line_num_len, + origin, + buffer_msg_line_offset, + ); + last_was_suggestion = false; + } + Element::Padding(_) => { + let current_line = buffer.num_lines(); + self.draw_col_separator_no_space( + &mut buffer, + current_line, + max_line_num_len + 1, + ); + } } - } - if g == 0 - && (matches!(section, Element::Origin(_)) - || (matches!(section, Element::Title(_)) && i == 0) - || matches!(section, Element::Title(level) if level.level.name == Some(None))) - { - let current_line = buffer.num_lines(); - if peek.is_none() && group_len > 1 { - self.draw_col_separator_end( - &mut buffer, - current_line, - max_line_num_len + 1, - ); - } else if matches!(peek, Some(Element::Title(level)) if level.level.name != Some(None)) + if g == 0 + && (matches!(section, Element::Origin(_)) + || (matches!(section, Element::Title(_)) && i == 0) + || matches!(section, Element::Title(level) if level.level.name == Some(None))) { - self.draw_col_separator_no_space( - &mut buffer, - current_line, - max_line_num_len + 1, - ); + let current_line = buffer.num_lines(); + if peek.is_none() && group_len > 1 { + self.draw_col_separator_end( + &mut buffer, + current_line, + max_line_num_len + 1, + ); + } else if matches!(peek, Some(Element::Title(level)) if level.level.name != Some(None)) + { + self.draw_col_separator_no_space( + &mut buffer, + current_line, + max_line_num_len + 1, + ); + } } } - } - buffer.render(level, &self.stylesheet, &mut out_string)?; - if g != group_len - 1 { - use std::fmt::Write; + buffer + .render(&level, &self.stylesheet, &mut out_string) + .unwrap(); + if g != group_len - 1 { + use std::fmt::Write; - writeln!(out_string)?; + writeln!(out_string).unwrap(); + } } + out_string } - Ok(out_string) } - fn render_short_message(&self, mut message: Message<'_>) -> Result<String, fmt::Error> { + fn render_short_message(&self, groups: &[Group<'_>]) -> Result<String, fmt::Error> { let mut buffer = StyledBuffer::new(); + let mut labels = None; + let group = groups.first().expect("Expected at least one group"); - let Element::Title(title) = message.groups.remove(0).elements.remove(0) else { + let Some(Element::Title(title)) = group.elements.first() else { panic!( "Expected first element to be a Title, got: {:?}", - message.groups + group.elements.first() ); }; - let mut labels = None; - - if let Some(Element::Cause(cause)) = message.groups.first().and_then(|group| { - group - .elements - .iter() - .find(|e| matches!(e, Element::Cause(_))) - }) { + if let Some(Element::Cause(cause)) = group + .elements + .iter() + .find(|e| matches!(e, Element::Cause(_))) + { let labels_inner = cause .markers .iter() @@ -514,7 +472,7 @@ impl Renderer { self.render_title( &mut buffer, - &title, + title, 0, // No line numbers in short messages TitleStyle::MainHeader, false, @@ -526,7 +484,7 @@ impl Renderer { } let mut out_string = String::new(); - buffer.render(title.level, &self.stylesheet, &mut out_string)?; + buffer.render(&title.level, &self.stylesheet, &mut out_string)?; Ok(out_string) } @@ -2871,6 +2829,57 @@ enum TitleStyle { Secondary, } +fn max_line_number(groups: &[Group<'_>]) -> usize { + groups + .iter() + .map(|v| { + v.elements + .iter() + .map(|s| match s { + Element::Title(_) | Element::Origin(_) | Element::Padding(_) => 0, + Element::Cause(cause) => { + let end = cause + .markers + .iter() + .map(|a| a.span.end) + .max() + .unwrap_or(cause.source.len()) + .min(cause.source.len()); + + cause.line_start + newline_count(&cause.source[..end]) + } + Element::Suggestion(suggestion) => { + let end = suggestion + .markers + .iter() + .map(|a| a.span.end) + .max() + .unwrap_or(suggestion.source.len()) + .min(suggestion.source.len()); + + suggestion.line_start + newline_count(&suggestion.source[..end]) + } + }) + .max() + .unwrap_or(1) + }) + .max() + .unwrap_or(1) +} + +fn newline_count(body: &str) -> usize { + #[cfg(feature = "simd")] + { + memchr::memchr_iter(b'\n', body.as_bytes()) + .count() + .saturating_sub(1) + } + #[cfg(not(feature = "simd"))] + { + body.lines().count().saturating_sub(1) + } +} + #[cfg(test)] mod test { use super::OUTPUT_REPLACEMENTS; diff --git a/src/renderer/styled_buffer.rs b/src/renderer/styled_buffer.rs index f72c58c6..de3d0815 100644 --- a/src/renderer/styled_buffer.rs +++ b/src/renderer/styled_buffer.rs @@ -41,14 +41,14 @@ impl StyledBuffer { pub(crate) fn render( &self, - level: Level<'_>, + level: &Level<'_>, stylesheet: &Stylesheet, str: &mut String, ) -> Result<(), fmt::Error> { for (i, line) in self.lines.iter().enumerate() { let mut current_style = stylesheet.none; for StyledChar { ch, style } in line { - let ch_style = style.color_spec(&level, stylesheet); + let ch_style = style.color_spec(level, stylesheet); if ch_style != current_style { if !line.is_empty() { write!(str, "{}", current_style.render_reset())?; diff --git a/src/snippet.rs b/src/snippet.rs index e5080a4c..6e9a78c7 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -10,96 +10,6 @@ pub(crate) const INFO_TXT: &str = "info"; pub(crate) const NOTE_TXT: &str = "note"; pub(crate) const WARNING_TXT: &str = "warning"; -/// Top-level user message -#[derive(Clone, Debug)] -pub struct Message<'a> { - pub(crate) groups: Vec<Group<'a>>, -} - -impl<'a> Message<'a> { - /// <div class="warning"> - /// - /// Text passed to this function is considered "untrusted input", as such - /// all text is passed through a normalization function. Pre-styled text is - /// not allowed to be passed to this function. - /// - /// </div> - pub fn id(mut self, id: &'a str) -> Self { - let Some(Element::Title(title)) = - self.groups.get_mut(0).and_then(|g| g.elements.first_mut()) - else { - panic!( - "Expected first element to be a Title, got: {:?}", - self.groups - ); - }; - title.id.get_or_insert(Id::default()).id = Some(id); - self - } - - /// <div class="warning"> - /// - /// This is only relevant if the `id` present - /// - /// </div> - pub fn id_url(mut self, url: &'a str) -> Self { - let Some(Element::Title(title)) = - self.groups.get_mut(0).and_then(|g| g.elements.first_mut()) - else { - panic!( - "Expected first element to be a Title, got: {:?}", - self.groups - ); - }; - title.id.get_or_insert(Id::default()).url = Some(url); - self - } - - /// Add an [`Element`] container - pub fn group(mut self, group: Group<'a>) -> Self { - self.groups.push(group); - self - } - - pub(crate) fn max_line_number(&self) -> usize { - self.groups - .iter() - .map(|v| { - v.elements - .iter() - .map(|s| match s { - Element::Title(_) | Element::Origin(_) | Element::Padding(_) => 0, - Element::Cause(cause) => { - let end = cause - .markers - .iter() - .map(|a| a.span.end) - .max() - .unwrap_or(cause.source.len()) - .min(cause.source.len()); - - cause.line_start + newline_count(&cause.source[..end]) - } - Element::Suggestion(suggestion) => { - let end = suggestion - .markers - .iter() - .map(|a| a.span.end) - .max() - .unwrap_or(suggestion.source.len()) - .min(suggestion.source.len()); - - suggestion.line_start + newline_count(&suggestion.source[..end]) - } - }) - .max() - .unwrap_or(1) - }) - .max() - .unwrap_or(1) - } -} - #[derive(Clone, Debug, Default)] pub(crate) struct Id<'a> { pub(crate) id: Option<&'a str>, @@ -350,7 +260,8 @@ impl<'a> Annotation<'a> { /// The category of the [`Annotation`] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum AnnotationKind { - /// Color to [`Message`]'s [`Level`] + /// Color to the [`Level`] the first [`Title`] in [`Group`]. If no [`Title`] + /// is present, it will default to `error`. Primary, /// "secondary"; fixed color Context, @@ -496,19 +407,6 @@ impl<'a> Origin<'a> { } } -fn newline_count(body: &str) -> usize { - #[cfg(feature = "simd")] - { - memchr::memchr_iter(b'\n', body.as_bytes()) - .count() - .saturating_sub(1) - } - #[cfg(not(feature = "simd"))] - { - body.lines().count().saturating_sub(1) - } -} - /// Given an original string like `AACC`, and a suggestion like `AABBCC`, try to detect /// the case where a substring of the suggestion is "sandwiched" in the original, like /// `BB` is. Return the length of the prefix, the "trimmed" suggestion, and the length diff --git a/tests/color/ann_eof.rs b/tests/color/ann_eof.rs index ea0c95b8..e550ba55 100644 --- a/tests/color/ann_eof.rs +++ b/tests/color/ann_eof.rs @@ -4,14 +4,14 @@ use snapbox::{assert_data_eq, file}; #[test] fn case() { - let input = Level::ERROR.header("expected `.`, `=`").group( - Group::new().element( + let input = &[Group::new() + .element(Level::ERROR.title("expected `.`, `=`")) + .element( Snippet::source("asdf") .path("Cargo.toml") .line_start(1) .annotation(AnnotationKind::Primary.span(4..4).label("")), - ), - ); + )]; let expected = file!["ann_eof.term.svg"]; let renderer = Renderer::styled(); assert_data_eq!(renderer.render(input), expected); diff --git a/tests/color/ann_insertion.rs b/tests/color/ann_insertion.rs index a0c538b8..73dd7d80 100644 --- a/tests/color/ann_insertion.rs +++ b/tests/color/ann_insertion.rs @@ -4,14 +4,14 @@ use snapbox::{assert_data_eq, file}; #[test] fn case() { - let input = Level::ERROR.header("expected `.`, `=`").group( - Group::new().element( + let input = &[Group::new() + .element(Level::ERROR.title("expected `.`, `=`")) + .element( Snippet::source("asf") .path("Cargo.toml") .line_start(1) .annotation(AnnotationKind::Primary.span(2..2).label("'d' belongs here")), - ), - ); + )]; let expected = file!["ann_insertion.term.svg"]; let renderer = Renderer::styled(); assert_data_eq!(renderer.render(input), expected); diff --git a/tests/color/ann_multiline.rs b/tests/color/ann_multiline.rs index 127c462e..29d4c309 100644 --- a/tests/color/ann_multiline.rs +++ b/tests/color/ann_multiline.rs @@ -9,22 +9,23 @@ fn case() { } = body[body_idx] "#; - let input = Level::ERROR - .header("pattern does not mention fields `lineno`, `content`") - .id("E0027") - .group( - Group::new().element( - Snippet::source(source) - .path("src/display_list.rs") - .line_start(139) - .fold(false) - .annotation( - AnnotationKind::Primary - .span(31..128) - .label("missing fields `lineno`, `content`"), - ), - ), - ); + let input = &[Group::new() + .element( + Level::ERROR + .title("pattern does not mention fields `lineno`, `content`") + .id("E0027"), + ) + .element( + Snippet::source(source) + .path("src/display_list.rs") + .line_start(139) + .fold(false) + .annotation( + AnnotationKind::Primary + .span(31..128) + .label("missing fields `lineno`, `content`"), + ), + )]; let expected = file!["ann_multiline.term.svg"]; let renderer = Renderer::styled(); assert_data_eq!(renderer.render(input), expected); diff --git a/tests/color/ann_multiline2.rs b/tests/color/ann_multiline2.rs index b8e36197..cf21e5ea 100644 --- a/tests/color/ann_multiline2.rs +++ b/tests/color/ann_multiline2.rs @@ -9,22 +9,19 @@ of an edge case of an annotation overflowing to exactly one character on next line. "#; - let input = Level::ERROR - .header("spacing error found") - .id("E####") - .group( - Group::new().element( - Snippet::source(source) - .path("foo.txt") - .line_start(26) - .fold(false) - .annotation( - AnnotationKind::Primary - .span(11..19) - .label("this should not be on separate lines"), - ), - ), - ); + let input = &[Group::new() + .element(Level::ERROR.title("spacing error found").id("E####")) + .element( + Snippet::source(source) + .path("foo.txt") + .line_start(26) + .fold(false) + .annotation( + AnnotationKind::Primary + .span(11..19) + .label("this should not be on separate lines"), + ), + )]; let expected = file!["ann_multiline2.term.svg"]; let renderer = Renderer::styled(); assert_data_eq!(renderer.render(input), expected); diff --git a/tests/color/ann_removed_nl.rs b/tests/color/ann_removed_nl.rs index b4398c47..68ec8326 100644 --- a/tests/color/ann_removed_nl.rs +++ b/tests/color/ann_removed_nl.rs @@ -4,14 +4,14 @@ use snapbox::{assert_data_eq, file}; #[test] fn case() { - let input = Level::ERROR.header("expected `.`, `=`").group( - Group::new().element( + let input = &[Group::new() + .element(Level::ERROR.title("expected `.`, `=`")) + .element( Snippet::source("asdf") .path("Cargo.toml") .line_start(1) .annotation(AnnotationKind::Primary.span(4..5).label("")), - ), - ); + )]; let expected = file!["ann_removed_nl.term.svg"]; let renderer = Renderer::styled(); assert_data_eq!(renderer.render(input), expected); diff --git a/tests/color/ensure_emoji_highlight_width.rs b/tests/color/ensure_emoji_highlight_width.rs index 59dcdaa2..bc22f9ab 100644 --- a/tests/color/ensure_emoji_highlight_width.rs +++ b/tests/color/ensure_emoji_highlight_width.rs @@ -7,17 +7,14 @@ fn case() { let source = r#""haha this isn't a valid name 🐛" = { package = "libc", version = "0.1" } "#; - let input = Level::ERROR.header("invalid character ` ` in package name: `haha this isn't a valid name 🐛`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)") - .group( - Group::new() - .element( - Snippet::source(source) - .path("<file>") - .line_start(7) - .annotation(AnnotationKind::Primary.span(0..35).label("")) - ) - ) -; + let input = &[Group::new() + .element(Level::ERROR.title("invalid character ` ` in package name: `haha this isn't a valid name 🐛`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)")) + .element( + Snippet::source(source) + .path("<file>") + .line_start(7) + .annotation(AnnotationKind::Primary.span(0..35).label("")) + )]; let expected = file!["ensure_emoji_highlight_width.term.svg"]; let renderer = Renderer::styled(); assert_data_eq!(renderer.render(input), expected); diff --git a/tests/color/fold_ann_multiline.rs b/tests/color/fold_ann_multiline.rs index b0ccdd55..1c035f41 100644 --- a/tests/color/fold_ann_multiline.rs +++ b/tests/color/fold_ann_multiline.rs @@ -28,8 +28,9 @@ fn case() { } "#; - let input = Level::ERROR.header("mismatched types").id("E0308").group( - Group::new().element( + let input = &[Group::new() + .element(Level::ERROR.title("mismatched types").id("E0308")) + .element( Snippet::source(source) .path("src/format.rs") .line_start(51) @@ -42,8 +43,7 @@ fn case() { .span(22..766) .label("expected enum `std::option::Option`, found ()"), ), - ), - ); + )]; let expected = file!["fold_ann_multiline.term.svg"]; let renderer = Renderer::styled(); assert_data_eq!(renderer.render(input), expected); diff --git a/tests/color/fold_bad_origin_line.rs b/tests/color/fold_bad_origin_line.rs index 852f9b54..9e4c5c0c 100644 --- a/tests/color/fold_bad_origin_line.rs +++ b/tests/color/fold_bad_origin_line.rs @@ -9,15 +9,13 @@ fn case() { invalid syntax "#; - let input = Level::ERROR.header("").group( - Group::new().element( - Snippet::source(source) - .path("path/to/error.rs") - .line_start(1) - .fold(true) - .annotation(AnnotationKind::Context.span(2..16).label("error here")), - ), - ); + let input = &[Group::new().element(Level::ERROR.title("")).element( + Snippet::source(source) + .path("path/to/error.rs") + .line_start(1) + .fold(true) + .annotation(AnnotationKind::Context.span(2..16).label("error here")), + )]; let expected = file!["fold_bad_origin_line.term.svg"]; let renderer = Renderer::styled(); assert_data_eq!(renderer.render(input), expected); diff --git a/tests/color/fold_leading.rs b/tests/color/fold_leading.rs index 965741d5..0e4ae61d 100644 --- a/tests/color/fold_leading.rs +++ b/tests/color/fold_leading.rs @@ -17,18 +17,19 @@ edition = "2021" workspace = 20 "#; - let input = Level::ERROR - .header("invalid type: integer `20`, expected a bool") - .id("E0308") - .group( - Group::new().element( - Snippet::source(source) - .path("Cargo.toml") - .line_start(1) - .fold(true) - .annotation(AnnotationKind::Primary.span(132..134).label("")), - ), - ); + let input = &[Group::new() + .element( + Level::ERROR + .title("invalid type: integer `20`, expected a bool") + .id("E0308"), + ) + .element( + Snippet::source(source) + .path("Cargo.toml") + .line_start(1) + .fold(true) + .annotation(AnnotationKind::Primary.span(132..134).label("")), + )]; let expected = file!["fold_leading.term.svg"]; let renderer = Renderer::styled(); assert_data_eq!(renderer.render(input), expected); diff --git a/tests/color/fold_trailing.rs b/tests/color/fold_trailing.rs index bbcf5d80..6421dd45 100644 --- a/tests/color/fold_trailing.rs +++ b/tests/color/fold_trailing.rs @@ -16,18 +16,19 @@ rust-version = "1.70" edition = "2021" "#; - let input = Level::ERROR - .header("invalid type: integer `20`, expected a lints table") - .id("E0308") - .group( - Group::new().element( - Snippet::source(source) - .path("Cargo.toml") - .line_start(1) - .fold(true) - .annotation(AnnotationKind::Primary.span(8..10).label("")), - ), - ); + let input = &[Group::new() + .element( + Level::ERROR + .title("invalid type: integer `20`, expected a lints table") + .id("E0308"), + ) + .element( + Snippet::source(source) + .path("Cargo.toml") + .line_start(1) + .fold(true) + .annotation(AnnotationKind::Primary.span(8..10).label("")), + )]; let expected = file!["fold_trailing.term.svg"]; let renderer = Renderer::styled(); assert_data_eq!(renderer.render(input), expected); diff --git a/tests/color/issue_9.rs b/tests/color/issue_9.rs index 36d13f44..f42c30b9 100644 --- a/tests/color/issue_9.rs +++ b/tests/color/issue_9.rs @@ -4,27 +4,24 @@ use snapbox::{assert_data_eq, file}; #[test] fn case() { - let input = Level::ERROR.header("expected one of `.`, `;`, `?`, or an operator, found `for`") - .group( - Group::new() - .element( - Snippet::source("let x = vec![1];") - .path("/code/rust/src/test/ui/annotate-snippet/suggestion.rs") - .line_start(4) - .annotation(AnnotationKind::Context.span(4..5).label("move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait")) - ) - .element( - Snippet::source("let y = x;") - .line_start(7) - .annotation(AnnotationKind::Context.span(8..9).label("value moved here")) - ) - .element( - Snippet::source("x;") - .line_start(9) - .annotation(AnnotationKind::Primary.span(0..1).label("value used here after move")) - ) - ) -; + let input = &[Group::new().element(Level::ERROR.title("expected one of `.`, `;`, `?`, or an operator, found `for`")) + .element( + Snippet::source("let x = vec![1];") + .path("/code/rust/src/test/ui/annotate-snippet/suggestion.rs") + .line_start(4) + .annotation(AnnotationKind::Context.span(4..5).label("move occurs because `x` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait")) + ) + .element( + Snippet::source("let y = x;") + .line_start(7) + .annotation(AnnotationKind::Context.span(8..9).label("value moved here")) + ) + .element( + Snippet::source("x;") + .line_start(9) + .annotation(AnnotationKind::Primary.span(0..1).label("value used here after move")) + ) + ]; let expected = file!["issue_9.term.svg"]; let renderer = Renderer::styled(); assert_data_eq!(renderer.render(input), expected); diff --git a/tests/color/multiline_removal_suggestion.rs b/tests/color/multiline_removal_suggestion.rs index ced5e09d..fbaf4fff 100644 --- a/tests/color/multiline_removal_suggestion.rs +++ b/tests/color/multiline_removal_suggestion.rs @@ -64,49 +64,44 @@ fn bay() -> Vec<(bool, HashSet<u8>)> { fn main() {} "#; - let input = Level::ERROR - .header("`(bool, HashSet<u8>)` is not an iterator") - .id("E0277") - .group( - Group::new() - .element( - Snippet::source(source) - .path("$DIR/multiline-removal-suggestion.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(769..776) - .label("`(bool, HashSet<u8>)` is not an iterator"), - ), - ) - .element( - Level::HELP - .title("the trait `Iterator` is not implemented for `(bool, HashSet<u8>)`"), - ) - .element( - Level::NOTE - .title("required for `(bool, HashSet<u8>)` to implement `IntoIterator`"), - ), - ) - .group( - Group::new() - .element(Level::NOTE.title("required by a bound in `flatten`")) - .element( - Origin::new("/rustc/FAKE_PREFIX/library/core/src/iter/traits/iterator.rs") - .line(1556) - .char_column(4), - ), - ) - .group( - Group::new() - .element(Level::HELP.title("consider removing this method call, as the receiver has type `std::vec::IntoIter<HashSet<u8>>` and `std::vec::IntoIter<HashSet<u8>>: Iterator` trivially holds")) - .element( - Snippet::source(source) - .path("$DIR/multiline-removal-suggestion.rs") - .fold(true) - .patch(Patch::new(708..768, "")), - ), - ); + let input = &[ + Group::new() + .element( + Level::ERROR + .title("`(bool, HashSet<u8>)` is not an iterator") + .id("E0277"), + ) + .element( + Snippet::source(source) + .path("$DIR/multiline-removal-suggestion.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(769..776) + .label("`(bool, HashSet<u8>)` is not an iterator"), + ), + ) + .element( + Level::HELP + .title("the trait `Iterator` is not implemented for `(bool, HashSet<u8>)`"), + ) + .element( + Level::NOTE.title("required for `(bool, HashSet<u8>)` to implement `IntoIterator`"), + ), + Group::new() + .element(Level::NOTE.title("required by a bound in `flatten`")) + .element( + Origin::new("/rustc/FAKE_PREFIX/library/core/src/iter/traits/iterator.rs") + .line(1556) + .char_column(4), + ), + Group::new().element(Level::HELP.title("consider removing this method call, as the receiver has type `std::vec::IntoIter<HashSet<u8>>` and `std::vec::IntoIter<HashSet<u8>>: Iterator` trivially holds")).element( + Snippet::source(source) + .path("$DIR/multiline-removal-suggestion.rs") + .fold(true) + .patch(Patch::new(708..768, "")), + ), + ]; let expected = file!["multiline_removal_suggestion.term.svg"]; let renderer = Renderer::styled(); assert_data_eq!(renderer.render(input), expected); diff --git a/tests/color/multiple_annotations.rs b/tests/color/multiple_annotations.rs index b568b919..464d7672 100644 --- a/tests/color/multiple_annotations.rs +++ b/tests/color/multiple_annotations.rs @@ -15,27 +15,25 @@ fn case() { } "#; - let input = Level::ERROR.header("").group( - Group::new().element( - Snippet::source(source) - .line_start(96) - .annotation( - AnnotationKind::Primary - .span(100..110) - .label("Variable defined here"), - ) - .annotation( - AnnotationKind::Primary - .span(184..194) - .label("Referenced here"), - ) - .annotation( - AnnotationKind::Primary - .span(243..253) - .label("Referenced again here"), - ), - ), - ); + let input = &[Group::new().element(Level::ERROR.title("")).element( + Snippet::source(source) + .line_start(96) + .annotation( + AnnotationKind::Primary + .span(100..110) + .label("Variable defined here"), + ) + .annotation( + AnnotationKind::Primary + .span(184..194) + .label("Referenced here"), + ) + .annotation( + AnnotationKind::Primary + .span(243..253) + .label("Referenced again here"), + ), + )]; let expected = file!["multiple_annotations.term.svg"]; let renderer = Renderer::styled(); assert_data_eq!(renderer.render(input), expected); diff --git a/tests/color/simple.rs b/tests/color/simple.rs index 83834295..17287d94 100644 --- a/tests/color/simple.rs +++ b/tests/color/simple.rs @@ -9,25 +9,23 @@ fn case() { for line in &self.body { "#; - let input = Level::ERROR - .header("expected one of `.`, `;`, `?`, or an operator, found `for`") - .group( - Group::new().element( - Snippet::source(source) - .path("src/format_color.rs") - .line_start(169) - .annotation( - AnnotationKind::Primary - .span(20..23) - .label("unexpected token"), - ) - .annotation( - AnnotationKind::Context - .span(10..11) - .label("expected one of `.`, `;`, `?`, or an operator here"), - ), - ), - ); + let input = &[Group::new() + .element(Level::ERROR.title("expected one of `.`, `;`, `?`, or an operator, found `for`")) + .element( + Snippet::source(source) + .path("src/format_color.rs") + .line_start(169) + .annotation( + AnnotationKind::Primary + .span(20..23) + .label("unexpected token"), + ) + .annotation( + AnnotationKind::Context + .span(10..11) + .label("expected one of `.`, `;`, `?`, or an operator here"), + ), + )]; let expected = file!["simple.term.svg"]; let renderer = Renderer::styled(); assert_data_eq!(renderer.render(input), expected); diff --git a/tests/color/strip_line.rs b/tests/color/strip_line.rs index 4b21f9a1..fbb72506 100644 --- a/tests/color/strip_line.rs +++ b/tests/color/strip_line.rs @@ -6,8 +6,9 @@ use snapbox::{assert_data_eq, file}; fn case() { let source = r#" let _: () = 42;"#; - let input = Level::ERROR.header("mismatched types").id("E0308").group( - Group::new().element( + let input = &[Group::new() + .element(Level::ERROR.title("mismatched types").id("E0308")) + .element( Snippet::source(source) .path("$DIR/whitespace-trimming.rs") .line_start(4) @@ -16,8 +17,7 @@ fn case() { .span(192..194) .label("expected (), found integer"), ), - ), - ); + )]; let expected = file!["strip_line.term.svg"]; let renderer = Renderer::styled().anonymized_line_numbers(true); assert_data_eq!(renderer.render(input), expected); diff --git a/tests/color/strip_line_char.rs b/tests/color/strip_line_char.rs index f30d5e90..090e1dba 100644 --- a/tests/color/strip_line_char.rs +++ b/tests/color/strip_line_char.rs @@ -6,8 +6,9 @@ use snapbox::{assert_data_eq, file}; fn case() { let source = r#" let _: () = 42ñ"#; - let input = Level::ERROR.header("mismatched types").id("E0308").group( - Group::new().element( + let input = &[Group::new() + .element(Level::ERROR.title("mismatched types").id("E0308")) + .element( Snippet::source(source) .path("$DIR/whitespace-trimming.rs") .line_start(4) @@ -16,8 +17,7 @@ fn case() { .span(192..194) .label("expected (), found integer"), ), - ), - ); + )]; let expected = file!["strip_line_char.term.svg"]; let renderer = Renderer::styled().anonymized_line_numbers(true); assert_data_eq!(renderer.render(input), expected); diff --git a/tests/color/strip_line_non_ws.rs b/tests/color/strip_line_non_ws.rs index a67d70d1..da65e6a3 100644 --- a/tests/color/strip_line_non_ws.rs +++ b/tests/color/strip_line_non_ws.rs @@ -7,8 +7,9 @@ fn case() { let source = r#" let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); "#; - let input = Level::ERROR.header("mismatched types").id("E0308").group( - Group::new().element( + let input = &[Group::new() + .element(Level::ERROR.title("mismatched types").id("E0308")) + .element( Snippet::source(source) .path("$DIR/non-whitespace-trimming.rs") .line_start(4) @@ -22,8 +23,7 @@ fn case() { .span(232..234) .label("expected due to this"), ), - ), - ); + )]; let expected = file!["strip_line_non_ws.term.svg"]; let renderer = Renderer::styled().anonymized_line_numbers(true); assert_data_eq!(renderer.render(input), expected); diff --git a/tests/formatter.rs b/tests/formatter.rs index 7e431215..34c40bf8 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -5,14 +5,12 @@ use snapbox::{assert_data_eq, str}; #[test] fn test_i_29() { - let snippets = Level::ERROR.header("oops").group( - Group::new().element( - Snippet::source("First line\r\nSecond oops line") - .path("<current file>") - .annotation(AnnotationKind::Primary.span(19..23).label("oops")) - .fold(true), - ), - ); + let snippets = &[Group::new().element(Level::ERROR.title("oops")).element( + Snippet::source("First line\r\nSecond oops line") + .path("<current file>") + .annotation(AnnotationKind::Primary.span(19..23).label("oops")) + .fold(true), + )]; let expected = str![[r#" error: oops --> <current file>:2:8 @@ -27,13 +25,11 @@ error: oops #[test] fn test_point_to_double_width_characters() { - let snippets = Level::ERROR.header("").group( - Group::new().element( - Snippet::source("こんにちは、世界") - .path("<current file>") - .annotation(AnnotationKind::Primary.span(18..24).label("world")), - ), - ); + let snippets = &[Group::new().element(Level::ERROR.title("")).element( + Snippet::source("こんにちは、世界") + .path("<current file>") + .annotation(AnnotationKind::Primary.span(18..24).label("world")), + )]; let expected = str![[r#" error: @@ -49,13 +45,11 @@ error: #[test] fn test_point_to_double_width_characters_across_lines() { - let snippets = Level::ERROR.header("").group( - Group::new().element( - Snippet::source("おはよう\nございます") - .path("<current file>") - .annotation(AnnotationKind::Primary.span(6..22).label("Good morning")), - ), - ); + let snippets = &[Group::new().element(Level::ERROR.title("")).element( + Snippet::source("おはよう\nございます") + .path("<current file>") + .annotation(AnnotationKind::Primary.span(6..22).label("Good morning")), + )]; let expected = str![[r#" error: @@ -73,14 +67,12 @@ error: #[test] fn test_point_to_double_width_characters_multiple() { - let snippets = Level::ERROR.header("").group( - Group::new().element( - Snippet::source("お寿司\n食べたい🍣") - .path("<current file>") - .annotation(AnnotationKind::Primary.span(0..9).label("Sushi1")) - .annotation(AnnotationKind::Context.span(16..22).label("Sushi2")), - ), - ); + let snippets = &[Group::new().element(Level::ERROR.title("")).element( + Snippet::source("お寿司\n食べたい🍣") + .path("<current file>") + .annotation(AnnotationKind::Primary.span(0..9).label("Sushi1")) + .annotation(AnnotationKind::Context.span(16..22).label("Sushi2")), + )]; let expected = str![[r#" error: @@ -98,13 +90,11 @@ error: #[test] fn test_point_to_double_width_characters_mixed() { - let snippets = Level::ERROR.header("").group( - Group::new().element( - Snippet::source("こんにちは、新しいWorld!") - .path("<current file>") - .annotation(AnnotationKind::Primary.span(18..32).label("New world")), - ), - ); + let snippets = &[Group::new().element(Level::ERROR.title("")).element( + Snippet::source("こんにちは、新しいWorld!") + .path("<current file>") + .annotation(AnnotationKind::Primary.span(18..32).label("New world")), + )]; let expected = str![[r#" error: @@ -120,7 +110,7 @@ error: #[test] fn test_format_title() { - let input = Level::ERROR.header("This is a title").id("E0001"); + let input = &[Group::new().element(Level::ERROR.title("This is a title").id("E0001"))]; let expected = str![r#"error[E0001]: This is a title"#]; let renderer = Renderer::plain(); @@ -130,9 +120,9 @@ fn test_format_title() { #[test] fn test_format_snippet_only() { let source = "This is line 1\nThis is line 2"; - let input = Level::ERROR - .header("") - .group(Group::new().element(Snippet::<Annotation<'_>>::source(source).line_start(5402))); + let input = &[Group::new() + .element(Level::ERROR.title("")) + .element(Snippet::<Annotation<'_>>::source(source).line_start(5402))]; let expected = str![[r#" error: @@ -148,19 +138,18 @@ error: fn test_format_snippets_continuation() { let src_0 = "This is slice 1"; let src_1 = "This is slice 2"; - let input = Level::ERROR.header("").group( - Group::new() - .element( - Snippet::<Annotation<'_>>::source(src_0) - .line_start(5402) - .path("file1.rs"), - ) - .element( - Snippet::<Annotation<'_>>::source(src_1) - .line_start(2) - .path("file2.rs"), - ), - ); + let input = &[Group::new() + .element(Level::ERROR.title("")) + .element( + Snippet::<Annotation<'_>>::source(src_0) + .line_start(5402) + .path("file1.rs"), + ) + .element( + Snippet::<Annotation<'_>>::source(src_1) + .line_start(2) + .path("file2.rs"), + )]; let expected = str![[r#" error: --> file1.rs @@ -182,15 +171,13 @@ fn test_format_snippet_annotation_standalone() { let source = [line_1, line_2].join("\n"); // In line 2 let range = 22..24; - let input = Level::ERROR.header("").group( - Group::new().element( - Snippet::source(&source).line_start(5402).annotation( - AnnotationKind::Context - .span(range.clone()) - .label("Test annotation"), - ), + let input = &[Group::new().element(Level::ERROR.title("")).element( + Snippet::source(&source).line_start(5402).annotation( + AnnotationKind::Context + .span(range.clone()) + .label("Test annotation"), ), - ); + )]; let expected = str![[r#" error: | @@ -204,9 +191,9 @@ error: #[test] fn test_format_footer_title() { - let input = Level::ERROR - .header("") - .group(Group::new().element(Level::ERROR.title("This __is__ a title"))); + let input = &[Group::new() + .element(Level::ERROR.title("")) + .element(Level::ERROR.title("This __is__ a title"))]; let expected = str![[r#" error: | @@ -221,15 +208,13 @@ error: fn test_i26() { let source = "short"; let label = "label"; - let input = Level::ERROR.header("").group( - Group::new().element( - Snippet::source(source).line_start(0).annotation( - AnnotationKind::Primary - .span(0..source.len() + 2) - .label(label), - ), + let input = &[Group::new().element(Level::ERROR.title("")).element( + Snippet::source(source).line_start(0).annotation( + AnnotationKind::Primary + .span(0..source.len() + 2) + .label(label), ), - ); + )]; let renderer = Renderer::plain(); let _ = renderer.render(input); } @@ -237,9 +222,9 @@ fn test_i26() { #[test] fn test_source_content() { let source = "This is an example\nof content lines"; - let input = Level::ERROR - .header("") - .group(Group::new().element(Snippet::<Annotation<'_>>::source(source).line_start(56))); + let input = &[Group::new() + .element(Level::ERROR.title("")) + .element(Snippet::<Annotation<'_>>::source(source).line_start(56))]; let expected = str![[r#" error: | @@ -253,13 +238,11 @@ error: #[test] fn test_source_annotation_standalone_singleline() { let source = "tests"; - let input = Level::ERROR.header("").group( - Group::new().element( - Snippet::source(source) - .line_start(1) - .annotation(AnnotationKind::Context.span(0..5).label("Example string")), - ), - ); + let input = &[Group::new().element(Level::ERROR.title("")).element( + Snippet::source(source) + .line_start(1) + .annotation(AnnotationKind::Context.span(0..5).label("Example string")), + )]; let expected = str![[r#" error: | @@ -273,14 +256,12 @@ error: #[test] fn test_source_annotation_standalone_multiline() { let source = "tests"; - let input = Level::ERROR.header("").group( - Group::new().element( - Snippet::source(source) - .line_start(1) - .annotation(AnnotationKind::Context.span(0..5).label("Example string")) - .annotation(AnnotationKind::Context.span(0..5).label("Second line")), - ), - ); + let input = &[Group::new().element(Level::ERROR.title("")).element( + Snippet::source(source) + .line_start(1) + .annotation(AnnotationKind::Context.span(0..5).label("Example string")) + .annotation(AnnotationKind::Context.span(0..5).label("Second line")), + )]; let expected = str![[r#" error: | @@ -296,9 +277,9 @@ error: #[test] fn test_only_source() { - let input = Level::ERROR - .header("") - .group(Group::new().element(Snippet::<Annotation<'_>>::source("").path("file.rs"))); + let input = &[Group::new() + .element(Level::ERROR.title("")) + .element(Snippet::<Annotation<'_>>::source("").path("file.rs"))]; let expected = str![[r#" error: --> file.rs @@ -312,9 +293,9 @@ error: #[test] fn test_anon_lines() { let source = "This is an example\nof content lines\n\nabc"; - let input = Level::ERROR - .header("") - .group(Group::new().element(Snippet::<Annotation<'_>>::source(source).line_start(56))); + let input = &[Group::new() + .element(Level::ERROR.title("")) + .element(Snippet::<Annotation<'_>>::source(source).line_start(56))]; let expected = str![[r#" error: | @@ -329,15 +310,14 @@ LL | abc #[test] fn issue_130() { - let input = Level::ERROR.header("dummy").group( - Group::new().element( - Snippet::source("foo\nbar\nbaz") - .path("file/path") - .line_start(3) - .fold(true) - .annotation(AnnotationKind::Primary.span(4..11)), - ), // bar\nbaz - ); + let input = &[Group::new().element(Level::ERROR.title("dummy")).element( + Snippet::source("foo\nbar\nbaz") + .path("file/path") + .line_start(3) + .fold(true) + .annotation(AnnotationKind::Primary.span(4..11)), + // bar\nbaz + )]; let expected = str![[r#" error: dummy @@ -357,15 +337,14 @@ fn unterminated_string_multiline() { a\" // ... "; - let input = Level::ERROR.header("").group( - Group::new().element( - Snippet::source(source) - .path("file/path") - .line_start(3) - .fold(true) - .annotation(AnnotationKind::Primary.span(0..10)), - ), // 1..10 works - ); + let input = &[Group::new().element(Level::ERROR.title("")).element( + Snippet::source(source) + .path("file/path") + .line_start(3) + .fold(true) + .annotation(AnnotationKind::Primary.span(0..10)), + // 1..10 works + )]; let expected = str![[r#" error: --> file/path:3:1 @@ -381,14 +360,13 @@ error: #[test] fn char_and_nl_annotate_char() { let source = "a\r\nb"; - let input = Level::ERROR.header("").group( - Group::new().element( - Snippet::source(source) - .path("file/path") - .line_start(3) - .annotation(AnnotationKind::Primary.span(0..2)), - ), // a\r - ); + let input = &[Group::new().element(Level::ERROR.title("")).element( + Snippet::source(source) + .path("file/path") + .line_start(3) + .annotation(AnnotationKind::Primary.span(0..2)), + // a\r + )]; let expected = str![[r#" error: --> file/path:3:1 @@ -404,14 +382,13 @@ error: #[test] fn char_eol_annotate_char() { let source = "a\r\nb"; - let input = Level::ERROR.header("").group( - Group::new().element( - Snippet::source(source) - .path("file/path") - .line_start(3) - .annotation(AnnotationKind::Primary.span(0..3)), - ), // a\r\n - ); + let input = &[Group::new().element(Level::ERROR.title("")).element( + Snippet::source(source) + .path("file/path") + .line_start(3) + .annotation(AnnotationKind::Primary.span(0..3)), + // a\r\n + )]; let expected = str![[r#" error: --> file/path:3:1 @@ -426,13 +403,12 @@ error: #[test] fn char_eol_annotate_char_double_width() { - let snippets = Level::ERROR.header("").group( - Group::new().element( - Snippet::source("こん\r\nにちは\r\n世界") - .path("<current file>") - .annotation(AnnotationKind::Primary.span(3..8)), - ), // ん\r\n - ); + let snippets = &[Group::new().element(Level::ERROR.title("")).element( + Snippet::source("こん\r\nにちは\r\n世界") + .path("<current file>") + .annotation(AnnotationKind::Primary.span(3..8)), + // ん\r\n + )]; let expected = str![[r#" error: @@ -452,14 +428,13 @@ error: #[test] fn annotate_eol() { let source = "a\r\nb"; - let input = Level::ERROR.header("").group( - Group::new().element( - Snippet::source(source) - .path("file/path") - .line_start(3) - .annotation(AnnotationKind::Primary.span(1..2)), - ), // \r - ); + let input = &[Group::new().element(Level::ERROR.title("")).element( + Snippet::source(source) + .path("file/path") + .line_start(3) + .annotation(AnnotationKind::Primary.span(1..2)), + // \r + )]; let expected = str![[r#" error: --> file/path:3:2 @@ -475,14 +450,13 @@ error: #[test] fn annotate_eol2() { let source = "a\r\nb"; - let input = Level::ERROR.header("").group( - Group::new().element( - Snippet::source(source) - .path("file/path") - .line_start(3) - .annotation(AnnotationKind::Primary.span(1..3)), - ), // \r\n - ); + let input = &[Group::new().element(Level::ERROR.title("")).element( + Snippet::source(source) + .path("file/path") + .line_start(3) + .annotation(AnnotationKind::Primary.span(1..3)), + // \r\n + )]; let expected = str![[r#" error: --> file/path:3:2 @@ -499,14 +473,13 @@ error: #[test] fn annotate_eol3() { let source = "a\r\nb"; - let input = Level::ERROR.header("").group( - Group::new().element( - Snippet::source(source) - .path("file/path") - .line_start(3) - .annotation(AnnotationKind::Primary.span(2..3)), - ), // \n - ); + let input = &[Group::new().element(Level::ERROR.title("")).element( + Snippet::source(source) + .path("file/path") + .line_start(3) + .annotation(AnnotationKind::Primary.span(2..3)), + // \n + )]; let expected = str![[r#" error: --> file/path:3:3 @@ -523,14 +496,13 @@ error: #[test] fn annotate_eol4() { let source = "a\r\nb"; - let input = Level::ERROR.header("").group( - Group::new().element( - Snippet::source(source) - .path("file/path") - .line_start(3) - .annotation(AnnotationKind::Primary.span(2..2)), - ), // \n - ); + let input = &[Group::new().element(Level::ERROR.title("")).element( + Snippet::source(source) + .path("file/path") + .line_start(3) + .annotation(AnnotationKind::Primary.span(2..2)), + // \n + )]; let expected = str![[r#" error: --> file/path:3:3 @@ -545,13 +517,12 @@ error: #[test] fn annotate_eol_double_width() { - let snippets = Level::ERROR.header("").group( - Group::new().element( - Snippet::source("こん\r\nにちは\r\n世界") - .path("<current file>") - .annotation(AnnotationKind::Primary.span(7..8)), - ), // \n - ); + let snippets = &[Group::new().element(Level::ERROR.title("")).element( + Snippet::source("こん\r\nにちは\r\n世界") + .path("<current file>") + .annotation(AnnotationKind::Primary.span(7..8)), + // \n + )]; let expected = str![[r#" error: @@ -571,14 +542,13 @@ error: #[test] fn multiline_eol_start() { let source = "a\r\nb"; - let input = Level::ERROR.header("").group( - Group::new().element( - Snippet::source(source) - .path("file/path") - .line_start(3) - .annotation(AnnotationKind::Primary.span(1..4)), - ), // \r\nb - ); + let input = &[Group::new().element(Level::ERROR.title("")).element( + Snippet::source(source) + .path("file/path") + .line_start(3) + .annotation(AnnotationKind::Primary.span(1..4)), + // \r\nb + )]; let expected = str![[r#" error: --> file/path:3:2 @@ -595,14 +565,13 @@ error: #[test] fn multiline_eol_start2() { let source = "a\r\nb"; - let input = Level::ERROR.header("").group( - Group::new().element( - Snippet::source(source) - .path("file/path") - .line_start(3) - .annotation(AnnotationKind::Primary.span(2..4)), - ), // \nb - ); + let input = &[Group::new().element(Level::ERROR.title("")).element( + Snippet::source(source) + .path("file/path") + .line_start(3) + .annotation(AnnotationKind::Primary.span(2..4)), + // \nb + )]; let expected = str![[r#" error: --> file/path:3:3 @@ -619,14 +588,13 @@ error: #[test] fn multiline_eol_start3() { let source = "a\nb"; - let input = Level::ERROR.header("").group( - Group::new().element( - Snippet::source(source) - .path("file/path") - .line_start(3) - .annotation(AnnotationKind::Primary.span(1..3)), - ), // \nb - ); + let input = &[Group::new().element(Level::ERROR.title("")).element( + Snippet::source(source) + .path("file/path") + .line_start(3) + .annotation(AnnotationKind::Primary.span(1..3)), + // \nb + )]; let expected = str![[r#" error: --> file/path:3:2 @@ -642,13 +610,12 @@ error: #[test] fn multiline_eol_start_double_width() { - let snippets = Level::ERROR.header("").group( - Group::new().element( - Snippet::source("こん\r\nにちは\r\n世界") - .path("<current file>") - .annotation(AnnotationKind::Primary.span(7..11)), - ), // \r\nに - ); + let snippets = &[Group::new().element(Level::ERROR.title("")).element( + Snippet::source("こん\r\nにちは\r\n世界") + .path("<current file>") + .annotation(AnnotationKind::Primary.span(7..11)), + // \r\nに + )]; let expected = str![[r#" error: @@ -668,14 +635,13 @@ error: #[test] fn multiline_eol_start_eol_end() { let source = "a\nb\nc"; - let input = Level::ERROR.header("").group( - Group::new().element( - Snippet::source(source) - .path("file/path") - .line_start(3) - .annotation(AnnotationKind::Primary.span(1..4)), - ), // \nb\n - ); + let input = &[Group::new().element(Level::ERROR.title("")).element( + Snippet::source(source) + .path("file/path") + .line_start(3) + .annotation(AnnotationKind::Primary.span(1..4)), + // \nb\n + )]; let expected = str![[r#" error: --> file/path:3:2 @@ -693,14 +659,13 @@ error: #[test] fn multiline_eol_start_eol_end2() { let source = "a\r\nb\r\nc"; - let input = Level::ERROR.header("").group( - Group::new().element( - Snippet::source(source) - .path("file/path") - .line_start(3) - .annotation(AnnotationKind::Primary.span(2..5)), - ), // \nb\r - ); + let input = &[Group::new().element(Level::ERROR.title("")).element( + Snippet::source(source) + .path("file/path") + .line_start(3) + .annotation(AnnotationKind::Primary.span(2..5)), + // \nb\r + )]; let expected = str![[r#" error: --> file/path:3:3 @@ -718,14 +683,13 @@ error: #[test] fn multiline_eol_start_eol_end3() { let source = "a\r\nb\r\nc"; - let input = Level::ERROR.header("").group( - Group::new().element( - Snippet::source(source) - .path("file/path") - .line_start(3) - .annotation(AnnotationKind::Primary.span(2..6)), - ), // \nb\r\n - ); + let input = &[Group::new().element(Level::ERROR.title("")).element( + Snippet::source(source) + .path("file/path") + .line_start(3) + .annotation(AnnotationKind::Primary.span(2..6)), + // \nb\r\n + )]; let expected = str![[r#" error: --> file/path:3:3 @@ -743,14 +707,13 @@ error: #[test] fn multiline_eol_start_eof_end() { let source = "a\r\nb"; - let input = Level::ERROR.header("").group( - Group::new().element( - Snippet::source(source) - .path("file/path") - .line_start(3) - .annotation(AnnotationKind::Primary.span(1..5)), - ), // \r\nb(EOF) - ); + let input = &[Group::new().element(Level::ERROR.title("")).element( + Snippet::source(source) + .path("file/path") + .line_start(3) + .annotation(AnnotationKind::Primary.span(1..5)), + // \r\nb(EOF) + )]; let expected = str![[r#" error: --> file/path:3:2 @@ -767,14 +730,13 @@ error: #[test] fn multiline_eol_start_eof_end_double_width() { let source = "ん\r\nに"; - let input = Level::ERROR.header("").group( - Group::new().element( - Snippet::source(source) - .path("file/path") - .line_start(3) - .annotation(AnnotationKind::Primary.span(3..9)), - ), // \r\nに(EOF) - ); + let input = &[Group::new().element(Level::ERROR.title("")).element( + Snippet::source(source) + .path("file/path") + .line_start(3) + .annotation(AnnotationKind::Primary.span(3..9)), + // \r\nに(EOF) + )]; let expected = str![[r#" error: --> file/path:3:2 @@ -791,8 +753,9 @@ error: #[test] fn two_single_line_same_line() { let source = r#"bar = { version = "0.1.0", optional = true }"#; - let input = Level::ERROR.header("unused optional dependency").group( - Group::new().element( + let input = &[Group::new() + .element(Level::ERROR.title("unused optional dependency")) + .element( Snippet::source(source) .path("Cargo.toml") .line_start(4) @@ -806,8 +769,7 @@ fn two_single_line_same_line() { .span(27..42) .label("This should also be long but not too long"), ), - ), - ); + )]; let expected = str![[r#" error: unused optional dependency --> Cargo.toml:4:1 @@ -828,8 +790,9 @@ this is another line so is this bar = { version = "0.1.0", optional = true } "#; - let input = Level::ERROR.header("unused optional dependency").group( - Group::new().element( + let input = &[Group::new() + .element(Level::ERROR.title("unused optional dependency")) + .element( Snippet::source(source) .line_start(4) .annotation( @@ -842,8 +805,7 @@ bar = { version = "0.1.0", optional = true } .span(27..42) .label("This should also be long but not too long"), ), - ), - ); + )]; let expected = str![[r#" error: unused optional dependency | @@ -867,8 +829,9 @@ this is another line so is this bar = { version = "0.1.0", optional = true } "#; - let input = Level::ERROR.header("unused optional dependency").group( - Group::new().element( + let input = &[Group::new() + .element(Level::ERROR.title("unused optional dependency")) + .element( Snippet::source(source) .line_start(4) .annotation( @@ -886,8 +849,7 @@ bar = { version = "0.1.0", optional = true } .span(27..42) .label("This should also be long but not too long"), ), - ), - ); + )]; let expected = str![[r#" error: unused optional dependency | @@ -915,8 +877,9 @@ so is this bar = { version = "0.1.0", optional = true } this is another line "#; - let input = Level::ERROR.header("unused optional dependency").group( - Group::new().element( + let input = &[Group::new() + .element(Level::ERROR.title("unused optional dependency")) + .element( Snippet::source(source) .line_start(4) .annotation( @@ -939,8 +902,7 @@ this is another line .span(27..42) .label("This should also be long but not too long"), ), - ), - ); + )]; let expected = str![[r#" error: unused optional dependency | @@ -966,14 +928,12 @@ error: unused optional dependency #[test] fn origin_correct_start_line() { let source = "aaa\nbbb\nccc\nddd\n"; - let input = Level::ERROR.header("title").group( - Group::new().element( - Snippet::source(source) - .path("origin.txt") - .fold(false) - .annotation(AnnotationKind::Primary.span(8..8 + 3).label("annotation")), - ), - ); + let input = &[Group::new().element(Level::ERROR.title("title")).element( + Snippet::source(source) + .path("origin.txt") + .fold(false) + .annotation(AnnotationKind::Primary.span(8..8 + 3).label("annotation")), + )]; let expected = str![[r#" error: title @@ -992,18 +952,16 @@ error: title #[test] fn origin_correct_mid_line() { let source = "aaa\nbbb\nccc\nddd\n"; - let input = Level::ERROR.header("title").group( - Group::new().element( - Snippet::source(source) - .path("origin.txt") - .fold(false) - .annotation( - AnnotationKind::Primary - .span(8 + 1..8 + 3) - .label("annotation"), - ), - ), - ); + let input = &[Group::new().element(Level::ERROR.title("title")).element( + Snippet::source(source) + .path("origin.txt") + .fold(false) + .annotation( + AnnotationKind::Primary + .span(8 + 1..8 + 3) + .label("annotation"), + ), + )]; let expected = str![[r#" error: title @@ -1022,33 +980,33 @@ error: title #[test] fn two_suggestions_same_span() { let source = r#" A.foo();"#; - let input_new = Level::ERROR - .header("expected value, found enum `A`") - .id("E0423") - .group( - Group::new().element( + let input_new = &[ + Group::new() + .element( + Level::ERROR + .title("expected value, found enum `A`") + .id("E0423"), + ) + .element( Snippet::source(source) .fold(true) .annotation(AnnotationKind::Primary.span(4..5)), ), - ) - .group( - Group::new() - .element( - Level::HELP - .title("you might have meant to use one of the following enum variants"), - ) - .element( - Snippet::source(source) - .fold(true) - .patch(Patch::new(4..5, "(A::Tuple())")), - ) - .element( - Snippet::source(source) - .fold(true) - .patch(Patch::new(4..5, "A::Unit")), - ), - ); + Group::new() + .element( + Level::HELP.title("you might have meant to use one of the following enum variants"), + ) + .element( + Snippet::source(source) + .fold(true) + .patch(Patch::new(4..5, "(A::Tuple())")), + ) + .element( + Snippet::source(source) + .fold(true) + .patch(Patch::new(4..5, "A::Unit")), + ), + ]; let expected = str![[r#" error[E0423]: expected value, found enum `A` @@ -1090,11 +1048,9 @@ fn main() { banana::Chaenomeles.pick() }"#; let input_new = - Level::ERROR - .header("no method named `pick` found for struct `Chaenomeles` in the current scope") - .id("E0599") - .group( - Group::new().element( + &[Group::new().element(Level::ERROR + .title("no method named `pick` found for struct `Chaenomeles` in the current scope") + .id("E0599")).element( Snippet::source(source) .line_start(1) .fold(true) @@ -1109,8 +1065,6 @@ fn main() { .label("method not found in `Chaenomeles`"), ), ), - ) - .group( Group::new() .element(Level::HELP.title( "the following traits which provide `pick` are implemented but not in scope; perhaps you want to import one of them", @@ -1124,8 +1078,7 @@ fn main() { Snippet::source(source) .fold(true) .patch(Patch::new(1..1, "use banana::Peach;\n")), - ), - ); + )]; let expected = str![[r#" error[E0599]: no method named `pick` found for struct `Chaenomeles` in the current scope | @@ -1150,28 +1103,29 @@ LL + use banana::Peach; fn single_line_non_overlapping_suggestions() { let source = r#" A.foo();"#; - let input_new = Level::ERROR - .header("expected value, found enum `A`") - .id("E0423") - .group( - Group::new().element( + let input_new = &[ + Group::new() + .element( + Level::ERROR + .title("expected value, found enum `A`") + .id("E0423"), + ) + .element( Snippet::source(source) .fold(true) .line_start(1) .annotation(AnnotationKind::Primary.span(4..5)), ), - ) - .group( - Group::new() - .element(Level::HELP.title("make these changes and things will work")) - .element( - Snippet::source(source) - .fold(true) - .fold(true) - .patch(Patch::new(4..5, "(A::Tuple())")) - .patch(Patch::new(6..9, "bar")), - ), - ); + Group::new() + .element(Level::HELP.title("make these changes and things will work")) + .element( + Snippet::source(source) + .fold(true) + .fold(true) + .patch(Patch::new(4..5, "(A::Tuple())")) + .patch(Patch::new(6..9, "bar")), + ), + ]; let expected = str![[r#" error[E0423]: expected value, found enum `A` @@ -1192,28 +1146,25 @@ LL + (A::Tuple()).bar(); #[test] fn single_line_non_overlapping_suggestions2() { let source = r#" ThisIsVeryLong.foo();"#; - let input_new = Level::ERROR - .header("Found `ThisIsVeryLong`") - .id("E0423") - .group( - Group::new().element( + let input_new = &[ + Group::new() + .element(Level::ERROR.title("Found `ThisIsVeryLong`").id("E0423")) + .element( Snippet::source(source) .fold(true) .line_start(1) .annotation(AnnotationKind::Primary.span(4..18)), ), - ) - .group( - Group::new() - .element(Level::HELP.title("make these changes and things will work")) - .element( - Snippet::source(source) - .fold(true) - .fold(true) - .patch(Patch::new(4..18, "(A::Tuple())")) - .patch(Patch::new(19..22, "bar")), - ), - ); + Group::new() + .element(Level::HELP.title("make these changes and things will work")) + .element( + Snippet::source(source) + .fold(true) + .fold(true) + .patch(Patch::new(4..18, "(A::Tuple())")) + .patch(Patch::new(19..22, "bar")), + ), + ]; let expected = str![[r#" error[E0423]: Found `ThisIsVeryLong` @@ -1241,11 +1192,16 @@ fn multiple_replacements() { y(); "#; - let input_new = Level::ERROR - .header("cannot borrow `*self` as mutable because it is also borrowed as immutable") - .id("E0502") - .group( - Group::new().element( + let input_new = &[ + Group::new() + .element( + Level::ERROR + .title( + "cannot borrow `*self` as mutable because it is also borrowed as immutable", + ) + .id("E0502"), + ) + .element( Snippet::source(source) .line_start(1) .fold(true) @@ -1270,21 +1226,18 @@ fn multiple_replacements() { .label("immutable borrow later used here"), ), ), - ) - .group( - Group::new() - .element( - Level::HELP - .title("try explicitly pass `&Self` into the Closure as an argument"), - ) - .element( - Snippet::source(source) - .fold(true) - .patch(Patch::new(14..14, "this: &Self")) - .patch(Patch::new(26..30, "this")) - .patch(Patch::new(66..68, "(self)")), - ), - ); + Group::new() + .element( + Level::HELP.title("try explicitly pass `&Self` into the Closure as an argument"), + ) + .element( + Snippet::source(source) + .fold(true) + .patch(Patch::new(14..14, "this: &Self")) + .patch(Patch::new(26..30, "this")) + .patch(Patch::new(66..68, "(self)")), + ), + ]; let expected = str![[r#" error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable | @@ -1325,11 +1278,9 @@ fn main() { test1(); }"#; - let input_new = Level::ERROR - .header("cannot borrow `chars` as mutable more than once at a time") - .id("E0499") - .group( - Group::new().element( + let input_new = &[Group::new().element(Level::ERROR + .title("cannot borrow `chars` as mutable more than once at a time") + .id("E0499")).element( Snippet::source(source) .line_start(1) .fold(true) @@ -1349,8 +1300,6 @@ fn main() { .label("first borrow later used here"), ), ), - ) - .group( Group::new() .element( Level::HELP @@ -1365,8 +1314,7 @@ fn main() { )) .patch(Patch::new(61..79, ") = iter.next()")) .patch(Patch::new(90..95, "iter")), - ), - ); + )]; let expected = str![[r#" error[E0499]: cannot borrow `chars` as mutable more than once at a time @@ -1409,45 +1357,42 @@ struct Foo { fn main() {}"#; - let input_new = Level::ERROR - .header("failed to resolve: use of undeclared crate or module `st`") - .id("E0433") - .group( - Group::new().element( + let input_new = &[ + Group::new() + .element( + Level::ERROR + .title("failed to resolve: use of undeclared crate or module `st`") + .id("E0433"), + ) + .element( Snippet::source(source).line_start(1).fold(true).annotation( AnnotationKind::Primary .span(122..124) .label("use of undeclared crate or module `st`"), ), ), - ) - .group( - Group::new() - .element(Level::HELP.title("there is a crate or module with a similar name")) - .element( - Snippet::source(source) - .fold(true) - .patch(Patch::new(122..124, "std")), - ), - ) - .group( - Group::new() - .element(Level::HELP.title("consider importing this module")) - .element( - Snippet::source(source) - .fold(true) - .patch(Patch::new(1..1, "use std::cell;\n")), - ), - ) - .group( - Group::new() - .element(Level::HELP.title("if you import `cell`, refer to it directly")) - .element( - Snippet::source(source) - .fold(true) - .patch(Patch::new(122..126, "")), - ), - ); + Group::new() + .element(Level::HELP.title("there is a crate or module with a similar name")) + .element( + Snippet::source(source) + .fold(true) + .patch(Patch::new(122..124, "std")), + ), + Group::new() + .element(Level::HELP.title("consider importing this module")) + .element( + Snippet::source(source) + .fold(true) + .patch(Patch::new(1..1, "use std::cell;\n")), + ), + Group::new() + .element(Level::HELP.title("if you import `cell`, refer to it directly")) + .element( + Snippet::source(source) + .fold(true) + .patch(Patch::new(122..126, "")), + ), + ]; let expected = str![[r#" error[E0433]: failed to resolve: use of undeclared crate or module `st` | @@ -1491,11 +1436,14 @@ where fn main() {}"#; - let input_new = Level::ERROR - .header("the size for values of type `T` cannot be known at compilation time") - .id("E0277") - .group( - Group::new().element( + let input_new = &[ + Group::new() + .element( + Level::ERROR + .title("the size for values of type `T` cannot be known at compilation time") + .id("E0277"), + ) + .element( Snippet::source(source) .line_start(1) .fold(true) @@ -1510,18 +1458,18 @@ fn main() {}"#; .label("this type parameter needs to be `Sized`"), ), ), - ) - .group( - Group::new() - .element(Level::HELP.title( + Group::new() + .element( + Level::HELP.title( "consider removing the `?Sized` bound to make the type parameter `Sized`", - )) - .element( - Snippet::source(source) - .fold(true) - .patch(Patch::new(52..85, "")), ), - ); + ) + .element( + Snippet::source(source) + .fold(true) + .patch(Patch::new(52..85, "")), + ), + ]; let expected = str![[r#" error[E0277]: the size for values of type `T` cannot be known at compilation time | @@ -1560,10 +1508,9 @@ and where } fn main() {}"#; - let input_new = Level::ERROR - .header("the size for values of type `T` cannot be known at compilation time") - .id("E0277") - .group(Group::new().element(Snippet::source(source) + let input_new = &[Group::new().element(Level::ERROR + .title("the size for values of type `T` cannot be known at compilation time") + .id("E0277")).element(Snippet::source(source) .line_start(1) .path("$DIR/removal-of-multiline-trait-bound-in-where-clause.rs") .fold(true) @@ -1576,8 +1523,8 @@ fn main() {}"#; AnnotationKind::Context .span(31..32) .label("this type parameter needs to be `Sized`"), - ))) - .group(Group::new().element( + )) + ,Group::new().element( Level::NOTE .title("required by an implicit `Sized` bound in `Wrapper`") ).element( @@ -1590,8 +1537,7 @@ fn main() {}"#; .span(16..17) .label("required by the implicit `Sized` requirement on this type parameter in `Wrapper`"), ) - )) - .group(Group::new().element( + ), Group::new().element( Level::HELP .title("you could relax the implicit `Sized` bound on `T` if it were used through indirection like `&T` or `Box<T>`") ) @@ -1611,8 +1557,7 @@ fn main() {}"#; .label("...if indirection were used here: `Box<T>`"), ) - )) - .group(Group::new().element( + ),Group::new().element( Level::HELP .title("consider removing the `?Sized` bound to make the type parameter `Sized`") ).element( @@ -1621,7 +1566,7 @@ fn main() {}"#; .patch(Patch::new(56..89, "")) .patch(Patch::new(89..89, "+ Send")) , - )); + )]; let expected = str![[r#" error[E0277]: the size for values of type `T` cannot be known at compilation time --> $DIR/removal-of-multiline-trait-bound-in-where-clause.rs:4:16 @@ -1669,12 +1614,14 @@ quack zappy "#; - let input_new = Level::ERROR - .header("the size for values of type `T` cannot be known at compilation time") - .id("E0277") - // We need an empty group here to ensure the HELP line is rendered correctly - .group(Group::new()) - .group( + let input_new = + &[ + Group::new().element( + Level::ERROR + .title("the size for values of type `T` cannot be known at compilation time") + .id("E0277"), + ), + // We need an empty group here to ensure the HELP line is rendered correctly Group::new() .element(Level::HELP.title( "consider removing the `?Sized` bound to make the type parameter `Sized`", @@ -1686,7 +1633,7 @@ zappy .patch(Patch::new(3..21, "")) .patch(Patch::new(22..40, "")), ), - ); + ]; let expected = str![[r#" error[E0277]: the size for values of type `T` cannot be known at compilation time | @@ -1739,10 +1686,9 @@ fn main() { } "#; - let input_new = Level::ERROR - .header("type mismatch resolving `<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ...>>, ...>>, ...> as Future>::Error == Foo`") - .id("E0271") - .group(Group::new().element(Snippet::source(source) + let input_new = &[Group::new().element(Level::ERROR + .title("type mismatch resolving `<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ...>>, ...>>, ...> as Future>::Error == Foo`") + .id("E0271")).element(Snippet::source(source) .line_start(4) .path("$DIR/E0271.rs") .fold(true) @@ -1750,8 +1696,7 @@ fn main() { AnnotationKind::Primary .span(208..510) .label("type mismatch resolving `<Result<Result<(), Result<Result<(), ...>, ...>>, ...> as Future>::Error == Foo`"), - ))) - .group(Group::new().element( + )),Group::new().element( Level::NOTE.title("expected this to be `Foo`") ).element( Snippet::source(source) @@ -1763,7 +1708,7 @@ fn main() { Level::NOTE .title("required for the cast from `Box<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ()>>, ()>>, ()>>` to `Box<(dyn Future<Error = Foo> + 'static)>`") , - )); + )]; let expected = str![[r#" error[E0271]: type mismatch resolving `<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ...>>, ...>>, ...> as Future>::Error == Foo` @@ -1827,10 +1772,9 @@ fn main() { } "#; - let input_new = Level::ERROR - .header("type mismatch resolving `<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ...>>, ...>>, ...> as Future>::Error == Foo`") - .id("E0271") - .group(Group::new().element(Snippet::source(source) + let input_new = &[Group::new().element(Level::ERROR + .title("type mismatch resolving `<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ...>>, ...>>, ...> as Future>::Error == Foo`") + .id("E0271")).element(Snippet::source(source) .line_start(4) .path("$DIR/E0271.rs") .fold(true) @@ -1838,8 +1782,7 @@ fn main() { AnnotationKind::Primary .span(208..510) .label("type mismatch resolving `<Result<Result<(), Result<Result<(), ...>, ...>>, ...> as Future>::Error == Foo`"), - ))) - .group(Group::new().element( + )),Group::new().element( Level::NOTE.title("expected this to be `Foo`") ).element( Snippet::source(source) @@ -1852,7 +1795,7 @@ fn main() { .title("required for the cast from `Box<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ()>>, ()>>, ()>>` to `Box<(dyn Future<Error = Foo> + 'static)>`") ).element( Level::NOTE.title("a second note"), - )); + )]; let expected = str![[r#" error[E0271]: type mismatch resolving `<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ...>>, ...>>, ...> as Future>::Error == Foo` @@ -1928,7 +1871,7 @@ fn main() { Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok( Ok("") )))))))))))))))))))))))))))))) - )))))))))))))))))))))))))))))); + )))))))))))))))))))))))))))))]; //~^^^^^ ERROR E0308 let _ = Some(Ok(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some(Some( @@ -1941,7 +1884,7 @@ fn main() { Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok( Ok(Ok(Ok(Ok(Ok(Ok(Ok(""))))))) )))))))))))))))))))))))))))))) - )))))))))))))))))))))))); + )))))))))))))))))))))))]; //~^^^^^ ERROR E0308 let x: Atype< @@ -1975,15 +1918,14 @@ fn main() { Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok( Ok(Ok(Ok(Ok(Ok(Ok(Ok(""))))))) )))))))))))))))))))))))))))))) - )))))))))))))))))))))))); + )))))))))))))))))))))))]; //~^^^^^ ERROR E0308 } "#; - let input_new = Level::ERROR - .header("mismatched types") - .id("E0308") - .group(Group::new().element( + let input_new = &[Group::new().element(Level::ERROR + .title("mismatched types") + .id("E0308")).element( Snippet::source(source) .line_start(7) .path("$DIR/long-E0308.rs") @@ -2008,7 +1950,7 @@ fn main() { Level::NOTE .title("consider using `--verbose` to print the full type name to the console") , - )); + )]; let expected = str![[r#" error[E0308]: mismatched types @@ -2028,7 +1970,7 @@ LL │ │ > = Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(O… LL │ ┃ Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(Ok(O… LL │ ┃ Ok("") LL │ ┃ )))))))))))))))))))))))))))))) -LL │ ┃ )))))))))))))))))))))))))))))); +LL │ ┃ )))))))))))))))))))))))))))))]; │ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ expected `Atype<Btype<Ctype<..., i32>, i32>, i32>`, found `Result<Result<Result<..., _>, _>, _>` │ ├ note: expected struct `Atype<Btype<..., i32>, i32>` @@ -2065,10 +2007,9 @@ fn main() { } "#; - let input_new = Level::ERROR - .header("mismatched types") - .id("E0308") - .group(Group::new().element( + let input_new = &[Group::new().element(Level::ERROR + .title("mismatched types") + .id("E0308")).element( Snippet::source(source) .line_start(7) .path("$DIR/unicode-output.rs") @@ -2087,8 +2028,7 @@ fn main() { Level::NOTE .title("expected fn pointer `for<'a> fn(Box<(dyn Any + Send + 'a)>) -> Pin<_>`\n found fn item `fn(Box<(dyn Any + Send + 'static)>) -> Pin<_> {wrapped_fn}`") , - )) - .group(Group::new().element( + ),Group::new().element( Level::NOTE.title("function defined here"), ).element( Snippet::source(source) @@ -2097,7 +2037,7 @@ fn main() { .fold(true) .annotation(AnnotationKind::Primary.span(77..210)) .annotation(AnnotationKind::Context.span(71..76)), - )); + )]; let expected = str![[r#" error[E0308]: mismatched types @@ -2134,13 +2074,11 @@ LL │ ┃ )>>) {} #[test] fn unicode_cut_handling() { let source = "version = \"0.1.0\"\n# Ensure that the spans from toml handle utf-8 correctly\nauthors = [\n { name = \"Z\u{351}\u{36b}\u{343}\u{36a}\u{302}\u{36b}\u{33d}\u{34f}\u{334}\u{319}\u{324}\u{31e}\u{349}\u{35a}\u{32f}\u{31e}\u{320}\u{34d}A\u{36b}\u{357}\u{334}\u{362}\u{335}\u{31c}\u{330}\u{354}L\u{368}\u{367}\u{369}\u{358}\u{320}G\u{311}\u{357}\u{30e}\u{305}\u{35b}\u{341}\u{334}\u{33b}\u{348}\u{34d}\u{354}\u{339}O\u{342}\u{30c}\u{30c}\u{358}\u{328}\u{335}\u{339}\u{33b}\u{31d}\u{333}\", email = 1 }\n]\n"; - let input = Level::ERROR.header("title").group( - Group::new().element( - Snippet::source(source) - .fold(false) - .annotation(AnnotationKind::Primary.span(85..228).label("annotation")), - ), - ); + let input = &[Group::new().element(Level::ERROR.title("title")).element( + Snippet::source(source) + .fold(false) + .annotation(AnnotationKind::Primary.span(85..228).label("annotation")), + )]; let expected_ascii = str![[r#" error: title | @@ -2153,7 +2091,7 @@ error: title | |_^ annotation "#]]; let renderer_ascii = Renderer::plain(); - assert_data_eq!(renderer_ascii.render(input.clone()), expected_ascii); + assert_data_eq!(renderer_ascii.render(input), expected_ascii); let expected_unicode = str![[r#" error: title @@ -2173,17 +2111,15 @@ error: title #[test] fn unicode_cut_handling2() { let source = "/*这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?"; - let input = Level::ERROR - .header("expected item, found `?`") - .group( - Group::new().element( + let input = &[Group::new().element(Level::ERROR + .title("expected item, found `?`")).element( Snippet::source(source) .fold(false) .annotation(AnnotationKind::Primary.span(499..500).label("expected item")) ).element( Level::NOTE.title("for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>") - ) - ); + + )]; let expected_ascii = str![[r#" error: expected item, found `?` @@ -2195,7 +2131,7 @@ error: expected item, found `?` "#]]; let renderer_ascii = Renderer::plain(); - assert_data_eq!(renderer_ascii.render(input.clone()), expected_ascii); + assert_data_eq!(renderer_ascii.render(input), expected_ascii); let expected_unicode = str![[r#" error: expected item, found `?` @@ -2212,17 +2148,15 @@ error: expected item, found `?` #[test] fn unicode_cut_handling3() { let source = "/*这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?"; - let input = Level::ERROR - .header("expected item, found `?`") - .group( - Group::new().element( + let input = &[Group::new().element(Level::ERROR + .title("expected item, found `?`")).element( Snippet::source(source) .fold(false) .annotation(AnnotationKind::Primary.span(251..254).label("expected item")) ).element( Level::NOTE.title("for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>") - ) - ); + + )]; let expected_ascii = str![[r#" error: expected item, found `?` @@ -2234,7 +2168,7 @@ error: expected item, found `?` "#]]; let renderer_ascii = Renderer::plain().term_width(43); - assert_data_eq!(renderer_ascii.render(input.clone()), expected_ascii); + assert_data_eq!(renderer_ascii.render(input), expected_ascii); let expected_unicode = str![[r#" error: expected item, found `?` @@ -2251,17 +2185,15 @@ error: expected item, found `?` #[test] fn unicode_cut_handling4() { let source = "/*aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/?"; - let input = Level::ERROR - .header("expected item, found `?`") - .group( - Group::new().element( + let input = &[Group::new().element(Level::ERROR + .title("expected item, found `?`")).element( Snippet::source(source) .fold(false) .annotation(AnnotationKind::Primary.span(334..335).label("expected item")) ).element( Level::NOTE.title("for a full list of items that can appear in modules, see <https://doc.rust-lang.org/reference/items.html>") - ) - ); + + )]; let expected_ascii = str![[r#" error: expected item, found `?` @@ -2273,7 +2205,7 @@ error: expected item, found `?` "#]]; let renderer_ascii = Renderer::plain(); - assert_data_eq!(renderer_ascii.render(input.clone()), expected_ascii); + assert_data_eq!(renderer_ascii.render(input), expected_ascii); let expected_unicode = str![[r#" error: expected item, found `?` @@ -2296,8 +2228,9 @@ fn main() { //~^ ERROR mismatched types } "##; - let input = Level::ERROR.header("mismatched types").id("E0308").group( - Group::new().element( + let input = &[Group::new() + .element(Level::ERROR.title("mismatched types").id("E0308")) + .element( Snippet::source(source) .path("$DIR/non-whitespace-trimming-unicode.rs") .fold(true) @@ -2311,8 +2244,7 @@ fn main() { .span(1202..1204) .label("expected due to this"), ), - ), - ); + )]; let expected_ascii = str![[r#" error[E0308]: mismatched types @@ -2325,7 +2257,7 @@ LL | ...♧♨♩♪♫♬♭♮♯♰♱♲♳♴♵♶♷♸♹♺♻♼♽♾ "#]]; let renderer_ascii = Renderer::plain().anonymized_line_numbers(true); - assert_data_eq!(renderer_ascii.render(input.clone()), expected_ascii); + assert_data_eq!(renderer_ascii.render(input), expected_ascii); let expected_unicode = str![[r#" error[E0308]: mismatched types @@ -2352,38 +2284,37 @@ fn main() { //[ascii]~^ ERROR cannot add `&str` to `&str` } "##; - let input = Level::ERROR - .header("cannot add `&str` to `&str`") - .id("E0369") - .group( - Group::new() - .element( - Snippet::source(source) - .path("$DIR/non-1-width-unicode-multiline-label.rs") - .fold(true) - .annotation(AnnotationKind::Context.span(970..984).label("&str")) - .annotation(AnnotationKind::Context.span(987..1001).label("&str")) - .annotation( - AnnotationKind::Primary - .span(985..986) - .label("`+` cannot be used to concatenate two `&str` strings"), - ), - ) - .element( - Level::NOTE - .title("string concatenation requires an owned `String` on the left"), - ), - ) - .group( - Group::new() - .element(Level::HELP.title("create an owned `String` from a string reference")) - .element( - Snippet::source(source) - .path("$DIR/non-1-width-unicode-multiline-label.rs") - .fold(true) - .patch(Patch::new(984..984, ".to_owned()")), - ), - ); + let input = &[ + Group::new() + .element( + Level::ERROR + .title("cannot add `&str` to `&str`") + .id("E0369"), + ) + .element( + Snippet::source(source) + .path("$DIR/non-1-width-unicode-multiline-label.rs") + .fold(true) + .annotation(AnnotationKind::Context.span(970..984).label("&str")) + .annotation(AnnotationKind::Context.span(987..1001).label("&str")) + .annotation( + AnnotationKind::Primary + .span(985..986) + .label("`+` cannot be used to concatenate two `&str` strings"), + ), + ) + .element( + Level::NOTE.title("string concatenation requires an owned `String` on the left"), + ), + Group::new() + .element(Level::HELP.title("create an owned `String` from a string reference")) + .element( + Snippet::source(source) + .path("$DIR/non-1-width-unicode-multiline-label.rs") + .fold(true) + .patch(Patch::new(984..984, ".to_owned()")), + ), + ]; let expected_ascii = str![[r#" error[E0369]: cannot add `&str` to `&str` @@ -2403,7 +2334,7 @@ LL | let _ = "ༀ༁༂༃༄༅༆༇༈༉༊་༌།༎༏༐༑༒༓ "#]]; let renderer_ascii = Renderer::plain().anonymized_line_numbers(true); - assert_data_eq!(renderer_ascii.render(input.clone()), expected_ascii); + assert_data_eq!(renderer_ascii.render(input), expected_ascii); let expected_unicode = str![[r#" error[E0369]: cannot add `&str` to `&str` @@ -2437,17 +2368,13 @@ fn foo() { } "##; let bin_source = "�|�\u{0002}!5�cc\u{0015}\u{0002}�Ӻi��WWj�ȥ�'�}�\u{0012}�J�ȉ��W�\u{001e}O�@����\u{001c}w�V���LO����\u{0014}[ \u{0003}_�'���SQ�~ذ��ų&��-\t��lN~��!@␌ _#���kQ��h�\u{001d}�:�\u{001c}\u{0007}�"; - let input = Level::ERROR - .header("couldn't read `$DIR/not-utf8.bin`: stream did not contain valid UTF-8") - .group( - Group::new().element( + let input = &[Group::new().element(Level::ERROR + .title("couldn't read `$DIR/not-utf8.bin`: stream did not contain valid UTF-8")).element( Snippet::source(source) .path("$DIR/not-utf8.rs") .fold(true) .annotation(AnnotationKind::Primary.span(136..160)), ), - ) - .group( Group::new() .element(Level::NOTE.title("byte `193` is not valid utf-8")) .element( @@ -2457,7 +2384,7 @@ fn foo() { .annotation(AnnotationKind::Primary.span(0..0)), ) .element(Level::NOTE.title("this error originates in the macro `include` (in Nightly builds, run with -Z macro-backtrace for more info)")), - ); + ]; let expected_ascii = str![[r#" error: couldn't read `$DIR/not-utf8.bin`: stream did not contain valid UTF-8 @@ -2475,7 +2402,7 @@ LL | �|�␂!5�cc␕␂�Ӻi��WWj�ȥ�'�}�␒�J�ȉ��W "#]]; let renderer_ascii = Renderer::plain().anonymized_line_numbers(true); - assert_data_eq!(renderer_ascii.render(input.clone()), expected_ascii); + assert_data_eq!(renderer_ascii.render(input), expected_ascii); let expected_unicode = str![[r#" error: couldn't read `$DIR/not-utf8.bin`: stream did not contain valid UTF-8 @@ -2502,29 +2429,28 @@ fn secondary_title_no_level_text() { let s: &str = include_bytes!("file.txt"); //~ ERROR mismatched types }"#; - let input = Level::ERROR.header("mismatched types").id("E0308").group( - Group::new() - .element( - Snippet::source(source) - .path("$DIR/mismatched-types.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(105..131) - .label("expected `&str`, found `&[u8; 0]`"), - ) - .annotation( - AnnotationKind::Context - .span(98..102) - .label("expected due to this"), - ), - ) - .element( - Level::NOTE - .text(None) - .title("expected reference `&str`\nfound reference `&'static [u8; 0]`"), - ), - ); + let input = &[Group::new() + .element(Level::ERROR.title("mismatched types").id("E0308")) + .element( + Snippet::source(source) + .path("$DIR/mismatched-types.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(105..131) + .label("expected `&str`, found `&[u8; 0]`"), + ) + .annotation( + AnnotationKind::Context + .span(98..102) + .label("expected due to this"), + ), + ) + .element( + Level::NOTE + .text(None) + .title("expected reference `&str`\nfound reference `&'static [u8; 0]`"), + )]; let expected = str![[r#" error[E0308]: mismatched types @@ -2548,29 +2474,28 @@ fn secondary_title_custom_level_text() { let s: &str = include_bytes!("file.txt"); //~ ERROR mismatched types }"#; - let input = Level::ERROR.header("mismatched types").id("E0308").group( - Group::new() - .element( - Snippet::source(source) - .path("$DIR/mismatched-types.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(105..131) - .label("expected `&str`, found `&[u8; 0]`"), - ) - .annotation( - AnnotationKind::Context - .span(98..102) - .label("expected due to this"), - ), - ) - .element( - Level::NOTE - .text(Some("custom")) - .title("expected reference `&str`\nfound reference `&'static [u8; 0]`"), - ), - ); + let input = &[Group::new() + .element(Level::ERROR.title("mismatched types").id("E0308")) + .element( + Snippet::source(source) + .path("$DIR/mismatched-types.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(105..131) + .label("expected `&str`, found `&[u8; 0]`"), + ) + .annotation( + AnnotationKind::Context + .span(98..102) + .label("expected due to this"), + ), + ) + .element( + Level::NOTE + .text(Some("custom")) + .title("expected reference `&str`\nfound reference `&'static [u8; 0]`"), + )]; let expected = str![[r#" error[E0308]: mismatched types @@ -2617,11 +2542,14 @@ fn main() { } } "#; - let input = Level::ERROR - .header("`break` with value from a `while` loop") - .id("E0571") - .group( - Group::new().element( + let input = &[ + Group::new() + .element( + Level::ERROR + .title("`break` with value from a `while` loop") + .id("E0571"), + ) + .element( Snippet::source(source) .line_start(1) .path("$DIR/issue-114529-illegal-break-with-value.rs") @@ -2637,23 +2565,21 @@ fn main() { .label("you can't `break` with a value in a `while` loop"), ), ), - ) - .group( - Group::new() - .element( - Level::HELP - .text(Some("suggestion")) - .title("use `break` on its own without a value inside this `while` loop") - .id("S0123"), - ) - .element( - Snippet::source(source) - .line_start(1) - .path("$DIR/issue-114529-illegal-break-with-value.rs") - .fold(true) - .patch(Patch::new(483..581, "break")), - ), - ); + Group::new() + .element( + Level::HELP + .text(Some("suggestion")) + .title("use `break` on its own without a value inside this `while` loop") + .id("S0123"), + ) + .element( + Snippet::source(source) + .line_start(1) + .path("$DIR/issue-114529-illegal-break-with-value.rs") + .fold(true) + .patch(Patch::new(483..581, "break")), + ), + ]; let expected_ascii = str![[r#" error[E0571]: `break` with value from a `while` loop @@ -2676,7 +2602,7 @@ LL + break; "#]]; let renderer_ascii = Renderer::plain().anonymized_line_numbers(true); - assert_data_eq!(renderer_ascii.render(input.clone()), expected_ascii); + assert_data_eq!(renderer_ascii.render(input), expected_ascii); let expected_unicode = str![[r#" error[E0571]: `break` with value from a `while` loop diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index c5e0602f..de5f615f 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -13,15 +13,13 @@ fn ends_on_col0() { fn foo() { } "#; - let input = Level::ERROR.header("foo").group( - Group::new().element( - Snippet::source(source) - .line_start(1) - .path("test.rs") - .fold(true) - .annotation(AnnotationKind::Primary.span(10..13).label("test")), - ), - ); + let input = &[Group::new().element(Level::ERROR.title("foo")).element( + Snippet::source(source) + .line_start(1) + .path("test.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(10..13).label("test")), + )]; let expected = str![[r#" error: foo @@ -43,16 +41,13 @@ fn foo() { } "#; - let input = Level::ERROR.header("foo").group( - Group::new().element( - Snippet::source(source) - .line_start(1) - .path("test.rs") - .fold(true) - .annotation(AnnotationKind::Primary.span(10..17).label("test")), - ), - ); - + let input = &[Group::new().element(Level::ERROR.title("foo")).element( + Snippet::source(source) + .line_start(1) + .path("test.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(10..17).label("test")), + )]; let expected = str![[r#" error: foo --> test.rs:2:10 @@ -75,24 +70,22 @@ fn foo() { X2 Y2 } "#; - let input = Level::ERROR.header("foo").group( - Group::new().element( - Snippet::source(source) - .line_start(1) - .path("test.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(14..32) - .label("`X` is a good letter"), - ) - .annotation( - AnnotationKind::Context - .span(17..35) - .label("`Y` is a good letter too"), - ), - ), - ); + let input = &[Group::new().element(Level::ERROR.title("foo")).element( + Snippet::source(source) + .line_start(1) + .path("test.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(14..32) + .label("`X` is a good letter"), + ) + .annotation( + AnnotationKind::Context + .span(17..35) + .label("`Y` is a good letter too"), + ), + )]; let expected = str![[r#" error: foo @@ -118,24 +111,22 @@ fn foo() { Y1 X1 } "#; - let input = Level::ERROR.header("foo").group( - Group::new().element( - Snippet::source(source) - .line_start(1) - .path("test.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(14..27) - .label("`X` is a good letter"), - ) - .annotation( - AnnotationKind::Context - .span(17..24) - .label("`Y` is a good letter too"), - ), - ), - ); + let input = &[Group::new().element(Level::ERROR.title("foo")).element( + Snippet::source(source) + .line_start(1) + .path("test.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(14..27) + .label("`X` is a good letter"), + ) + .annotation( + AnnotationKind::Context + .span(17..24) + .label("`Y` is a good letter too"), + ), + )]; let expected = str![[r#" error: foo @@ -162,24 +153,22 @@ fn foo() { X3 Y3 Z3 } "#; - let input = Level::ERROR.header("foo").group( - Group::new().element( - Snippet::source(source) - .line_start(1) - .path("test.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(17..38) - .label("`X` is a good letter"), - ) - .annotation( - AnnotationKind::Context - .span(31..49) - .label("`Y` is a good letter too"), - ), - ), - ); + let input = &[Group::new().element(Level::ERROR.title("foo")).element( + Snippet::source(source) + .line_start(1) + .path("test.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(17..38) + .label("`X` is a good letter"), + ) + .annotation( + AnnotationKind::Context + .span(31..49) + .label("`Y` is a good letter too"), + ), + )]; let expected = str![[r#" error: foo @@ -206,25 +195,23 @@ fn foo() { X2 Y2 Z2 } "#; - let input = Level::ERROR.header("foo").group( - Group::new().element( - Snippet::source(source) - .line_start(1) - .path("test.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(14..38) - .label("`X` is a good letter"), - ) - .annotation( - AnnotationKind::Context - .span(17..41) - .label("`Y` is a good letter too"), - ) - .annotation(AnnotationKind::Context.span(20..44).label("`Z` label")), - ), - ); + let input = &[Group::new().element(Level::ERROR.title("foo")).element( + Snippet::source(source) + .line_start(1) + .path("test.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(14..38) + .label("`X` is a good letter"), + ) + .annotation( + AnnotationKind::Context + .span(17..41) + .label("`Y` is a good letter too"), + ) + .annotation(AnnotationKind::Context.span(20..44).label("`Z` label")), + )]; let expected = str![[r#" error: foo @@ -253,25 +240,23 @@ fn foo() { X2 Y2 Z2 } "#; - let input = Level::ERROR.header("foo").group( - Group::new().element( - Snippet::source(source) - .line_start(1) - .path("test.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(14..38) - .label("`X` is a good letter"), - ) - .annotation( - AnnotationKind::Context - .span(14..38) - .label("`Y` is a good letter too"), - ) - .annotation(AnnotationKind::Context.span(14..38).label("`Z` label")), - ), - ); + let input = &[Group::new().element(Level::ERROR.title("foo")).element( + Snippet::source(source) + .line_start(1) + .path("test.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(14..38) + .label("`X` is a good letter"), + ) + .annotation( + AnnotationKind::Context + .span(14..38) + .label("`Y` is a good letter too"), + ) + .annotation(AnnotationKind::Context.span(14..38).label("`Z` label")), + )]; // This should have a `^` but we currently don't support the idea of a // "primary" annotation, which would solve this @@ -301,25 +286,23 @@ fn foo() { X3 Y3 Z3 } "#; - let input = Level::ERROR.header("foo").group( - Group::new().element( - Snippet::source(source) - .line_start(1) - .path("test.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(17..27) - .label("`X` is a good letter"), - ) - .annotation( - AnnotationKind::Context - .span(28..44) - .label("`Y` is a good letter too"), - ) - .annotation(AnnotationKind::Context.span(36..52).label("`Z`")), - ), - ); + let input = &[Group::new().element(Level::ERROR.title("foo")).element( + Snippet::source(source) + .line_start(1) + .path("test.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(17..27) + .label("`X` is a good letter"), + ) + .annotation( + AnnotationKind::Context + .span(28..44) + .label("`Y` is a good letter too"), + ) + .annotation(AnnotationKind::Context.span(36..52).label("`Z`")), + )]; let expected = str![[r#" error: foo @@ -351,24 +334,22 @@ fn foo() { X3 Y3 Z3 } "#; - let input = Level::ERROR.header("foo").group( - Group::new().element( - Snippet::source(source) - .line_start(1) - .path("test.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(14..27) - .label("`X` is a good letter"), - ) - .annotation( - AnnotationKind::Context - .span(39..55) - .label("`Y` is a good letter too"), - ), - ), - ); + let input = &[Group::new().element(Level::ERROR.title("foo")).element( + Snippet::source(source) + .line_start(1) + .path("test.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(14..27) + .label("`X` is a good letter"), + ) + .annotation( + AnnotationKind::Context + .span(39..55) + .label("`Y` is a good letter too"), + ), + )]; let expected = str![[r#" error: foo @@ -395,24 +376,22 @@ fn foo() { X3 Y3 Z3 } "#; - let input = Level::ERROR.header("foo").group( - Group::new().element( - Snippet::source(source) - .line_start(1) - .path("test.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(17..27) - .label("`X` is a good letter"), - ) - .annotation( - AnnotationKind::Context - .span(31..55) - .label("`Y` is a good letter too"), - ), - ), - ); + let input = &[Group::new().element(Level::ERROR.title("foo")).element( + Snippet::source(source) + .line_start(1) + .path("test.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(17..27) + .label("`X` is a good letter"), + ) + .annotation( + AnnotationKind::Context + .span(31..55) + .label("`Y` is a good letter too"), + ), + )]; let expected = str![[r#" error: foo @@ -438,21 +417,19 @@ fn foo() { a { b { c } d } } "#; - let input = Level::ERROR.header("foo").group( - Group::new().element( - Snippet::source(source) - .line_start(1) - .path("test.rs") - .fold(true) - .annotation(AnnotationKind::Primary.span(18..25).label("")) - .annotation( - AnnotationKind::Context - .span(14..27) - .label("`a` is a good letter"), - ) - .annotation(AnnotationKind::Context.span(22..23).label("")), - ), - ); + let input = &[Group::new().element(Level::ERROR.title("foo")).element( + Snippet::source(source) + .line_start(1) + .path("test.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(18..25).label("")) + .annotation( + AnnotationKind::Context + .span(14..27) + .label("`a` is a good letter"), + ) + .annotation(AnnotationKind::Context.span(22..23).label("")), + )]; let expected = str![[r#" error: foo @@ -471,20 +448,18 @@ fn foo() { a { b { c } d } } "#; - let input = Level::ERROR.header("foo").group( - Group::new().element( - Snippet::source(source) - .line_start(1) - .path("test.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(14..27) - .label("`a` is a good letter"), - ) - .annotation(AnnotationKind::Context.span(18..25).label("")), - ), - ); + let input = &[Group::new().element(Level::ERROR.title("foo")).element( + Snippet::source(source) + .line_start(1) + .path("test.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(14..27) + .label("`a` is a good letter"), + ) + .annotation(AnnotationKind::Context.span(18..25).label("")), + )]; let expected = str![[r#" error: foo @@ -503,21 +478,19 @@ fn foo() { a { b { c } d } } "#; - let input = Level::ERROR.header("foo").group( - Group::new().element( - Snippet::source(source) - .line_start(1) - .path("test.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(18..25) - .label("`b` is a good letter"), - ) - .annotation(AnnotationKind::Context.span(14..27).label("")) - .annotation(AnnotationKind::Context.span(22..23).label("")), - ), - ); + let input = &[Group::new().element(Level::ERROR.title("foo")).element( + Snippet::source(source) + .line_start(1) + .path("test.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(18..25) + .label("`b` is a good letter"), + ) + .annotation(AnnotationKind::Context.span(14..27).label("")) + .annotation(AnnotationKind::Context.span(22..23).label("")), + )]; let expected = str![[r#" error: foo @@ -538,20 +511,18 @@ fn foo() { a { b { c } d } } "#; - let input = Level::ERROR.header("foo").group( - Group::new().element( - Snippet::source(source) - .line_start(1) - .path("test.rs") - .fold(true) - .annotation(AnnotationKind::Primary.span(14..27).label("")) - .annotation( - AnnotationKind::Context - .span(18..25) - .label("`b` is a good letter"), - ), - ), - ); + let input = &[Group::new().element(Level::ERROR.title("foo")).element( + Snippet::source(source) + .line_start(1) + .path("test.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(14..27).label("")) + .annotation( + AnnotationKind::Context + .span(18..25) + .label("`b` is a good letter"), + ), + )]; let expected = str![[r#" error: foo @@ -572,20 +543,18 @@ fn foo() { a bc d } "#; - let input = Level::ERROR.header("foo").group( - Group::new().element( - Snippet::source(source) - .line_start(1) - .path("test.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(14..18) - .label("`a` is a good letter"), - ) - .annotation(AnnotationKind::Context.span(18..22).label("")), - ), - ); + let input = &[Group::new().element(Level::ERROR.title("foo")).element( + Snippet::source(source) + .line_start(1) + .path("test.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(14..18) + .label("`a` is a good letter"), + ) + .annotation(AnnotationKind::Context.span(18..22).label("")), + )]; let expected = str![[r#" error: foo @@ -606,16 +575,14 @@ fn foo() { a { b { c } d } } "#; - let input = Level::ERROR.header("foo").group( - Group::new().element( - Snippet::source(source) - .line_start(1) - .path("test.rs") - .fold(true) - .annotation(AnnotationKind::Primary.span(14..27).label("")) - .annotation(AnnotationKind::Context.span(18..25).label("")), - ), - ); + let input = &[Group::new().element(Level::ERROR.title("foo")).element( + Snippet::source(source) + .line_start(1) + .path("test.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(14..27).label("")) + .annotation(AnnotationKind::Context.span(18..25).label("")), + )]; let expected = str![[r#" error: foo @@ -634,17 +601,15 @@ fn foo() { a { b { c } d } } "#; - let input = Level::ERROR.header("foo").group( - Group::new().element( - Snippet::source(source) - .line_start(1) - .path("test.rs") - .fold(true) - .annotation(AnnotationKind::Primary.span(18..25).label("")) - .annotation(AnnotationKind::Context.span(14..27).label("")) - .annotation(AnnotationKind::Context.span(22..23).label("")), - ), - ); + let input = &[Group::new().element(Level::ERROR.title("foo")).element( + Snippet::source(source) + .line_start(1) + .path("test.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(18..25).label("")) + .annotation(AnnotationKind::Context.span(14..27).label("")) + .annotation(AnnotationKind::Context.span(22..23).label("")), + )]; let expected = str![[r#" error: foo @@ -663,24 +628,22 @@ fn foo() { a { b { c } d } } "#; - let input = Level::ERROR.header("foo").group( - Group::new().element( - Snippet::source(source) - .line_start(1) - .path("test.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(14..27) - .label("`a` is a good letter"), - ) - .annotation( - AnnotationKind::Context - .span(18..25) - .label("`b` is a good letter"), - ), - ), - ); + let input = &[Group::new().element(Level::ERROR.title("foo")).element( + Snippet::source(source) + .line_start(1) + .path("test.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(14..27) + .label("`a` is a good letter"), + ) + .annotation( + AnnotationKind::Context + .span(18..25) + .label("`b` is a good letter"), + ), + )]; let expected = str![[r#" error: foo @@ -702,19 +665,17 @@ fn foo() { a { b { c } d } } "#; - let input = Level::ERROR.header("foo").group( - Group::new().element( - Snippet::source(source) - .line_start(1) - .path("test.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(14..27) - .label("`a` is a good letter"), - ), - ), - ); + let input = &[Group::new().element(Level::ERROR.title("foo")).element( + Snippet::source(source) + .line_start(1) + .path("test.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(14..27) + .label("`a` is a good letter"), + ), + )]; let expected = str![[r#" error: foo @@ -733,15 +694,13 @@ fn foo() { a { b { c } d } } "#; - let input = Level::ERROR.header("foo").group( - Group::new().element( - Snippet::source(source) - .line_start(1) - .path("test.rs") - .fold(true) - .annotation(AnnotationKind::Primary.span(14..27).label("")), - ), - ); + let input = &[Group::new().element(Level::ERROR.title("foo")).element( + Snippet::source(source) + .line_start(1) + .path("test.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(14..27).label("")), + )]; let expected = str![[r#" error: foo @@ -773,24 +732,22 @@ fn foo() { X3 Y3 Z3 } "#; - let input = Level::ERROR.header("foo").group( - Group::new().element( - Snippet::source(source) - .line_start(1) - .path("test.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(17..27) - .label("`X` is a good letter"), - ) - .annotation( - AnnotationKind::Context - .span(31..76) - .label("`Y` is a good letter too"), - ), - ), - ); + let input = &[Group::new().element(Level::ERROR.title("foo")).element( + Snippet::source(source) + .line_start(1) + .path("test.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(17..27) + .label("`X` is a good letter"), + ) + .annotation( + AnnotationKind::Context + .span(31..76) + .label("`Y` is a good letter too"), + ), + )]; let expected = str![[r#" error: foo @@ -833,24 +790,22 @@ fn foo() { X3 Y3 Z3 } "#; - let input = Level::ERROR.header("foo").group( - Group::new().element( - Snippet::source(source) - .line_start(1) - .path("test.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(17..73) - .label("`Y` is a good letter"), - ) - .annotation( - AnnotationKind::Context - .span(37..56) - .label("`Z` is a good letter too"), - ), - ), - ); + let input = &[Group::new().element(Level::ERROR.title("foo")).element( + Snippet::source(source) + .line_start(1) + .path("test.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(17..73) + .label("`Y` is a good letter"), + ) + .annotation( + AnnotationKind::Context + .span(37..56) + .label("`Z` is a good letter too"), + ), + )]; let expected = str![[r#" error: foo @@ -887,32 +842,30 @@ fn issue_91334() { fn f(){||yield(((){), "#; - let input = Level::ERROR - .header("this file contains an unclosed delimiter") - .group( - Group::new().element( - Snippet::source(source) - .line_start(1) - .path("$DIR/issue-91334.rs") - .fold(true) - .annotation( - AnnotationKind::Context - .span(151..152) - .label("unclosed delimiter"), - ) - .annotation( - AnnotationKind::Context - .span(159..160) - .label("unclosed delimiter"), - ) - .annotation( - AnnotationKind::Context - .span(164..164) - .label("missing open `(` for this delimiter"), - ) - .annotation(AnnotationKind::Primary.span(167..167)), - ), - ); + let input = &[Group::new() + .element(Level::ERROR.title("this file contains an unclosed delimiter")) + .element( + Snippet::source(source) + .line_start(1) + .path("$DIR/issue-91334.rs") + .fold(true) + .annotation( + AnnotationKind::Context + .span(151..152) + .label("unclosed delimiter"), + ) + .annotation( + AnnotationKind::Context + .span(159..160) + .label("unclosed delimiter"), + ) + .annotation( + AnnotationKind::Context + .span(164..164) + .label("missing open `(` for this delimiter"), + ) + .annotation(AnnotationKind::Primary.span(167..167)), + )]; let expected = str![[r#" error: this file contains an unclosed delimiter --> $DIR/issue-91334.rs:7:23 @@ -958,11 +911,14 @@ fn main() { } } "#; - let input = Level::ERROR - .header("`break` with value from a `while` loop") - .id("E0571") - .group( - Group::new().element( + let input = &[ + Group::new() + .element( + Level::ERROR + .title("`break` with value from a `while` loop") + .id("E0571"), + ) + .element( Snippet::source(source) .line_start(1) .path("$DIR/issue-114529-illegal-break-with-value.rs") @@ -978,21 +934,19 @@ fn main() { .label("you can't `break` with a value in a `while` loop"), ), ), - ) - .group( - Group::new() - .element( - Level::HELP - .title("use `break` on its own without a value inside this `while` loop"), - ) - .element( - Snippet::source(source) - .line_start(1) - .path("$DIR/issue-114529-illegal-break-with-value.rs") - .fold(true) - .annotation(AnnotationKind::Context.span(483..581).label("break")), - ), - ); + Group::new() + .element( + Level::HELP + .title("use `break` on its own without a value inside this `while` loop"), + ) + .element( + Snippet::source(source) + .line_start(1) + .path("$DIR/issue-114529-illegal-break-with-value.rs") + .fold(true) + .annotation(AnnotationKind::Context.span(483..581).label("break")), + ), + ]; let expected = str![[r#" error[E0571]: `break` with value from a `while` loop --> $DIR/issue-114529-illegal-break-with-value.rs:22:9 @@ -1168,11 +1122,14 @@ fn nsize() { } "#; let input = - Level::ERROR - .header("`V0usize` cannot be safely transmuted into `[usize; 2]`") - .id("E0277") - .group( - Group::new().element( + &[ + Group::new() + .element( + Level::ERROR + .title("`V0usize` cannot be safely transmuted into `[usize; 2]`") + .id("E0277"), + ) + .element( Snippet::source(source) .line_start(1) .path("$DIR/primitive_reprs_should_have_correct_length.rs") @@ -1181,27 +1138,25 @@ fn nsize() { "the size of `V0usize` is smaller than the size of `[usize; 2]`", )), ), - ) - .group( - Group::new() - .element(Level::NOTE.title("required by a bound in `is_transmutable`")) - .element( - Snippet::source(source) - .line_start(1) - .path("$DIR/primitive_reprs_should_have_correct_length.rs") - .fold(true) - .annotation( - AnnotationKind::Context - .span(225..240) - .label("required by a bound in this function"), - ) - .annotation( - AnnotationKind::Primary - .span(276..470) - .label("required by this bound in `is_transmutable`"), - ), - ), - ); + Group::new() + .element(Level::NOTE.title("required by a bound in `is_transmutable`")) + .element( + Snippet::source(source) + .line_start(1) + .path("$DIR/primitive_reprs_should_have_correct_length.rs") + .fold(true) + .annotation( + AnnotationKind::Context + .span(225..240) + .label("required by a bound in this function"), + ) + .annotation( + AnnotationKind::Primary + .span(276..470) + .label("required by this bound in `is_transmutable`"), + ), + ), + ]; let expected = str![[r#" error[E0277]: `V0usize` cannot be safely transmuted into `[usize; 2]` --> $DIR/primitive_reprs_should_have_correct_length.rs:144:44 @@ -1254,11 +1209,9 @@ fn main() { assert::is_maybe_transmutable::<&'static [u8; 0], &'static [u16; 0]>(); //~ ERROR `&[u8; 0]` cannot be safely transmuted into `&[u16; 0]` } "#; - let input = Level::ERROR - .header("`&[u8; 0]` cannot be safely transmuted into `&[u16; 0]`") - .id("E027s7") - .group( - Group::new().element( + let input = &[Group::new().element(Level::ERROR + .title("`&[u8; 0]` cannot be safely transmuted into `&[u16; 0]`") + .id("E027s7")).element( Snippet::source(source) .line_start(1) .fold(true) @@ -1268,8 +1221,7 @@ fn main() { .span(442..459) .label("the minimum alignment of `&[u8; 0]` (1) should be greater than that of `&[u16; 0]` (2)") ), - ), - ); + )]; let expected = str![[r#" error[E027s7]: `&[u8; 0]` cannot be safely transmuted into `&[u16; 0]` --> $DIR/align-fail.rs:21:55 @@ -1323,11 +1275,14 @@ fn g() { } fn main() {} "#; - let input = Level::ERROR - .header("expected function, found `{integer}`") - .id("E0618") - .group( - Group::new().element( + let input = + &[Group::new() + .element( + Level::ERROR + .title("expected function, found `{integer}`") + .id("E0618"), + ) + .element( Snippet::source(source) .line_start(1) .path("$DIR/missing-semicolon.rs") @@ -1346,8 +1301,7 @@ fn main() {} "help: consider using a semicolon here to finish the statement: `;`", )) .annotation(AnnotationKind::Primary.span(108..109)), - ), - ); + )]; let expected = str![[r#" error[E0618]: expected function, found `{integer}` --> $DIR/missing-semicolon.rs:5:13 @@ -1414,10 +1368,9 @@ macro_rules! outer_macro { outer_macro!(FirstStruct, FirstAttrStruct); "#; - let input = Level::WARNING - .header("non-local `macro_rules!` definition, `#[macro_export]` macro should be written at top level module") - .group( - Group::new() + let input = + &[ Group::new().element(Level::WARNING + .title("non-local `macro_rules!` definition, `#[macro_export]` macro should be written at top level module")) .element( Snippet::source(aux_source) .line_start(1) @@ -1449,8 +1402,6 @@ outer_macro!(FirstStruct, FirstAttrStruct); Level::NOTE .title("a `macro_rules!` definition is non-local if it is nested inside an item and has a `#[macro_export]` attribute") ), - ) - .group( Group::new() .element(Level::NOTE.title("the lint level is defined here")) .element( @@ -1459,8 +1410,7 @@ outer_macro!(FirstStruct, FirstAttrStruct); .path("$DIR/nested-macro-rules.rs") .fold(true) .annotation(AnnotationKind::Primary.span(224..245)), - ), - ); + )]; let expected = str![[r#" warning: non-local `macro_rules!` definition, `#[macro_export]` macro should be written at top level module --> $DIR/auxiliary/nested-macro-rules.rs:7:9 @@ -1547,29 +1497,30 @@ macro_rules! inline { () => () } "#; - let input = Level::ERROR - .header("can't call method `pow` on ambiguous numeric type `{integer}`") - .id("E0689") - .group( - Group::new().element( + let input = &[ + Group::new() + .element( + Level::ERROR + .title("can't call method `pow` on ambiguous numeric type `{integer}`") + .id("E0689"), + ) + .element( Snippet::source(source) .line_start(1) .path("$DIR/method-on-ambiguous-numeric-type.rs") .fold(true) .annotation(AnnotationKind::Primary.span(916..919)), ), - ) - .group( - Group::new() - .element(Level::HELP.title("you must specify a type for this binding, like `i32`")) - .element( - Snippet::source(aux_source) - .line_start(1) - .path("$DIR/auxiliary/macro-in-other-crate.rs") - .fold(true) - .annotation(AnnotationKind::Context.span(69..69).label(": i32")), - ), - ); + Group::new() + .element(Level::HELP.title("you must specify a type for this binding, like `i32`")) + .element( + Snippet::source(aux_source) + .line_start(1) + .path("$DIR/auxiliary/macro-in-other-crate.rs") + .fold(true) + .annotation(AnnotationKind::Context.span(69..69).label(": i32")), + ), + ]; let expected = str![[r#" error[E0689]: can't call method `pow` on ambiguous numeric type `{integer}` --> $DIR/method-on-ambiguous-numeric-type.rs:37:9 @@ -1611,20 +1562,17 @@ fn courier_to_des_moines_and_points_west(data: &[u32]) -> String { fn main() {} "#; - let input = Level::ERROR - .header("type annotations needed") - .id("E0282") - .group( - Group::new().element( - Snippet::source(source) - .line_start(1) - .path("$DIR/issue-42234-unknown-receiver-type.rs") - .fold(true) - .annotation(AnnotationKind::Primary.span(536..539).label( - "cannot infer type of the type parameter `S` declared on the method `sum`", - )), - ), - ); + let input = &[Group::new() + .element(Level::ERROR.title("type annotations needed").id("E0282")) + .element( + Snippet::source(source) + .line_start(1) + .path("$DIR/issue-42234-unknown-receiver-type.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(536..539).label( + "cannot infer type of the type parameter `S` declared on the method `sum`", + )), + )]; let expected = str![[r#" error[E0282]: type annotations needed --> $DIR/issue-42234-unknown-receiver-type.rs:15:10 @@ -1717,13 +1665,12 @@ fn nonempty<const N: usize>(arrayN_of_empty: [!; N]) { fn main() {} "##; - let input = Level::ERROR - .header( - "non-exhaustive patterns: `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered" - ) - .id("E0004") - .group( - Group::new().element( + let input = + &[ Group::new().element( Level::ERROR + .title( + "non-exhaustive patterns: `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered" + ) + .id("E0004")).element( Snippet::source(source) .line_start(1) .path("$DIR/empty-match.rs") @@ -1734,8 +1681,6 @@ fn main() {} .label("patterns `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered") ), ), - ) - .group( Group::new() .element(Level::NOTE.title("`NonEmptyEnum5` defined here")) .element( @@ -1751,9 +1696,8 @@ fn main() {} .annotation(AnnotationKind::Context.span(890..892).label("not covered")) ) .element(Level::NOTE.title("the matched value is of type `NonEmptyEnum5`")) - .element(Level::NOTE.title("match arms with guards don't count towards exhaustivity")) - ) - .group( + .element(Level::NOTE.title("match arms with guards don't count towards exhaustivity") + ), Group::new() .element( Level::HELP @@ -1765,8 +1709,8 @@ fn main() {} .path("$DIR/empty-match.rs") .fold(true) .annotation(AnnotationKind::Context.span(485..485).label(",\n _ => todo!()")) - ) - ); + + )]; let expected = str![[r#" error[E0004]: non-exhaustive patterns: `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered --> $DIR/empty-match.rs:71:24 @@ -1819,11 +1763,9 @@ fn main() { //~^ ERROR must be specified } "#; - let input = Level::ERROR - .header("the trait alias `EqAlias` is not dyn compatible") - .id("E0038") - .group( - Group::new().element( + let input = &[Group::new().element(Level::ERROR + .title("the trait alias `EqAlias` is not dyn compatible") + .id("E0038")).element( Snippet::source(source) .line_start(1) .path("$DIR/object-fail.rs") @@ -1834,8 +1776,6 @@ fn main() { .label("`EqAlias` is not dyn compatible"), ), ), - ) - .group( Group::new() .element( Level::NOTE @@ -1858,8 +1798,7 @@ fn main() { .span(32..39) .label("this trait is not dyn compatible..."), ), - ), - ); + )]; let expected = str![[r#" error[E0038]: the trait alias `EqAlias` is not dyn compatible --> $DIR/object-fail.rs:7:17 @@ -1891,8 +1830,9 @@ const C: u8 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, fn main() {} "#; - let input = Level::ERROR.header("mismatched types").id("E0038").group( - Group::new().element( + let input = &[Group::new() + .element(Level::ERROR.title("mismatched types").id("E0038")) + .element( Snippet::source(source) .path("$DIR/long-span.rs") .fold(true) @@ -1901,8 +1841,7 @@ fn main() {} .span(15..5055) .label("expected `u8`, found `[{integer}; 1680]`"), ), - ), - ); + )]; let expected = str![[r#" error[E0038]: mismatched types --> $DIR/long-span.rs:2:15 @@ -1925,8 +1864,9 @@ const C: u8 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, fn main() {} "#; - let input = Level::ERROR.header("mismatched types").id("E0038").group( - Group::new().element( + let input = &[Group::new() + .element(Level::ERROR.title("mismatched types").id("E0038")) + .element( Snippet::source(source) .path("$DIR/long-span.rs") .fold(true) @@ -1935,8 +1875,7 @@ fn main() {} .span(15..5055) .label("expected `u8`, found `[{integer}; 1680]`"), ), - ), - ); + )]; let expected = str![[r#" error[E0038]: mismatched types ╭▸ $DIR/long-span.rs:2:15 @@ -1960,8 +1899,9 @@ const C: u8 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, fn main() {} "#; - let input = Level::ERROR.header("mismatched types").id("E0038").group( - Group::new().element( + let input = &[Group::new() + .element(Level::ERROR.title("mismatched types").id("E0038")) + .element( Snippet::source(source) .path("$DIR/long-span.rs") .fold(true) @@ -1970,8 +1910,7 @@ fn main() {} .span(15..5055) .label("expected `u8`, found `[{integer}; 1680]`"), ), - ), - ); + )]; let expected = str![[r#" error[E0038]: mismatched types ╭▸ $DIR/long-span.rs:2:15 @@ -1995,8 +1934,9 @@ const C: u8 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, fn main() {} "#; - let input = Level::ERROR.header("mismatched types").id("E0038").group( - Group::new().element( + let input = &[Group::new() + .element(Level::ERROR.title("mismatched types").id("E0038")) + .element( Snippet::source(source) .path("$DIR/long-span.rs") .fold(true) @@ -2005,8 +1945,7 @@ fn main() {} .span(15..5055) .label("expected `u8`, found `[{integer}; 1680]`"), ), - ), - ); + )]; let expected = str![[r#" error[E0038]: mismatched types --> $DIR/long-span.rs:2:15 @@ -2046,10 +1985,8 @@ fn main() { } "#; - let input = Level::ERROR - .header("`Iterator::map` call that discard the iterator's values") - .group( - Group::new() + let input = &[Group::new().element(Level::ERROR + .title("`Iterator::map` call that discard the iterator's values")) .element( Snippet::source(source) .path("$DIR/lint_map_unit_fn.rs") @@ -2071,8 +2008,6 @@ fn main() { ) .element( Level::NOTE.title("`Iterator::map`, like many of the methods on `Iterator`, gets executed lazily, meaning that its effects won't be visible until it is iterated")), - ) - .group( Group::new() .element(Level::HELP.title("you might have meant to use `Iterator::for_each`")) .element( @@ -2080,8 +2015,7 @@ fn main() { .path("$DIR/lint_map_unit_fn.rs") .fold(true) .patch(Patch::new(267..270, r#"for_each"#)), - ), - ); + )]; let expected = str![[r#" error: `Iterator::map` call that discard the iterator's values @@ -2141,27 +2075,25 @@ fn main() { } "#; - let input = Level::ERROR - .header("character constant must be escaped: `\\n`") - .group( - Group::new().element( + let input = &[ + Group::new() + .element(Level::ERROR.title("character constant must be escaped: `\\n`")) + .element( Snippet::source(source) .path("$DIR/bad-char-literals.rs") .fold(true) .annotation(AnnotationKind::Primary.span(204..205)), ), - ) - .group( - Group::new() - .element(Level::HELP.title("escape the character")) - .element( - Snippet::source(source) - .path("$DIR/bad-char-literals.rs") - .line_start(1) - .fold(true) - .patch(Patch::new(204..205, r#"\n"#)), - ), - ); + Group::new() + .element(Level::HELP.title("escape the character")) + .element( + Snippet::source(source) + .path("$DIR/bad-char-literals.rs") + .line_start(1) + .fold(true) + .patch(Patch::new(204..205, r#"\n"#)), + ), + ]; let expected = str![[r#" error: character constant must be escaped: `/n` --> $DIR/bad-char-literals.rs:10:6 @@ -2196,26 +2128,24 @@ fn unclosed_1() { fn main() {} "#; - let input = Level::ERROR - .header("unclosed frontmatter") - .group( - Group::new().element( + let input = &[ + Group::new() + .element(Level::ERROR.title("unclosed frontmatter")) + .element( Snippet::source(source) .path("$DIR/unclosed-1.rs") .fold(true) .annotation(AnnotationKind::Primary.span(0..221)), ), - ) - .group( - Group::new() - .element(Level::NOTE.title("frontmatter opening here was not closed")) - .element( - Snippet::source(source) - .path("$DIR/unclosed-1.rs") - .fold(true) - .annotation(AnnotationKind::Primary.span(0..4)), - ), - ); + Group::new() + .element(Level::NOTE.title("frontmatter opening here was not closed")) + .element( + Snippet::source(source) + .path("$DIR/unclosed-1.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(0..4)), + ), + ]; let expected = str![[r#" error: unclosed frontmatter --> $DIR/unclosed-1.rs:1:1 @@ -2256,26 +2186,24 @@ fn foo() -> &str { } "#; - let input = Level::ERROR - .header("unclosed frontmatter") - .group( - Group::new().element( + let input = &[ + Group::new() + .element(Level::ERROR.title("unclosed frontmatter")) + .element( Snippet::source(source) .path("$DIR/unclosed-2.rs") .fold(true) .annotation(AnnotationKind::Primary.span(0..377)), ), - ) - .group( - Group::new() - .element(Level::NOTE.title("frontmatter opening here was not closed")) - .element( - Snippet::source(source) - .path("$DIR/unclosed-2.rs") - .fold(true) - .annotation(AnnotationKind::Primary.span(0..4)), - ), - ); + Group::new() + .element(Level::NOTE.title("frontmatter opening here was not closed")) + .element( + Snippet::source(source) + .path("$DIR/unclosed-2.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(0..4)), + ), + ]; let expected = str![[r#" error: unclosed frontmatter --> $DIR/unclosed-2.rs:1:1 @@ -2318,28 +2246,24 @@ fn foo(x: i32) -> i32 { //~^ ERROR: unexpected closing delimiter: `}` "#; - let input = Level::ERROR - .header("invalid preceding whitespace for frontmatter close") - .group( - Group::new().element( + let input = &[ + Group::new() + .element(Level::ERROR.title("invalid preceding whitespace for frontmatter close")) + .element( Snippet::source(source) .path("$DIR/unclosed-3.rs") .fold(true) .annotation(AnnotationKind::Primary.span(302..310)), ), - ) - .group( - Group::new() - .element( - Level::NOTE.title("frontmatter close should not be preceded by whitespace"), - ) - .element( - Snippet::source(source) - .path("$DIR/unclosed-3.rs") - .fold(true) - .annotation(AnnotationKind::Primary.span(302..306)), - ), - ); + Group::new() + .element(Level::NOTE.title("frontmatter close should not be preceded by whitespace")) + .element( + Snippet::source(source) + .path("$DIR/unclosed-3.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(302..306)), + ), + ]; let expected = str![[r#" error: invalid preceding whitespace for frontmatter close --> $DIR/unclosed-3.rs:12:1 @@ -2372,26 +2296,24 @@ fn unclosed_4() { fn main() {} "#; - let input = Level::ERROR - .header("unclosed frontmatter") - .group( - Group::new().element( + let input = &[ + Group::new() + .element(Level::ERROR.title("unclosed frontmatter")) + .element( Snippet::source(source) .path("$DIR/unclosed-4.rs") .fold(true) .annotation(AnnotationKind::Primary.span(0..43)), ), - ) - .group( - Group::new() - .element(Level::NOTE.title("frontmatter opening here was not closed")) - .element( - Snippet::source(source) - .path("$DIR/unclosed-4.rs") - .fold(true) - .annotation(AnnotationKind::Primary.span(0..4)), - ), - ); + Group::new() + .element(Level::NOTE.title("frontmatter opening here was not closed")) + .element( + Snippet::source(source) + .path("$DIR/unclosed-4.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(0..4)), + ), + ]; let expected = str![[r#" error: unclosed frontmatter --> $DIR/unclosed-4.rs:1:1 @@ -2427,26 +2349,24 @@ use std::env; fn main() {} "#; - let input = Level::ERROR - .header("unclosed frontmatter") - .group( - Group::new().element( + let input = &[ + Group::new() + .element(Level::ERROR.title("unclosed frontmatter")) + .element( Snippet::source(source) .path("$DIR/unclosed-5.rs") .fold(true) .annotation(AnnotationKind::Primary.span(0..176)), ), - ) - .group( - Group::new() - .element(Level::NOTE.title("frontmatter opening here was not closed")) - .element( - Snippet::source(source) - .path("$DIR/unclosed-5.rs") - .fold(true) - .annotation(AnnotationKind::Primary.span(0..4)), - ), - ); + Group::new() + .element(Level::NOTE.title("frontmatter opening here was not closed")) + .element( + Snippet::source(source) + .path("$DIR/unclosed-5.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(0..4)), + ), + ]; let expected = str![[r#" error: unclosed frontmatter @@ -2551,11 +2471,9 @@ pub enum E2 { } "#; - let input = Level::ERROR - .header("expected unit struct, unit variant or constant, found tuple variant `E1::Z1`") - .id(r#"E0532"#) - .group( - Group::new() + let input = &[Group::new().element(Level::ERROR + .title("expected unit struct, unit variant or constant, found tuple variant `E1::Z1`") + .id(r#"E0532"#)) .element( Snippet::source(source) .path("$DIR/pat-tuple-field-count-cross.rs") @@ -2577,8 +2495,6 @@ pub enum E2 { .label("similarly named unit variant `Z0` defined here"), ), ), - ) - .group( Group::new() .element(Level::HELP.title("use the tuple variant pattern syntax instead")) .element( @@ -2587,8 +2503,6 @@ pub enum E2 { .fold(true) .patch(Patch::new(1760..1766, r#"E1::Z1()"#)), ), - ) - .group( Group::new() .element(Level::HELP.title("a unit variant with a similar name exists")) .element( @@ -2596,8 +2510,7 @@ pub enum E2 { .path("$DIR/pat-tuple-field-count-cross.rs") .fold(true) .patch(Patch::new(1764..1766, r#"Z0"#)), - ), - ); + )]; let expected = str![[r#" error[E0532]: expected unit struct, unit variant or constant, found tuple variant `E1::Z1` --> $DIR/pat-tuple-field-count-cross.rs:35:9 @@ -2636,8 +2549,9 @@ fn unterminated_nested_comment() { */ "#; - let input = Level::ERROR.header("unterminated block comment").id("E0758").group( - Group::new().element( + let input = &[Group::new() + .element(Level::ERROR.title("unterminated block comment").id("E0758")) + .element( Snippet::source(source) .path("$DIR/unterminated-nested-comment.rs") .fold(true) @@ -2655,8 +2569,7 @@ fn unterminated_nested_comment() { .label("...and last nested comment terminates here."), ) .annotation(AnnotationKind::Primary.span(0..31)), - ), - ); + )]; let expected = str![[r#" error[E0758]: unterminated block comment @@ -2692,38 +2605,37 @@ fn mismatched_types1() { let s: &str = include_bytes!("file.txt"); //~ ERROR mismatched types }"#; - let input = Level::ERROR.header("mismatched types").id("E0308").group( - Group::new() - .element( - Snippet::source(file_txt_source) - .fold(true) - .line_start(3) - .path("$DIR/file.txt") - .annotation( - AnnotationKind::Primary - .span(0..0) - .label("expected `&[u8]`, found `&str`"), - ), - ) - .element( - Snippet::source(rust_source) - .path("$DIR/mismatched-types.rs") - .fold(true) - .annotation( - AnnotationKind::Context - .span(23..28) - .label("expected due to this"), - ) - .annotation( - AnnotationKind::Context - .span(31..55) - .label("in this macro invocation"), - ), - ) - .element( - Level::NOTE.title("expected reference `&[u8]`\n found reference `&'static str`"), - ), - ); + let input = &[Group::new() + .element(Level::ERROR.title("mismatched types").id("E0308")) + .element( + Snippet::source(file_txt_source) + .fold(true) + .line_start(3) + .path("$DIR/file.txt") + .annotation( + AnnotationKind::Primary + .span(0..0) + .label("expected `&[u8]`, found `&str`"), + ), + ) + .element( + Snippet::source(rust_source) + .path("$DIR/mismatched-types.rs") + .fold(true) + .annotation( + AnnotationKind::Context + .span(23..28) + .label("expected due to this"), + ) + .annotation( + AnnotationKind::Context + .span(31..55) + .label("in this macro invocation"), + ), + ) + .element( + Level::NOTE.title("expected reference `&[u8]`\n found reference `&'static str`"), + )]; let expected = str![[r#" error[E0308]: mismatched types @@ -2755,28 +2667,26 @@ fn mismatched_types2() { let s: &str = include_bytes!("file.txt"); //~ ERROR mismatched types }"#; - let input = Level::ERROR.header("mismatched types").id("E0308").group( - Group::new() - .element( - Snippet::source(source) - .path("$DIR/mismatched-types.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(105..131) - .label("expected `&str`, found `&[u8; 0]`"), - ) - .annotation( - AnnotationKind::Context - .span(98..102) - .label("expected due to this"), - ), - ) - .element( - Level::NOTE - .title("expected reference `&str`\n found reference `&'static [u8; 0]`"), - ), - ); + let input = &[Group::new() + .element(Level::ERROR.title("mismatched types").id("E0308")) + .element( + Snippet::source(source) + .path("$DIR/mismatched-types.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(105..131) + .label("expected `&str`, found `&[u8; 0]`"), + ) + .annotation( + AnnotationKind::Context + .span(98..102) + .label("expected due to this"), + ), + ) + .element( + Level::NOTE.title("expected reference `&str`\n found reference `&'static [u8; 0]`"), + )]; let expected = str![[r#" error[E0308]: mismatched types @@ -2809,11 +2719,10 @@ fn main() { } "#; - let input = Level::ERROR - .header("mismatched types") - .id("E0308") - .group( - Group::new().element( + let input = &[ + Group::new() + .element(Level::ERROR.title("mismatched types").id("E0308")) + .element( Snippet::source(source) .path("$DIR/short-error-format.rs") .fold(true) @@ -2828,18 +2737,16 @@ fn main() { .label("arguments to this function are incorrect"), ), ), - ) - .group( - Group::new() - .element(Level::NOTE.title("function defined here")) - .element( - Snippet::source(source) - .path("$DIR/short-error-format.rs") - .fold(true) - .annotation(AnnotationKind::Context.span(48..54).label("")) - .annotation(AnnotationKind::Primary.span(44..47)), - ), - ); + Group::new() + .element(Level::NOTE.title("function defined here")) + .element( + Snippet::source(source) + .path("$DIR/short-error-format.rs") + .fold(true) + .annotation(AnnotationKind::Context.span(48..54).label("")) + .annotation(AnnotationKind::Primary.span(44..47)), + ), + ]; let expected = str![[r#" $DIR/short-error-format.rs:6:9: error[E0308]: mismatched types: expected `u32`, found `String` @@ -2865,21 +2772,22 @@ fn main() { } "#; - let input = Level::ERROR - .header("no method named `salut` found for type `u32` in the current scope") - .id("E0599") - .group( - Group::new().element( - Snippet::source(source) - .path("$DIR/short-error-format.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(127..132) - .label("method not found in `u32`"), - ), - ), - ); + let input = &[Group::new() + .element( + Level::ERROR + .title("no method named `salut` found for type `u32` in the current scope") + .id("E0599"), + ) + .element( + Snippet::source(source) + .path("$DIR/short-error-format.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(127..132) + .label("method not found in `u32`"), + ), + )]; let expected = str![[r#" $DIR/short-error-format.rs:8:7: error[E0599]: no method named `salut` found for type `u32` in the current scope: method not found in `u32` @@ -2903,43 +2811,37 @@ pub struct Foo; //~^ ERROR let source_1 = r#"/// This is a long line that contains a http://link.com "#; - let input = Level::ERROR - .header("this URL is not a hyperlink") - .group( - Group::new() - .element( - Snippet::source(source_0) - .path("$DIR/diagnostic-width.rs") - .fold(true) - .annotation(AnnotationKind::Primary.span(111..126)), - ) - .element( - Level::NOTE - .title("bare URLs are not automatically turned into clickable links"), - ), - ) - .group( - Group::new() - .element(Level::NOTE.title("the lint level is defined here")) - .element( - Snippet::source(source_0) - .path("$DIR/diagnostic-width.rs") - .fold(true) - .annotation(AnnotationKind::Primary.span(49..67)), - ), - ) - .group( - Group::new() - .element(Level::HELP.title("use an automatic link instead")) - .element( - Snippet::source(source_1) - .path("$DIR/diagnostic-width.rs") - .line_start(4) - .fold(true) - .patch(Patch::new(40..40, "<")) - .patch(Patch::new(55..55, ">")), - ), - ); + let input = &[ + Group::new() + .element(Level::ERROR.title("this URL is not a hyperlink")) + .element( + Snippet::source(source_0) + .path("$DIR/diagnostic-width.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(111..126)), + ) + .element( + Level::NOTE.title("bare URLs are not automatically turned into clickable links"), + ), + Group::new() + .element(Level::NOTE.title("the lint level is defined here")) + .element( + Snippet::source(source_0) + .path("$DIR/diagnostic-width.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(49..67)), + ), + Group::new() + .element(Level::HELP.title("use an automatic link instead")) + .element( + Snippet::source(source_1) + .path("$DIR/diagnostic-width.rs") + .line_start(4) + .fold(true) + .patch(Patch::new(40..40, "<")) + .patch(Patch::new(55..55, ">")), + ), + ]; let expected = str![[r#" error: this URL is not a hyperlink @@ -2979,45 +2881,40 @@ fn main() { let long_title2 = "for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2021/IntoIterator-for-arrays.html>"; let long_title3 = "or use `IntoIterator::into_iter(..)` instead of `.into_iter()` to explicitly iterate by value"; - let input = Level::WARNING - .header(long_title1) - .group( - Group::new() - .element( - Snippet::source(source1) - .path("lint_example.rs") - .fold(true) - .annotation(AnnotationKind::Primary.span(40..49)), - ) - .element(Level::WARNING.title("this changes meaning in Rust 2021")) - .element(Level::NOTE.title(long_title2)) - .element(Level::NOTE.title("`#[warn(array_into_iter)]` on by default")), - ) - .group( - Group::new() - .element( - Level::HELP.title("use `.iter()` instead of `.into_iter()` to avoid ambiguity"), - ) - .element( - Snippet::source(source2) - .path("lint_example.rs") - .line_start(3) - .fold(true) - .patch(Patch::new(10..19, "iter")), - ), - ) - .group( - Group::new() - .element(Level::HELP.title(long_title3)) - .element( - Snippet::source(source2) - .path("lint_example.rs") - .line_start(3) - .fold(true) - .patch(Patch::new(0..0, "IntoIterator::into_iter(")) - .patch(Patch::new(9..21, ")")), - ), - ); + let input = &[ + Group::new() + .element(Level::WARNING.title(long_title1)) + .element( + Snippet::source(source1) + .path("lint_example.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(40..49)), + ) + .element(Level::WARNING.title("this changes meaning in Rust 2021")) + .element(Level::NOTE.title(long_title2)) + .element(Level::NOTE.title("`#[warn(array_into_iter)]` on by default")), + Group::new() + .element( + Level::HELP.title("use `.iter()` instead of `.into_iter()` to avoid ambiguity"), + ) + .element( + Snippet::source(source2) + .path("lint_example.rs") + .line_start(3) + .fold(true) + .patch(Patch::new(10..19, "iter")), + ), + Group::new() + .element(Level::HELP.title(long_title3)) + .element( + Snippet::source(source2) + .path("lint_example.rs") + .line_start(3) + .fold(true) + .patch(Patch::new(0..0, "IntoIterator::into_iter(")) + .patch(Patch::new(9..21, ")")), + ), + ]; let expected = str![[r#" warning: this method call resolves to `<&[T; N] as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<[T; N] as IntoIterator>::into_iter` in Rust 2021 From 37542efce234d5dd9c192ead275f7944a8872773 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 24 Jun 2025 03:30:20 -0600 Subject: [PATCH 399/455] fix: Take Into<Cow> instead of &str --- src/level.rs | 23 ++++----- src/renderer/mod.rs | 56 +++++++++++----------- src/renderer/source_map.rs | 11 +++-- src/snippet.rs | 96 ++++++++++++++++++++++++++++---------- tests/formatter.rs | 2 +- 5 files changed, 119 insertions(+), 69 deletions(-) diff --git a/src/level.rs b/src/level.rs index d3db1814..5934a280 100644 --- a/src/level.rs +++ b/src/level.rs @@ -2,8 +2,9 @@ use crate::renderer::stylesheet::Stylesheet; use crate::snippet::{ERROR_TXT, HELP_TXT, INFO_TXT, NOTE_TXT, WARNING_TXT}; -use crate::Title; +use crate::{OptionCow, Title}; use anstyle::Style; +use std::borrow::Cow; /// Default `error:` [`Level`] pub const ERROR: Level<'_> = Level { @@ -38,7 +39,7 @@ pub const HELP: Level<'_> = Level { /// [`Title`] severity level #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct Level<'a> { - pub(crate) name: Option<Option<&'a str>>, + pub(crate) name: Option<Option<Cow<'a, str>>>, pub(crate) level: LevelInner, } @@ -56,9 +57,9 @@ impl<'a> Level<'a> { /// not allowed to be passed to this function. /// /// </div> - pub fn text(self, text: Option<&'a str>) -> Level<'a> { + pub fn text(self, text: impl Into<OptionCow<'a>>) -> Level<'a> { Level { - name: Some(text), + name: Some(text.into().0), level: self.level, } } @@ -72,11 +73,11 @@ impl<'a> Level<'a> { /// not allowed to be passed to this function. /// /// </div> - pub fn title(self, title: &'a str) -> Title<'a> { + pub fn title(self, title: impl Into<Cow<'a, str>>) -> Title<'a> { Title { level: self, id: None, - title, + title: title.into(), is_pre_styled: false, } } @@ -89,18 +90,18 @@ impl<'a> Level<'a> { /// used to normalize untrusted text before it is passed to this function. /// /// </div> - pub fn pre_styled_title(self, title: &'a str) -> Title<'a> { + pub fn pre_styled_title(self, title: impl Into<Cow<'a, str>>) -> Title<'a> { Title { level: self, id: None, - title, + title: title.into(), is_pre_styled: true, } } - pub(crate) fn as_str(&self) -> &'a str { - match (self.name, self.level) { - (Some(Some(name)), _) => name, + pub(crate) fn as_str(&'a self) -> &'a str { + match (&self.name, self.level) { + (Some(Some(name)), _) => name.as_ref(), (Some(None), _) => "", (None, LevelInner::Error) => ERROR_TXT, (None, LevelInner::Warning) => WARNING_TXT, diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 12139c38..0cc43d26 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -229,14 +229,14 @@ impl Renderer { .find_map(|s| match &s { Element::Cause(cause) => { if cause.markers.iter().any(|m| m.kind.is_primary()) { - Some(cause.path) + Some(cause.path.as_ref()) } else { None } } Element::Origin(origin) => { if origin.primary { - Some(Some(origin.path)) + Some(Some(&origin.path)) } else { None } @@ -248,8 +248,8 @@ impl Renderer { .elements .iter() .find_map(|s| match &s { - Element::Cause(cause) => Some(cause.path), - Element::Origin(origin) => Some(Some(origin.path)), + Element::Cause(cause) => Some(cause.path.as_ref()), + Element::Origin(origin) => Some(Some(&origin.path)), _ => None, }) .unwrap_or_default(), @@ -269,7 +269,7 @@ impl Renderer { let mut max_depth = 0; for e in &group.elements { if let Element::Cause(cause) = e { - let source_map = SourceMap::new(cause.source, cause.line_start); + let source_map = SourceMap::new(&cause.source, cause.line_start); let (depth, annotated_lines) = source_map.annotated_lines(cause.markers.clone(), cause.fold); max_depth = max(max_depth, depth); @@ -340,7 +340,7 @@ impl Renderer { } Element::Suggestion(suggestion) => { let source_map = - SourceMap::new(suggestion.source, suggestion.line_start); + SourceMap::new(&suggestion.source, suggestion.line_start); self.emit_suggestion_default( &mut buffer, suggestion, @@ -426,10 +426,10 @@ impl Renderer { let labels_inner = cause .markers .iter() - .filter_map(|ann| match ann.label { + .filter_map(|ann| match &ann.label { Some(msg) if ann.kind.is_primary() => { if !msg.trim().is_empty() { - Some(msg.to_owned()) + Some(msg.to_string()) } else { None } @@ -442,11 +442,11 @@ impl Renderer { labels = Some(labels_inner); } - if let Some(path) = cause.path { - let mut origin = Origin::new(path); + if let Some(path) = &cause.path { + let mut origin = Origin::new(path.as_ref()); origin.primary = true; - let source_map = SourceMap::new(cause.source, cause.line_start); + let source_map = SourceMap::new(&cause.source, cause.line_start); let (_depth, annotated_lines) = source_map.annotated_lines(cause.markers.clone(), cause.fold); @@ -531,7 +531,7 @@ impl Renderer { if title.level.name != Some(None) { buffer.append(buffer_msg_line_offset, title.level.as_str(), label_style); label_width += title.level.as_str().len(); - if let Some(Id { id: Some(id), url }) = title.id { + if let Some(Id { id: Some(id), url }) = &title.id { buffer.append(buffer_msg_line_offset, "[", label_style); if let Some(url) = url.as_ref() { buffer.append( @@ -575,9 +575,9 @@ impl Renderer { }); let (title_str, style) = if title.is_pre_styled { - (title.title.to_owned(), ElementStyle::NoStyle) + (title.title.to_string(), ElementStyle::NoStyle) } else { - (normalize_whitespace(title.title), title_element_style) + (normalize_whitespace(&title.title), title_element_style) }; for (i, text) in title_str.lines().enumerate() { if i != 0 { @@ -654,7 +654,7 @@ impl Renderer { format!("{}:{}:{}", origin.path, line, col) } (Some(line), None) => format!("{}:{}", origin.path, line), - _ => origin.path.to_owned(), + _ => origin.path.to_string(), }; buffer.append(buffer_msg_line_offset, &str, ElementStyle::LineAndColumn); @@ -671,17 +671,17 @@ impl Renderer { buffer: &mut StyledBuffer, max_line_num_len: usize, snippet: &Snippet<'_, Annotation<'_>>, - primary_path: Option<&str>, + primary_path: Option<&Cow<'_, str>>, sm: &SourceMap<'_>, annotated_lines: &[AnnotatedLineInfo<'_>], multiline_depth: usize, is_cont: bool, ) { - if let Some(path) = snippet.path { - let mut origin = Origin::new(path); + if let Some(path) = &snippet.path { + let mut origin = Origin::new(path.as_ref()); // print out the span location and spacer before we print the annotated source // to do this, we need to know if this span will be primary - let is_primary = primary_path == Some(origin.path); + let is_primary = primary_path == Some(&origin.path); if is_primary { origin.primary = true; @@ -1394,7 +1394,7 @@ impl Renderer { } else { (pos + 2, annotation.start.display.saturating_sub(left)) }; - if let Some(label) = annotation.label { + if let Some(label) = &annotation.label { buffer.puts(line_offset + pos, code_offset + col, label, style); } } @@ -1535,7 +1535,7 @@ impl Renderer { suggestion: &Snippet<'_, Patch<'_>>, max_line_num_len: usize, sm: &SourceMap<'_>, - primary_path: Option<&str>, + primary_path: Option<&Cow<'_, str>>, is_cont: bool, ) { let suggestions = sm.splice_lines(suggestion.markers.clone()); @@ -1558,8 +1558,8 @@ impl Renderer { ElementStyle::LineNumber, ); } - if suggestion.path != primary_path { - if let Some(path) = suggestion.path { + if suggestion.path.as_ref() != primary_path { + if let Some(path) = suggestion.path.as_ref() { let (loc, _) = sm.span_to_locations(parts[0].span.clone()); // --> file.rs:line:col // | @@ -1781,7 +1781,7 @@ impl Renderer { // ...or trailing spaces. Account for substitutions containing unicode // characters. let sub_len: usize = str_width(if is_whitespace_addition { - part.replacement + &part.replacement } else { part.replacement.trim() }); @@ -1802,7 +1802,7 @@ impl Renderer { let padding: usize = max_line_num_len + 3; for p in underline_start..underline_end { if matches!(show_code_change, DisplaySuggestion::Underline) - && is_different(sm, part.replacement, part.span.clone()) + && is_different(sm, &part.replacement, part.span.clone()) { // If this is a replacement, underline with `~`, if this is an addition // underline with `+`. @@ -1903,7 +1903,7 @@ impl Renderer { } // length of the code after substitution - let full_sub_len = str_width(part.replacement) as isize; + let full_sub_len = str_width(&part.replacement) as isize; // length of the code to be substituted let snippet_len = span_end_pos as isize - span_start_pos as isize; @@ -2631,7 +2631,7 @@ pub(crate) struct LineAnnotation<'a> { pub kind: AnnotationKind, /// Optional label to display adjacent to the annotation. - pub label: Option<&'a str>, + pub label: Option<Cow<'a, str>>, /// Is this a single line, multiline or multiline span minimized down to a /// smaller span. @@ -2658,7 +2658,7 @@ impl LineAnnotation<'_> { } pub(crate) fn has_label(&self) -> bool { - if let Some(label) = self.label { + if let Some(label) = &self.label { // Consider labels with no text as effectively not being there // to avoid weird output with unnecessary vertical lines, like: // diff --git a/src/renderer/source_map.rs b/src/renderer/source_map.rs index 026b2a42..e2e4b61c 100644 --- a/src/renderer/source_map.rs +++ b/src/renderer/source_map.rs @@ -1,5 +1,6 @@ use crate::renderer::{char_width, is_different, num_overlap, LineAnnotation, LineAnnotationType}; use crate::{Annotation, AnnotationKind, Patch}; +use std::borrow::Cow; use std::cmp::{max, min}; use std::ops::Range; @@ -453,14 +454,14 @@ impl<'a> SourceMap<'a> { .replacement .split('\n') .next() - .unwrap_or(part.replacement) + .unwrap_or(&part.replacement) .chars() .map(|c| match c { '\t' => 4, _ => 1, }) .sum(); - if !is_different(self, part.replacement, part.span.clone()) { + if !is_different(self, &part.replacement, part.span.clone()) { // Account for cases where we are suggesting the same code that's already // there. This shouldn't happen often, but in some cases for multipart // suggestions it's much easier to handle it here than in the origin. @@ -470,7 +471,7 @@ impl<'a> SourceMap<'a> { end: (cur_lo.char as isize + acc + len) as usize, }); } - buf.push_str(part.replacement); + buf.push_str(&part.replacement); // Account for the difference between the width of the current code and the // snippet being suggested, so that the *later* suggestions are correctly // aligned on the screen. Note that cur_hi and cur_lo can be on different @@ -514,7 +515,7 @@ pub(crate) struct MultilineAnnotation<'a> { pub start: Loc, pub end: Loc, pub kind: AnnotationKind, - pub label: Option<&'a str>, + pub label: Option<Cow<'a, str>>, pub overlaps_exactly: bool, pub highlight_source: bool, } @@ -555,7 +556,7 @@ impl<'a> MultilineAnnotation<'a> { }, end: self.end, kind: self.kind, - label: self.label, + label: self.label.clone(), annotation_type: LineAnnotationType::MultilineEnd(self.depth), highlight_source: self.highlight_source, } diff --git a/src/snippet.rs b/src/snippet.rs index 6e9a78c7..f8b243fb 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -2,6 +2,7 @@ use crate::renderer::source_map::SourceMap; use crate::Level; +use std::borrow::Cow; use std::ops::Range; pub(crate) const ERROR_TXT: &str = "error"; @@ -12,8 +13,8 @@ pub(crate) const WARNING_TXT: &str = "warning"; #[derive(Clone, Debug, Default)] pub(crate) struct Id<'a> { - pub(crate) id: Option<&'a str>, - pub(crate) url: Option<&'a str>, + pub(crate) id: Option<Cow<'a, str>>, + pub(crate) url: Option<Cow<'a, str>>, } /// An [`Element`] container @@ -100,7 +101,7 @@ pub struct Padding; pub struct Title<'a> { pub(crate) level: Level<'a>, pub(crate) id: Option<Id<'a>>, - pub(crate) title: &'a str, + pub(crate) title: Cow<'a, str>, pub(crate) is_pre_styled: bool, } @@ -117,8 +118,8 @@ impl<'a> Title<'a> { /// not allowed to be passed to this function. /// /// </div> - pub fn id(mut self, id: &'a str) -> Self { - self.id.get_or_insert(Id::default()).id = Some(id); + pub fn id(mut self, id: impl Into<Cow<'a, str>>) -> Self { + self.id.get_or_insert(Id::default()).id = Some(id.into()); self } @@ -128,8 +129,8 @@ impl<'a> Title<'a> { /// `id` present /// /// </div> - pub fn id_url(mut self, url: &'a str) -> Self { - self.id.get_or_insert(Id::default()).url = Some(url); + pub fn id_url(mut self, url: impl Into<Cow<'a, str>>) -> Self { + self.id.get_or_insert(Id::default()).url = Some(url.into()); self } } @@ -139,9 +140,9 @@ impl<'a> Title<'a> { /// If you do not have [source][Snippet::source] available, see instead [`Origin`] #[derive(Clone, Debug)] pub struct Snippet<'a, T> { - pub(crate) path: Option<&'a str>, + pub(crate) path: Option<Cow<'a, str>>, pub(crate) line_start: usize, - pub(crate) source: &'a str, + pub(crate) source: Cow<'a, str>, pub(crate) markers: Vec<T>, pub(crate) fold: bool, } @@ -156,11 +157,11 @@ impl<'a, T: Clone> Snippet<'a, T> { /// not allowed to be passed to this function. /// /// </div> - pub fn source(source: &'a str) -> Self { + pub fn source(source: impl Into<Cow<'a, str>>) -> Self { Self { path: None, line_start: 1, - source, + source: source.into(), markers: vec![], fold: false, } @@ -182,8 +183,8 @@ impl<'a, T: Clone> Snippet<'a, T> { /// not allowed to be passed to this function. /// /// </div> - pub fn path(mut self, path: &'a str) -> Self { - self.path = Some(path); + pub fn path(mut self, path: impl Into<OptionCow<'a>>) -> Self { + self.path = path.into().0; self } @@ -228,7 +229,7 @@ impl<'a> Snippet<'a, Patch<'a>> { #[derive(Clone, Debug)] pub struct Annotation<'a> { pub(crate) span: Range<usize>, - pub(crate) label: Option<&'a str>, + pub(crate) label: Option<Cow<'a, str>>, pub(crate) kind: AnnotationKind, pub(crate) highlight_source: bool, } @@ -245,8 +246,8 @@ impl<'a> Annotation<'a> { /// not allowed to be passed to this function. /// /// </div> - pub fn label(mut self, label: &'a str) -> Self { - self.label = Some(label); + pub fn label(mut self, label: impl Into<OptionCow<'a>>) -> Self { + self.label = label.into().0; self } @@ -286,7 +287,7 @@ impl AnnotationKind { #[derive(Clone, Debug)] pub struct Patch<'a> { pub(crate) span: Range<usize>, - pub(crate) replacement: &'a str, + pub(crate) replacement: Cow<'a, str>, } impl<'a> Patch<'a> { @@ -299,8 +300,11 @@ impl<'a> Patch<'a> { /// not allowed to be passed to this function. /// /// </div> - pub fn new(span: Range<usize>, replacement: &'a str) -> Self { - Self { span, replacement } + pub fn new(span: Range<usize>, replacement: impl Into<Cow<'a, str>>) -> Self { + Self { + span, + replacement: replacement.into(), + } } pub(crate) fn is_addition(&self, sm: &SourceMap<'_>) -> bool { @@ -344,9 +348,9 @@ impl<'a> Patch<'a> { return; }; - if let Some((prefix, substr, suffix)) = as_substr(snippet, self.replacement) { + if let Some((prefix, substr, suffix)) = as_substr(snippet, &self.replacement) { self.span = self.span.start + prefix..self.span.end.saturating_sub(suffix); - self.replacement = substr; + self.replacement = Cow::Owned(substr.to_owned()); } } } @@ -356,7 +360,7 @@ impl<'a> Patch<'a> { /// If you have source available, see instead [`Snippet`] #[derive(Clone, Debug)] pub struct Origin<'a> { - pub(crate) path: &'a str, + pub(crate) path: Cow<'a, str>, pub(crate) line: Option<usize>, pub(crate) char_column: Option<usize>, pub(crate) primary: bool, @@ -370,9 +374,9 @@ impl<'a> Origin<'a> { /// not allowed to be passed to this function. /// /// </div> - pub fn new(path: &'a str) -> Self { + pub fn new(path: impl Into<Cow<'a, str>>) -> Self { Self { - path, + path: path.into(), line: None, char_column: None, primary: false, @@ -407,6 +411,50 @@ impl<'a> Origin<'a> { } } +impl<'a> From<Cow<'a, str>> for Origin<'a> { + fn from(origin: Cow<'a, str>) -> Self { + Self::new(origin) + } +} + +#[derive(Debug)] +pub struct OptionCow<'a>(pub(crate) Option<Cow<'a, str>>); + +impl<'a, T: Into<Cow<'a, str>>> From<Option<T>> for OptionCow<'a> { + fn from(value: Option<T>) -> Self { + Self(value.map(Into::into)) + } +} + +impl<'a> From<&'a Cow<'a, str>> for OptionCow<'a> { + fn from(value: &'a Cow<'a, str>) -> Self { + Self(Some(Cow::Borrowed(value))) + } +} + +impl<'a> From<Cow<'a, str>> for OptionCow<'a> { + fn from(value: Cow<'a, str>) -> Self { + Self(Some(value)) + } +} + +impl<'a> From<&'a str> for OptionCow<'a> { + fn from(value: &'a str) -> Self { + Self(Some(Cow::Borrowed(value))) + } +} +impl<'a> From<String> for OptionCow<'a> { + fn from(value: String) -> Self { + Self(Some(Cow::Owned(value))) + } +} + +impl<'a> From<&'a String> for OptionCow<'a> { + fn from(value: &'a String) -> Self { + Self(Some(Cow::Borrowed(value.as_str()))) + } +} + /// Given an original string like `AACC`, and a suggestion like `AABBCC`, try to detect /// the case where a substring of the suggestion is "sandwiched" in the original, like /// `BB` is. Return the length of the prefix, the "trimmed" suggestion, and the length diff --git a/tests/formatter.rs b/tests/formatter.rs index 34c40bf8..c538a9e8 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -2448,7 +2448,7 @@ fn secondary_title_no_level_text() { ) .element( Level::NOTE - .text(None) + .text(None::<&str>) .title("expected reference `&str`\nfound reference `&'static [u8; 0]`"), )]; From 1b950821f4ed792578490f1cd0e9cb8b3780c140 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Mon, 30 Jun 2025 10:27:22 -0500 Subject: [PATCH 400/455] fix!: Make AnnotationKind non-exhaustive This will allow adding more cases in the future. --- src/snippet.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/snippet.rs b/src/snippet.rs index 6e9a78c7..2cbf3d7c 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -259,6 +259,7 @@ impl<'a> Annotation<'a> { /// The category of the [`Annotation`] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[non_exhaustive] pub enum AnnotationKind { /// Color to the [`Level`] the first [`Title`] in [`Group`]. If no [`Title`] /// is present, it will default to `error`. From ac0fcf34fb3e160dc76e21db14cd6148d586e4a2 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Mon, 30 Jun 2025 11:39:59 -0600 Subject: [PATCH 401/455] fix: Add missing functions for setting stylesheet colors --- src/renderer/mod.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 0cc43d26..85bdf628 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -206,6 +206,24 @@ impl Renderer { self.stylesheet.none = style; self } + + /// Set the output style for [`AnnotationKind::Context`] + pub const fn context(mut self, style: Style) -> Self { + self.stylesheet.context = style; + self + } + + /// Set the output style for additions + pub const fn addition(mut self, style: Style) -> Self { + self.stylesheet.addition = style; + self + } + + /// Set the output style for removals + pub const fn removal(mut self, style: Style) -> Self { + self.stylesheet.removal = style; + self + } } impl Renderer { From 4e70f5649ed6fd623c00252e1b3023d5d7b14aac Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Mon, 30 Jun 2025 18:10:46 -0600 Subject: [PATCH 402/455] docs: More accuratly describe AnnotationKind variants --- src/snippet.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/snippet.rs b/src/snippet.rs index c2ed5c77..8c213260 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -262,10 +262,14 @@ impl<'a> Annotation<'a> { #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] #[non_exhaustive] pub enum AnnotationKind { - /// Color to the [`Level`] the first [`Title`] in [`Group`]. If no [`Title`] - /// is present, it will default to `error`. + /// Match the primary [`Level`] of the group. Primary, - /// "secondary"; fixed color + /// Additional context to explain the [`Primary`][Self::Primary] + /// [`Annotation`] + /// + /// See also [`Renderer::context`]. + /// + /// [`Renderer::context`]: crate::renderer::Renderer Context, } From 52fd5df0e0c3f3faf5ddc1ea3fdf3c88a500ddeb Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 24 Jun 2025 03:30:20 -0600 Subject: [PATCH 403/455] test: Add a test for a group with no Title --- examples/elide_header.rs | 21 +++++++++++++++++++ examples/elide_header.svg | 44 +++++++++++++++++++++++++++++++++++++++ tests/examples.rs | 7 +++++++ 3 files changed, 72 insertions(+) create mode 100644 examples/elide_header.rs create mode 100644 examples/elide_header.svg diff --git a/examples/elide_header.rs b/examples/elide_header.rs new file mode 100644 index 00000000..716f8e56 --- /dev/null +++ b/examples/elide_header.rs @@ -0,0 +1,21 @@ +use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; + +fn main() { + let source = r#"# Docstring followed by a newline + +def foobar(door, bar={}): + """ + """ +"#; + + let message = &[Group::new() + .element( + Snippet::source(source) + .fold(false) + .annotation(AnnotationKind::Primary.span(56..58).label("B006")), + ) + .element(Level::HELP.title("Replace with `None`; initialize within function"))]; + + let renderer = Renderer::styled(); + anstream::println!("{}", renderer.render(message)); +} diff --git a/examples/elide_header.svg b/examples/elide_header.svg new file mode 100644 index 00000000..cd4eead7 --- /dev/null +++ b/examples/elide_header.svg @@ -0,0 +1,44 @@ +<svg width="740px" height="200px" xmlns="http://www.w3.org/2000/svg"> + <style> + .fg { fill: #AAAAAA } + .bg { background: #000000 } + .fg-bright-blue { fill: #5555FF } + .fg-bright-cyan { fill: #55FFFF } + .container { + padding: 0 10px; + line-height: 18px; + } + .bold { font-weight: bold; } + tspan { + font: 14px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace; + white-space: pre; + line-height: 18px; + } + </style> + + <rect width="100%" height="100%" y="0" rx="4.5" class="bg" /> + + <text xml:space="preserve" class="container fg"> + <tspan x="10px" y="28px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> +</tspan> + <tspan x="10px" y="46px"><tspan class="fg-bright-blue bold">1</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> # Docstring followed by a newline</tspan> +</tspan> + <tspan x="10px" y="64px"><tspan class="fg-bright-blue bold">2</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> +</tspan> + <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">3</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> def foobar(door, bar={}):</tspan> +</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-cyan bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-cyan bold">B006</tspan> +</tspan> + <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">4</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> """</tspan> +</tspan> + <tspan x="10px" y="136px"><tspan class="fg-bright-blue bold">5</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> """</tspan> +</tspan> + <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> +</tspan> + <tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">= </tspan><tspan class="bold">help</tspan><tspan>: Replace with `None`; initialize within function</tspan> +</tspan> + <tspan x="10px" y="190px"> +</tspan> + </text> + +</svg> diff --git a/tests/examples.rs b/tests/examples.rs index ec2643e9..db00bc1f 100644 --- a/tests/examples.rs +++ b/tests/examples.rs @@ -14,6 +14,13 @@ fn custom_level() { assert_example(target, expected); } +#[test] +fn elide_header() { + let target = "elide_header"; + let expected = snapbox::file!["../examples/elide_header.svg": TermSvg]; + assert_example(target, expected); +} + #[test] fn expected_type() { let target = "expected_type"; From 74cc62b1e7f2dd93aba78057707714598671486c Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Mon, 30 Jun 2025 17:56:38 -0600 Subject: [PATCH 404/455] fix: Only the first element in a Group can set Level --- examples/elide_header.svg | 4 ++-- src/renderer/mod.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/elide_header.svg b/examples/elide_header.svg index cd4eead7..080837d6 100644 --- a/examples/elide_header.svg +++ b/examples/elide_header.svg @@ -3,7 +3,7 @@ .fg { fill: #AAAAAA } .bg { background: #000000 } .fg-bright-blue { fill: #5555FF } - .fg-bright-cyan { fill: #55FFFF } + .fg-bright-red { fill: #FF5555 } .container { padding: 0 10px; line-height: 18px; @@ -27,7 +27,7 @@ </tspan> <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">3</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> def foobar(door, bar={}):</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-cyan bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-cyan bold">B006</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">B006</tspan> </tspan> <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">4</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> """</tspan> </tspan> diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 85bdf628..9912d52c 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -277,8 +277,8 @@ impl Renderer { } let level = group .elements - .iter() - .find_map(|s| match &s { + .first() + .and_then(|s| match &s { Element::Title(title) => Some(title.level.clone()), _ => None, }) From 5f0b4252f744ba2e02e5dadadc2c2837074a7eaf Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 24 Jun 2025 03:30:20 -0600 Subject: [PATCH 405/455] feat: Allow setting the primary level for a group --- examples/elide_header.rs | 1 + examples/elide_header.svg | 4 ++-- src/renderer/mod.rs | 18 ++++++++++-------- src/snippet.rs | 17 ++++++++++++++++- 4 files changed, 29 insertions(+), 11 deletions(-) diff --git a/examples/elide_header.rs b/examples/elide_header.rs index 716f8e56..a280616c 100644 --- a/examples/elide_header.rs +++ b/examples/elide_header.rs @@ -9,6 +9,7 @@ def foobar(door, bar={}): "#; let message = &[Group::new() + .primary_level(Level::NOTE) .element( Snippet::source(source) .fold(false) diff --git a/examples/elide_header.svg b/examples/elide_header.svg index 080837d6..ccec3e10 100644 --- a/examples/elide_header.svg +++ b/examples/elide_header.svg @@ -3,7 +3,7 @@ .fg { fill: #AAAAAA } .bg { background: #000000 } .fg-bright-blue { fill: #5555FF } - .fg-bright-red { fill: #FF5555 } + .fg-bright-green { fill: #55FF55 } .container { padding: 0 10px; line-height: 18px; @@ -27,7 +27,7 @@ </tspan> <tspan x="10px" y="82px"><tspan class="fg-bright-blue bold">3</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> def foobar(door, bar={}):</tspan> </tspan> - <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">B006</tspan> + <tspan x="10px" y="100px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-green bold">^^</tspan><tspan> </tspan><tspan class="fg-bright-green bold">B006</tspan> </tspan> <tspan x="10px" y="118px"><tspan class="fg-bright-blue bold">4</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> """</tspan> </tspan> diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 9912d52c..9d89026c 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -275,14 +275,16 @@ impl Renderer { if og_primary_path.is_none() && primary_path.is_some() { og_primary_path = primary_path; } - let level = group - .elements - .first() - .and_then(|s| match &s { - Element::Title(title) => Some(title.level.clone()), - _ => None, - }) - .unwrap_or(Level::ERROR); + let level = group.primary_level.clone().unwrap_or_else(|| { + group + .elements + .first() + .and_then(|s| match &s { + Element::Title(title) => Some(title.level.clone()), + _ => None, + }) + .unwrap_or(Level::ERROR) + }); let mut source_map_annotated_lines = VecDeque::new(); let mut max_depth = 0; for e in &group.elements { diff --git a/src/snippet.rs b/src/snippet.rs index 8c213260..8206f600 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -20,6 +20,7 @@ pub(crate) struct Id<'a> { /// An [`Element`] container #[derive(Clone, Debug)] pub struct Group<'a> { + pub(crate) primary_level: Option<Level<'a>>, pub(crate) elements: Vec<Element<'a>>, } @@ -31,7 +32,10 @@ impl Default for Group<'_> { impl<'a> Group<'a> { pub fn new() -> Self { - Self { elements: vec![] } + Self { + primary_level: None, + elements: vec![], + } } pub fn element(mut self, section: impl Into<Element<'a>>) -> Self { @@ -44,6 +48,15 @@ impl<'a> Group<'a> { self } + /// Set the primary [`Level`] for this [`Group`]. + /// + /// If not specified, use the [`Level`] of the first element in a [`Group`] + /// if it is a [`Title`]. If not it will default to [`Level::ERROR`]. + pub fn primary_level(mut self, level: Level<'a>) -> Self { + self.primary_level = Some(level); + self + } + pub fn is_empty(&self) -> bool { self.elements.is_empty() } @@ -263,6 +276,8 @@ impl<'a> Annotation<'a> { #[non_exhaustive] pub enum AnnotationKind { /// Match the primary [`Level`] of the group. + /// + /// See [`Group::primary_level`] for details about how this is determined Primary, /// Additional context to explain the [`Primary`][Self::Primary] /// [`Annotation`] From 08e68a37dc92370a0763097a3cb209ad7194bb87 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 1 Jul 2025 02:00:09 +0000 Subject: [PATCH 406/455] chore(deps): Update Rust crate anstream to v0.6.19 (#236) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8cfa6638..d44cfe5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,9 +17,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", From 49322a7599e21ce5b7b251eff2a297ff522eefa8 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Tue, 1 Jul 2025 16:23:45 -0500 Subject: [PATCH 407/455] fix: Force group levels to be explicitly given Moved from `Group::new().primary_level(level)` to `Group::with_level(level)`. As a convenience for titled-groups, we have `Group::with_title(title)`. I had considered `child`/`children` methods on `Title` to make a group but that didn't work out well for title-only groups and I feel like this design is more consistent / easier to discover. --- benches/bench.rs | 16 +- examples/custom_error.rs | 33 +- examples/custom_level.rs | 68 +- examples/elide_header.rs | 3 +- examples/expected_type.rs | 8 +- examples/footer.rs | 15 +- examples/format.rs | 8 +- examples/highlight_source.rs | 2 +- examples/highlight_title.rs | 19 +- examples/id_hyperlink.rs | 45 +- examples/multislice.rs | 3 +- src/renderer/mod.rs | 44 +- src/snippet.rs | 28 +- tests/color/ann_eof.rs | 8 +- tests/color/ann_insertion.rs | 8 +- tests/color/ann_multiline.rs | 33 +- tests/color/ann_multiline2.rs | 8 +- tests/color/ann_removed_nl.rs | 8 +- tests/color/ensure_emoji_highlight_width.rs | 3 +- tests/color/fold_ann_multiline.rs | 8 +- tests/color/fold_bad_origin_line.rs | 2 +- tests/color/fold_leading.rs | 25 +- tests/color/fold_trailing.rs | 25 +- tests/color/issue_9.rs | 2 +- tests/color/multiline_removal_suggestion.rs | 8 +- tests/color/multiple_annotations.rs | 2 +- tests/color/simple.rs | 35 +- tests/color/strip_line.rs | 8 +- tests/color/strip_line_char.rs | 8 +- tests/color/strip_line_non_ws.rs | 8 +- tests/formatter.rs | 788 +++++++++-------- tests/rustc_tests.rs | 896 ++++++++++---------- 32 files changed, 1038 insertions(+), 1137 deletions(-) diff --git a/benches/bench.rs b/benches/bench.rs index 6390628f..4e03ae8c 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -24,9 +24,8 @@ fn simple() -> String { _ => continue, } }"#; - let message = &[Group::new() - .element(Level::ERROR.title("mismatched types").id("E0308")) - .element( + let message = &[ + Group::with_title(Level::ERROR.title("mismatched types").id("E0308")).element( Snippet::source(source) .line_start(51) .path("src/format.rs") @@ -40,7 +39,8 @@ fn simple() -> String { .span(26..724) .label("expected enum `std::option::Option`"), ), - )]; + ), + ]; let renderer = Renderer::plain(); let rendered = renderer.render(message); @@ -69,9 +69,8 @@ fn fold(bencher: divan::Bencher<'_, '_>, context: usize) { (input, span) }) .bench_values(|(input, span)| { - let message = &[Group::new() - .element(Level::ERROR.title("mismatched types").id("E0308")) - .element( + let message = &[ + Group::with_title(Level::ERROR.title("mismatched types").id("E0308")).element( Snippet::source(&input) .fold(true) .path("src/format.rs") @@ -80,7 +79,8 @@ fn fold(bencher: divan::Bencher<'_, '_>, context: usize) { .span(span) .label("expected `Option<String>` because of return type"), ), - )]; + ), + ]; let renderer = Renderer::plain(); let rendered = renderer.render(message); diff --git a/examples/custom_error.rs b/examples/custom_error.rs index e80b1466..a26dc4af 100644 --- a/examples/custom_error.rs +++ b/examples/custom_error.rs @@ -15,23 +15,22 @@ fn main() { pub static C: u32 = 0 - 1; //~^ ERROR could not evaluate static initializer "#; - let message = &[Group::new() - .element( - Level::ERROR - .text(Some("error: internal compiler error")) - .title("could not evaluate static initializer") - .id("E0080"), - ) - .element( - Snippet::source(source) - .path("$DIR/err.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(386..391) - .label("attempt to compute `0_u32 - 1_u32`, which would overflow"), - ), - )]; + let message = &[Group::with_title( + Level::ERROR + .text(Some("error: internal compiler error")) + .title("could not evaluate static initializer") + .id("E0080"), + ) + .element( + Snippet::source(source) + .path("$DIR/err.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(386..391) + .label("attempt to compute `0_u32 - 1_u32`, which would overflow"), + ), + )]; let renderer = Renderer::styled().theme(OutputTheme::Unicode); anstream::println!("{}", renderer.render(message)); diff --git a/examples/custom_level.rs b/examples/custom_level.rs index b500c063..3cf475d0 100644 --- a/examples/custom_level.rs +++ b/examples/custom_level.rs @@ -30,41 +30,39 @@ fn main() { } "#; let message = &[ - Group::new() - .element( - Level::ERROR - .title("`break` with value from a `while` loop") - .id("E0571"), - ) - .element( - Snippet::source(source) - .line_start(1) - .path("$DIR/issue-114529-illegal-break-with-value.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(483..581) - .label("can only break with a value inside `loop` or breakable block"), - ) - .annotation( - AnnotationKind::Context - .span(462..472) - .label("you can't `break` with a value in a `while` loop"), - ), - ), - Group::new() - .element( - Level::HELP - .text(Some("suggestion")) - .title("use `break` on its own without a value inside this `while` loop"), - ) - .element( - Snippet::source(source) - .line_start(1) - .path("$DIR/issue-114529-illegal-break-with-value.rs") - .fold(true) - .patch(Patch::new(483..581, "break")), - ), + Group::with_title( + Level::ERROR + .title("`break` with value from a `while` loop") + .id("E0571"), + ) + .element( + Snippet::source(source) + .line_start(1) + .path("$DIR/issue-114529-illegal-break-with-value.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(483..581) + .label("can only break with a value inside `loop` or breakable block"), + ) + .annotation( + AnnotationKind::Context + .span(462..472) + .label("you can't `break` with a value in a `while` loop"), + ), + ), + Group::with_title( + Level::HELP + .text(Some("suggestion")) + .title("use `break` on its own without a value inside this `while` loop"), + ) + .element( + Snippet::source(source) + .line_start(1) + .path("$DIR/issue-114529-illegal-break-with-value.rs") + .fold(true) + .patch(Patch::new(483..581, "break")), + ), ]; let renderer = Renderer::styled().theme(OutputTheme::Unicode); diff --git a/examples/elide_header.rs b/examples/elide_header.rs index a280616c..436bb25e 100644 --- a/examples/elide_header.rs +++ b/examples/elide_header.rs @@ -8,8 +8,7 @@ def foobar(door, bar={}): """ "#; - let message = &[Group::new() - .primary_level(Level::NOTE) + let message = &[Group::with_level(Level::NOTE) .element( Snippet::source(source) .fold(false) diff --git a/examples/expected_type.rs b/examples/expected_type.rs index 0fce9938..6bbf0812 100644 --- a/examples/expected_type.rs +++ b/examples/expected_type.rs @@ -6,9 +6,8 @@ fn main() { , range: <22, 25>,"#; let message = - &[Group::new() - .element(Level::ERROR.title("expected type, found `22`")) - .element( + &[ + Group::with_title(Level::ERROR.title("expected type, found `22`")).element( Snippet::source(source) .line_start(26) .path("examples/footer.rs") @@ -21,7 +20,8 @@ fn main() { .span(34..50) .label("while parsing this struct"), ), - )]; + ), + ]; let renderer = Renderer::styled(); anstream::println!("{}", renderer.render(message)); diff --git a/examples/footer.rs b/examples/footer.rs index aa9b784f..e9d6b7ee 100644 --- a/examples/footer.rs +++ b/examples/footer.rs @@ -1,10 +1,9 @@ use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; fn main() { - let message = &[ - Group::new() - .element(Level::ERROR.title("mismatched types").id("E0308")) - .element( + let message = + &[ + Group::with_title(Level::ERROR.title("mismatched types").id("E0308")).element( Snippet::source(" slices: vec![\"A\",") .line_start(13) .path("src/multislice.rs") @@ -12,10 +11,10 @@ fn main() { "expected struct `annotate_snippets::snippet::Slice`, found reference", )), ), - Group::new().element(Level::NOTE.title( - "expected type: `snippet::Annotation`\n found type: `__&__snippet::Annotation`", - )), - ]; + Group::with_title(Level::NOTE.title( + "expected type: `snippet::Annotation`\n found type: `__&__snippet::Annotation`", + )), + ]; let renderer = Renderer::styled(); anstream::println!("{}", renderer.render(message)); diff --git a/examples/format.rs b/examples/format.rs index ae603259..384453b2 100644 --- a/examples/format.rs +++ b/examples/format.rs @@ -23,9 +23,8 @@ fn main() { _ => continue, } }"#; - let message = &[Group::new() - .element(Level::ERROR.title("mismatched types").id("E0308")) - .element( + let message = &[ + Group::with_title(Level::ERROR.title("mismatched types").id("E0308")).element( Snippet::source(source) .line_start(51) .path("src/format.rs") @@ -39,7 +38,8 @@ fn main() { .span(26..724) .label("expected enum `std::option::Option`"), ), - )]; + ), + ]; let renderer = Renderer::styled(); anstream::println!("{}", renderer.render(message)); diff --git a/examples/highlight_source.rs b/examples/highlight_source.rs index f897a3f5..297ad871 100644 --- a/examples/highlight_source.rs +++ b/examples/highlight_source.rs @@ -9,7 +9,7 @@ const CON: Vec<i32> = vec![1, 2, 3]; //~ ERROR E0010 //~| ERROR cannot call non-const method fn main() {} "#; - let message = &[Group::new().element(Level::ERROR.title("allocations are not allowed in constants") + let message = &[Group::with_title(Level::ERROR.title("allocations are not allowed in constants") .id("E0010")) .element( Snippet::source(source) diff --git a/examples/highlight_title.rs b/examples/highlight_title.rs index 6ed3817f..7f6ccb77 100644 --- a/examples/highlight_title.rs +++ b/examples/highlight_title.rs @@ -42,8 +42,7 @@ fn main() { ); let message = &[ - Group::new() - .element(Level::ERROR.title("mismatched types").id("E0308")) + Group::with_title(Level::ERROR.title("mismatched types").id("E0308")) .element( Snippet::source(source) .fold(true) @@ -60,15 +59,13 @@ fn main() { ), ) .element(Level::NOTE.pre_styled_title(&title)), - Group::new() - .element(Level::NOTE.title("function defined here")) - .element( - Snippet::source(source) - .fold(true) - .path("$DIR/highlighting.rs") - .annotation(AnnotationKind::Context.span(200..333).label("")) - .annotation(AnnotationKind::Primary.span(194..199)), - ), + Group::with_title(Level::NOTE.title("function defined here")).element( + Snippet::source(source) + .fold(true) + .path("$DIR/highlighting.rs") + .annotation(AnnotationKind::Context.span(200..333).label("")) + .annotation(AnnotationKind::Primary.span(194..199)), + ), ]; let renderer = Renderer::styled().anonymized_line_numbers(true); diff --git a/examples/id_hyperlink.rs b/examples/id_hyperlink.rs index 2d49b275..6259e4d1 100644 --- a/examples/id_hyperlink.rs +++ b/examples/id_hyperlink.rs @@ -7,29 +7,28 @@ fn main() { let () = 4; //~ ERROR } "#; - let message = &[Group::new() - .element( - Level::ERROR - .title("mismatched types") - .id("E0308") - .id_url("https://doc.rust-lang.org/error_codes/E0308.html"), - ) - .element( - Snippet::source(source) - .line_start(1) - .path("$DIR/terminal_urls.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(59..61) - .label("expected integer, found `()`"), - ) - .annotation( - AnnotationKind::Context - .span(64..65) - .label("this expression has type `{integer}`"), - ), - )]; + let message = &[Group::with_title( + Level::ERROR + .title("mismatched types") + .id("E0308") + .id_url("https://doc.rust-lang.org/error_codes/E0308.html"), + ) + .element( + Snippet::source(source) + .line_start(1) + .path("$DIR/terminal_urls.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(59..61) + .label("expected integer, found `()`"), + ) + .annotation( + AnnotationKind::Context + .span(64..65) + .label("this expression has type `{integer}`"), + ), + )]; let renderer = Renderer::styled().theme(OutputTheme::Unicode); anstream::println!("{}", renderer.render(message)); diff --git a/examples/multislice.rs b/examples/multislice.rs index 9393aadb..35b745c1 100644 --- a/examples/multislice.rs +++ b/examples/multislice.rs @@ -1,8 +1,7 @@ use annotate_snippets::{Annotation, Group, Level, Renderer, Snippet}; fn main() { - let message = &[Group::new() - .element(Level::ERROR.title("mismatched types")) + let message = &[Group::with_title(Level::ERROR.title("mismatched types")) .element( Snippet::<Annotation<'_>>::source("Foo") .line_start(51) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 9d89026c..fe29822f 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -20,23 +20,22 @@ //! "#; //! //! -//! Group::new() -//! .element( -//! Level::ERROR -//! .title("unresolved import `baz::zed`") -//! .id("E0432") -//! ) -//! .element( -//! Snippet::source(source) -//! .path("temp.rs") -//! .line_start(1) -//! .fold(true) -//! .annotation( -//! AnnotationKind::Primary -//! .span(10..13) -//! .label("could not find `zed` in `baz`"), -//! ) -//! ); +//! Group::with_title( +//! Level::ERROR +//! .title("unresolved import `baz::zed`") +//! .id("E0432") +//! ) +//! .element( +//! Snippet::source(source) +//! .path("temp.rs") +//! .line_start(1) +//! .fold(true) +//! .annotation( +//! AnnotationKind::Primary +//! .span(10..13) +//! .label("could not find `zed` in `baz`"), +//! ) +//! ); //! ``` mod margin; @@ -275,16 +274,7 @@ impl Renderer { if og_primary_path.is_none() && primary_path.is_some() { og_primary_path = primary_path; } - let level = group.primary_level.clone().unwrap_or_else(|| { - group - .elements - .first() - .and_then(|s| match &s { - Element::Title(title) => Some(title.level.clone()), - _ => None, - }) - .unwrap_or(Level::ERROR) - }); + let level = group.primary_level.clone(); let mut source_map_annotated_lines = VecDeque::new(); let mut max_depth = 0; for e in &group.elements { diff --git a/src/snippet.rs b/src/snippet.rs index 8206f600..3045b2a7 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -20,20 +20,21 @@ pub(crate) struct Id<'a> { /// An [`Element`] container #[derive(Clone, Debug)] pub struct Group<'a> { - pub(crate) primary_level: Option<Level<'a>>, + pub(crate) primary_level: Level<'a>, pub(crate) elements: Vec<Element<'a>>, } -impl Default for Group<'_> { - fn default() -> Self { - Self::new() +impl<'a> Group<'a> { + /// Create group with a title, deriving the primary [`Level`] for [`Annotation`]s from it + pub fn with_title(title: Title<'a>) -> Self { + let level = title.level.clone(); + Self::with_level(level).element(title) } -} -impl<'a> Group<'a> { - pub fn new() -> Self { + /// Create a title-less group with a primary [`Level`] for [`Annotation`]s + pub fn with_level(level: Level<'a>) -> Self { Self { - primary_level: None, + primary_level: level, elements: vec![], } } @@ -48,15 +49,6 @@ impl<'a> Group<'a> { self } - /// Set the primary [`Level`] for this [`Group`]. - /// - /// If not specified, use the [`Level`] of the first element in a [`Group`] - /// if it is a [`Title`]. If not it will default to [`Level::ERROR`]. - pub fn primary_level(mut self, level: Level<'a>) -> Self { - self.primary_level = Some(level); - self - } - pub fn is_empty(&self) -> bool { self.elements.is_empty() } @@ -277,7 +269,7 @@ impl<'a> Annotation<'a> { pub enum AnnotationKind { /// Match the primary [`Level`] of the group. /// - /// See [`Group::primary_level`] for details about how this is determined + /// See [`Group::with_level`] for details about how this is determined Primary, /// Additional context to explain the [`Primary`][Self::Primary] /// [`Annotation`] diff --git a/tests/color/ann_eof.rs b/tests/color/ann_eof.rs index e550ba55..d188fd1a 100644 --- a/tests/color/ann_eof.rs +++ b/tests/color/ann_eof.rs @@ -4,14 +4,14 @@ use snapbox::{assert_data_eq, file}; #[test] fn case() { - let input = &[Group::new() - .element(Level::ERROR.title("expected `.`, `=`")) - .element( + let input = &[ + Group::with_title(Level::ERROR.title("expected `.`, `=`")).element( Snippet::source("asdf") .path("Cargo.toml") .line_start(1) .annotation(AnnotationKind::Primary.span(4..4).label("")), - )]; + ), + ]; let expected = file!["ann_eof.term.svg"]; let renderer = Renderer::styled(); assert_data_eq!(renderer.render(input), expected); diff --git a/tests/color/ann_insertion.rs b/tests/color/ann_insertion.rs index 73dd7d80..a0748e59 100644 --- a/tests/color/ann_insertion.rs +++ b/tests/color/ann_insertion.rs @@ -4,14 +4,14 @@ use snapbox::{assert_data_eq, file}; #[test] fn case() { - let input = &[Group::new() - .element(Level::ERROR.title("expected `.`, `=`")) - .element( + let input = &[ + Group::with_title(Level::ERROR.title("expected `.`, `=`")).element( Snippet::source("asf") .path("Cargo.toml") .line_start(1) .annotation(AnnotationKind::Primary.span(2..2).label("'d' belongs here")), - )]; + ), + ]; let expected = file!["ann_insertion.term.svg"]; let renderer = Renderer::styled(); assert_data_eq!(renderer.render(input), expected); diff --git a/tests/color/ann_multiline.rs b/tests/color/ann_multiline.rs index 29d4c309..1801c3bc 100644 --- a/tests/color/ann_multiline.rs +++ b/tests/color/ann_multiline.rs @@ -9,23 +9,22 @@ fn case() { } = body[body_idx] "#; - let input = &[Group::new() - .element( - Level::ERROR - .title("pattern does not mention fields `lineno`, `content`") - .id("E0027"), - ) - .element( - Snippet::source(source) - .path("src/display_list.rs") - .line_start(139) - .fold(false) - .annotation( - AnnotationKind::Primary - .span(31..128) - .label("missing fields `lineno`, `content`"), - ), - )]; + let input = &[Group::with_title( + Level::ERROR + .title("pattern does not mention fields `lineno`, `content`") + .id("E0027"), + ) + .element( + Snippet::source(source) + .path("src/display_list.rs") + .line_start(139) + .fold(false) + .annotation( + AnnotationKind::Primary + .span(31..128) + .label("missing fields `lineno`, `content`"), + ), + )]; let expected = file!["ann_multiline.term.svg"]; let renderer = Renderer::styled(); assert_data_eq!(renderer.render(input), expected); diff --git a/tests/color/ann_multiline2.rs b/tests/color/ann_multiline2.rs index cf21e5ea..b8fa6152 100644 --- a/tests/color/ann_multiline2.rs +++ b/tests/color/ann_multiline2.rs @@ -9,9 +9,8 @@ of an edge case of an annotation overflowing to exactly one character on next line. "#; - let input = &[Group::new() - .element(Level::ERROR.title("spacing error found").id("E####")) - .element( + let input = &[ + Group::with_title(Level::ERROR.title("spacing error found").id("E####")).element( Snippet::source(source) .path("foo.txt") .line_start(26) @@ -21,7 +20,8 @@ to exactly one character on next line. .span(11..19) .label("this should not be on separate lines"), ), - )]; + ), + ]; let expected = file!["ann_multiline2.term.svg"]; let renderer = Renderer::styled(); assert_data_eq!(renderer.render(input), expected); diff --git a/tests/color/ann_removed_nl.rs b/tests/color/ann_removed_nl.rs index 68ec8326..e13b4bd5 100644 --- a/tests/color/ann_removed_nl.rs +++ b/tests/color/ann_removed_nl.rs @@ -4,14 +4,14 @@ use snapbox::{assert_data_eq, file}; #[test] fn case() { - let input = &[Group::new() - .element(Level::ERROR.title("expected `.`, `=`")) - .element( + let input = &[ + Group::with_title(Level::ERROR.title("expected `.`, `=`")).element( Snippet::source("asdf") .path("Cargo.toml") .line_start(1) .annotation(AnnotationKind::Primary.span(4..5).label("")), - )]; + ), + ]; let expected = file!["ann_removed_nl.term.svg"]; let renderer = Renderer::styled(); assert_data_eq!(renderer.render(input), expected); diff --git a/tests/color/ensure_emoji_highlight_width.rs b/tests/color/ensure_emoji_highlight_width.rs index bc22f9ab..c454e238 100644 --- a/tests/color/ensure_emoji_highlight_width.rs +++ b/tests/color/ensure_emoji_highlight_width.rs @@ -7,8 +7,7 @@ fn case() { let source = r#""haha this isn't a valid name 🐛" = { package = "libc", version = "0.1" } "#; - let input = &[Group::new() - .element(Level::ERROR.title("invalid character ` ` in package name: `haha this isn't a valid name 🐛`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)")) + let input = &[Group::with_title(Level::ERROR.title("invalid character ` ` in package name: `haha this isn't a valid name 🐛`, characters must be Unicode XID characters (numbers, `-`, `_`, or most letters)")) .element( Snippet::source(source) .path("<file>") diff --git a/tests/color/fold_ann_multiline.rs b/tests/color/fold_ann_multiline.rs index 1c035f41..68fd4f1b 100644 --- a/tests/color/fold_ann_multiline.rs +++ b/tests/color/fold_ann_multiline.rs @@ -28,9 +28,8 @@ fn case() { } "#; - let input = &[Group::new() - .element(Level::ERROR.title("mismatched types").id("E0308")) - .element( + let input = &[ + Group::with_title(Level::ERROR.title("mismatched types").id("E0308")).element( Snippet::source(source) .path("src/format.rs") .line_start(51) @@ -43,7 +42,8 @@ fn case() { .span(22..766) .label("expected enum `std::option::Option`, found ()"), ), - )]; + ), + ]; let expected = file!["fold_ann_multiline.term.svg"]; let renderer = Renderer::styled(); assert_data_eq!(renderer.render(input), expected); diff --git a/tests/color/fold_bad_origin_line.rs b/tests/color/fold_bad_origin_line.rs index 9e4c5c0c..99da3c5d 100644 --- a/tests/color/fold_bad_origin_line.rs +++ b/tests/color/fold_bad_origin_line.rs @@ -9,7 +9,7 @@ fn case() { invalid syntax "#; - let input = &[Group::new().element(Level::ERROR.title("")).element( + let input = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source(source) .path("path/to/error.rs") .line_start(1) diff --git a/tests/color/fold_leading.rs b/tests/color/fold_leading.rs index 0e4ae61d..e941c805 100644 --- a/tests/color/fold_leading.rs +++ b/tests/color/fold_leading.rs @@ -17,19 +17,18 @@ edition = "2021" workspace = 20 "#; - let input = &[Group::new() - .element( - Level::ERROR - .title("invalid type: integer `20`, expected a bool") - .id("E0308"), - ) - .element( - Snippet::source(source) - .path("Cargo.toml") - .line_start(1) - .fold(true) - .annotation(AnnotationKind::Primary.span(132..134).label("")), - )]; + let input = &[Group::with_title( + Level::ERROR + .title("invalid type: integer `20`, expected a bool") + .id("E0308"), + ) + .element( + Snippet::source(source) + .path("Cargo.toml") + .line_start(1) + .fold(true) + .annotation(AnnotationKind::Primary.span(132..134).label("")), + )]; let expected = file!["fold_leading.term.svg"]; let renderer = Renderer::styled(); assert_data_eq!(renderer.render(input), expected); diff --git a/tests/color/fold_trailing.rs b/tests/color/fold_trailing.rs index 6421dd45..9c85d873 100644 --- a/tests/color/fold_trailing.rs +++ b/tests/color/fold_trailing.rs @@ -16,19 +16,18 @@ rust-version = "1.70" edition = "2021" "#; - let input = &[Group::new() - .element( - Level::ERROR - .title("invalid type: integer `20`, expected a lints table") - .id("E0308"), - ) - .element( - Snippet::source(source) - .path("Cargo.toml") - .line_start(1) - .fold(true) - .annotation(AnnotationKind::Primary.span(8..10).label("")), - )]; + let input = &[Group::with_title( + Level::ERROR + .title("invalid type: integer `20`, expected a lints table") + .id("E0308"), + ) + .element( + Snippet::source(source) + .path("Cargo.toml") + .line_start(1) + .fold(true) + .annotation(AnnotationKind::Primary.span(8..10).label("")), + )]; let expected = file!["fold_trailing.term.svg"]; let renderer = Renderer::styled(); assert_data_eq!(renderer.render(input), expected); diff --git a/tests/color/issue_9.rs b/tests/color/issue_9.rs index f42c30b9..0ac5d4de 100644 --- a/tests/color/issue_9.rs +++ b/tests/color/issue_9.rs @@ -4,7 +4,7 @@ use snapbox::{assert_data_eq, file}; #[test] fn case() { - let input = &[Group::new().element(Level::ERROR.title("expected one of `.`, `;`, `?`, or an operator, found `for`")) + let input = &[Group::with_title(Level::ERROR.title("expected one of `.`, `;`, `?`, or an operator, found `for`")) .element( Snippet::source("let x = vec![1];") .path("/code/rust/src/test/ui/annotate-snippet/suggestion.rs") diff --git a/tests/color/multiline_removal_suggestion.rs b/tests/color/multiline_removal_suggestion.rs index fbaf4fff..6a98ec40 100644 --- a/tests/color/multiline_removal_suggestion.rs +++ b/tests/color/multiline_removal_suggestion.rs @@ -65,8 +65,7 @@ fn main() {} "#; let input = &[ - Group::new() - .element( + Group::with_title( Level::ERROR .title("`(bool, HashSet<u8>)` is not an iterator") .id("E0277"), @@ -88,14 +87,13 @@ fn main() {} .element( Level::NOTE.title("required for `(bool, HashSet<u8>)` to implement `IntoIterator`"), ), - Group::new() - .element(Level::NOTE.title("required by a bound in `flatten`")) + Group::with_title(Level::NOTE.title("required by a bound in `flatten`")) .element( Origin::new("/rustc/FAKE_PREFIX/library/core/src/iter/traits/iterator.rs") .line(1556) .char_column(4), ), - Group::new().element(Level::HELP.title("consider removing this method call, as the receiver has type `std::vec::IntoIter<HashSet<u8>>` and `std::vec::IntoIter<HashSet<u8>>: Iterator` trivially holds")).element( + Group::with_title(Level::HELP.title("consider removing this method call, as the receiver has type `std::vec::IntoIter<HashSet<u8>>` and `std::vec::IntoIter<HashSet<u8>>: Iterator` trivially holds")).element( Snippet::source(source) .path("$DIR/multiline-removal-suggestion.rs") .fold(true) diff --git a/tests/color/multiple_annotations.rs b/tests/color/multiple_annotations.rs index 464d7672..4533019a 100644 --- a/tests/color/multiple_annotations.rs +++ b/tests/color/multiple_annotations.rs @@ -15,7 +15,7 @@ fn case() { } "#; - let input = &[Group::new().element(Level::ERROR.title("")).element( + let input = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source(source) .line_start(96) .annotation( diff --git a/tests/color/simple.rs b/tests/color/simple.rs index 17287d94..9e35cf58 100644 --- a/tests/color/simple.rs +++ b/tests/color/simple.rs @@ -9,23 +9,24 @@ fn case() { for line in &self.body { "#; - let input = &[Group::new() - .element(Level::ERROR.title("expected one of `.`, `;`, `?`, or an operator, found `for`")) - .element( - Snippet::source(source) - .path("src/format_color.rs") - .line_start(169) - .annotation( - AnnotationKind::Primary - .span(20..23) - .label("unexpected token"), - ) - .annotation( - AnnotationKind::Context - .span(10..11) - .label("expected one of `.`, `;`, `?`, or an operator here"), - ), - )]; + let input = &[Group::with_title( + Level::ERROR.title("expected one of `.`, `;`, `?`, or an operator, found `for`"), + ) + .element( + Snippet::source(source) + .path("src/format_color.rs") + .line_start(169) + .annotation( + AnnotationKind::Primary + .span(20..23) + .label("unexpected token"), + ) + .annotation( + AnnotationKind::Context + .span(10..11) + .label("expected one of `.`, `;`, `?`, or an operator here"), + ), + )]; let expected = file!["simple.term.svg"]; let renderer = Renderer::styled(); assert_data_eq!(renderer.render(input), expected); diff --git a/tests/color/strip_line.rs b/tests/color/strip_line.rs index fbb72506..8e30ac4e 100644 --- a/tests/color/strip_line.rs +++ b/tests/color/strip_line.rs @@ -6,9 +6,8 @@ use snapbox::{assert_data_eq, file}; fn case() { let source = r#" let _: () = 42;"#; - let input = &[Group::new() - .element(Level::ERROR.title("mismatched types").id("E0308")) - .element( + let input = &[ + Group::with_title(Level::ERROR.title("mismatched types").id("E0308")).element( Snippet::source(source) .path("$DIR/whitespace-trimming.rs") .line_start(4) @@ -17,7 +16,8 @@ fn case() { .span(192..194) .label("expected (), found integer"), ), - )]; + ), + ]; let expected = file!["strip_line.term.svg"]; let renderer = Renderer::styled().anonymized_line_numbers(true); assert_data_eq!(renderer.render(input), expected); diff --git a/tests/color/strip_line_char.rs b/tests/color/strip_line_char.rs index 090e1dba..e78b530b 100644 --- a/tests/color/strip_line_char.rs +++ b/tests/color/strip_line_char.rs @@ -6,9 +6,8 @@ use snapbox::{assert_data_eq, file}; fn case() { let source = r#" let _: () = 42ñ"#; - let input = &[Group::new() - .element(Level::ERROR.title("mismatched types").id("E0308")) - .element( + let input = &[ + Group::with_title(Level::ERROR.title("mismatched types").id("E0308")).element( Snippet::source(source) .path("$DIR/whitespace-trimming.rs") .line_start(4) @@ -17,7 +16,8 @@ fn case() { .span(192..194) .label("expected (), found integer"), ), - )]; + ), + ]; let expected = file!["strip_line_char.term.svg"]; let renderer = Renderer::styled().anonymized_line_numbers(true); assert_data_eq!(renderer.render(input), expected); diff --git a/tests/color/strip_line_non_ws.rs b/tests/color/strip_line_non_ws.rs index da65e6a3..7ef3ad57 100644 --- a/tests/color/strip_line_non_ws.rs +++ b/tests/color/strip_line_non_ws.rs @@ -7,9 +7,8 @@ fn case() { let source = r#" let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = 42; let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); let _: () = (); "#; - let input = &[Group::new() - .element(Level::ERROR.title("mismatched types").id("E0308")) - .element( + let input = &[ + Group::with_title(Level::ERROR.title("mismatched types").id("E0308")).element( Snippet::source(source) .path("$DIR/non-whitespace-trimming.rs") .line_start(4) @@ -23,7 +22,8 @@ fn case() { .span(232..234) .label("expected due to this"), ), - )]; + ), + ]; let expected = file!["strip_line_non_ws.term.svg"]; let renderer = Renderer::styled().anonymized_line_numbers(true); assert_data_eq!(renderer.render(input), expected); diff --git a/tests/formatter.rs b/tests/formatter.rs index c538a9e8..a4c6f6ff 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -5,7 +5,7 @@ use snapbox::{assert_data_eq, str}; #[test] fn test_i_29() { - let snippets = &[Group::new().element(Level::ERROR.title("oops")).element( + let snippets = &[Group::with_title(Level::ERROR.title("oops")).element( Snippet::source("First line\r\nSecond oops line") .path("<current file>") .annotation(AnnotationKind::Primary.span(19..23).label("oops")) @@ -25,7 +25,7 @@ error: oops #[test] fn test_point_to_double_width_characters() { - let snippets = &[Group::new().element(Level::ERROR.title("")).element( + let snippets = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source("こんにちは、世界") .path("<current file>") .annotation(AnnotationKind::Primary.span(18..24).label("world")), @@ -45,7 +45,7 @@ error: #[test] fn test_point_to_double_width_characters_across_lines() { - let snippets = &[Group::new().element(Level::ERROR.title("")).element( + let snippets = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source("おはよう\nございます") .path("<current file>") .annotation(AnnotationKind::Primary.span(6..22).label("Good morning")), @@ -67,7 +67,7 @@ error: #[test] fn test_point_to_double_width_characters_multiple() { - let snippets = &[Group::new().element(Level::ERROR.title("")).element( + let snippets = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source("お寿司\n食べたい🍣") .path("<current file>") .annotation(AnnotationKind::Primary.span(0..9).label("Sushi1")) @@ -90,7 +90,7 @@ error: #[test] fn test_point_to_double_width_characters_mixed() { - let snippets = &[Group::new().element(Level::ERROR.title("")).element( + let snippets = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source("こんにちは、新しいWorld!") .path("<current file>") .annotation(AnnotationKind::Primary.span(18..32).label("New world")), @@ -110,7 +110,9 @@ error: #[test] fn test_format_title() { - let input = &[Group::new().element(Level::ERROR.title("This is a title").id("E0001"))]; + let input = &[Group::with_title( + Level::ERROR.title("This is a title").id("E0001"), + )]; let expected = str![r#"error[E0001]: This is a title"#]; let renderer = Renderer::plain(); @@ -120,8 +122,7 @@ fn test_format_title() { #[test] fn test_format_snippet_only() { let source = "This is line 1\nThis is line 2"; - let input = &[Group::new() - .element(Level::ERROR.title("")) + let input = &[Group::with_title(Level::ERROR.title("")) .element(Snippet::<Annotation<'_>>::source(source).line_start(5402))]; let expected = str![[r#" @@ -138,8 +139,7 @@ error: fn test_format_snippets_continuation() { let src_0 = "This is slice 1"; let src_1 = "This is slice 2"; - let input = &[Group::new() - .element(Level::ERROR.title("")) + let input = &[Group::with_title(Level::ERROR.title("")) .element( Snippet::<Annotation<'_>>::source(src_0) .line_start(5402) @@ -171,7 +171,7 @@ fn test_format_snippet_annotation_standalone() { let source = [line_1, line_2].join("\n"); // In line 2 let range = 22..24; - let input = &[Group::new().element(Level::ERROR.title("")).element( + let input = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source(&source).line_start(5402).annotation( AnnotationKind::Context .span(range.clone()) @@ -191,8 +191,7 @@ error: #[test] fn test_format_footer_title() { - let input = &[Group::new() - .element(Level::ERROR.title("")) + let input = &[Group::with_title(Level::ERROR.title("")) .element(Level::ERROR.title("This __is__ a title"))]; let expected = str![[r#" error: @@ -208,7 +207,7 @@ error: fn test_i26() { let source = "short"; let label = "label"; - let input = &[Group::new().element(Level::ERROR.title("")).element( + let input = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source(source).line_start(0).annotation( AnnotationKind::Primary .span(0..source.len() + 2) @@ -222,8 +221,7 @@ fn test_i26() { #[test] fn test_source_content() { let source = "This is an example\nof content lines"; - let input = &[Group::new() - .element(Level::ERROR.title("")) + let input = &[Group::with_title(Level::ERROR.title("")) .element(Snippet::<Annotation<'_>>::source(source).line_start(56))]; let expected = str![[r#" error: @@ -238,7 +236,7 @@ error: #[test] fn test_source_annotation_standalone_singleline() { let source = "tests"; - let input = &[Group::new().element(Level::ERROR.title("")).element( + let input = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source(source) .line_start(1) .annotation(AnnotationKind::Context.span(0..5).label("Example string")), @@ -256,7 +254,7 @@ error: #[test] fn test_source_annotation_standalone_multiline() { let source = "tests"; - let input = &[Group::new().element(Level::ERROR.title("")).element( + let input = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source(source) .line_start(1) .annotation(AnnotationKind::Context.span(0..5).label("Example string")) @@ -277,8 +275,7 @@ error: #[test] fn test_only_source() { - let input = &[Group::new() - .element(Level::ERROR.title("")) + let input = &[Group::with_title(Level::ERROR.title("")) .element(Snippet::<Annotation<'_>>::source("").path("file.rs"))]; let expected = str![[r#" error: @@ -293,8 +290,7 @@ error: #[test] fn test_anon_lines() { let source = "This is an example\nof content lines\n\nabc"; - let input = &[Group::new() - .element(Level::ERROR.title("")) + let input = &[Group::with_title(Level::ERROR.title("")) .element(Snippet::<Annotation<'_>>::source(source).line_start(56))]; let expected = str![[r#" error: @@ -310,7 +306,7 @@ LL | abc #[test] fn issue_130() { - let input = &[Group::new().element(Level::ERROR.title("dummy")).element( + let input = &[Group::with_title(Level::ERROR.title("dummy")).element( Snippet::source("foo\nbar\nbaz") .path("file/path") .line_start(3) @@ -337,7 +333,7 @@ fn unterminated_string_multiline() { a\" // ... "; - let input = &[Group::new().element(Level::ERROR.title("")).element( + let input = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source(source) .path("file/path") .line_start(3) @@ -360,7 +356,7 @@ error: #[test] fn char_and_nl_annotate_char() { let source = "a\r\nb"; - let input = &[Group::new().element(Level::ERROR.title("")).element( + let input = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source(source) .path("file/path") .line_start(3) @@ -382,7 +378,7 @@ error: #[test] fn char_eol_annotate_char() { let source = "a\r\nb"; - let input = &[Group::new().element(Level::ERROR.title("")).element( + let input = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source(source) .path("file/path") .line_start(3) @@ -403,7 +399,7 @@ error: #[test] fn char_eol_annotate_char_double_width() { - let snippets = &[Group::new().element(Level::ERROR.title("")).element( + let snippets = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source("こん\r\nにちは\r\n世界") .path("<current file>") .annotation(AnnotationKind::Primary.span(3..8)), @@ -428,7 +424,7 @@ error: #[test] fn annotate_eol() { let source = "a\r\nb"; - let input = &[Group::new().element(Level::ERROR.title("")).element( + let input = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source(source) .path("file/path") .line_start(3) @@ -450,7 +446,7 @@ error: #[test] fn annotate_eol2() { let source = "a\r\nb"; - let input = &[Group::new().element(Level::ERROR.title("")).element( + let input = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source(source) .path("file/path") .line_start(3) @@ -473,7 +469,7 @@ error: #[test] fn annotate_eol3() { let source = "a\r\nb"; - let input = &[Group::new().element(Level::ERROR.title("")).element( + let input = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source(source) .path("file/path") .line_start(3) @@ -496,7 +492,7 @@ error: #[test] fn annotate_eol4() { let source = "a\r\nb"; - let input = &[Group::new().element(Level::ERROR.title("")).element( + let input = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source(source) .path("file/path") .line_start(3) @@ -517,7 +513,7 @@ error: #[test] fn annotate_eol_double_width() { - let snippets = &[Group::new().element(Level::ERROR.title("")).element( + let snippets = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source("こん\r\nにちは\r\n世界") .path("<current file>") .annotation(AnnotationKind::Primary.span(7..8)), @@ -542,7 +538,7 @@ error: #[test] fn multiline_eol_start() { let source = "a\r\nb"; - let input = &[Group::new().element(Level::ERROR.title("")).element( + let input = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source(source) .path("file/path") .line_start(3) @@ -565,7 +561,7 @@ error: #[test] fn multiline_eol_start2() { let source = "a\r\nb"; - let input = &[Group::new().element(Level::ERROR.title("")).element( + let input = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source(source) .path("file/path") .line_start(3) @@ -588,7 +584,7 @@ error: #[test] fn multiline_eol_start3() { let source = "a\nb"; - let input = &[Group::new().element(Level::ERROR.title("")).element( + let input = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source(source) .path("file/path") .line_start(3) @@ -610,7 +606,7 @@ error: #[test] fn multiline_eol_start_double_width() { - let snippets = &[Group::new().element(Level::ERROR.title("")).element( + let snippets = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source("こん\r\nにちは\r\n世界") .path("<current file>") .annotation(AnnotationKind::Primary.span(7..11)), @@ -635,7 +631,7 @@ error: #[test] fn multiline_eol_start_eol_end() { let source = "a\nb\nc"; - let input = &[Group::new().element(Level::ERROR.title("")).element( + let input = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source(source) .path("file/path") .line_start(3) @@ -659,7 +655,7 @@ error: #[test] fn multiline_eol_start_eol_end2() { let source = "a\r\nb\r\nc"; - let input = &[Group::new().element(Level::ERROR.title("")).element( + let input = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source(source) .path("file/path") .line_start(3) @@ -683,7 +679,7 @@ error: #[test] fn multiline_eol_start_eol_end3() { let source = "a\r\nb\r\nc"; - let input = &[Group::new().element(Level::ERROR.title("")).element( + let input = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source(source) .path("file/path") .line_start(3) @@ -707,7 +703,7 @@ error: #[test] fn multiline_eol_start_eof_end() { let source = "a\r\nb"; - let input = &[Group::new().element(Level::ERROR.title("")).element( + let input = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source(source) .path("file/path") .line_start(3) @@ -730,7 +726,7 @@ error: #[test] fn multiline_eol_start_eof_end_double_width() { let source = "ん\r\nに"; - let input = &[Group::new().element(Level::ERROR.title("")).element( + let input = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source(source) .path("file/path") .line_start(3) @@ -753,9 +749,8 @@ error: #[test] fn two_single_line_same_line() { let source = r#"bar = { version = "0.1.0", optional = true }"#; - let input = &[Group::new() - .element(Level::ERROR.title("unused optional dependency")) - .element( + let input = &[ + Group::with_title(Level::ERROR.title("unused optional dependency")).element( Snippet::source(source) .path("Cargo.toml") .line_start(4) @@ -769,7 +764,8 @@ fn two_single_line_same_line() { .span(27..42) .label("This should also be long but not too long"), ), - )]; + ), + ]; let expected = str![[r#" error: unused optional dependency --> Cargo.toml:4:1 @@ -790,9 +786,8 @@ this is another line so is this bar = { version = "0.1.0", optional = true } "#; - let input = &[Group::new() - .element(Level::ERROR.title("unused optional dependency")) - .element( + let input = &[ + Group::with_title(Level::ERROR.title("unused optional dependency")).element( Snippet::source(source) .line_start(4) .annotation( @@ -805,7 +800,8 @@ bar = { version = "0.1.0", optional = true } .span(27..42) .label("This should also be long but not too long"), ), - )]; + ), + ]; let expected = str![[r#" error: unused optional dependency | @@ -829,9 +825,8 @@ this is another line so is this bar = { version = "0.1.0", optional = true } "#; - let input = &[Group::new() - .element(Level::ERROR.title("unused optional dependency")) - .element( + let input = &[ + Group::with_title(Level::ERROR.title("unused optional dependency")).element( Snippet::source(source) .line_start(4) .annotation( @@ -849,7 +844,8 @@ bar = { version = "0.1.0", optional = true } .span(27..42) .label("This should also be long but not too long"), ), - )]; + ), + ]; let expected = str![[r#" error: unused optional dependency | @@ -877,9 +873,8 @@ so is this bar = { version = "0.1.0", optional = true } this is another line "#; - let input = &[Group::new() - .element(Level::ERROR.title("unused optional dependency")) - .element( + let input = &[ + Group::with_title(Level::ERROR.title("unused optional dependency")).element( Snippet::source(source) .line_start(4) .annotation( @@ -902,7 +897,8 @@ this is another line .span(27..42) .label("This should also be long but not too long"), ), - )]; + ), + ]; let expected = str![[r#" error: unused optional dependency | @@ -928,7 +924,7 @@ error: unused optional dependency #[test] fn origin_correct_start_line() { let source = "aaa\nbbb\nccc\nddd\n"; - let input = &[Group::new().element(Level::ERROR.title("title")).element( + let input = &[Group::with_title(Level::ERROR.title("title")).element( Snippet::source(source) .path("origin.txt") .fold(false) @@ -952,7 +948,7 @@ error: title #[test] fn origin_correct_mid_line() { let source = "aaa\nbbb\nccc\nddd\n"; - let input = &[Group::new().element(Level::ERROR.title("title")).element( + let input = &[Group::with_title(Level::ERROR.title("title")).element( Snippet::source(source) .path("origin.txt") .fold(false) @@ -981,31 +977,29 @@ error: title fn two_suggestions_same_span() { let source = r#" A.foo();"#; let input_new = &[ - Group::new() - .element( - Level::ERROR - .title("expected value, found enum `A`") - .id("E0423"), - ) - .element( - Snippet::source(source) - .fold(true) - .annotation(AnnotationKind::Primary.span(4..5)), - ), - Group::new() - .element( - Level::HELP.title("you might have meant to use one of the following enum variants"), - ) - .element( - Snippet::source(source) - .fold(true) - .patch(Patch::new(4..5, "(A::Tuple())")), - ) - .element( - Snippet::source(source) - .fold(true) - .patch(Patch::new(4..5, "A::Unit")), - ), + Group::with_title( + Level::ERROR + .title("expected value, found enum `A`") + .id("E0423"), + ) + .element( + Snippet::source(source) + .fold(true) + .annotation(AnnotationKind::Primary.span(4..5)), + ), + Group::with_title( + Level::HELP.title("you might have meant to use one of the following enum variants"), + ) + .element( + Snippet::source(source) + .fold(true) + .patch(Patch::new(4..5, "(A::Tuple())")), + ) + .element( + Snippet::source(source) + .fold(true) + .patch(Patch::new(4..5, "A::Unit")), + ), ]; let expected = str![[r#" @@ -1048,7 +1042,7 @@ fn main() { banana::Chaenomeles.pick() }"#; let input_new = - &[Group::new().element(Level::ERROR + &[Group::with_title(Level::ERROR .title("no method named `pick` found for struct `Chaenomeles` in the current scope") .id("E0599")).element( Snippet::source(source) @@ -1065,8 +1059,7 @@ fn main() { .label("method not found in `Chaenomeles`"), ), ), - Group::new() - .element(Level::HELP.title( + Group::with_title(Level::HELP.title( "the following traits which provide `pick` are implemented but not in scope; perhaps you want to import one of them", )) .element( @@ -1104,27 +1097,24 @@ fn single_line_non_overlapping_suggestions() { let source = r#" A.foo();"#; let input_new = &[ - Group::new() - .element( - Level::ERROR - .title("expected value, found enum `A`") - .id("E0423"), - ) - .element( - Snippet::source(source) - .fold(true) - .line_start(1) - .annotation(AnnotationKind::Primary.span(4..5)), - ), - Group::new() - .element(Level::HELP.title("make these changes and things will work")) - .element( - Snippet::source(source) - .fold(true) - .fold(true) - .patch(Patch::new(4..5, "(A::Tuple())")) - .patch(Patch::new(6..9, "bar")), - ), + Group::with_title( + Level::ERROR + .title("expected value, found enum `A`") + .id("E0423"), + ) + .element( + Snippet::source(source) + .fold(true) + .line_start(1) + .annotation(AnnotationKind::Primary.span(4..5)), + ), + Group::with_title(Level::HELP.title("make these changes and things will work")).element( + Snippet::source(source) + .fold(true) + .fold(true) + .patch(Patch::new(4..5, "(A::Tuple())")) + .patch(Patch::new(6..9, "bar")), + ), ]; let expected = str![[r#" @@ -1147,23 +1137,19 @@ LL + (A::Tuple()).bar(); fn single_line_non_overlapping_suggestions2() { let source = r#" ThisIsVeryLong.foo();"#; let input_new = &[ - Group::new() - .element(Level::ERROR.title("Found `ThisIsVeryLong`").id("E0423")) - .element( - Snippet::source(source) - .fold(true) - .line_start(1) - .annotation(AnnotationKind::Primary.span(4..18)), - ), - Group::new() - .element(Level::HELP.title("make these changes and things will work")) - .element( - Snippet::source(source) - .fold(true) - .fold(true) - .patch(Patch::new(4..18, "(A::Tuple())")) - .patch(Patch::new(19..22, "bar")), - ), + Group::with_title(Level::ERROR.title("Found `ThisIsVeryLong`").id("E0423")).element( + Snippet::source(source) + .fold(true) + .line_start(1) + .annotation(AnnotationKind::Primary.span(4..18)), + ), + Group::with_title(Level::HELP.title("make these changes and things will work")).element( + Snippet::source(source) + .fold(true) + .fold(true) + .patch(Patch::new(4..18, "(A::Tuple())")) + .patch(Patch::new(19..22, "bar")), + ), ]; let expected = str![[r#" @@ -1193,50 +1179,46 @@ fn multiple_replacements() { "#; let input_new = &[ - Group::new() - .element( - Level::ERROR - .title( - "cannot borrow `*self` as mutable because it is also borrowed as immutable", - ) - .id("E0502"), - ) - .element( - Snippet::source(source) - .line_start(1) - .fold(true) - .annotation( - AnnotationKind::Primary - .span(49..59) - .label("mutable borrow occurs here"), - ) - .annotation( - AnnotationKind::Primary - .span(13..15) - .label("immutable borrow occurs here"), - ) - .annotation( - AnnotationKind::Primary - .span(26..30) - .label("first borrow occurs due to use of `*self` in closure"), - ) - .annotation( - AnnotationKind::Primary - .span(65..66) - .label("immutable borrow later used here"), - ), - ), - Group::new() - .element( - Level::HELP.title("try explicitly pass `&Self` into the Closure as an argument"), - ) - .element( - Snippet::source(source) - .fold(true) - .patch(Patch::new(14..14, "this: &Self")) - .patch(Patch::new(26..30, "this")) - .patch(Patch::new(66..68, "(self)")), - ), + Group::with_title( + Level::ERROR + .title("cannot borrow `*self` as mutable because it is also borrowed as immutable") + .id("E0502"), + ) + .element( + Snippet::source(source) + .line_start(1) + .fold(true) + .annotation( + AnnotationKind::Primary + .span(49..59) + .label("mutable borrow occurs here"), + ) + .annotation( + AnnotationKind::Primary + .span(13..15) + .label("immutable borrow occurs here"), + ) + .annotation( + AnnotationKind::Primary + .span(26..30) + .label("first borrow occurs due to use of `*self` in closure"), + ) + .annotation( + AnnotationKind::Primary + .span(65..66) + .label("immutable borrow later used here"), + ), + ), + Group::with_title( + Level::HELP.title("try explicitly pass `&Self` into the Closure as an argument"), + ) + .element( + Snippet::source(source) + .fold(true) + .patch(Patch::new(14..14, "this: &Self")) + .patch(Patch::new(26..30, "this")) + .patch(Patch::new(66..68, "(self)")), + ), ]; let expected = str![[r#" error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable @@ -1278,43 +1260,46 @@ fn main() { test1(); }"#; - let input_new = &[Group::new().element(Level::ERROR - .title("cannot borrow `chars` as mutable more than once at a time") - .id("E0499")).element( - Snippet::source(source) - .line_start(1) - .fold(true) - .annotation( - AnnotationKind::Context - .span(65..70) - .label("first mutable borrow occurs here"), - ) - .annotation( - AnnotationKind::Primary - .span(90..95) - .label("second mutable borrow occurs here"), - ) - .annotation( - AnnotationKind::Context - .span(65..79) - .label("first borrow later used here"), - ), - ), - Group::new() - .element( - Level::HELP - .title("if you want to call `next` on a iterator within the loop, consider using `while let`") + let input_new = &[ + Group::with_title( + Level::ERROR + .title("cannot borrow `chars` as mutable more than once at a time") + .id("E0499"), + ) + .element( + Snippet::source(source) + .line_start(1) + .fold(true) + .annotation( + AnnotationKind::Context + .span(65..70) + .label("first mutable borrow occurs here"), ) - .element( - Snippet::source(source) - .fold(true) - .patch(Patch::new( - 55..59, - "let iter = chars.by_ref();\n while let Some(", - )) - .patch(Patch::new(61..79, ") = iter.next()")) - .patch(Patch::new(90..95, "iter")), - )]; + .annotation( + AnnotationKind::Primary + .span(90..95) + .label("second mutable borrow occurs here"), + ) + .annotation( + AnnotationKind::Context + .span(65..79) + .label("first borrow later used here"), + ), + ), + Group::with_title(Level::HELP.title( + "if you want to call `next` on a iterator within the loop, consider using `while let`", + )) + .element( + Snippet::source(source) + .fold(true) + .patch(Patch::new( + 55..59, + "let iter = chars.by_ref();\n while let Some(", + )) + .patch(Patch::new(61..79, ") = iter.next()")) + .patch(Patch::new(90..95, "iter")), + ), + ]; let expected = str![[r#" error[E0499]: cannot borrow `chars` as mutable more than once at a time @@ -1358,40 +1343,34 @@ struct Foo { fn main() {}"#; let input_new = &[ - Group::new() - .element( - Level::ERROR - .title("failed to resolve: use of undeclared crate or module `st`") - .id("E0433"), - ) - .element( - Snippet::source(source).line_start(1).fold(true).annotation( - AnnotationKind::Primary - .span(122..124) - .label("use of undeclared crate or module `st`"), - ), + Group::with_title( + Level::ERROR + .title("failed to resolve: use of undeclared crate or module `st`") + .id("E0433"), + ) + .element( + Snippet::source(source).line_start(1).fold(true).annotation( + AnnotationKind::Primary + .span(122..124) + .label("use of undeclared crate or module `st`"), ), - Group::new() - .element(Level::HELP.title("there is a crate or module with a similar name")) + ), + Group::with_title(Level::HELP.title("there is a crate or module with a similar name")) .element( Snippet::source(source) .fold(true) .patch(Patch::new(122..124, "std")), ), - Group::new() - .element(Level::HELP.title("consider importing this module")) - .element( - Snippet::source(source) - .fold(true) - .patch(Patch::new(1..1, "use std::cell;\n")), - ), - Group::new() - .element(Level::HELP.title("if you import `cell`, refer to it directly")) - .element( - Snippet::source(source) - .fold(true) - .patch(Patch::new(122..126, "")), - ), + Group::with_title(Level::HELP.title("consider importing this module")).element( + Snippet::source(source) + .fold(true) + .patch(Patch::new(1..1, "use std::cell;\n")), + ), + Group::with_title(Level::HELP.title("if you import `cell`, refer to it directly")).element( + Snippet::source(source) + .fold(true) + .patch(Patch::new(122..126, "")), + ), ]; let expected = str![[r#" error[E0433]: failed to resolve: use of undeclared crate or module `st` @@ -1437,38 +1416,35 @@ where fn main() {}"#; let input_new = &[ - Group::new() - .element( - Level::ERROR - .title("the size for values of type `T` cannot be known at compilation time") - .id("E0277"), - ) - .element( - Snippet::source(source) - .line_start(1) - .fold(true) - .annotation( - AnnotationKind::Primary - .span(39..49) - .label("doesn't have a size known at compile-time"), - ) - .annotation( - AnnotationKind::Context - .span(31..32) - .label("this type parameter needs to be `Sized`"), - ), - ), - Group::new() - .element( - Level::HELP.title( - "consider removing the `?Sized` bound to make the type parameter `Sized`", + Group::with_title( + Level::ERROR + .title("the size for values of type `T` cannot be known at compilation time") + .id("E0277"), + ) + .element( + Snippet::source(source) + .line_start(1) + .fold(true) + .annotation( + AnnotationKind::Primary + .span(39..49) + .label("doesn't have a size known at compile-time"), + ) + .annotation( + AnnotationKind::Context + .span(31..32) + .label("this type parameter needs to be `Sized`"), ), - ) - .element( - Snippet::source(source) - .fold(true) - .patch(Patch::new(52..85, "")), - ), + ), + Group::with_title( + Level::HELP + .title("consider removing the `?Sized` bound to make the type parameter `Sized`"), + ) + .element( + Snippet::source(source) + .fold(true) + .patch(Patch::new(52..85, "")), + ), ]; let expected = str![[r#" error[E0277]: the size for values of type `T` cannot be known at compilation time @@ -1508,7 +1484,7 @@ and where } fn main() {}"#; - let input_new = &[Group::new().element(Level::ERROR + let input_new = &[Group::with_title(Level::ERROR .title("the size for values of type `T` cannot be known at compilation time") .id("E0277")).element(Snippet::source(source) .line_start(1) @@ -1524,7 +1500,7 @@ fn main() {}"#; .span(31..32) .label("this type parameter needs to be `Sized`"), )) - ,Group::new().element( + ,Group::with_title( Level::NOTE .title("required by an implicit `Sized` bound in `Wrapper`") ).element( @@ -1537,7 +1513,7 @@ fn main() {}"#; .span(16..17) .label("required by the implicit `Sized` requirement on this type parameter in `Wrapper`"), ) - ), Group::new().element( + ), Group::with_title( Level::HELP .title("you could relax the implicit `Sized` bound on `T` if it were used through indirection like `&T` or `Box<T>`") ) @@ -1557,7 +1533,7 @@ fn main() {}"#; .label("...if indirection were used here: `Box<T>`"), ) - ),Group::new().element( + ),Group::with_title( Level::HELP .title("consider removing the `?Sized` bound to make the type parameter `Sized`") ).element( @@ -1614,26 +1590,25 @@ quack zappy "#; - let input_new = - &[ - Group::new().element( - Level::ERROR - .title("the size for values of type `T` cannot be known at compilation time") - .id("E0277"), - ), - // We need an empty group here to ensure the HELP line is rendered correctly - Group::new() - .element(Level::HELP.title( - "consider removing the `?Sized` bound to make the type parameter `Sized`", - )) - .element( - Snippet::source(source) - .line_start(7) - .fold(true) - .patch(Patch::new(3..21, "")) - .patch(Patch::new(22..40, "")), - ), - ]; + let input_new = &[ + Group::with_title( + Level::ERROR + .title("the size for values of type `T` cannot be known at compilation time") + .id("E0277"), + ), + // We need an empty group here to ensure the HELP line is rendered correctly + Group::with_title( + Level::HELP + .title("consider removing the `?Sized` bound to make the type parameter `Sized`"), + ) + .element( + Snippet::source(source) + .line_start(7) + .fold(true) + .patch(Patch::new(3..21, "")) + .patch(Patch::new(22..40, "")), + ), + ]; let expected = str![[r#" error[E0277]: the size for values of type `T` cannot be known at compilation time | @@ -1686,7 +1661,7 @@ fn main() { } "#; - let input_new = &[Group::new().element(Level::ERROR + let input_new = &[Group::with_title(Level::ERROR .title("type mismatch resolving `<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ...>>, ...>>, ...> as Future>::Error == Foo`") .id("E0271")).element(Snippet::source(source) .line_start(4) @@ -1696,7 +1671,7 @@ fn main() { AnnotationKind::Primary .span(208..510) .label("type mismatch resolving `<Result<Result<(), Result<Result<(), ...>, ...>>, ...> as Future>::Error == Foo`"), - )),Group::new().element( + )),Group::with_title( Level::NOTE.title("expected this to be `Foo`") ).element( Snippet::source(source) @@ -1772,7 +1747,7 @@ fn main() { } "#; - let input_new = &[Group::new().element(Level::ERROR + let input_new = &[Group::with_title(Level::ERROR .title("type mismatch resolving `<Result<Result<(), Result<Result<(), Result<Result<(), Option<{integer}>>, ...>>, ...>>, ...> as Future>::Error == Foo`") .id("E0271")).element(Snippet::source(source) .line_start(4) @@ -1782,7 +1757,7 @@ fn main() { AnnotationKind::Primary .span(208..510) .label("type mismatch resolving `<Result<Result<(), Result<Result<(), ...>, ...>>, ...> as Future>::Error == Foo`"), - )),Group::new().element( + )),Group::with_title( Level::NOTE.title("expected this to be `Foo`") ).element( Snippet::source(source) @@ -1923,7 +1898,7 @@ fn main() { } "#; - let input_new = &[Group::new().element(Level::ERROR + let input_new = &[Group::with_title(Level::ERROR .title("mismatched types") .id("E0308")).element( Snippet::source(source) @@ -2007,7 +1982,7 @@ fn main() { } "#; - let input_new = &[Group::new().element(Level::ERROR + let input_new = &[Group::with_title(Level::ERROR .title("mismatched types") .id("E0308")).element( Snippet::source(source) @@ -2028,7 +2003,7 @@ fn main() { Level::NOTE .title("expected fn pointer `for<'a> fn(Box<(dyn Any + Send + 'a)>) -> Pin<_>`\n found fn item `fn(Box<(dyn Any + Send + 'static)>) -> Pin<_> {wrapped_fn}`") , - ),Group::new().element( + ),Group::with_title( Level::NOTE.title("function defined here"), ).element( Snippet::source(source) @@ -2074,7 +2049,7 @@ LL │ ┃ )>>) {} #[test] fn unicode_cut_handling() { let source = "version = \"0.1.0\"\n# Ensure that the spans from toml handle utf-8 correctly\nauthors = [\n { name = \"Z\u{351}\u{36b}\u{343}\u{36a}\u{302}\u{36b}\u{33d}\u{34f}\u{334}\u{319}\u{324}\u{31e}\u{349}\u{35a}\u{32f}\u{31e}\u{320}\u{34d}A\u{36b}\u{357}\u{334}\u{362}\u{335}\u{31c}\u{330}\u{354}L\u{368}\u{367}\u{369}\u{358}\u{320}G\u{311}\u{357}\u{30e}\u{305}\u{35b}\u{341}\u{334}\u{33b}\u{348}\u{34d}\u{354}\u{339}O\u{342}\u{30c}\u{30c}\u{358}\u{328}\u{335}\u{339}\u{33b}\u{31d}\u{333}\", email = 1 }\n]\n"; - let input = &[Group::new().element(Level::ERROR.title("title")).element( + let input = &[Group::with_title(Level::ERROR.title("title")).element( Snippet::source(source) .fold(false) .annotation(AnnotationKind::Primary.span(85..228).label("annotation")), @@ -2111,7 +2086,7 @@ error: title #[test] fn unicode_cut_handling2() { let source = "/*这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?"; - let input = &[Group::new().element(Level::ERROR + let input = &[Group::with_title(Level::ERROR .title("expected item, found `?`")).element( Snippet::source(source) .fold(false) @@ -2148,7 +2123,7 @@ error: expected item, found `?` #[test] fn unicode_cut_handling3() { let source = "/*这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/?"; - let input = &[Group::new().element(Level::ERROR + let input = &[Group::with_title(Level::ERROR .title("expected item, found `?`")).element( Snippet::source(source) .fold(false) @@ -2185,7 +2160,7 @@ error: expected item, found `?` #[test] fn unicode_cut_handling4() { let source = "/*aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/?"; - let input = &[Group::new().element(Level::ERROR + let input = &[Group::with_title(Level::ERROR .title("expected item, found `?`")).element( Snippet::source(source) .fold(false) @@ -2228,9 +2203,8 @@ fn main() { //~^ ERROR mismatched types } "##; - let input = &[Group::new() - .element(Level::ERROR.title("mismatched types").id("E0308")) - .element( + let input = &[ + Group::with_title(Level::ERROR.title("mismatched types").id("E0308")).element( Snippet::source(source) .path("$DIR/non-whitespace-trimming-unicode.rs") .fold(true) @@ -2244,7 +2218,8 @@ fn main() { .span(1202..1204) .label("expected due to this"), ), - )]; + ), + ]; let expected_ascii = str![[r#" error[E0308]: mismatched types @@ -2285,29 +2260,25 @@ fn main() { } "##; let input = &[ - Group::new() - .element( - Level::ERROR - .title("cannot add `&str` to `&str`") - .id("E0369"), - ) - .element( - Snippet::source(source) - .path("$DIR/non-1-width-unicode-multiline-label.rs") - .fold(true) - .annotation(AnnotationKind::Context.span(970..984).label("&str")) - .annotation(AnnotationKind::Context.span(987..1001).label("&str")) - .annotation( - AnnotationKind::Primary - .span(985..986) - .label("`+` cannot be used to concatenate two `&str` strings"), - ), - ) - .element( - Level::NOTE.title("string concatenation requires an owned `String` on the left"), - ), - Group::new() - .element(Level::HELP.title("create an owned `String` from a string reference")) + Group::with_title( + Level::ERROR + .title("cannot add `&str` to `&str`") + .id("E0369"), + ) + .element( + Snippet::source(source) + .path("$DIR/non-1-width-unicode-multiline-label.rs") + .fold(true) + .annotation(AnnotationKind::Context.span(970..984).label("&str")) + .annotation(AnnotationKind::Context.span(987..1001).label("&str")) + .annotation( + AnnotationKind::Primary + .span(985..986) + .label("`+` cannot be used to concatenate two `&str` strings"), + ), + ) + .element(Level::NOTE.title("string concatenation requires an owned `String` on the left")), + Group::with_title(Level::HELP.title("create an owned `String` from a string reference")) .element( Snippet::source(source) .path("$DIR/non-1-width-unicode-multiline-label.rs") @@ -2368,15 +2339,14 @@ fn foo() { } "##; let bin_source = "�|�\u{0002}!5�cc\u{0015}\u{0002}�Ӻi��WWj�ȥ�'�}�\u{0012}�J�ȉ��W�\u{001e}O�@����\u{001c}w�V���LO����\u{0014}[ \u{0003}_�'���SQ�~ذ��ų&��-\t��lN~��!@␌ _#���kQ��h�\u{001d}�:�\u{001c}\u{0007}�"; - let input = &[Group::new().element(Level::ERROR + let input = &[Group::with_title(Level::ERROR .title("couldn't read `$DIR/not-utf8.bin`: stream did not contain valid UTF-8")).element( Snippet::source(source) .path("$DIR/not-utf8.rs") .fold(true) .annotation(AnnotationKind::Primary.span(136..160)), ), - Group::new() - .element(Level::NOTE.title("byte `193` is not valid utf-8")) + Group::with_title(Level::NOTE.title("byte `193` is not valid utf-8")) .element( Snippet::source(bin_source) .path("$DIR/not-utf8.bin") @@ -2429,28 +2399,29 @@ fn secondary_title_no_level_text() { let s: &str = include_bytes!("file.txt"); //~ ERROR mismatched types }"#; - let input = &[Group::new() - .element(Level::ERROR.title("mismatched types").id("E0308")) - .element( - Snippet::source(source) - .path("$DIR/mismatched-types.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(105..131) - .label("expected `&str`, found `&[u8; 0]`"), - ) - .annotation( - AnnotationKind::Context - .span(98..102) - .label("expected due to this"), - ), - ) - .element( - Level::NOTE - .text(None::<&str>) - .title("expected reference `&str`\nfound reference `&'static [u8; 0]`"), - )]; + let input = &[ + Group::with_title(Level::ERROR.title("mismatched types").id("E0308")) + .element( + Snippet::source(source) + .path("$DIR/mismatched-types.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(105..131) + .label("expected `&str`, found `&[u8; 0]`"), + ) + .annotation( + AnnotationKind::Context + .span(98..102) + .label("expected due to this"), + ), + ) + .element( + Level::NOTE + .text(None::<&str>) + .title("expected reference `&str`\nfound reference `&'static [u8; 0]`"), + ), + ]; let expected = str![[r#" error[E0308]: mismatched types @@ -2474,28 +2445,29 @@ fn secondary_title_custom_level_text() { let s: &str = include_bytes!("file.txt"); //~ ERROR mismatched types }"#; - let input = &[Group::new() - .element(Level::ERROR.title("mismatched types").id("E0308")) - .element( - Snippet::source(source) - .path("$DIR/mismatched-types.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(105..131) - .label("expected `&str`, found `&[u8; 0]`"), - ) - .annotation( - AnnotationKind::Context - .span(98..102) - .label("expected due to this"), - ), - ) - .element( - Level::NOTE - .text(Some("custom")) - .title("expected reference `&str`\nfound reference `&'static [u8; 0]`"), - )]; + let input = &[ + Group::with_title(Level::ERROR.title("mismatched types").id("E0308")) + .element( + Snippet::source(source) + .path("$DIR/mismatched-types.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(105..131) + .label("expected `&str`, found `&[u8; 0]`"), + ) + .annotation( + AnnotationKind::Context + .span(98..102) + .label("expected due to this"), + ), + ) + .element( + Level::NOTE + .text(Some("custom")) + .title("expected reference `&str`\nfound reference `&'static [u8; 0]`"), + ), + ]; let expected = str![[r#" error[E0308]: mismatched types @@ -2543,42 +2515,40 @@ fn main() { } "#; let input = &[ - Group::new() - .element( - Level::ERROR - .title("`break` with value from a `while` loop") - .id("E0571"), - ) - .element( - Snippet::source(source) - .line_start(1) - .path("$DIR/issue-114529-illegal-break-with-value.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(483..581) - .label("can only break with a value inside `loop` or breakable block"), - ) - .annotation( - AnnotationKind::Context - .span(462..472) - .label("you can't `break` with a value in a `while` loop"), - ), - ), - Group::new() - .element( - Level::HELP - .text(Some("suggestion")) - .title("use `break` on its own without a value inside this `while` loop") - .id("S0123"), - ) - .element( - Snippet::source(source) - .line_start(1) - .path("$DIR/issue-114529-illegal-break-with-value.rs") - .fold(true) - .patch(Patch::new(483..581, "break")), - ), + Group::with_title( + Level::ERROR + .title("`break` with value from a `while` loop") + .id("E0571"), + ) + .element( + Snippet::source(source) + .line_start(1) + .path("$DIR/issue-114529-illegal-break-with-value.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(483..581) + .label("can only break with a value inside `loop` or breakable block"), + ) + .annotation( + AnnotationKind::Context + .span(462..472) + .label("you can't `break` with a value in a `while` loop"), + ), + ), + Group::with_title( + Level::HELP + .text(Some("suggestion")) + .title("use `break` on its own without a value inside this `while` loop") + .id("S0123"), + ) + .element( + Snippet::source(source) + .line_start(1) + .path("$DIR/issue-114529-illegal-break-with-value.rs") + .fold(true) + .patch(Patch::new(483..581, "break")), + ), ]; let expected_ascii = str![[r#" diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index de5f615f..29b84006 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -13,7 +13,7 @@ fn ends_on_col0() { fn foo() { } "#; - let input = &[Group::new().element(Level::ERROR.title("foo")).element( + let input = &[Group::with_title(Level::ERROR.title("foo")).element( Snippet::source(source) .line_start(1) .path("test.rs") @@ -41,7 +41,7 @@ fn foo() { } "#; - let input = &[Group::new().element(Level::ERROR.title("foo")).element( + let input = &[Group::with_title(Level::ERROR.title("foo")).element( Snippet::source(source) .line_start(1) .path("test.rs") @@ -70,7 +70,7 @@ fn foo() { X2 Y2 } "#; - let input = &[Group::new().element(Level::ERROR.title("foo")).element( + let input = &[Group::with_title(Level::ERROR.title("foo")).element( Snippet::source(source) .line_start(1) .path("test.rs") @@ -111,7 +111,7 @@ fn foo() { Y1 X1 } "#; - let input = &[Group::new().element(Level::ERROR.title("foo")).element( + let input = &[Group::with_title(Level::ERROR.title("foo")).element( Snippet::source(source) .line_start(1) .path("test.rs") @@ -153,7 +153,7 @@ fn foo() { X3 Y3 Z3 } "#; - let input = &[Group::new().element(Level::ERROR.title("foo")).element( + let input = &[Group::with_title(Level::ERROR.title("foo")).element( Snippet::source(source) .line_start(1) .path("test.rs") @@ -195,7 +195,7 @@ fn foo() { X2 Y2 Z2 } "#; - let input = &[Group::new().element(Level::ERROR.title("foo")).element( + let input = &[Group::with_title(Level::ERROR.title("foo")).element( Snippet::source(source) .line_start(1) .path("test.rs") @@ -240,7 +240,7 @@ fn foo() { X2 Y2 Z2 } "#; - let input = &[Group::new().element(Level::ERROR.title("foo")).element( + let input = &[Group::with_title(Level::ERROR.title("foo")).element( Snippet::source(source) .line_start(1) .path("test.rs") @@ -286,7 +286,7 @@ fn foo() { X3 Y3 Z3 } "#; - let input = &[Group::new().element(Level::ERROR.title("foo")).element( + let input = &[Group::with_title(Level::ERROR.title("foo")).element( Snippet::source(source) .line_start(1) .path("test.rs") @@ -334,7 +334,7 @@ fn foo() { X3 Y3 Z3 } "#; - let input = &[Group::new().element(Level::ERROR.title("foo")).element( + let input = &[Group::with_title(Level::ERROR.title("foo")).element( Snippet::source(source) .line_start(1) .path("test.rs") @@ -376,7 +376,7 @@ fn foo() { X3 Y3 Z3 } "#; - let input = &[Group::new().element(Level::ERROR.title("foo")).element( + let input = &[Group::with_title(Level::ERROR.title("foo")).element( Snippet::source(source) .line_start(1) .path("test.rs") @@ -417,7 +417,7 @@ fn foo() { a { b { c } d } } "#; - let input = &[Group::new().element(Level::ERROR.title("foo")).element( + let input = &[Group::with_title(Level::ERROR.title("foo")).element( Snippet::source(source) .line_start(1) .path("test.rs") @@ -448,7 +448,7 @@ fn foo() { a { b { c } d } } "#; - let input = &[Group::new().element(Level::ERROR.title("foo")).element( + let input = &[Group::with_title(Level::ERROR.title("foo")).element( Snippet::source(source) .line_start(1) .path("test.rs") @@ -478,7 +478,7 @@ fn foo() { a { b { c } d } } "#; - let input = &[Group::new().element(Level::ERROR.title("foo")).element( + let input = &[Group::with_title(Level::ERROR.title("foo")).element( Snippet::source(source) .line_start(1) .path("test.rs") @@ -511,7 +511,7 @@ fn foo() { a { b { c } d } } "#; - let input = &[Group::new().element(Level::ERROR.title("foo")).element( + let input = &[Group::with_title(Level::ERROR.title("foo")).element( Snippet::source(source) .line_start(1) .path("test.rs") @@ -543,7 +543,7 @@ fn foo() { a bc d } "#; - let input = &[Group::new().element(Level::ERROR.title("foo")).element( + let input = &[Group::with_title(Level::ERROR.title("foo")).element( Snippet::source(source) .line_start(1) .path("test.rs") @@ -575,7 +575,7 @@ fn foo() { a { b { c } d } } "#; - let input = &[Group::new().element(Level::ERROR.title("foo")).element( + let input = &[Group::with_title(Level::ERROR.title("foo")).element( Snippet::source(source) .line_start(1) .path("test.rs") @@ -601,7 +601,7 @@ fn foo() { a { b { c } d } } "#; - let input = &[Group::new().element(Level::ERROR.title("foo")).element( + let input = &[Group::with_title(Level::ERROR.title("foo")).element( Snippet::source(source) .line_start(1) .path("test.rs") @@ -628,7 +628,7 @@ fn foo() { a { b { c } d } } "#; - let input = &[Group::new().element(Level::ERROR.title("foo")).element( + let input = &[Group::with_title(Level::ERROR.title("foo")).element( Snippet::source(source) .line_start(1) .path("test.rs") @@ -665,7 +665,7 @@ fn foo() { a { b { c } d } } "#; - let input = &[Group::new().element(Level::ERROR.title("foo")).element( + let input = &[Group::with_title(Level::ERROR.title("foo")).element( Snippet::source(source) .line_start(1) .path("test.rs") @@ -694,7 +694,7 @@ fn foo() { a { b { c } d } } "#; - let input = &[Group::new().element(Level::ERROR.title("foo")).element( + let input = &[Group::with_title(Level::ERROR.title("foo")).element( Snippet::source(source) .line_start(1) .path("test.rs") @@ -732,7 +732,7 @@ fn foo() { X3 Y3 Z3 } "#; - let input = &[Group::new().element(Level::ERROR.title("foo")).element( + let input = &[Group::with_title(Level::ERROR.title("foo")).element( Snippet::source(source) .line_start(1) .path("test.rs") @@ -790,7 +790,7 @@ fn foo() { X3 Y3 Z3 } "#; - let input = &[Group::new().element(Level::ERROR.title("foo")).element( + let input = &[Group::with_title(Level::ERROR.title("foo")).element( Snippet::source(source) .line_start(1) .path("test.rs") @@ -842,30 +842,32 @@ fn issue_91334() { fn f(){||yield(((){), "#; - let input = &[Group::new() - .element(Level::ERROR.title("this file contains an unclosed delimiter")) - .element( - Snippet::source(source) - .line_start(1) - .path("$DIR/issue-91334.rs") - .fold(true) - .annotation( - AnnotationKind::Context - .span(151..152) - .label("unclosed delimiter"), - ) - .annotation( - AnnotationKind::Context - .span(159..160) - .label("unclosed delimiter"), - ) - .annotation( - AnnotationKind::Context - .span(164..164) - .label("missing open `(` for this delimiter"), - ) - .annotation(AnnotationKind::Primary.span(167..167)), - )]; + let input = + &[ + Group::with_title(Level::ERROR.title("this file contains an unclosed delimiter")) + .element( + Snippet::source(source) + .line_start(1) + .path("$DIR/issue-91334.rs") + .fold(true) + .annotation( + AnnotationKind::Context + .span(151..152) + .label("unclosed delimiter"), + ) + .annotation( + AnnotationKind::Context + .span(159..160) + .label("unclosed delimiter"), + ) + .annotation( + AnnotationKind::Context + .span(164..164) + .label("missing open `(` for this delimiter"), + ) + .annotation(AnnotationKind::Primary.span(167..167)), + ), + ]; let expected = str![[r#" error: this file contains an unclosed delimiter --> $DIR/issue-91334.rs:7:23 @@ -912,40 +914,37 @@ fn main() { } "#; let input = &[ - Group::new() - .element( - Level::ERROR - .title("`break` with value from a `while` loop") - .id("E0571"), - ) - .element( - Snippet::source(source) - .line_start(1) - .path("$DIR/issue-114529-illegal-break-with-value.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(483..581) - .label("can only break with a value inside `loop` or breakable block"), - ) - .annotation( - AnnotationKind::Context - .span(462..472) - .label("you can't `break` with a value in a `while` loop"), - ), - ), - Group::new() - .element( - Level::HELP - .title("use `break` on its own without a value inside this `while` loop"), - ) - .element( - Snippet::source(source) - .line_start(1) - .path("$DIR/issue-114529-illegal-break-with-value.rs") - .fold(true) - .annotation(AnnotationKind::Context.span(483..581).label("break")), - ), + Group::with_title( + Level::ERROR + .title("`break` with value from a `while` loop") + .id("E0571"), + ) + .element( + Snippet::source(source) + .line_start(1) + .path("$DIR/issue-114529-illegal-break-with-value.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(483..581) + .label("can only break with a value inside `loop` or breakable block"), + ) + .annotation( + AnnotationKind::Context + .span(462..472) + .label("you can't `break` with a value in a `while` loop"), + ), + ), + Group::with_title( + Level::HELP.title("use `break` on its own without a value inside this `while` loop"), + ) + .element( + Snippet::source(source) + .line_start(1) + .path("$DIR/issue-114529-illegal-break-with-value.rs") + .fold(true) + .annotation(AnnotationKind::Context.span(483..581).label("break")), + ), ]; let expected = str![[r#" error[E0571]: `break` with value from a `while` loop @@ -1121,42 +1120,40 @@ fn nsize() { } } "#; - let input = - &[ - Group::new() - .element( - Level::ERROR - .title("`V0usize` cannot be safely transmuted into `[usize; 2]`") - .id("E0277"), - ) - .element( - Snippet::source(source) - .line_start(1) - .path("$DIR/primitive_reprs_should_have_correct_length.rs") - .fold(true) - .annotation(AnnotationKind::Primary.span(4375..4381).label( - "the size of `V0usize` is smaller than the size of `[usize; 2]`", - )), + let input = &[ + Group::with_title( + Level::ERROR + .title("`V0usize` cannot be safely transmuted into `[usize; 2]`") + .id("E0277"), + ) + .element( + Snippet::source(source) + .line_start(1) + .path("$DIR/primitive_reprs_should_have_correct_length.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(4375..4381) + .label("the size of `V0usize` is smaller than the size of `[usize; 2]`"), ), - Group::new() - .element(Level::NOTE.title("required by a bound in `is_transmutable`")) - .element( - Snippet::source(source) - .line_start(1) - .path("$DIR/primitive_reprs_should_have_correct_length.rs") - .fold(true) - .annotation( - AnnotationKind::Context - .span(225..240) - .label("required by a bound in this function"), - ) - .annotation( - AnnotationKind::Primary - .span(276..470) - .label("required by this bound in `is_transmutable`"), - ), + ), + Group::with_title(Level::NOTE.title("required by a bound in `is_transmutable`")).element( + Snippet::source(source) + .line_start(1) + .path("$DIR/primitive_reprs_should_have_correct_length.rs") + .fold(true) + .annotation( + AnnotationKind::Context + .span(225..240) + .label("required by a bound in this function"), + ) + .annotation( + AnnotationKind::Primary + .span(276..470) + .label("required by this bound in `is_transmutable`"), ), - ]; + ), + ]; let expected = str![[r#" error[E0277]: `V0usize` cannot be safely transmuted into `[usize; 2]` --> $DIR/primitive_reprs_should_have_correct_length.rs:144:44 @@ -1209,7 +1206,7 @@ fn main() { assert::is_maybe_transmutable::<&'static [u8; 0], &'static [u16; 0]>(); //~ ERROR `&[u8; 0]` cannot be safely transmuted into `&[u16; 0]` } "#; - let input = &[Group::new().element(Level::ERROR + let input = &[Group::with_title(Level::ERROR .title("`&[u8; 0]` cannot be safely transmuted into `&[u16; 0]`") .id("E027s7")).element( Snippet::source(source) @@ -1275,33 +1272,33 @@ fn g() { } fn main() {} "#; - let input = - &[Group::new() - .element( - Level::ERROR - .title("expected function, found `{integer}`") - .id("E0618"), + let input = &[Group::with_title( + Level::ERROR + .title("expected function, found `{integer}`") + .id("E0618"), + ) + .element( + Snippet::source(source) + .line_start(1) + .path("$DIR/missing-semicolon.rs") + .fold(true) + .annotation( + AnnotationKind::Context + .span(108..144) + .label("call expression requires function"), ) - .element( - Snippet::source(source) - .line_start(1) - .path("$DIR/missing-semicolon.rs") - .fold(true) - .annotation( - AnnotationKind::Context - .span(108..144) - .label("call expression requires function"), - ) - .annotation( - AnnotationKind::Context - .span(89..90) - .label("`x` has type `{integer}`"), - ) - .annotation(AnnotationKind::Context.span(109..109).label( - "help: consider using a semicolon here to finish the statement: `;`", - )) - .annotation(AnnotationKind::Primary.span(108..109)), - )]; + .annotation( + AnnotationKind::Context + .span(89..90) + .label("`x` has type `{integer}`"), + ) + .annotation( + AnnotationKind::Context + .span(109..109) + .label("help: consider using a semicolon here to finish the statement: `;`"), + ) + .annotation(AnnotationKind::Primary.span(108..109)), + )]; let expected = str![[r#" error[E0618]: expected function, found `{integer}` --> $DIR/missing-semicolon.rs:5:13 @@ -1369,7 +1366,7 @@ macro_rules! outer_macro { outer_macro!(FirstStruct, FirstAttrStruct); "#; let input = - &[ Group::new().element(Level::WARNING + &[ Group::with_title(Level::WARNING .title("non-local `macro_rules!` definition, `#[macro_export]` macro should be written at top level module")) .element( Snippet::source(aux_source) @@ -1402,8 +1399,7 @@ outer_macro!(FirstStruct, FirstAttrStruct); Level::NOTE .title("a `macro_rules!` definition is non-local if it is nested inside an item and has a `#[macro_export]` attribute") ), - Group::new() - .element(Level::NOTE.title("the lint level is defined here")) + Group::with_title(Level::NOTE.title("the lint level is defined here")) .element( Snippet::source(source) .line_start(1) @@ -1498,28 +1494,28 @@ macro_rules! inline { } "#; let input = &[ - Group::new() - .element( - Level::ERROR - .title("can't call method `pow` on ambiguous numeric type `{integer}`") - .id("E0689"), - ) - .element( - Snippet::source(source) - .line_start(1) - .path("$DIR/method-on-ambiguous-numeric-type.rs") - .fold(true) - .annotation(AnnotationKind::Primary.span(916..919)), - ), - Group::new() - .element(Level::HELP.title("you must specify a type for this binding, like `i32`")) - .element( - Snippet::source(aux_source) - .line_start(1) - .path("$DIR/auxiliary/macro-in-other-crate.rs") - .fold(true) - .annotation(AnnotationKind::Context.span(69..69).label(": i32")), - ), + Group::with_title( + Level::ERROR + .title("can't call method `pow` on ambiguous numeric type `{integer}`") + .id("E0689"), + ) + .element( + Snippet::source(source) + .line_start(1) + .path("$DIR/method-on-ambiguous-numeric-type.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(916..919)), + ), + Group::with_title( + Level::HELP.title("you must specify a type for this binding, like `i32`"), + ) + .element( + Snippet::source(aux_source) + .line_start(1) + .path("$DIR/auxiliary/macro-in-other-crate.rs") + .fold(true) + .annotation(AnnotationKind::Context.span(69..69).label(": i32")), + ), ]; let expected = str![[r#" error[E0689]: can't call method `pow` on ambiguous numeric type `{integer}` @@ -1562,9 +1558,8 @@ fn courier_to_des_moines_and_points_west(data: &[u32]) -> String { fn main() {} "#; - let input = &[Group::new() - .element(Level::ERROR.title("type annotations needed").id("E0282")) - .element( + let input = &[ + Group::with_title(Level::ERROR.title("type annotations needed").id("E0282")).element( Snippet::source(source) .line_start(1) .path("$DIR/issue-42234-unknown-receiver-type.rs") @@ -1572,7 +1567,8 @@ fn main() {} .annotation(AnnotationKind::Primary.span(536..539).label( "cannot infer type of the type parameter `S` declared on the method `sum`", )), - )]; + ), + ]; let expected = str![[r#" error[E0282]: type annotations needed --> $DIR/issue-42234-unknown-receiver-type.rs:15:10 @@ -1666,7 +1662,7 @@ fn main() {} "##; let input = - &[ Group::new().element( Level::ERROR + &[ Group::with_title( Level::ERROR .title( "non-exhaustive patterns: `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered" ) @@ -1681,8 +1677,7 @@ fn main() {} .label("patterns `NonEmptyEnum5::V1`, `NonEmptyEnum5::V2`, `NonEmptyEnum5::V3` and 2 more not covered") ), ), - Group::new() - .element(Level::NOTE.title("`NonEmptyEnum5` defined here")) + Group::with_title(Level::NOTE.title("`NonEmptyEnum5` defined here")) .element( Snippet::source(source) .line_start(1) @@ -1698,8 +1693,7 @@ fn main() {} .element(Level::NOTE.title("the matched value is of type `NonEmptyEnum5`")) .element(Level::NOTE.title("match arms with guards don't count towards exhaustivity") ), - Group::new() - .element( + Group::with_title( Level::HELP .title("ensure that all possible cases are being handled by adding a match arm with a wildcard pattern as shown, or multiple match arms") ) @@ -1763,7 +1757,7 @@ fn main() { //~^ ERROR must be specified } "#; - let input = &[Group::new().element(Level::ERROR + let input = &[Group::with_title(Level::ERROR .title("the trait alias `EqAlias` is not dyn compatible") .id("E0038")).element( Snippet::source(source) @@ -1776,8 +1770,7 @@ fn main() { .label("`EqAlias` is not dyn compatible"), ), ), - Group::new() - .element( + Group::with_title( Level::NOTE .title("for a trait to be dyn compatible it needs to allow building a vtable\nfor more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>")) .element( @@ -1830,9 +1823,8 @@ const C: u8 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, fn main() {} "#; - let input = &[Group::new() - .element(Level::ERROR.title("mismatched types").id("E0038")) - .element( + let input = &[ + Group::with_title(Level::ERROR.title("mismatched types").id("E0038")).element( Snippet::source(source) .path("$DIR/long-span.rs") .fold(true) @@ -1841,7 +1833,8 @@ fn main() {} .span(15..5055) .label("expected `u8`, found `[{integer}; 1680]`"), ), - )]; + ), + ]; let expected = str![[r#" error[E0038]: mismatched types --> $DIR/long-span.rs:2:15 @@ -1864,9 +1857,8 @@ const C: u8 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, fn main() {} "#; - let input = &[Group::new() - .element(Level::ERROR.title("mismatched types").id("E0038")) - .element( + let input = &[ + Group::with_title(Level::ERROR.title("mismatched types").id("E0038")).element( Snippet::source(source) .path("$DIR/long-span.rs") .fold(true) @@ -1875,7 +1867,8 @@ fn main() {} .span(15..5055) .label("expected `u8`, found `[{integer}; 1680]`"), ), - )]; + ), + ]; let expected = str![[r#" error[E0038]: mismatched types ╭▸ $DIR/long-span.rs:2:15 @@ -1899,9 +1892,8 @@ const C: u8 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, fn main() {} "#; - let input = &[Group::new() - .element(Level::ERROR.title("mismatched types").id("E0038")) - .element( + let input = &[ + Group::with_title(Level::ERROR.title("mismatched types").id("E0038")).element( Snippet::source(source) .path("$DIR/long-span.rs") .fold(true) @@ -1910,7 +1902,8 @@ fn main() {} .span(15..5055) .label("expected `u8`, found `[{integer}; 1680]`"), ), - )]; + ), + ]; let expected = str![[r#" error[E0038]: mismatched types ╭▸ $DIR/long-span.rs:2:15 @@ -1934,9 +1927,8 @@ const C: u8 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, fn main() {} "#; - let input = &[Group::new() - .element(Level::ERROR.title("mismatched types").id("E0038")) - .element( + let input = &[ + Group::with_title(Level::ERROR.title("mismatched types").id("E0038")).element( Snippet::source(source) .path("$DIR/long-span.rs") .fold(true) @@ -1945,7 +1937,8 @@ fn main() {} .span(15..5055) .label("expected `u8`, found `[{integer}; 1680]`"), ), - )]; + ), + ]; let expected = str![[r#" error[E0038]: mismatched types --> $DIR/long-span.rs:2:15 @@ -1985,7 +1978,7 @@ fn main() { } "#; - let input = &[Group::new().element(Level::ERROR + let input = &[Group::with_title(Level::ERROR .title("`Iterator::map` call that discard the iterator's values")) .element( Snippet::source(source) @@ -2008,8 +2001,7 @@ fn main() { ) .element( Level::NOTE.title("`Iterator::map`, like many of the methods on `Iterator`, gets executed lazily, meaning that its effects won't be visible until it is iterated")), - Group::new() - .element(Level::HELP.title("you might have meant to use `Iterator::for_each`")) + Group::with_title(Level::HELP.title("you might have meant to use `Iterator::for_each`")) .element( Snippet::source(source) .path("$DIR/lint_map_unit_fn.rs") @@ -2076,23 +2068,19 @@ fn main() { "#; let input = &[ - Group::new() - .element(Level::ERROR.title("character constant must be escaped: `\\n`")) - .element( - Snippet::source(source) - .path("$DIR/bad-char-literals.rs") - .fold(true) - .annotation(AnnotationKind::Primary.span(204..205)), - ), - Group::new() - .element(Level::HELP.title("escape the character")) - .element( - Snippet::source(source) - .path("$DIR/bad-char-literals.rs") - .line_start(1) - .fold(true) - .patch(Patch::new(204..205, r#"\n"#)), - ), + Group::with_title(Level::ERROR.title("character constant must be escaped: `\\n`")).element( + Snippet::source(source) + .path("$DIR/bad-char-literals.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(204..205)), + ), + Group::with_title(Level::HELP.title("escape the character")).element( + Snippet::source(source) + .path("$DIR/bad-char-literals.rs") + .line_start(1) + .fold(true) + .patch(Patch::new(204..205, r#"\n"#)), + ), ]; let expected = str![[r#" error: character constant must be escaped: `/n` @@ -2129,22 +2117,18 @@ fn main() {} "#; let input = &[ - Group::new() - .element(Level::ERROR.title("unclosed frontmatter")) - .element( - Snippet::source(source) - .path("$DIR/unclosed-1.rs") - .fold(true) - .annotation(AnnotationKind::Primary.span(0..221)), - ), - Group::new() - .element(Level::NOTE.title("frontmatter opening here was not closed")) - .element( - Snippet::source(source) - .path("$DIR/unclosed-1.rs") - .fold(true) - .annotation(AnnotationKind::Primary.span(0..4)), - ), + Group::with_title(Level::ERROR.title("unclosed frontmatter")).element( + Snippet::source(source) + .path("$DIR/unclosed-1.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(0..221)), + ), + Group::with_title(Level::NOTE.title("frontmatter opening here was not closed")).element( + Snippet::source(source) + .path("$DIR/unclosed-1.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(0..4)), + ), ]; let expected = str![[r#" error: unclosed frontmatter @@ -2187,22 +2171,18 @@ fn foo() -> &str { "#; let input = &[ - Group::new() - .element(Level::ERROR.title("unclosed frontmatter")) - .element( - Snippet::source(source) - .path("$DIR/unclosed-2.rs") - .fold(true) - .annotation(AnnotationKind::Primary.span(0..377)), - ), - Group::new() - .element(Level::NOTE.title("frontmatter opening here was not closed")) - .element( - Snippet::source(source) - .path("$DIR/unclosed-2.rs") - .fold(true) - .annotation(AnnotationKind::Primary.span(0..4)), - ), + Group::with_title(Level::ERROR.title("unclosed frontmatter")).element( + Snippet::source(source) + .path("$DIR/unclosed-2.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(0..377)), + ), + Group::with_title(Level::NOTE.title("frontmatter opening here was not closed")).element( + Snippet::source(source) + .path("$DIR/unclosed-2.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(0..4)), + ), ]; let expected = str![[r#" error: unclosed frontmatter @@ -2247,22 +2227,22 @@ fn foo(x: i32) -> i32 { "#; let input = &[ - Group::new() - .element(Level::ERROR.title("invalid preceding whitespace for frontmatter close")) + Group::with_title(Level::ERROR.title("invalid preceding whitespace for frontmatter close")) .element( Snippet::source(source) .path("$DIR/unclosed-3.rs") .fold(true) .annotation(AnnotationKind::Primary.span(302..310)), ), - Group::new() - .element(Level::NOTE.title("frontmatter close should not be preceded by whitespace")) - .element( - Snippet::source(source) - .path("$DIR/unclosed-3.rs") - .fold(true) - .annotation(AnnotationKind::Primary.span(302..306)), - ), + Group::with_title( + Level::NOTE.title("frontmatter close should not be preceded by whitespace"), + ) + .element( + Snippet::source(source) + .path("$DIR/unclosed-3.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(302..306)), + ), ]; let expected = str![[r#" error: invalid preceding whitespace for frontmatter close @@ -2297,22 +2277,18 @@ fn main() {} "#; let input = &[ - Group::new() - .element(Level::ERROR.title("unclosed frontmatter")) - .element( - Snippet::source(source) - .path("$DIR/unclosed-4.rs") - .fold(true) - .annotation(AnnotationKind::Primary.span(0..43)), - ), - Group::new() - .element(Level::NOTE.title("frontmatter opening here was not closed")) - .element( - Snippet::source(source) - .path("$DIR/unclosed-4.rs") - .fold(true) - .annotation(AnnotationKind::Primary.span(0..4)), - ), + Group::with_title(Level::ERROR.title("unclosed frontmatter")).element( + Snippet::source(source) + .path("$DIR/unclosed-4.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(0..43)), + ), + Group::with_title(Level::NOTE.title("frontmatter opening here was not closed")).element( + Snippet::source(source) + .path("$DIR/unclosed-4.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(0..4)), + ), ]; let expected = str![[r#" error: unclosed frontmatter @@ -2350,22 +2326,18 @@ fn main() {} "#; let input = &[ - Group::new() - .element(Level::ERROR.title("unclosed frontmatter")) - .element( - Snippet::source(source) - .path("$DIR/unclosed-5.rs") - .fold(true) - .annotation(AnnotationKind::Primary.span(0..176)), - ), - Group::new() - .element(Level::NOTE.title("frontmatter opening here was not closed")) - .element( - Snippet::source(source) - .path("$DIR/unclosed-5.rs") - .fold(true) - .annotation(AnnotationKind::Primary.span(0..4)), - ), + Group::with_title(Level::ERROR.title("unclosed frontmatter")).element( + Snippet::source(source) + .path("$DIR/unclosed-5.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(0..176)), + ), + Group::with_title(Level::NOTE.title("frontmatter opening here was not closed")).element( + Snippet::source(source) + .path("$DIR/unclosed-5.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(0..4)), + ), ]; let expected = str![[r#" @@ -2471,46 +2443,49 @@ pub enum E2 { } "#; - let input = &[Group::new().element(Level::ERROR - .title("expected unit struct, unit variant or constant, found tuple variant `E1::Z1`") - .id(r#"E0532"#)) - .element( - Snippet::source(source) - .path("$DIR/pat-tuple-field-count-cross.rs") - .fold(true) - .annotation(AnnotationKind::Primary.span(1760..1766)), + let input = &[ + Group::with_title( + Level::ERROR + .title( + "expected unit struct, unit variant or constant, found tuple variant `E1::Z1`", ) - .element( - Snippet::source(source1) - .path("$DIR/auxiliary/declarations-for-tuple-field-count-errors.rs") - .fold(true) - .annotation( - AnnotationKind::Context - .span(143..145) - .label("`E1::Z1` defined here"), - ) - .annotation( - AnnotationKind::Context - .span(139..141) - .label("similarly named unit variant `Z0` defined here"), - ), - ), - Group::new() - .element(Level::HELP.title("use the tuple variant pattern syntax instead")) - .element( - Snippet::source(source) - .path("$DIR/pat-tuple-field-count-cross.rs") - .fold(true) - .patch(Patch::new(1760..1766, r#"E1::Z1()"#)), + .id(r#"E0532"#), + ) + .element( + Snippet::source(source) + .path("$DIR/pat-tuple-field-count-cross.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(1760..1766)), + ) + .element( + Snippet::source(source1) + .path("$DIR/auxiliary/declarations-for-tuple-field-count-errors.rs") + .fold(true) + .annotation( + AnnotationKind::Context + .span(143..145) + .label("`E1::Z1` defined here"), + ) + .annotation( + AnnotationKind::Context + .span(139..141) + .label("similarly named unit variant `Z0` defined here"), ), - Group::new() - .element(Level::HELP.title("a unit variant with a similar name exists")) - .element( - Snippet::source(source) - .path("$DIR/pat-tuple-field-count-cross.rs") - .fold(true) - .patch(Patch::new(1764..1766, r#"Z0"#)), - )]; + ), + Group::with_title(Level::HELP.title("use the tuple variant pattern syntax instead")) + .element( + Snippet::source(source) + .path("$DIR/pat-tuple-field-count-cross.rs") + .fold(true) + .patch(Patch::new(1760..1766, r#"E1::Z1()"#)), + ), + Group::with_title(Level::HELP.title("a unit variant with a similar name exists")).element( + Snippet::source(source) + .path("$DIR/pat-tuple-field-count-cross.rs") + .fold(true) + .patch(Patch::new(1764..1766, r#"Z0"#)), + ), + ]; let expected = str![[r#" error[E0532]: expected unit struct, unit variant or constant, found tuple variant `E1::Z1` --> $DIR/pat-tuple-field-count-cross.rs:35:9 @@ -2549,9 +2524,8 @@ fn unterminated_nested_comment() { */ "#; - let input = &[Group::new() - .element(Level::ERROR.title("unterminated block comment").id("E0758")) - .element( + let input = &[ + Group::with_title(Level::ERROR.title("unterminated block comment").id("E0758")).element( Snippet::source(source) .path("$DIR/unterminated-nested-comment.rs") .fold(true) @@ -2569,7 +2543,8 @@ fn unterminated_nested_comment() { .label("...and last nested comment terminates here."), ) .annotation(AnnotationKind::Primary.span(0..31)), - )]; + ), + ]; let expected = str![[r#" error[E0758]: unterminated block comment @@ -2605,37 +2580,38 @@ fn mismatched_types1() { let s: &str = include_bytes!("file.txt"); //~ ERROR mismatched types }"#; - let input = &[Group::new() - .element(Level::ERROR.title("mismatched types").id("E0308")) - .element( - Snippet::source(file_txt_source) - .fold(true) - .line_start(3) - .path("$DIR/file.txt") - .annotation( - AnnotationKind::Primary - .span(0..0) - .label("expected `&[u8]`, found `&str`"), - ), - ) - .element( - Snippet::source(rust_source) - .path("$DIR/mismatched-types.rs") - .fold(true) - .annotation( - AnnotationKind::Context - .span(23..28) - .label("expected due to this"), - ) - .annotation( - AnnotationKind::Context - .span(31..55) - .label("in this macro invocation"), - ), - ) - .element( - Level::NOTE.title("expected reference `&[u8]`\n found reference `&'static str`"), - )]; + let input = &[ + Group::with_title(Level::ERROR.title("mismatched types").id("E0308")) + .element( + Snippet::source(file_txt_source) + .fold(true) + .line_start(3) + .path("$DIR/file.txt") + .annotation( + AnnotationKind::Primary + .span(0..0) + .label("expected `&[u8]`, found `&str`"), + ), + ) + .element( + Snippet::source(rust_source) + .path("$DIR/mismatched-types.rs") + .fold(true) + .annotation( + AnnotationKind::Context + .span(23..28) + .label("expected due to this"), + ) + .annotation( + AnnotationKind::Context + .span(31..55) + .label("in this macro invocation"), + ), + ) + .element( + Level::NOTE.title("expected reference `&[u8]`\n found reference `&'static str`"), + ), + ]; let expected = str![[r#" error[E0308]: mismatched types @@ -2667,26 +2643,28 @@ fn mismatched_types2() { let s: &str = include_bytes!("file.txt"); //~ ERROR mismatched types }"#; - let input = &[Group::new() - .element(Level::ERROR.title("mismatched types").id("E0308")) - .element( - Snippet::source(source) - .path("$DIR/mismatched-types.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(105..131) - .label("expected `&str`, found `&[u8; 0]`"), - ) - .annotation( - AnnotationKind::Context - .span(98..102) - .label("expected due to this"), - ), - ) - .element( - Level::NOTE.title("expected reference `&str`\n found reference `&'static [u8; 0]`"), - )]; + let input = &[ + Group::with_title(Level::ERROR.title("mismatched types").id("E0308")) + .element( + Snippet::source(source) + .path("$DIR/mismatched-types.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(105..131) + .label("expected `&str`, found `&[u8; 0]`"), + ) + .annotation( + AnnotationKind::Context + .span(98..102) + .label("expected due to this"), + ), + ) + .element( + Level::NOTE + .title("expected reference `&str`\n found reference `&'static [u8; 0]`"), + ), + ]; let expected = str![[r#" error[E0308]: mismatched types @@ -2720,32 +2698,28 @@ fn main() { "#; let input = &[ - Group::new() - .element(Level::ERROR.title("mismatched types").id("E0308")) - .element( - Snippet::source(source) - .path("$DIR/short-error-format.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(80..100) - .label("expected `u32`, found `String`"), - ) - .annotation( - AnnotationKind::Context - .span(76..79) - .label("arguments to this function are incorrect"), - ), - ), - Group::new() - .element(Level::NOTE.title("function defined here")) - .element( - Snippet::source(source) - .path("$DIR/short-error-format.rs") - .fold(true) - .annotation(AnnotationKind::Context.span(48..54).label("")) - .annotation(AnnotationKind::Primary.span(44..47)), - ), + Group::with_title(Level::ERROR.title("mismatched types").id("E0308")).element( + Snippet::source(source) + .path("$DIR/short-error-format.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(80..100) + .label("expected `u32`, found `String`"), + ) + .annotation( + AnnotationKind::Context + .span(76..79) + .label("arguments to this function are incorrect"), + ), + ), + Group::with_title(Level::NOTE.title("function defined here")).element( + Snippet::source(source) + .path("$DIR/short-error-format.rs") + .fold(true) + .annotation(AnnotationKind::Context.span(48..54).label("")) + .annotation(AnnotationKind::Primary.span(44..47)), + ), ]; let expected = str![[r#" @@ -2772,22 +2746,21 @@ fn main() { } "#; - let input = &[Group::new() - .element( - Level::ERROR - .title("no method named `salut` found for type `u32` in the current scope") - .id("E0599"), - ) - .element( - Snippet::source(source) - .path("$DIR/short-error-format.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(127..132) - .label("method not found in `u32`"), - ), - )]; + let input = &[Group::with_title( + Level::ERROR + .title("no method named `salut` found for type `u32` in the current scope") + .id("E0599"), + ) + .element( + Snippet::source(source) + .path("$DIR/short-error-format.rs") + .fold(true) + .annotation( + AnnotationKind::Primary + .span(127..132) + .label("method not found in `u32`"), + ), + )]; let expected = str![[r#" $DIR/short-error-format.rs:8:7: error[E0599]: no method named `salut` found for type `u32` in the current scope: method not found in `u32` @@ -2812,8 +2785,7 @@ pub struct Foo; //~^ ERROR "#; let input = &[ - Group::new() - .element(Level::ERROR.title("this URL is not a hyperlink")) + Group::with_title(Level::ERROR.title("this URL is not a hyperlink")) .element( Snippet::source(source_0) .path("$DIR/diagnostic-width.rs") @@ -2823,24 +2795,20 @@ pub struct Foo; //~^ ERROR .element( Level::NOTE.title("bare URLs are not automatically turned into clickable links"), ), - Group::new() - .element(Level::NOTE.title("the lint level is defined here")) - .element( - Snippet::source(source_0) - .path("$DIR/diagnostic-width.rs") - .fold(true) - .annotation(AnnotationKind::Primary.span(49..67)), - ), - Group::new() - .element(Level::HELP.title("use an automatic link instead")) - .element( - Snippet::source(source_1) - .path("$DIR/diagnostic-width.rs") - .line_start(4) - .fold(true) - .patch(Patch::new(40..40, "<")) - .patch(Patch::new(55..55, ">")), - ), + Group::with_title(Level::NOTE.title("the lint level is defined here")).element( + Snippet::source(source_0) + .path("$DIR/diagnostic-width.rs") + .fold(true) + .annotation(AnnotationKind::Primary.span(49..67)), + ), + Group::with_title(Level::HELP.title("use an automatic link instead")).element( + Snippet::source(source_1) + .path("$DIR/diagnostic-width.rs") + .line_start(4) + .fold(true) + .patch(Patch::new(40..40, "<")) + .patch(Patch::new(55..55, ">")), + ), ]; let expected = str![[r#" @@ -2882,8 +2850,7 @@ fn main() { let long_title3 = "or use `IntoIterator::into_iter(..)` instead of `.into_iter()` to explicitly iterate by value"; let input = &[ - Group::new() - .element(Level::WARNING.title(long_title1)) + Group::with_title(Level::WARNING.title(long_title1)) .element( Snippet::source(source1) .path("lint_example.rs") @@ -2893,27 +2860,24 @@ fn main() { .element(Level::WARNING.title("this changes meaning in Rust 2021")) .element(Level::NOTE.title(long_title2)) .element(Level::NOTE.title("`#[warn(array_into_iter)]` on by default")), - Group::new() - .element( - Level::HELP.title("use `.iter()` instead of `.into_iter()` to avoid ambiguity"), - ) - .element( - Snippet::source(source2) - .path("lint_example.rs") - .line_start(3) - .fold(true) - .patch(Patch::new(10..19, "iter")), - ), - Group::new() - .element(Level::HELP.title(long_title3)) - .element( - Snippet::source(source2) - .path("lint_example.rs") - .line_start(3) - .fold(true) - .patch(Patch::new(0..0, "IntoIterator::into_iter(")) - .patch(Patch::new(9..21, ")")), - ), + Group::with_title( + Level::HELP.title("use `.iter()` instead of `.into_iter()` to avoid ambiguity"), + ) + .element( + Snippet::source(source2) + .path("lint_example.rs") + .line_start(3) + .fold(true) + .patch(Patch::new(10..19, "iter")), + ), + Group::with_title(Level::HELP.title(long_title3)).element( + Snippet::source(source2) + .path("lint_example.rs") + .line_start(3) + .fold(true) + .patch(Patch::new(0..0, "IntoIterator::into_iter(")) + .patch(Patch::new(9..21, ")")), + ), ]; let expected = str![[r#" From 82773497ab0c6c27ec64edf60a1a7137028c8f21 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Tue, 1 Jul 2025 16:43:07 -0500 Subject: [PATCH 408/455] docs: Remove bad comments about Origin --- src/snippet.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/snippet.rs b/src/snippet.rs index 3045b2a7..a58c32ae 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -396,8 +396,6 @@ impl<'a> Origin<'a> { } /// Set the default line number to display - /// - /// Otherwise this will be inferred from the primary [`Annotation`] pub fn line(mut self, line: usize) -> Self { self.line = Some(line); self @@ -405,8 +403,6 @@ impl<'a> Origin<'a> { /// Set the default column to display /// - /// Otherwise this will be inferred from the primary [`Annotation`] - /// /// <div class="warning"> /// /// `char_column` is only be respected if [`Origin::line`] is also set. From 838537fd5fdce918075865c168f683883f5f15c6 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Tue, 1 Jul 2025 16:45:36 -0500 Subject: [PATCH 409/455] docs: Describe Origin::primary --- src/snippet.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/snippet.rs b/src/snippet.rs index a58c32ae..851a55fe 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -267,9 +267,9 @@ impl<'a> Annotation<'a> { #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] #[non_exhaustive] pub enum AnnotationKind { - /// Match the primary [`Level`] of the group. + /// Shows the source that the [Group's Title][Group::with_title] references /// - /// See [`Group::with_level`] for details about how this is determined + /// For [`Title`]-less groups, see [`Group::with_level`] Primary, /// Additional context to explain the [`Primary`][Self::Primary] /// [`Annotation`] @@ -413,6 +413,7 @@ impl<'a> Origin<'a> { self } + /// Mark this as the source that the [Group's Title][Group::with_title] references pub fn primary(mut self, primary: bool) -> Self { self.primary = primary; self From 3e787fa6fe5cd1da58e2923952d9186382556685 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 1 Jul 2025 17:33:00 -0600 Subject: [PATCH 410/455] fix!: Make fold the default --- benches/bench.rs | 13 +- examples/custom_error.rs | 13 +- examples/custom_level.rs | 2 - examples/expected_type.rs | 1 - examples/format.rs | 1 + examples/highlight_source.rs | 1 - examples/highlight_title.rs | 2 - examples/id_hyperlink.rs | 1 - examples/multislice.rs | 2 + src/renderer/mod.rs | 1 - src/snippet.rs | 2 +- tests/color/fold_ann_multiline.rs | 1 - tests/color/fold_bad_origin_line.rs | 1 - tests/color/fold_leading.rs | 1 - tests/color/fold_trailing.rs | 1 - tests/color/multiline_removal_suggestion.rs | 3 +- tests/color/multiple_annotations.rs | 1 + tests/formatter.rs | 156 +++++++++----------- tests/rustc_tests.rs | 86 ++--------- 19 files changed, 93 insertions(+), 196 deletions(-) diff --git a/benches/bench.rs b/benches/bench.rs index 4e03ae8c..2adcc185 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -71,14 +71,11 @@ fn fold(bencher: divan::Bencher<'_, '_>, context: usize) { .bench_values(|(input, span)| { let message = &[ Group::with_title(Level::ERROR.title("mismatched types").id("E0308")).element( - Snippet::source(&input) - .fold(true) - .path("src/format.rs") - .annotation( - AnnotationKind::Context - .span(span) - .label("expected `Option<String>` because of return type"), - ), + Snippet::source(&input).path("src/format.rs").annotation( + AnnotationKind::Context + .span(span) + .label("expected `Option<String>` because of return type"), + ), ), ]; diff --git a/examples/custom_error.rs b/examples/custom_error.rs index a26dc4af..76ae0cce 100644 --- a/examples/custom_error.rs +++ b/examples/custom_error.rs @@ -22,14 +22,11 @@ pub static C: u32 = 0 - 1; .id("E0080"), ) .element( - Snippet::source(source) - .path("$DIR/err.rs") - .fold(true) - .annotation( - AnnotationKind::Primary - .span(386..391) - .label("attempt to compute `0_u32 - 1_u32`, which would overflow"), - ), + Snippet::source(source).path("$DIR/err.rs").annotation( + AnnotationKind::Primary + .span(386..391) + .label("attempt to compute `0_u32 - 1_u32`, which would overflow"), + ), )]; let renderer = Renderer::styled().theme(OutputTheme::Unicode); diff --git a/examples/custom_level.rs b/examples/custom_level.rs index 3cf475d0..41e8322f 100644 --- a/examples/custom_level.rs +++ b/examples/custom_level.rs @@ -39,7 +39,6 @@ fn main() { Snippet::source(source) .line_start(1) .path("$DIR/issue-114529-illegal-break-with-value.rs") - .fold(true) .annotation( AnnotationKind::Primary .span(483..581) @@ -60,7 +59,6 @@ fn main() { Snippet::source(source) .line_start(1) .path("$DIR/issue-114529-illegal-break-with-value.rs") - .fold(true) .patch(Patch::new(483..581, "break")), ), ]; diff --git a/examples/expected_type.rs b/examples/expected_type.rs index 6bbf0812..37120b38 100644 --- a/examples/expected_type.rs +++ b/examples/expected_type.rs @@ -11,7 +11,6 @@ fn main() { Snippet::source(source) .line_start(26) .path("examples/footer.rs") - .fold(true) .annotation(AnnotationKind::Primary.span(193..195).label( "expected struct `annotate_snippets::snippet::Slice`, found reference", )) diff --git a/examples/format.rs b/examples/format.rs index 384453b2..5f9bad3f 100644 --- a/examples/format.rs +++ b/examples/format.rs @@ -28,6 +28,7 @@ fn main() { Snippet::source(source) .line_start(51) .path("src/format.rs") + .fold(false) .annotation( AnnotationKind::Context .span(5..19) diff --git a/examples/highlight_source.rs b/examples/highlight_source.rs index 297ad871..22ab0d68 100644 --- a/examples/highlight_source.rs +++ b/examples/highlight_source.rs @@ -13,7 +13,6 @@ fn main() {} .id("E0010")) .element( Snippet::source(source) - .fold(true) .path("$DIR/E0010-teach.rs") .annotation( AnnotationKind::Primary diff --git a/examples/highlight_title.rs b/examples/highlight_title.rs index 7f6ccb77..1de99cf1 100644 --- a/examples/highlight_title.rs +++ b/examples/highlight_title.rs @@ -45,7 +45,6 @@ fn main() { Group::with_title(Level::ERROR.title("mismatched types").id("E0308")) .element( Snippet::source(source) - .fold(true) .path("$DIR/highlighting.rs") .annotation( AnnotationKind::Primary @@ -61,7 +60,6 @@ fn main() { .element(Level::NOTE.pre_styled_title(&title)), Group::with_title(Level::NOTE.title("function defined here")).element( Snippet::source(source) - .fold(true) .path("$DIR/highlighting.rs") .annotation(AnnotationKind::Context.span(200..333).label("")) .annotation(AnnotationKind::Primary.span(194..199)), diff --git a/examples/id_hyperlink.rs b/examples/id_hyperlink.rs index 6259e4d1..9070b260 100644 --- a/examples/id_hyperlink.rs +++ b/examples/id_hyperlink.rs @@ -17,7 +17,6 @@ fn main() { Snippet::source(source) .line_start(1) .path("$DIR/terminal_urls.rs") - .fold(true) .annotation( AnnotationKind::Primary .span(59..61) diff --git a/examples/multislice.rs b/examples/multislice.rs index 35b745c1..c494afa3 100644 --- a/examples/multislice.rs +++ b/examples/multislice.rs @@ -5,11 +5,13 @@ fn main() { .element( Snippet::<Annotation<'_>>::source("Foo") .line_start(51) + .fold(false) .path("src/format.rs"), ) .element( Snippet::<Annotation<'_>>::source("Faa") .line_start(129) + .fold(false) .path("src/display.rs"), )]; diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index fe29822f..65035c35 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -29,7 +29,6 @@ //! Snippet::source(source) //! .path("temp.rs") //! .line_start(1) -//! .fold(true) //! .annotation( //! AnnotationKind::Primary //! .span(10..13) diff --git a/src/snippet.rs b/src/snippet.rs index 851a55fe..1b317d72 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -168,7 +168,7 @@ impl<'a, T: Clone> Snippet<'a, T> { line_start: 1, source: source.into(), markers: vec![], - fold: false, + fold: true, } } diff --git a/tests/color/fold_ann_multiline.rs b/tests/color/fold_ann_multiline.rs index 68fd4f1b..5115b951 100644 --- a/tests/color/fold_ann_multiline.rs +++ b/tests/color/fold_ann_multiline.rs @@ -33,7 +33,6 @@ fn case() { Snippet::source(source) .path("src/format.rs") .line_start(51) - .fold(true) .annotation(AnnotationKind::Context.span(5..19).label( "expected `std::option::Option<std::string::String>` because of return type", )) diff --git a/tests/color/fold_bad_origin_line.rs b/tests/color/fold_bad_origin_line.rs index 99da3c5d..1a04adbd 100644 --- a/tests/color/fold_bad_origin_line.rs +++ b/tests/color/fold_bad_origin_line.rs @@ -13,7 +13,6 @@ invalid syntax Snippet::source(source) .path("path/to/error.rs") .line_start(1) - .fold(true) .annotation(AnnotationKind::Context.span(2..16).label("error here")), )]; let expected = file!["fold_bad_origin_line.term.svg"]; diff --git a/tests/color/fold_leading.rs b/tests/color/fold_leading.rs index e941c805..f4d29e3a 100644 --- a/tests/color/fold_leading.rs +++ b/tests/color/fold_leading.rs @@ -26,7 +26,6 @@ workspace = 20 Snippet::source(source) .path("Cargo.toml") .line_start(1) - .fold(true) .annotation(AnnotationKind::Primary.span(132..134).label("")), )]; let expected = file!["fold_leading.term.svg"]; diff --git a/tests/color/fold_trailing.rs b/tests/color/fold_trailing.rs index 9c85d873..59455c02 100644 --- a/tests/color/fold_trailing.rs +++ b/tests/color/fold_trailing.rs @@ -25,7 +25,6 @@ edition = "2021" Snippet::source(source) .path("Cargo.toml") .line_start(1) - .fold(true) .annotation(AnnotationKind::Primary.span(8..10).label("")), )]; let expected = file!["fold_trailing.term.svg"]; diff --git a/tests/color/multiline_removal_suggestion.rs b/tests/color/multiline_removal_suggestion.rs index 6a98ec40..8559ee93 100644 --- a/tests/color/multiline_removal_suggestion.rs +++ b/tests/color/multiline_removal_suggestion.rs @@ -73,7 +73,6 @@ fn main() {} .element( Snippet::source(source) .path("$DIR/multiline-removal-suggestion.rs") - .fold(true) .annotation( AnnotationKind::Primary .span(769..776) @@ -96,7 +95,7 @@ fn main() {} Group::with_title(Level::HELP.title("consider removing this method call, as the receiver has type `std::vec::IntoIter<HashSet<u8>>` and `std::vec::IntoIter<HashSet<u8>>: Iterator` trivially holds")).element( Snippet::source(source) .path("$DIR/multiline-removal-suggestion.rs") - .fold(true) + .patch(Patch::new(708..768, "")), ), ]; diff --git a/tests/color/multiple_annotations.rs b/tests/color/multiple_annotations.rs index 4533019a..a92c72f6 100644 --- a/tests/color/multiple_annotations.rs +++ b/tests/color/multiple_annotations.rs @@ -18,6 +18,7 @@ fn case() { let input = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source(source) .line_start(96) + .fold(false) .annotation( AnnotationKind::Primary .span(100..110) diff --git a/tests/formatter.rs b/tests/formatter.rs index a4c6f6ff..2c109577 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -8,8 +8,7 @@ fn test_i_29() { let snippets = &[Group::with_title(Level::ERROR.title("oops")).element( Snippet::source("First line\r\nSecond oops line") .path("<current file>") - .annotation(AnnotationKind::Primary.span(19..23).label("oops")) - .fold(true), + .annotation(AnnotationKind::Primary.span(19..23).label("oops")), )]; let expected = str![[r#" error: oops @@ -122,8 +121,11 @@ fn test_format_title() { #[test] fn test_format_snippet_only() { let source = "This is line 1\nThis is line 2"; - let input = &[Group::with_title(Level::ERROR.title("")) - .element(Snippet::<Annotation<'_>>::source(source).line_start(5402))]; + let input = &[Group::with_title(Level::ERROR.title("")).element( + Snippet::<Annotation<'_>>::source(source) + .line_start(5402) + .fold(false), + )]; let expected = str![[r#" error: @@ -143,12 +145,14 @@ fn test_format_snippets_continuation() { .element( Snippet::<Annotation<'_>>::source(src_0) .line_start(5402) - .path("file1.rs"), + .path("file1.rs") + .fold(false), ) .element( Snippet::<Annotation<'_>>::source(src_1) .line_start(2) - .path("file2.rs"), + .path("file2.rs") + .fold(false), )]; let expected = str![[r#" error: @@ -172,11 +176,14 @@ fn test_format_snippet_annotation_standalone() { // In line 2 let range = 22..24; let input = &[Group::with_title(Level::ERROR.title("")).element( - Snippet::source(&source).line_start(5402).annotation( - AnnotationKind::Context - .span(range.clone()) - .label("Test annotation"), - ), + Snippet::source(&source) + .line_start(5402) + .fold(false) + .annotation( + AnnotationKind::Context + .span(range.clone()) + .label("Test annotation"), + ), )]; let expected = str![[r#" error: @@ -221,8 +228,11 @@ fn test_i26() { #[test] fn test_source_content() { let source = "This is an example\nof content lines"; - let input = &[Group::with_title(Level::ERROR.title("")) - .element(Snippet::<Annotation<'_>>::source(source).line_start(56))]; + let input = &[Group::with_title(Level::ERROR.title("")).element( + Snippet::<Annotation<'_>>::source(source) + .line_start(56) + .fold(false), + )]; let expected = str![[r#" error: | @@ -275,8 +285,11 @@ error: #[test] fn test_only_source() { - let input = &[Group::with_title(Level::ERROR.title("")) - .element(Snippet::<Annotation<'_>>::source("").path("file.rs"))]; + let input = &[Group::with_title(Level::ERROR.title("")).element( + Snippet::<Annotation<'_>>::source("") + .path("file.rs") + .fold(false), + )]; let expected = str![[r#" error: --> file.rs @@ -290,8 +303,11 @@ error: #[test] fn test_anon_lines() { let source = "This is an example\nof content lines\n\nabc"; - let input = &[Group::with_title(Level::ERROR.title("")) - .element(Snippet::<Annotation<'_>>::source(source).line_start(56))]; + let input = &[Group::with_title(Level::ERROR.title("")).element( + Snippet::<Annotation<'_>>::source(source) + .line_start(56) + .fold(false), + )]; let expected = str![[r#" error: | @@ -310,7 +326,6 @@ fn issue_130() { Snippet::source("foo\nbar\nbaz") .path("file/path") .line_start(3) - .fold(true) .annotation(AnnotationKind::Primary.span(4..11)), // bar\nbaz )]; @@ -337,7 +352,6 @@ a\" Snippet::source(source) .path("file/path") .line_start(3) - .fold(true) .annotation(AnnotationKind::Primary.span(0..10)), // 1..10 works )]; @@ -360,6 +374,7 @@ fn char_and_nl_annotate_char() { Snippet::source(source) .path("file/path") .line_start(3) + .fold(false) .annotation(AnnotationKind::Primary.span(0..2)), // a\r )]; @@ -402,6 +417,7 @@ fn char_eol_annotate_char_double_width() { let snippets = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source("こん\r\nにちは\r\n世界") .path("<current file>") + .fold(false) .annotation(AnnotationKind::Primary.span(3..8)), // ん\r\n )]; @@ -428,6 +444,7 @@ fn annotate_eol() { Snippet::source(source) .path("file/path") .line_start(3) + .fold(false) .annotation(AnnotationKind::Primary.span(1..2)), // \r )]; @@ -496,6 +513,7 @@ fn annotate_eol4() { Snippet::source(source) .path("file/path") .line_start(3) + .fold(false) .annotation(AnnotationKind::Primary.span(2..2)), // \n )]; @@ -516,6 +534,7 @@ fn annotate_eol_double_width() { let snippets = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source("こん\r\nにちは\r\n世界") .path("<current file>") + .fold(false) .annotation(AnnotationKind::Primary.span(7..8)), // \n )]; @@ -609,6 +628,7 @@ fn multiline_eol_start_double_width() { let snippets = &[Group::with_title(Level::ERROR.title("")).element( Snippet::source("こん\r\nにちは\r\n世界") .path("<current file>") + .fold(false) .annotation(AnnotationKind::Primary.span(7..11)), // \r\nに )]; @@ -659,6 +679,7 @@ fn multiline_eol_start_eol_end2() { Snippet::source(source) .path("file/path") .line_start(3) + .fold(false) .annotation(AnnotationKind::Primary.span(2..5)), // \nb\r )]; @@ -982,24 +1003,12 @@ fn two_suggestions_same_span() { .title("expected value, found enum `A`") .id("E0423"), ) - .element( - Snippet::source(source) - .fold(true) - .annotation(AnnotationKind::Primary.span(4..5)), - ), + .element(Snippet::source(source).annotation(AnnotationKind::Primary.span(4..5))), Group::with_title( Level::HELP.title("you might have meant to use one of the following enum variants"), ) - .element( - Snippet::source(source) - .fold(true) - .patch(Patch::new(4..5, "(A::Tuple())")), - ) - .element( - Snippet::source(source) - .fold(true) - .patch(Patch::new(4..5, "A::Unit")), - ), + .element(Snippet::source(source).patch(Patch::new(4..5, "(A::Tuple())"))) + .element(Snippet::source(source).patch(Patch::new(4..5, "A::Unit"))), ]; let expected = str![[r#" @@ -1047,7 +1056,7 @@ fn main() { .id("E0599")).element( Snippet::source(source) .line_start(1) - .fold(true) + .annotation( AnnotationKind::Context .span(18..40) @@ -1064,12 +1073,12 @@ fn main() { )) .element( Snippet::source(source) - .fold(true) + .patch(Patch::new(1..1, "use banana::Apple;\n")), ) .element( Snippet::source(source) - .fold(true) + .patch(Patch::new(1..1, "use banana::Peach;\n")), )]; let expected = str![[r#" @@ -1104,14 +1113,11 @@ fn single_line_non_overlapping_suggestions() { ) .element( Snippet::source(source) - .fold(true) .line_start(1) .annotation(AnnotationKind::Primary.span(4..5)), ), Group::with_title(Level::HELP.title("make these changes and things will work")).element( Snippet::source(source) - .fold(true) - .fold(true) .patch(Patch::new(4..5, "(A::Tuple())")) .patch(Patch::new(6..9, "bar")), ), @@ -1139,14 +1145,11 @@ fn single_line_non_overlapping_suggestions2() { let input_new = &[ Group::with_title(Level::ERROR.title("Found `ThisIsVeryLong`").id("E0423")).element( Snippet::source(source) - .fold(true) .line_start(1) .annotation(AnnotationKind::Primary.span(4..18)), ), Group::with_title(Level::HELP.title("make these changes and things will work")).element( Snippet::source(source) - .fold(true) - .fold(true) .patch(Patch::new(4..18, "(A::Tuple())")) .patch(Patch::new(19..22, "bar")), ), @@ -1187,7 +1190,6 @@ fn multiple_replacements() { .element( Snippet::source(source) .line_start(1) - .fold(true) .annotation( AnnotationKind::Primary .span(49..59) @@ -1214,7 +1216,6 @@ fn multiple_replacements() { ) .element( Snippet::source(source) - .fold(true) .patch(Patch::new(14..14, "this: &Self")) .patch(Patch::new(26..30, "this")) .patch(Patch::new(66..68, "(self)")), @@ -1269,7 +1270,6 @@ fn main() { .element( Snippet::source(source) .line_start(1) - .fold(true) .annotation( AnnotationKind::Context .span(65..70) @@ -1291,7 +1291,6 @@ fn main() { )) .element( Snippet::source(source) - .fold(true) .patch(Patch::new( 55..59, "let iter = chars.by_ref();\n while let Some(", @@ -1349,28 +1348,18 @@ fn main() {}"#; .id("E0433"), ) .element( - Snippet::source(source).line_start(1).fold(true).annotation( + Snippet::source(source).line_start(1).annotation( AnnotationKind::Primary .span(122..124) .label("use of undeclared crate or module `st`"), ), ), Group::with_title(Level::HELP.title("there is a crate or module with a similar name")) - .element( - Snippet::source(source) - .fold(true) - .patch(Patch::new(122..124, "std")), - ), - Group::with_title(Level::HELP.title("consider importing this module")).element( - Snippet::source(source) - .fold(true) - .patch(Patch::new(1..1, "use std::cell;\n")), - ), - Group::with_title(Level::HELP.title("if you import `cell`, refer to it directly")).element( - Snippet::source(source) - .fold(true) - .patch(Patch::new(122..126, "")), - ), + .element(Snippet::source(source).patch(Patch::new(122..124, "std"))), + Group::with_title(Level::HELP.title("consider importing this module")) + .element(Snippet::source(source).patch(Patch::new(1..1, "use std::cell;\n"))), + Group::with_title(Level::HELP.title("if you import `cell`, refer to it directly")) + .element(Snippet::source(source).patch(Patch::new(122..126, ""))), ]; let expected = str![[r#" error[E0433]: failed to resolve: use of undeclared crate or module `st` @@ -1424,7 +1413,6 @@ fn main() {}"#; .element( Snippet::source(source) .line_start(1) - .fold(true) .annotation( AnnotationKind::Primary .span(39..49) @@ -1440,11 +1428,7 @@ fn main() {}"#; Level::HELP .title("consider removing the `?Sized` bound to make the type parameter `Sized`"), ) - .element( - Snippet::source(source) - .fold(true) - .patch(Patch::new(52..85, "")), - ), + .element(Snippet::source(source).patch(Patch::new(52..85, ""))), ]; let expected = str![[r#" error[E0277]: the size for values of type `T` cannot be known at compilation time @@ -1489,7 +1473,7 @@ fn main() {}"#; .id("E0277")).element(Snippet::source(source) .line_start(1) .path("$DIR/removal-of-multiline-trait-bound-in-where-clause.rs") - .fold(true) + .annotation( AnnotationKind::Primary .span(39..49) @@ -1507,7 +1491,7 @@ fn main() {}"#; Snippet::source(source) .line_start(1) .path("$DIR/removal-of-multiline-trait-bound-in-where-clause.rs") - .fold(true) + .annotation( AnnotationKind::Primary .span(16..17) @@ -1521,7 +1505,7 @@ fn main() {}"#; Snippet::source(source) .line_start(1) .path("$DIR/removal-of-multiline-trait-bound-in-where-clause.rs") - .fold(true) + .annotation( AnnotationKind::Primary .span(16..17) @@ -1538,7 +1522,7 @@ fn main() {}"#; .title("consider removing the `?Sized` bound to make the type parameter `Sized`") ).element( Snippet::source(source) - .fold(true) + .patch(Patch::new(56..89, "")) .patch(Patch::new(89..89, "+ Send")) , @@ -1604,7 +1588,6 @@ zappy .element( Snippet::source(source) .line_start(7) - .fold(true) .patch(Patch::new(3..21, "")) .patch(Patch::new(22..40, "")), ), @@ -1666,7 +1649,7 @@ fn main() { .id("E0271")).element(Snippet::source(source) .line_start(4) .path("$DIR/E0271.rs") - .fold(true) + .annotation( AnnotationKind::Primary .span(208..510) @@ -1677,7 +1660,7 @@ fn main() { Snippet::source(source) .line_start(4) .path("$DIR/E0271.rs") - .fold(true) + .annotation(AnnotationKind::Primary.span(89..90)) ).element( Level::NOTE @@ -1752,7 +1735,7 @@ fn main() { .id("E0271")).element(Snippet::source(source) .line_start(4) .path("$DIR/E0271.rs") - .fold(true) + .annotation( AnnotationKind::Primary .span(208..510) @@ -1763,7 +1746,7 @@ fn main() { Snippet::source(source) .line_start(4) .path("$DIR/E0271.rs") - .fold(true) + .annotation(AnnotationKind::Primary.span(89..90)) ).element( Level::NOTE @@ -1904,7 +1887,7 @@ fn main() { Snippet::source(source) .line_start(7) .path("$DIR/long-E0308.rs") - .fold(true) + .annotation( AnnotationKind::Primary .span(719..1001) @@ -1988,7 +1971,7 @@ fn main() { Snippet::source(source) .line_start(7) .path("$DIR/unicode-output.rs") - .fold(true) + .annotation( AnnotationKind::Primary .span(430..440) @@ -2009,7 +1992,7 @@ fn main() { Snippet::source(source) .line_start(7) .path("$DIR/unicode-output.rs") - .fold(true) + .annotation(AnnotationKind::Primary.span(77..210)) .annotation(AnnotationKind::Context.span(71..76)), )]; @@ -2207,7 +2190,6 @@ fn main() { Group::with_title(Level::ERROR.title("mismatched types").id("E0308")).element( Snippet::source(source) .path("$DIR/non-whitespace-trimming-unicode.rs") - .fold(true) .annotation( AnnotationKind::Primary .span(1207..1209) @@ -2268,7 +2250,6 @@ fn main() { .element( Snippet::source(source) .path("$DIR/non-1-width-unicode-multiline-label.rs") - .fold(true) .annotation(AnnotationKind::Context.span(970..984).label("&str")) .annotation(AnnotationKind::Context.span(987..1001).label("&str")) .annotation( @@ -2282,7 +2263,6 @@ fn main() { .element( Snippet::source(source) .path("$DIR/non-1-width-unicode-multiline-label.rs") - .fold(true) .patch(Patch::new(984..984, ".to_owned()")), ), ]; @@ -2343,14 +2323,14 @@ fn foo() { .title("couldn't read `$DIR/not-utf8.bin`: stream did not contain valid UTF-8")).element( Snippet::source(source) .path("$DIR/not-utf8.rs") - .fold(true) + .annotation(AnnotationKind::Primary.span(136..160)), ), Group::with_title(Level::NOTE.title("byte `193` is not valid utf-8")) .element( Snippet::source(bin_source) .path("$DIR/not-utf8.bin") - .fold(true) + .annotation(AnnotationKind::Primary.span(0..0)), ) .element(Level::NOTE.title("this error originates in the macro `include` (in Nightly builds, run with -Z macro-backtrace for more info)")), @@ -2404,7 +2384,6 @@ fn secondary_title_no_level_text() { .element( Snippet::source(source) .path("$DIR/mismatched-types.rs") - .fold(true) .annotation( AnnotationKind::Primary .span(105..131) @@ -2450,7 +2429,6 @@ fn secondary_title_custom_level_text() { .element( Snippet::source(source) .path("$DIR/mismatched-types.rs") - .fold(true) .annotation( AnnotationKind::Primary .span(105..131) @@ -2524,7 +2502,6 @@ fn main() { Snippet::source(source) .line_start(1) .path("$DIR/issue-114529-illegal-break-with-value.rs") - .fold(true) .annotation( AnnotationKind::Primary .span(483..581) @@ -2546,7 +2523,6 @@ fn main() { Snippet::source(source) .line_start(1) .path("$DIR/issue-114529-illegal-break-with-value.rs") - .fold(true) .patch(Patch::new(483..581, "break")), ), ]; diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index 29b84006..4b1944a5 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -17,7 +17,6 @@ fn foo() { Snippet::source(source) .line_start(1) .path("test.rs") - .fold(true) .annotation(AnnotationKind::Primary.span(10..13).label("test")), )]; @@ -45,7 +44,6 @@ fn foo() { Snippet::source(source) .line_start(1) .path("test.rs") - .fold(true) .annotation(AnnotationKind::Primary.span(10..17).label("test")), )]; let expected = str![[r#" @@ -74,7 +72,6 @@ fn foo() { Snippet::source(source) .line_start(1) .path("test.rs") - .fold(true) .annotation( AnnotationKind::Primary .span(14..32) @@ -115,7 +112,6 @@ fn foo() { Snippet::source(source) .line_start(1) .path("test.rs") - .fold(true) .annotation( AnnotationKind::Primary .span(14..27) @@ -157,7 +153,6 @@ fn foo() { Snippet::source(source) .line_start(1) .path("test.rs") - .fold(true) .annotation( AnnotationKind::Primary .span(17..38) @@ -199,7 +194,6 @@ fn foo() { Snippet::source(source) .line_start(1) .path("test.rs") - .fold(true) .annotation( AnnotationKind::Primary .span(14..38) @@ -244,7 +238,6 @@ fn foo() { Snippet::source(source) .line_start(1) .path("test.rs") - .fold(true) .annotation( AnnotationKind::Primary .span(14..38) @@ -290,7 +283,6 @@ fn foo() { Snippet::source(source) .line_start(1) .path("test.rs") - .fold(true) .annotation( AnnotationKind::Primary .span(17..27) @@ -338,7 +330,6 @@ fn foo() { Snippet::source(source) .line_start(1) .path("test.rs") - .fold(true) .annotation( AnnotationKind::Primary .span(14..27) @@ -380,7 +371,6 @@ fn foo() { Snippet::source(source) .line_start(1) .path("test.rs") - .fold(true) .annotation( AnnotationKind::Primary .span(17..27) @@ -421,7 +411,6 @@ fn foo() { Snippet::source(source) .line_start(1) .path("test.rs") - .fold(true) .annotation(AnnotationKind::Primary.span(18..25).label("")) .annotation( AnnotationKind::Context @@ -452,7 +441,6 @@ fn foo() { Snippet::source(source) .line_start(1) .path("test.rs") - .fold(true) .annotation( AnnotationKind::Primary .span(14..27) @@ -482,7 +470,6 @@ fn foo() { Snippet::source(source) .line_start(1) .path("test.rs") - .fold(true) .annotation( AnnotationKind::Primary .span(18..25) @@ -515,7 +502,6 @@ fn foo() { Snippet::source(source) .line_start(1) .path("test.rs") - .fold(true) .annotation(AnnotationKind::Primary.span(14..27).label("")) .annotation( AnnotationKind::Context @@ -547,7 +533,6 @@ fn foo() { Snippet::source(source) .line_start(1) .path("test.rs") - .fold(true) .annotation( AnnotationKind::Primary .span(14..18) @@ -579,7 +564,6 @@ fn foo() { Snippet::source(source) .line_start(1) .path("test.rs") - .fold(true) .annotation(AnnotationKind::Primary.span(14..27).label("")) .annotation(AnnotationKind::Context.span(18..25).label("")), )]; @@ -605,7 +589,6 @@ fn foo() { Snippet::source(source) .line_start(1) .path("test.rs") - .fold(true) .annotation(AnnotationKind::Primary.span(18..25).label("")) .annotation(AnnotationKind::Context.span(14..27).label("")) .annotation(AnnotationKind::Context.span(22..23).label("")), @@ -632,7 +615,6 @@ fn foo() { Snippet::source(source) .line_start(1) .path("test.rs") - .fold(true) .annotation( AnnotationKind::Primary .span(14..27) @@ -669,7 +651,6 @@ fn foo() { Snippet::source(source) .line_start(1) .path("test.rs") - .fold(true) .annotation( AnnotationKind::Primary .span(14..27) @@ -698,7 +679,6 @@ fn foo() { Snippet::source(source) .line_start(1) .path("test.rs") - .fold(true) .annotation(AnnotationKind::Primary.span(14..27).label("")), )]; @@ -736,7 +716,6 @@ fn foo() { Snippet::source(source) .line_start(1) .path("test.rs") - .fold(true) .annotation( AnnotationKind::Primary .span(17..27) @@ -794,7 +773,6 @@ fn foo() { Snippet::source(source) .line_start(1) .path("test.rs") - .fold(true) .annotation( AnnotationKind::Primary .span(17..73) @@ -849,7 +827,6 @@ fn f(){||yield(((){), Snippet::source(source) .line_start(1) .path("$DIR/issue-91334.rs") - .fold(true) .annotation( AnnotationKind::Context .span(151..152) @@ -923,7 +900,6 @@ fn main() { Snippet::source(source) .line_start(1) .path("$DIR/issue-114529-illegal-break-with-value.rs") - .fold(true) .annotation( AnnotationKind::Primary .span(483..581) @@ -942,7 +918,6 @@ fn main() { Snippet::source(source) .line_start(1) .path("$DIR/issue-114529-illegal-break-with-value.rs") - .fold(true) .annotation(AnnotationKind::Context.span(483..581).label("break")), ), ]; @@ -1130,7 +1105,6 @@ fn nsize() { Snippet::source(source) .line_start(1) .path("$DIR/primitive_reprs_should_have_correct_length.rs") - .fold(true) .annotation( AnnotationKind::Primary .span(4375..4381) @@ -1141,7 +1115,6 @@ fn nsize() { Snippet::source(source) .line_start(1) .path("$DIR/primitive_reprs_should_have_correct_length.rs") - .fold(true) .annotation( AnnotationKind::Context .span(225..240) @@ -1211,7 +1184,7 @@ fn main() { .id("E027s7")).element( Snippet::source(source) .line_start(1) - .fold(true) + .path("$DIR/align-fail.rs") .annotation( AnnotationKind::Primary @@ -1281,7 +1254,6 @@ fn main() {} Snippet::source(source) .line_start(1) .path("$DIR/missing-semicolon.rs") - .fold(true) .annotation( AnnotationKind::Context .span(108..144) @@ -1372,7 +1344,7 @@ outer_macro!(FirstStruct, FirstAttrStruct); Snippet::source(aux_source) .line_start(1) .path("$DIR/auxiliary/nested-macro-rules.rs") - .fold(true) + .annotation( AnnotationKind::Context .span(41..65) @@ -1384,7 +1356,7 @@ outer_macro!(FirstStruct, FirstAttrStruct); Snippet::source(source) .line_start(1) .path("$DIR/nested-macro-rules.rs") - .fold(true) + .annotation( AnnotationKind::Context .span(510..574) @@ -1404,7 +1376,7 @@ outer_macro!(FirstStruct, FirstAttrStruct); Snippet::source(source) .line_start(1) .path("$DIR/nested-macro-rules.rs") - .fold(true) + .annotation(AnnotationKind::Primary.span(224..245)), )]; let expected = str![[r#" @@ -1503,7 +1475,6 @@ macro_rules! inline { Snippet::source(source) .line_start(1) .path("$DIR/method-on-ambiguous-numeric-type.rs") - .fold(true) .annotation(AnnotationKind::Primary.span(916..919)), ), Group::with_title( @@ -1513,7 +1484,6 @@ macro_rules! inline { Snippet::source(aux_source) .line_start(1) .path("$DIR/auxiliary/macro-in-other-crate.rs") - .fold(true) .annotation(AnnotationKind::Context.span(69..69).label(": i32")), ), ]; @@ -1563,7 +1533,6 @@ fn main() {} Snippet::source(source) .line_start(1) .path("$DIR/issue-42234-unknown-receiver-type.rs") - .fold(true) .annotation(AnnotationKind::Primary.span(536..539).label( "cannot infer type of the type parameter `S` declared on the method `sum`", )), @@ -1670,7 +1639,7 @@ fn main() {} Snippet::source(source) .line_start(1) .path("$DIR/empty-match.rs") - .fold(true) + .annotation( AnnotationKind::Primary .span(2911..2928) @@ -1682,7 +1651,7 @@ fn main() {} Snippet::source(source) .line_start(1) .path("$DIR/empty-match.rs") - .fold(true) + .annotation(AnnotationKind::Primary.span(818..831)) .annotation(AnnotationKind::Context.span(842..844).label("not covered")) .annotation(AnnotationKind::Context.span(854..856).label("not covered")) @@ -1701,7 +1670,7 @@ fn main() {} Snippet::source(source) .line_start(1) .path("$DIR/empty-match.rs") - .fold(true) + .annotation(AnnotationKind::Context.span(485..485).label(",\n _ => todo!()")) )]; @@ -1763,7 +1732,7 @@ fn main() { Snippet::source(source) .line_start(1) .path("$DIR/object-fail.rs") - .fold(true) + .annotation( AnnotationKind::Primary .span(107..114) @@ -1785,7 +1754,7 @@ fn main() { Snippet::source(source) .line_start(1) .path("$DIR/object-fail.rs") - .fold(true) + .annotation( AnnotationKind::Context .span(32..39) @@ -1827,7 +1796,6 @@ fn main() {} Group::with_title(Level::ERROR.title("mismatched types").id("E0038")).element( Snippet::source(source) .path("$DIR/long-span.rs") - .fold(true) .annotation( AnnotationKind::Primary .span(15..5055) @@ -1861,7 +1829,6 @@ fn main() {} Group::with_title(Level::ERROR.title("mismatched types").id("E0038")).element( Snippet::source(source) .path("$DIR/long-span.rs") - .fold(true) .annotation( AnnotationKind::Primary .span(15..5055) @@ -1896,7 +1863,6 @@ fn main() {} Group::with_title(Level::ERROR.title("mismatched types").id("E0038")).element( Snippet::source(source) .path("$DIR/long-span.rs") - .fold(true) .annotation( AnnotationKind::Primary .span(15..5055) @@ -1931,7 +1897,6 @@ fn main() {} Group::with_title(Level::ERROR.title("mismatched types").id("E0038")).element( Snippet::source(source) .path("$DIR/long-span.rs") - .fold(true) .annotation( AnnotationKind::Primary .span(15..5055) @@ -1983,7 +1948,7 @@ fn main() { .element( Snippet::source(source) .path("$DIR/lint_map_unit_fn.rs") - .fold(true) + .annotation(AnnotationKind::Context.span(271..278).label( "this function returns `()`, which is likely not what you wanted", )) @@ -2005,7 +1970,7 @@ fn main() { .element( Snippet::source(source) .path("$DIR/lint_map_unit_fn.rs") - .fold(true) + .patch(Patch::new(267..270, r#"for_each"#)), )]; @@ -2071,14 +2036,12 @@ fn main() { Group::with_title(Level::ERROR.title("character constant must be escaped: `\\n`")).element( Snippet::source(source) .path("$DIR/bad-char-literals.rs") - .fold(true) .annotation(AnnotationKind::Primary.span(204..205)), ), Group::with_title(Level::HELP.title("escape the character")).element( Snippet::source(source) .path("$DIR/bad-char-literals.rs") .line_start(1) - .fold(true) .patch(Patch::new(204..205, r#"\n"#)), ), ]; @@ -2120,13 +2083,11 @@ fn main() {} Group::with_title(Level::ERROR.title("unclosed frontmatter")).element( Snippet::source(source) .path("$DIR/unclosed-1.rs") - .fold(true) .annotation(AnnotationKind::Primary.span(0..221)), ), Group::with_title(Level::NOTE.title("frontmatter opening here was not closed")).element( Snippet::source(source) .path("$DIR/unclosed-1.rs") - .fold(true) .annotation(AnnotationKind::Primary.span(0..4)), ), ]; @@ -2174,13 +2135,11 @@ fn foo() -> &str { Group::with_title(Level::ERROR.title("unclosed frontmatter")).element( Snippet::source(source) .path("$DIR/unclosed-2.rs") - .fold(true) .annotation(AnnotationKind::Primary.span(0..377)), ), Group::with_title(Level::NOTE.title("frontmatter opening here was not closed")).element( Snippet::source(source) .path("$DIR/unclosed-2.rs") - .fold(true) .annotation(AnnotationKind::Primary.span(0..4)), ), ]; @@ -2231,7 +2190,6 @@ fn foo(x: i32) -> i32 { .element( Snippet::source(source) .path("$DIR/unclosed-3.rs") - .fold(true) .annotation(AnnotationKind::Primary.span(302..310)), ), Group::with_title( @@ -2240,7 +2198,6 @@ fn foo(x: i32) -> i32 { .element( Snippet::source(source) .path("$DIR/unclosed-3.rs") - .fold(true) .annotation(AnnotationKind::Primary.span(302..306)), ), ]; @@ -2280,13 +2237,11 @@ fn main() {} Group::with_title(Level::ERROR.title("unclosed frontmatter")).element( Snippet::source(source) .path("$DIR/unclosed-4.rs") - .fold(true) .annotation(AnnotationKind::Primary.span(0..43)), ), Group::with_title(Level::NOTE.title("frontmatter opening here was not closed")).element( Snippet::source(source) .path("$DIR/unclosed-4.rs") - .fold(true) .annotation(AnnotationKind::Primary.span(0..4)), ), ]; @@ -2329,13 +2284,11 @@ fn main() {} Group::with_title(Level::ERROR.title("unclosed frontmatter")).element( Snippet::source(source) .path("$DIR/unclosed-5.rs") - .fold(true) .annotation(AnnotationKind::Primary.span(0..176)), ), Group::with_title(Level::NOTE.title("frontmatter opening here was not closed")).element( Snippet::source(source) .path("$DIR/unclosed-5.rs") - .fold(true) .annotation(AnnotationKind::Primary.span(0..4)), ), ]; @@ -2454,13 +2407,11 @@ pub enum E2 { .element( Snippet::source(source) .path("$DIR/pat-tuple-field-count-cross.rs") - .fold(true) .annotation(AnnotationKind::Primary.span(1760..1766)), ) .element( Snippet::source(source1) .path("$DIR/auxiliary/declarations-for-tuple-field-count-errors.rs") - .fold(true) .annotation( AnnotationKind::Context .span(143..145) @@ -2476,13 +2427,11 @@ pub enum E2 { .element( Snippet::source(source) .path("$DIR/pat-tuple-field-count-cross.rs") - .fold(true) .patch(Patch::new(1760..1766, r#"E1::Z1()"#)), ), Group::with_title(Level::HELP.title("a unit variant with a similar name exists")).element( Snippet::source(source) .path("$DIR/pat-tuple-field-count-cross.rs") - .fold(true) .patch(Patch::new(1764..1766, r#"Z0"#)), ), ]; @@ -2528,7 +2477,6 @@ fn unterminated_nested_comment() { Group::with_title(Level::ERROR.title("unterminated block comment").id("E0758")).element( Snippet::source(source) .path("$DIR/unterminated-nested-comment.rs") - .fold(true) .annotation( AnnotationKind::Context .span(0..2) @@ -2584,7 +2532,6 @@ fn mismatched_types1() { Group::with_title(Level::ERROR.title("mismatched types").id("E0308")) .element( Snippet::source(file_txt_source) - .fold(true) .line_start(3) .path("$DIR/file.txt") .annotation( @@ -2596,7 +2543,6 @@ fn mismatched_types1() { .element( Snippet::source(rust_source) .path("$DIR/mismatched-types.rs") - .fold(true) .annotation( AnnotationKind::Context .span(23..28) @@ -2648,7 +2594,6 @@ fn mismatched_types2() { .element( Snippet::source(source) .path("$DIR/mismatched-types.rs") - .fold(true) .annotation( AnnotationKind::Primary .span(105..131) @@ -2701,7 +2646,6 @@ fn main() { Group::with_title(Level::ERROR.title("mismatched types").id("E0308")).element( Snippet::source(source) .path("$DIR/short-error-format.rs") - .fold(true) .annotation( AnnotationKind::Primary .span(80..100) @@ -2716,7 +2660,6 @@ fn main() { Group::with_title(Level::NOTE.title("function defined here")).element( Snippet::source(source) .path("$DIR/short-error-format.rs") - .fold(true) .annotation(AnnotationKind::Context.span(48..54).label("")) .annotation(AnnotationKind::Primary.span(44..47)), ), @@ -2754,7 +2697,6 @@ fn main() { .element( Snippet::source(source) .path("$DIR/short-error-format.rs") - .fold(true) .annotation( AnnotationKind::Primary .span(127..132) @@ -2789,7 +2731,6 @@ pub struct Foo; //~^ ERROR .element( Snippet::source(source_0) .path("$DIR/diagnostic-width.rs") - .fold(true) .annotation(AnnotationKind::Primary.span(111..126)), ) .element( @@ -2798,14 +2739,12 @@ pub struct Foo; //~^ ERROR Group::with_title(Level::NOTE.title("the lint level is defined here")).element( Snippet::source(source_0) .path("$DIR/diagnostic-width.rs") - .fold(true) .annotation(AnnotationKind::Primary.span(49..67)), ), Group::with_title(Level::HELP.title("use an automatic link instead")).element( Snippet::source(source_1) .path("$DIR/diagnostic-width.rs") .line_start(4) - .fold(true) .patch(Patch::new(40..40, "<")) .patch(Patch::new(55..55, ">")), ), @@ -2854,7 +2793,6 @@ fn main() { .element( Snippet::source(source1) .path("lint_example.rs") - .fold(true) .annotation(AnnotationKind::Primary.span(40..49)), ) .element(Level::WARNING.title("this changes meaning in Rust 2021")) @@ -2867,14 +2805,12 @@ fn main() { Snippet::source(source2) .path("lint_example.rs") .line_start(3) - .fold(true) .patch(Patch::new(10..19, "iter")), ), Group::with_title(Level::HELP.title(long_title3)).element( Snippet::source(source2) .path("lint_example.rs") .line_start(3) - .fold(true) .patch(Patch::new(0..0, "IntoIterator::into_iter(")) .patch(Patch::new(9..21, ")")), ), From 5cf3738145e4a87c7da4a5d475546164e04ccae4 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 2 Jul 2025 09:10:15 -0500 Subject: [PATCH 411/455] docs: Cover Renderer::render --- src/renderer/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 65035c35..608613ba 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -225,6 +225,7 @@ impl Renderer { } impl Renderer { + /// Render a diagnostic, a series of [`Group`]s pub fn render(&self, groups: &[Group<'_>]) -> String { if self.short_message { self.render_short_message(groups).unwrap() From 981ef5b9f8031c15fb18b264090ec46d2a155f6e Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 2 Jul 2025 09:29:50 -0500 Subject: [PATCH 412/455] docs: Describe groups --- src/snippet.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/snippet.rs b/src/snippet.rs index 1b317d72..af06326b 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -18,6 +18,10 @@ pub(crate) struct Id<'a> { } /// An [`Element`] container +/// +/// A [diagnostic][crate::Renderer::render] is made of several `Group`s. +/// `Group`s are used to [annotate][AnnotationKind::Primary] [`Snippet`]s +/// with different [semantic reasons][Title]. #[derive(Clone, Debug)] pub struct Group<'a> { pub(crate) primary_level: Level<'a>, From 504b54a79a40f64c441b14f6230605d3102232fe Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 2 Jul 2025 09:57:45 -0500 Subject: [PATCH 413/455] docs: Describe Level::text --- src/level.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/level.rs b/src/level.rs index 5934a280..f608a8c9 100644 --- a/src/level.rs +++ b/src/level.rs @@ -50,6 +50,8 @@ impl<'a> Level<'a> { pub const NOTE: Level<'a> = NOTE; pub const HELP: Level<'a> = HELP; + /// Replace the text describing this [`Level`] + /// /// <div class="warning"> /// /// Text passed to this function is considered "untrusted input", as such From ac6423c2b488cebf15286c9932d5f0cdb470adc2 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 2 Jul 2025 10:01:12 -0500 Subject: [PATCH 414/455] fix: Rename `Label::ERROR.text().title()` with `Label::ERROR.with_name().title()` --- examples/custom_error.rs | 2 +- examples/custom_level.rs | 2 +- src/level.rs | 6 +++--- tests/formatter.rs | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/custom_error.rs b/examples/custom_error.rs index 76ae0cce..dde6e3f3 100644 --- a/examples/custom_error.rs +++ b/examples/custom_error.rs @@ -17,7 +17,7 @@ pub static C: u32 = 0 - 1; "#; let message = &[Group::with_title( Level::ERROR - .text(Some("error: internal compiler error")) + .with_name(Some("error: internal compiler error")) .title("could not evaluate static initializer") .id("E0080"), ) diff --git a/examples/custom_level.rs b/examples/custom_level.rs index 41e8322f..97ec9ab3 100644 --- a/examples/custom_level.rs +++ b/examples/custom_level.rs @@ -52,7 +52,7 @@ fn main() { ), Group::with_title( Level::HELP - .text(Some("suggestion")) + .with_name(Some("suggestion")) .title("use `break` on its own without a value inside this `while` loop"), ) .element( diff --git a/src/level.rs b/src/level.rs index f608a8c9..a068e0f9 100644 --- a/src/level.rs +++ b/src/level.rs @@ -50,7 +50,7 @@ impl<'a> Level<'a> { pub const NOTE: Level<'a> = NOTE; pub const HELP: Level<'a> = HELP; - /// Replace the text describing this [`Level`] + /// Replace the name describing this [`Level`] /// /// <div class="warning"> /// @@ -59,9 +59,9 @@ impl<'a> Level<'a> { /// not allowed to be passed to this function. /// /// </div> - pub fn text(self, text: impl Into<OptionCow<'a>>) -> Level<'a> { + pub fn with_name(self, name: impl Into<OptionCow<'a>>) -> Level<'a> { Level { - name: Some(text.into().0), + name: Some(name.into().0), level: self.level, } } diff --git a/tests/formatter.rs b/tests/formatter.rs index 2c109577..7968a9e8 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -2397,7 +2397,7 @@ fn secondary_title_no_level_text() { ) .element( Level::NOTE - .text(None::<&str>) + .with_name(None::<&str>) .title("expected reference `&str`\nfound reference `&'static [u8; 0]`"), ), ]; @@ -2442,7 +2442,7 @@ fn secondary_title_custom_level_text() { ) .element( Level::NOTE - .text(Some("custom")) + .with_name(Some("custom")) .title("expected reference `&str`\nfound reference `&'static [u8; 0]`"), ), ]; @@ -2515,7 +2515,7 @@ fn main() { ), Group::with_title( Level::HELP - .text(Some("suggestion")) + .with_name(Some("suggestion")) .title("use `break` on its own without a value inside this `while` loop") .id("S0123"), ) From 141206a6ea01c66b1c74f1a99db468c278e62e3f Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 2 Jul 2025 10:10:54 -0500 Subject: [PATCH 415/455] feat: Add Level::no_name --- src/level.rs | 5 +++++ tests/formatter.rs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/level.rs b/src/level.rs index a068e0f9..036cf7db 100644 --- a/src/level.rs +++ b/src/level.rs @@ -65,6 +65,11 @@ impl<'a> Level<'a> { level: self.level, } } + + /// Do not show the [`Level`]s name + pub fn no_name(self) -> Level<'a> { + self.with_name(None::<&str>) + } } impl<'a> Level<'a> { diff --git a/tests/formatter.rs b/tests/formatter.rs index 7968a9e8..6ebfd948 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -2397,7 +2397,7 @@ fn secondary_title_no_level_text() { ) .element( Level::NOTE - .with_name(None::<&str>) + .no_name() .title("expected reference `&str`\nfound reference `&'static [u8; 0]`"), ), ]; From 8cd2f36c779657ba1ed4fdad5ae59d3a63a0ad7f Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 2 Jul 2025 10:31:24 -0500 Subject: [PATCH 416/455] refactor: Generalize Title::title to Title::text --- src/level.rs | 8 ++++---- src/renderer/mod.rs | 4 ++-- src/snippet.rs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/level.rs b/src/level.rs index 036cf7db..4fec9959 100644 --- a/src/level.rs +++ b/src/level.rs @@ -80,11 +80,11 @@ impl<'a> Level<'a> { /// not allowed to be passed to this function. /// /// </div> - pub fn title(self, title: impl Into<Cow<'a, str>>) -> Title<'a> { + pub fn title(self, text: impl Into<Cow<'a, str>>) -> Title<'a> { Title { level: self, id: None, - title: title.into(), + text: text.into(), is_pre_styled: false, } } @@ -97,11 +97,11 @@ impl<'a> Level<'a> { /// used to normalize untrusted text before it is passed to this function. /// /// </div> - pub fn pre_styled_title(self, title: impl Into<Cow<'a, str>>) -> Title<'a> { + pub fn pre_styled_title(self, text: impl Into<Cow<'a, str>>) -> Title<'a> { Title { level: self, id: None, - title: title.into(), + text: text.into(), is_pre_styled: true, } } diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 608613ba..0970c749 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -585,9 +585,9 @@ impl Renderer { }); let (title_str, style) = if title.is_pre_styled { - (title.title.to_string(), ElementStyle::NoStyle) + (title.text.to_string(), ElementStyle::NoStyle) } else { - (normalize_whitespace(&title.title), title_element_style) + (normalize_whitespace(&title.text), title_element_style) }; for (i, text) in title_str.lines().enumerate() { if i != 0 { diff --git a/src/snippet.rs b/src/snippet.rs index af06326b..4ea0ed79 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -110,7 +110,7 @@ pub struct Padding; pub struct Title<'a> { pub(crate) level: Level<'a>, pub(crate) id: Option<Id<'a>>, - pub(crate) title: Cow<'a, str>, + pub(crate) text: Cow<'a, str>, pub(crate) is_pre_styled: bool, } From 6cefcbb243ff9d7e3e58f8a3faa77642cb38c54d Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 2 Jul 2025 10:36:20 -0500 Subject: [PATCH 417/455] docs(examples): Clarify we are highlighting a message, not a title --- examples/highlight_title.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/highlight_title.rs b/examples/highlight_title.rs index 1de99cf1..53575584 100644 --- a/examples/highlight_title.rs +++ b/examples/highlight_title.rs @@ -28,7 +28,7 @@ fn main() { let magenta = annotate_snippets::renderer::AnsiColor::Magenta .on_default() .effects(Effects::BOLD); - let title = format!( + let message = format!( "expected fn pointer `{}for<'a>{} fn(Box<{}(dyn Any + Send + 'a){}>) -> Pin<_>` found fn item `fn(Box<{}(dyn Any + Send + 'static){}>) -> Pin<_> {}{{wrapped_fn}}{}`", magenta.render(), @@ -57,7 +57,7 @@ fn main() { .label("arguments to this function are incorrect"), ), ) - .element(Level::NOTE.pre_styled_title(&title)), + .element(Level::NOTE.pre_styled_title(&message)), Group::with_title(Level::NOTE.title("function defined here")).element( Snippet::source(source) .path("$DIR/highlighting.rs") From 3a598feee1376ccacc1d76d7388c0426249f2f9e Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 2 Jul 2025 10:37:01 -0500 Subject: [PATCH 418/455] docs(examples): Clarify we are highlighting a message, not a title --- examples/{highlight_title.rs => highlight_message.rs} | 0 examples/{highlight_title.svg => highlight_message.svg} | 0 tests/examples.rs | 6 +++--- 3 files changed, 3 insertions(+), 3 deletions(-) rename examples/{highlight_title.rs => highlight_message.rs} (100%) rename examples/{highlight_title.svg => highlight_message.svg} (100%) diff --git a/examples/highlight_title.rs b/examples/highlight_message.rs similarity index 100% rename from examples/highlight_title.rs rename to examples/highlight_message.rs diff --git a/examples/highlight_title.svg b/examples/highlight_message.svg similarity index 100% rename from examples/highlight_title.svg rename to examples/highlight_message.svg diff --git a/tests/examples.rs b/tests/examples.rs index db00bc1f..226c31fd 100644 --- a/tests/examples.rs +++ b/tests/examples.rs @@ -50,9 +50,9 @@ fn highlight_source() { } #[test] -fn highlight_title() { - let target = "highlight_title"; - let expected = snapbox::file!["../examples/highlight_title.svg": TermSvg]; +fn highlight_message() { + let target = "highlight_message"; + let expected = snapbox::file!["../examples/highlight_message.svg": TermSvg]; assert_example(target, expected); } From ea73333d9d33b5f1218e1a0cded09d9e0cde9965 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 2 Jul 2025 10:38:16 -0500 Subject: [PATCH 419/455] fix: Rename Level::pre_styled_title to Level::message --- examples/highlight_message.rs | 2 +- src/level.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/highlight_message.rs b/examples/highlight_message.rs index 53575584..aaf49105 100644 --- a/examples/highlight_message.rs +++ b/examples/highlight_message.rs @@ -57,7 +57,7 @@ fn main() { .label("arguments to this function are incorrect"), ), ) - .element(Level::NOTE.pre_styled_title(&message)), + .element(Level::NOTE.message(&message)), Group::with_title(Level::NOTE.title("function defined here")).element( Snippet::source(source) .path("$DIR/highlighting.rs") diff --git a/src/level.rs b/src/level.rs index 4fec9959..972a2dd8 100644 --- a/src/level.rs +++ b/src/level.rs @@ -97,7 +97,7 @@ impl<'a> Level<'a> { /// used to normalize untrusted text before it is passed to this function. /// /// </div> - pub fn pre_styled_title(self, text: impl Into<Cow<'a, str>>) -> Title<'a> { + pub fn message(self, text: impl Into<Cow<'a, str>>) -> Title<'a> { Title { level: self, id: None, From 7ec47b1e2c0a7c0e06b7bc39c1a19cc2d706e976 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 2 Jul 2025 10:40:13 -0500 Subject: [PATCH 420/455] docs: Switch all messages to Level::message --- examples/elide_header.rs | 2 +- tests/formatter.rs | 8 +++++--- tests/rustc_tests.rs | 12 ++++++------ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/examples/elide_header.rs b/examples/elide_header.rs index 436bb25e..c7deda16 100644 --- a/examples/elide_header.rs +++ b/examples/elide_header.rs @@ -14,7 +14,7 @@ def foobar(door, bar={}): .fold(false) .annotation(AnnotationKind::Primary.span(56..58).label("B006")), ) - .element(Level::HELP.title("Replace with `None`; initialize within function"))]; + .element(Level::HELP.message("Replace with `None`; initialize within function"))]; let renderer = Renderer::styled(); anstream::println!("{}", renderer.render(message)); diff --git a/tests/formatter.rs b/tests/formatter.rs index 6ebfd948..fe16ca9c 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -199,7 +199,7 @@ error: #[test] fn test_format_footer_title() { let input = &[Group::with_title(Level::ERROR.title("")) - .element(Level::ERROR.title("This __is__ a title"))]; + .element(Level::ERROR.message("This __is__ a title"))]; let expected = str![[r#" error: | @@ -2258,7 +2258,9 @@ fn main() { .label("`+` cannot be used to concatenate two `&str` strings"), ), ) - .element(Level::NOTE.title("string concatenation requires an owned `String` on the left")), + .element( + Level::NOTE.message("string concatenation requires an owned `String` on the left"), + ), Group::with_title(Level::HELP.title("create an owned `String` from a string reference")) .element( Snippet::source(source) @@ -2333,7 +2335,7 @@ fn foo() { .annotation(AnnotationKind::Primary.span(0..0)), ) - .element(Level::NOTE.title("this error originates in the macro `include` (in Nightly builds, run with -Z macro-backtrace for more info)")), + .element(Level::NOTE.message("this error originates in the macro `include` (in Nightly builds, run with -Z macro-backtrace for more info)")), ]; let expected_ascii = str![[r#" diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index 4b1944a5..c9bba626 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -1659,8 +1659,8 @@ fn main() {} .annotation(AnnotationKind::Context.span(878..880).label("not covered")) .annotation(AnnotationKind::Context.span(890..892).label("not covered")) ) - .element(Level::NOTE.title("the matched value is of type `NonEmptyEnum5`")) - .element(Level::NOTE.title("match arms with guards don't count towards exhaustivity") + .element(Level::NOTE.message("the matched value is of type `NonEmptyEnum5`")) + .element(Level::NOTE.message("match arms with guards don't count towards exhaustivity") ), Group::with_title( Level::HELP @@ -1749,7 +1749,7 @@ fn main() { .primary(true) ) .element(Padding) - .element(Level::NOTE.title("...because it uses `Self` as a type parameter")) + .element(Level::NOTE.message("...because it uses `Self` as a type parameter")) .element( Snippet::source(source) .line_start(1) @@ -2795,9 +2795,9 @@ fn main() { .path("lint_example.rs") .annotation(AnnotationKind::Primary.span(40..49)), ) - .element(Level::WARNING.title("this changes meaning in Rust 2021")) - .element(Level::NOTE.title(long_title2)) - .element(Level::NOTE.title("`#[warn(array_into_iter)]` on by default")), + .element(Level::WARNING.message("this changes meaning in Rust 2021")) + .element(Level::NOTE.message(long_title2)) + .element(Level::NOTE.message("`#[warn(array_into_iter)]` on by default")), Group::with_title( Level::HELP.title("use `.iter()` instead of `.into_iter()` to avoid ambiguity"), ) From 169d8e231926dd7cc5d30ffc5f040d4e73fd840f Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 2 Jul 2025 10:51:28 -0500 Subject: [PATCH 421/455] docs: Clarify Level::title vs Level::message --- src/level.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/level.rs b/src/level.rs index 972a2dd8..74395237 100644 --- a/src/level.rs +++ b/src/level.rs @@ -73,6 +73,10 @@ impl<'a> Level<'a> { } impl<'a> Level<'a> { + /// A text [`Element`][crate::Element] to start a [`Group`][crate::Group] + /// + /// See [`Group::with_title`][crate::Group::with_title] + /// /// <div class="warning"> /// /// Text passed to this function is considered "untrusted input", as such @@ -89,6 +93,8 @@ impl<'a> Level<'a> { } } + /// A text [`Element`][crate::Element] in a [`Group`][crate::Group] + /// /// <div class="warning"> /// /// Text passed to this function is allowed to be pre-styled, as such all From 4373542d19cc4363f14ba40f32084c46a6961c07 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Wed, 2 Jul 2025 10:47:43 -0500 Subject: [PATCH 422/455] fix: Make Message a distinct type This makes it clearer that we shouldn't set `id` on this. If someone wants to set an `id`, they should create a new `Group` which will have a `Title`. --- src/level.rs | 9 ++--- src/renderer/mod.rs | 99 ++++++++++++++++++++++++++++++++++++++------- src/snippet.rs | 19 ++++++++- 3 files changed, 105 insertions(+), 22 deletions(-) diff --git a/src/level.rs b/src/level.rs index 74395237..8eaaa87d 100644 --- a/src/level.rs +++ b/src/level.rs @@ -2,7 +2,7 @@ use crate::renderer::stylesheet::Stylesheet; use crate::snippet::{ERROR_TXT, HELP_TXT, INFO_TXT, NOTE_TXT, WARNING_TXT}; -use crate::{OptionCow, Title}; +use crate::{Message, OptionCow, Title}; use anstyle::Style; use std::borrow::Cow; @@ -89,7 +89,6 @@ impl<'a> Level<'a> { level: self, id: None, text: text.into(), - is_pre_styled: false, } } @@ -103,12 +102,10 @@ impl<'a> Level<'a> { /// used to normalize untrusted text before it is passed to this function. /// /// </div> - pub fn message(self, text: impl Into<Cow<'a, str>>) -> Title<'a> { - Title { + pub fn message(self, text: impl Into<Cow<'a, str>>) -> Message<'a> { + Message { level: self, - id: None, text: text.into(), - is_pre_styled: true, } } diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 0970c749..b5eff063 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -48,7 +48,7 @@ use crate::renderer::source_map::{ }; use crate::renderer::styled_buffer::StyledBuffer; use crate::snippet::Id; -use crate::{Annotation, AnnotationKind, Element, Group, Origin, Patch, Snippet, Title}; +use crate::{Annotation, AnnotationKind, Element, Group, Message, Origin, Patch, Snippet, Title}; pub use anstyle::*; use margin::Margin; use std::borrow::Cow; @@ -303,7 +303,20 @@ impl Renderer { title, max_line_num_len, title_style, - matches!(peek, Some(Element::Title(_))), + matches!(peek, Some(Element::Title(_) | Element::Message(_))), + buffer_msg_line_offset, + ); + last_was_suggestion = false; + } + Element::Message(title) => { + let title_style = TitleStyle::Secondary; + let buffer_msg_line_offset = buffer.num_lines(); + self.render_title( + &mut buffer, + title, + max_line_num_len, + title_style, + matches!(peek, Some(Element::Title(_) | Element::Message(_))), buffer_msg_line_offset, ); last_was_suggestion = false; @@ -336,6 +349,16 @@ impl Renderer { ); } + Some(Element::Message(level)) + if level.level.name != Some(None) => + { + self.draw_col_separator_no_space( + &mut buffer, + current_line, + max_line_num_len + 1, + ); + } + None if group_len > 1 => self.draw_col_separator_end( &mut buffer, current_line, @@ -384,7 +407,8 @@ impl Renderer { if g == 0 && (matches!(section, Element::Origin(_)) || (matches!(section, Element::Title(_)) && i == 0) - || matches!(section, Element::Title(level) if level.level.name == Some(None))) + || matches!(section, Element::Title(level) if level.level.name == Some(None)) + || matches!(section, Element::Message(level) if level.level.name == Some(None))) { let current_line = buffer.num_lines(); if peek.is_none() && group_len > 1 { @@ -394,6 +418,13 @@ impl Renderer { max_line_num_len + 1, ); } else if matches!(peek, Some(Element::Title(level)) if level.level.name != Some(None)) + { + self.draw_col_separator_no_space( + &mut buffer, + current_line, + max_line_num_len + 1, + ); + } else if matches!(peek, Some(Element::Message(level)) if level.level.name != Some(None)) { self.draw_col_separator_no_space( &mut buffer, @@ -503,7 +534,7 @@ impl Renderer { fn render_title( &self, buffer: &mut StyledBuffer, - title: &Title<'_>, + title: &dyn MessageOrTitle, max_line_num_len: usize, title_style: TitleStyle, is_cont: bool, @@ -511,7 +542,7 @@ impl Renderer { ) { let (label_style, title_element_style) = match title_style { TitleStyle::MainHeader => ( - ElementStyle::Level(title.level.level), + ElementStyle::Level(title.level().level), if self.short_message { ElementStyle::NoStyle } else { @@ -519,7 +550,7 @@ impl Renderer { }, ), TitleStyle::Header => ( - ElementStyle::Level(title.level.level), + ElementStyle::Level(title.level().level), ElementStyle::HeaderMsg, ), TitleStyle::Secondary => { @@ -538,10 +569,10 @@ impl Renderer { }; let mut label_width = 0; - if title.level.name != Some(None) { - buffer.append(buffer_msg_line_offset, title.level.as_str(), label_style); - label_width += title.level.as_str().len(); - if let Some(Id { id: Some(id), url }) = &title.id { + if title.level().name != Some(None) { + buffer.append(buffer_msg_line_offset, title.level().as_str(), label_style); + label_width += title.level().as_str().len(); + if let Some(Id { id: Some(id), url }) = &title.id() { buffer.append(buffer_msg_line_offset, "[", label_style); if let Some(url) = url.as_ref() { buffer.append( @@ -584,10 +615,10 @@ impl Renderer { label_width }); - let (title_str, style) = if title.is_pre_styled { - (title.text.to_string(), ElementStyle::NoStyle) + let (title_str, style) = if title.is_pre_styled() { + (title.text().to_owned(), ElementStyle::NoStyle) } else { - (normalize_whitespace(&title.text), title_element_style) + (normalize_whitespace(title.text()), title_element_style) }; for (i, text) in title_str.lines().enumerate() { if i != 0 { @@ -2532,6 +2563,43 @@ impl Renderer { } } +trait MessageOrTitle { + fn level(&self) -> &Level<'_>; + fn id(&self) -> Option<&Id<'_>>; + fn text(&self) -> &str; + fn is_pre_styled(&self) -> bool; +} + +impl MessageOrTitle for Title<'_> { + fn level(&self) -> &Level<'_> { + &self.level + } + fn id(&self) -> Option<&Id<'_>> { + self.id.as_ref() + } + fn text(&self) -> &str { + self.text.as_ref() + } + fn is_pre_styled(&self) -> bool { + false + } +} + +impl MessageOrTitle for Message<'_> { + fn level(&self) -> &Level<'_> { + &self.level + } + fn id(&self) -> Option<&Id<'_>> { + None + } + fn text(&self) -> &str { + self.text.as_ref() + } + fn is_pre_styled(&self) -> bool { + true + } +} + // instead of taking the String length or dividing by 10 while > 0, we multiply a limit by 10 until // we're higher. If the loop isn't exited by the `return`, the last multiplication will wrap, which // is OK, because while we cannot fit a higher power of 10 in a usize, the loop will end anyway. @@ -2846,7 +2914,10 @@ fn max_line_number(groups: &[Group<'_>]) -> usize { v.elements .iter() .map(|s| match s { - Element::Title(_) | Element::Origin(_) | Element::Padding(_) => 0, + Element::Title(_) + | Element::Message(_) + | Element::Origin(_) + | Element::Padding(_) => 0, Element::Cause(cause) => { let end = cause .markers diff --git a/src/snippet.rs b/src/snippet.rs index 4ea0ed79..ef92ff4f 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -63,6 +63,7 @@ impl<'a> Group<'a> { #[non_exhaustive] pub enum Element<'a> { Title(Title<'a>), + Message(Message<'a>), Cause(Snippet<'a, Annotation<'a>>), Suggestion(Snippet<'a, Patch<'a>>), Origin(Origin<'a>), @@ -75,6 +76,12 @@ impl<'a> From<Title<'a>> for Element<'a> { } } +impl<'a> From<Message<'a>> for Element<'a> { + fn from(value: Message<'a>) -> Self { + Element::Message(value) + } +} + impl<'a> From<Snippet<'a, Annotation<'a>>> for Element<'a> { fn from(value: Snippet<'a, Annotation<'a>>) -> Self { Element::Cause(value) @@ -103,7 +110,7 @@ impl From<Padding> for Element<'_> { #[derive(Clone, Debug)] pub struct Padding; -/// A text [`Element`] in a [`Group`] +/// A text [`Element`] to start a [`Group`] /// /// See [`Level::title`] to create this. #[derive(Clone, Debug)] @@ -111,7 +118,6 @@ pub struct Title<'a> { pub(crate) level: Level<'a>, pub(crate) id: Option<Id<'a>>, pub(crate) text: Cow<'a, str>, - pub(crate) is_pre_styled: bool, } impl<'a> Title<'a> { @@ -144,6 +150,15 @@ impl<'a> Title<'a> { } } +/// A text [`Element`] in a [`Group`] +/// +/// See [`Level::message`] to create this. +#[derive(Clone, Debug)] +pub struct Message<'a> { + pub(crate) level: Level<'a>, + pub(crate) text: Cow<'a, str>, +} + /// A source view [`Element`] in a [`Group`] /// /// If you do not have [source][Snippet::source] available, see instead [`Origin`] From ee4ba27532d70643d9ef597fc695443a4ffbd323 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 3 Jul 2025 09:48:03 -0500 Subject: [PATCH 423/455] docs: Add message to Level's docs --- src/level.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/level.rs b/src/level.rs index 8eaaa87d..b718d72b 100644 --- a/src/level.rs +++ b/src/level.rs @@ -36,7 +36,7 @@ pub const HELP: Level<'_> = Level { level: LevelInner::Help, }; -/// [`Title`] severity level +/// Severity level for [`Title`]s and [`Message`]s #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct Level<'a> { pub(crate) name: Option<Option<Cow<'a, str>>>, From e4efef941776256682a1b75c7896362d1295a56b Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 3 Jul 2025 09:48:11 -0500 Subject: [PATCH 424/455] docs: Make Origin's summary consistent with the rest --- src/snippet.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/snippet.rs b/src/snippet.rs index ef92ff4f..df0cceec 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -386,7 +386,7 @@ impl<'a> Patch<'a> { } } -/// The referenced location (e.g. a path) +/// A source location [`Element`] in a [`Group`] /// /// If you have source available, see instead [`Snippet`] #[derive(Clone, Debug)] From 8375e0c310ef8d14178f2a994cbaa2f26544186e Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 3 Jul 2025 09:53:26 -0500 Subject: [PATCH 425/455] docs: Use inline format args --- examples/highlight_message.rs | 12 ++---------- src/renderer/styled_buffer.rs | 6 +++--- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/examples/highlight_message.rs b/examples/highlight_message.rs index aaf49105..ddcfb018 100644 --- a/examples/highlight_message.rs +++ b/examples/highlight_message.rs @@ -29,16 +29,8 @@ fn main() { .on_default() .effects(Effects::BOLD); let message = format!( - "expected fn pointer `{}for<'a>{} fn(Box<{}(dyn Any + Send + 'a){}>) -> Pin<_>` - found fn item `fn(Box<{}(dyn Any + Send + 'static){}>) -> Pin<_> {}{{wrapped_fn}}{}`", - magenta.render(), - magenta.render_reset(), - magenta.render(), - magenta.render_reset(), - magenta.render(), - magenta.render_reset(), - magenta.render(), - magenta.render_reset() + "expected fn pointer `{magenta}for<'a>{magenta:#} fn(Box<{magenta}(dyn Any + Send + 'a){magenta:#}>) -> Pin<_>` + found fn item `fn(Box<{magenta}(dyn Any + Send + 'static){magenta:#}>) -> Pin<_> {magenta}{{wrapped_fn}}{magenta:#}`", ); let message = &[ diff --git a/src/renderer/styled_buffer.rs b/src/renderer/styled_buffer.rs index de3d0815..b64aef9c 100644 --- a/src/renderer/styled_buffer.rs +++ b/src/renderer/styled_buffer.rs @@ -51,14 +51,14 @@ impl StyledBuffer { let ch_style = style.color_spec(level, stylesheet); if ch_style != current_style { if !line.is_empty() { - write!(str, "{}", current_style.render_reset())?; + write!(str, "{current_style:#}")?; } current_style = ch_style; - write!(str, "{}", current_style.render())?; + write!(str, "{current_style}")?; } write!(str, "{ch}")?; } - write!(str, "{}", current_style.render_reset())?; + write!(str, "{current_style:#}")?; if i != self.lines.len() - 1 { writeln!(str)?; } From da5100c70ea385c1341c04608a09915f5509bcb2 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 3 Jul 2025 09:55:33 -0500 Subject: [PATCH 426/455] docs: Make styling standout more --- examples/highlight_message.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/highlight_message.rs b/examples/highlight_message.rs index ddcfb018..4ebe5f50 100644 --- a/examples/highlight_message.rs +++ b/examples/highlight_message.rs @@ -1,5 +1,7 @@ use annotate_snippets::{AnnotationKind, Group, Level, Renderer, Snippet}; +use anstyle::AnsiColor; use anstyle::Effects; +use anstyle::Style; fn main() { let source = r#"// Make sure "highlighted" code is colored purple @@ -25,12 +27,10 @@ fn main() { query(wrapped_fn); }"#; - let magenta = annotate_snippets::renderer::AnsiColor::Magenta - .on_default() - .effects(Effects::BOLD); + const MAGENTA: Style = AnsiColor::Magenta.on_default().effects(Effects::BOLD); let message = format!( - "expected fn pointer `{magenta}for<'a>{magenta:#} fn(Box<{magenta}(dyn Any + Send + 'a){magenta:#}>) -> Pin<_>` - found fn item `fn(Box<{magenta}(dyn Any + Send + 'static){magenta:#}>) -> Pin<_> {magenta}{{wrapped_fn}}{magenta:#}`", + "expected fn pointer `{MAGENTA}for<'a>{MAGENTA:#} fn(Box<{MAGENTA}(dyn Any + Send + 'a){MAGENTA:#}>) -> Pin<_>` + found fn item `fn(Box<{MAGENTA}(dyn Any + Send + 'static){MAGENTA:#}>) -> Pin<_> {MAGENTA}{{wrapped_fn}}{MAGENTA:#}`", ); let message = &[ From 88072d6b081bd01596084bed04f9bf0de17861c0 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 3 Jul 2025 09:51:31 -0500 Subject: [PATCH 427/455] docs: Provide an example of multiple groups --- src/snippet.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/snippet.rs b/src/snippet.rs index df0cceec..984d9c98 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -22,6 +22,13 @@ pub(crate) struct Id<'a> { /// A [diagnostic][crate::Renderer::render] is made of several `Group`s. /// `Group`s are used to [annotate][AnnotationKind::Primary] [`Snippet`]s /// with different [semantic reasons][Title]. +/// +/// # Example +/// +/// ```rust +#[doc = include_str!("../examples/highlight_message.rs")] +/// ``` +#[doc = include_str!("../examples/highlight_message.svg")] #[derive(Clone, Debug)] pub struct Group<'a> { pub(crate) primary_level: Level<'a>, From 453a88f3b176997f86689def1acd844fd60895f9 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 3 Jul 2025 09:58:13 -0500 Subject: [PATCH 428/455] docs: Provide example for Group::with_level --- src/snippet.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/snippet.rs b/src/snippet.rs index 984d9c98..8fa76566 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -43,6 +43,13 @@ impl<'a> Group<'a> { } /// Create a title-less group with a primary [`Level`] for [`Annotation`]s + /// + /// # Example + /// + /// ```rust + #[doc = include_str!("../examples/elide_header.rs")] + /// ``` + #[doc = include_str!("../examples/elide_header.svg")] pub fn with_level(level: Level<'a>) -> Self { Self { primary_level: level, From 37c641c7507a32bcc731e55ab23d54d6cd985601 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 3 Jul 2025 10:10:55 -0500 Subject: [PATCH 429/455] test: Switch child elements from Title to Message These were missed --- tests/formatter.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/formatter.rs b/tests/formatter.rs index fe16ca9c..b8405b8d 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -2400,7 +2400,7 @@ fn secondary_title_no_level_text() { .element( Level::NOTE .no_name() - .title("expected reference `&str`\nfound reference `&'static [u8; 0]`"), + .message("expected reference `&str`\nfound reference `&'static [u8; 0]`"), ), ]; @@ -2445,7 +2445,7 @@ fn secondary_title_custom_level_text() { .element( Level::NOTE .with_name(Some("custom")) - .title("expected reference `&str`\nfound reference `&'static [u8; 0]`"), + .message("expected reference `&str`\nfound reference `&'static [u8; 0]`"), ), ]; From 7059fd6aed2a86b0b5277cd26dfe2b8c2e34e517 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 3 Jul 2025 10:03:35 -0500 Subject: [PATCH 430/455] docs: Provide example for Level::with_name --- src/level.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/level.rs b/src/level.rs index b718d72b..a0c21b04 100644 --- a/src/level.rs +++ b/src/level.rs @@ -59,6 +59,13 @@ impl<'a> Level<'a> { /// not allowed to be passed to this function. /// /// </div> + /// + /// # Example + /// + /// ```rust + #[doc = include_str!("../examples/custom_level.rs")] + /// ``` + #[doc = include_str!("../examples/custom_level.svg")] pub fn with_name(self, name: impl Into<OptionCow<'a>>) -> Level<'a> { Level { name: Some(name.into().0), From c8ac04b1549db352a2857860085c1f4aec1530f8 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 3 Jul 2025 10:03:42 -0500 Subject: [PATCH 431/455] docs: Provide example for Level::no_name --- src/level.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/level.rs b/src/level.rs index a0c21b04..150c6a88 100644 --- a/src/level.rs +++ b/src/level.rs @@ -74,6 +74,38 @@ impl<'a> Level<'a> { } /// Do not show the [`Level`]s name + /// + /// # Example + /// + /// ```rust + /// # use annotate_snippets::{Group, Snippet, AnnotationKind, Level}; + ///let source = r#"fn main() { + /// let b: &[u8] = include_str!("file.txt"); //~ ERROR mismatched types + /// let s: &str = include_bytes!("file.txt"); //~ ERROR mismatched types + /// }"#; + /// let input = &[ + /// Group::with_title(Level::ERROR.title("mismatched types").id("E0308")) + /// .element( + /// Snippet::source(source) + /// .path("$DIR/mismatched-types.rs") + /// .annotation( + /// AnnotationKind::Primary + /// .span(105..131) + /// .label("expected `&str`, found `&[u8; 0]`"), + /// ) + /// .annotation( + /// AnnotationKind::Context + /// .span(98..102) + /// .label("expected due to this"), + /// ), + /// ) + /// .element( + /// Level::NOTE + /// .no_name() + /// .message("expected reference `&str`\nfound reference `&'static [u8; 0]`"), + /// ), + /// ]; + /// ``` pub fn no_name(self) -> Level<'a> { self.with_name(None::<&str>) } From 683bea74cbcddb70a1e0b681a111b1029a137d51 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 3 Jul 2025 10:08:32 -0500 Subject: [PATCH 432/455] docs: Order Level by likelihood of use --- src/level.rs | 112 ++++++++++++++++++++++++++------------------------- 1 file changed, 58 insertions(+), 54 deletions(-) diff --git a/src/level.rs b/src/level.rs index 150c6a88..b1b7e4ea 100644 --- a/src/level.rs +++ b/src/level.rs @@ -43,13 +43,71 @@ pub struct Level<'a> { pub(crate) level: LevelInner, } +/// # Constructors impl<'a> Level<'a> { pub const ERROR: Level<'a> = ERROR; pub const WARNING: Level<'a> = WARNING; pub const INFO: Level<'a> = INFO; pub const NOTE: Level<'a> = NOTE; pub const HELP: Level<'a> = HELP; +} + +impl<'a> Level<'a> { + /// A text [`Element`][crate::Element] to start a [`Group`][crate::Group] + /// + /// See [`Group::with_title`][crate::Group::with_title] + /// + /// <div class="warning"> + /// + /// Text passed to this function is considered "untrusted input", as such + /// all text is passed through a normalization function. Pre-styled text is + /// not allowed to be passed to this function. + /// + /// </div> + pub fn title(self, text: impl Into<Cow<'a, str>>) -> Title<'a> { + Title { + level: self, + id: None, + text: text.into(), + } + } + + /// A text [`Element`][crate::Element] in a [`Group`][crate::Group] + /// + /// <div class="warning"> + /// + /// Text passed to this function is allowed to be pre-styled, as such all + /// text is considered "trusted input" and has no normalizations applied to + /// it. [`normalize_untrusted_str`](crate::normalize_untrusted_str) can be + /// used to normalize untrusted text before it is passed to this function. + /// + /// </div> + pub fn message(self, text: impl Into<Cow<'a, str>>) -> Message<'a> { + Message { + level: self, + text: text.into(), + } + } + + pub(crate) fn as_str(&'a self) -> &'a str { + match (&self.name, self.level) { + (Some(Some(name)), _) => name.as_ref(), + (Some(None), _) => "", + (None, LevelInner::Error) => ERROR_TXT, + (None, LevelInner::Warning) => WARNING_TXT, + (None, LevelInner::Info) => INFO_TXT, + (None, LevelInner::Note) => NOTE_TXT, + (None, LevelInner::Help) => HELP_TXT, + } + } + + pub(crate) fn style(&self, stylesheet: &Stylesheet) -> Style { + self.level.style(stylesheet) + } +} +/// # Customize the `Level` +impl<'a> Level<'a> { /// Replace the name describing this [`Level`] /// /// <div class="warning"> @@ -111,60 +169,6 @@ impl<'a> Level<'a> { } } -impl<'a> Level<'a> { - /// A text [`Element`][crate::Element] to start a [`Group`][crate::Group] - /// - /// See [`Group::with_title`][crate::Group::with_title] - /// - /// <div class="warning"> - /// - /// Text passed to this function is considered "untrusted input", as such - /// all text is passed through a normalization function. Pre-styled text is - /// not allowed to be passed to this function. - /// - /// </div> - pub fn title(self, text: impl Into<Cow<'a, str>>) -> Title<'a> { - Title { - level: self, - id: None, - text: text.into(), - } - } - - /// A text [`Element`][crate::Element] in a [`Group`][crate::Group] - /// - /// <div class="warning"> - /// - /// Text passed to this function is allowed to be pre-styled, as such all - /// text is considered "trusted input" and has no normalizations applied to - /// it. [`normalize_untrusted_str`](crate::normalize_untrusted_str) can be - /// used to normalize untrusted text before it is passed to this function. - /// - /// </div> - pub fn message(self, text: impl Into<Cow<'a, str>>) -> Message<'a> { - Message { - level: self, - text: text.into(), - } - } - - pub(crate) fn as_str(&'a self) -> &'a str { - match (&self.name, self.level) { - (Some(Some(name)), _) => name.as_ref(), - (Some(None), _) => "", - (None, LevelInner::Error) => ERROR_TXT, - (None, LevelInner::Warning) => WARNING_TXT, - (None, LevelInner::Info) => INFO_TXT, - (None, LevelInner::Note) => NOTE_TXT, - (None, LevelInner::Help) => HELP_TXT, - } - } - - pub(crate) fn style(&self, stylesheet: &Stylesheet) -> Style { - self.level.style(stylesheet) - } -} - #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] pub(crate) enum LevelInner { Error, From eb0d16da8bf43d21d28dc45953b17360a6806672 Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 3 Jul 2025 10:08:57 -0500 Subject: [PATCH 433/455] docs: Provide an example for Level::title --- src/level.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/level.rs b/src/level.rs index b1b7e4ea..4685fb01 100644 --- a/src/level.rs +++ b/src/level.rs @@ -64,6 +64,15 @@ impl<'a> Level<'a> { /// not allowed to be passed to this function. /// /// </div> + /// + /// # Example + /// + /// ```rust + /// # use annotate_snippets::{Group, Snippet, AnnotationKind, Level}; + /// let input = &[ + /// Group::with_title(Level::ERROR.title("mismatched types").id("E0308")) + /// ]; + /// ``` pub fn title(self, text: impl Into<Cow<'a, str>>) -> Title<'a> { Title { level: self, From c74a346907d659121a2c59055b19c8674ddd7cab Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 3 Jul 2025 10:08:51 -0500 Subject: [PATCH 434/455] docs: Provide an example for Level::message --- src/level.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/level.rs b/src/level.rs index 4685fb01..6596bf8e 100644 --- a/src/level.rs +++ b/src/level.rs @@ -91,6 +91,20 @@ impl<'a> Level<'a> { /// used to normalize untrusted text before it is passed to this function. /// /// </div> + /// + /// # Example + /// + /// ```rust + /// # use annotate_snippets::{Group, Snippet, AnnotationKind, Level}; + /// let input = &[ + /// Group::with_title(Level::ERROR.title("mismatched types").id("E0308")) + /// .element( + /// Level::NOTE + /// .no_name() + /// .message("expected reference `&str`\nfound reference `&'static [u8; 0]`"), + /// ), + /// ]; + /// ``` pub fn message(self, text: impl Into<Cow<'a, str>>) -> Message<'a> { Message { level: self, From 6129319507d78e1118894e90ce6c6fbe3935999d Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 3 Jul 2025 10:14:25 -0500 Subject: [PATCH 435/455] docs: Provide an example for Origin --- src/snippet.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/snippet.rs b/src/snippet.rs index 8fa76566..390f3385 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -403,6 +403,18 @@ impl<'a> Patch<'a> { /// A source location [`Element`] in a [`Group`] /// /// If you have source available, see instead [`Snippet`] +/// +/// # Example +/// +/// ```rust +/// # use annotate_snippets::{Group, Snippet, AnnotationKind, Level, Origin}; +/// let input = &[ +/// Group::with_title(Level::ERROR.title("mismatched types").id("E0308")) +/// .element( +/// Origin::new("$DIR/mismatched-types.rs") +/// ) +/// ]; +/// ``` #[derive(Clone, Debug)] pub struct Origin<'a> { pub(crate) path: Cow<'a, str>, From e2b0339fdb978b469a18cbce4939c7c8e03275ad Mon Sep 17 00:00:00 2001 From: Ed Page <eopage@gmail.com> Date: Thu, 3 Jul 2025 10:16:20 -0500 Subject: [PATCH 436/455] fix: Rename Origin::new to Origin::path This is to align with `Snippet::source` --- src/renderer/mod.rs | 4 ++-- src/snippet.rs | 6 +++--- tests/color/multiline_removal_suggestion.rs | 2 +- tests/rustc_tests.rs | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index b5eff063..759fbfb5 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -484,7 +484,7 @@ impl Renderer { } if let Some(path) = &cause.path { - let mut origin = Origin::new(path.as_ref()); + let mut origin = Origin::path(path.as_ref()); origin.primary = true; let source_map = SourceMap::new(&cause.source, cause.line_start); @@ -719,7 +719,7 @@ impl Renderer { is_cont: bool, ) { if let Some(path) = &snippet.path { - let mut origin = Origin::new(path.as_ref()); + let mut origin = Origin::path(path.as_ref()); // print out the span location and spacer before we print the annotated source // to do this, we need to know if this span will be primary let is_primary = primary_path == Some(&origin.path); diff --git a/src/snippet.rs b/src/snippet.rs index 390f3385..1d0d0e87 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -411,7 +411,7 @@ impl<'a> Patch<'a> { /// let input = &[ /// Group::with_title(Level::ERROR.title("mismatched types").id("E0308")) /// .element( -/// Origin::new("$DIR/mismatched-types.rs") +/// Origin::path("$DIR/mismatched-types.rs") /// ) /// ]; /// ``` @@ -431,7 +431,7 @@ impl<'a> Origin<'a> { /// not allowed to be passed to this function. /// /// </div> - pub fn new(path: impl Into<Cow<'a, str>>) -> Self { + pub fn path(path: impl Into<Cow<'a, str>>) -> Self { Self { path: path.into(), line: None, @@ -467,7 +467,7 @@ impl<'a> Origin<'a> { impl<'a> From<Cow<'a, str>> for Origin<'a> { fn from(origin: Cow<'a, str>) -> Self { - Self::new(origin) + Self::path(origin) } } diff --git a/tests/color/multiline_removal_suggestion.rs b/tests/color/multiline_removal_suggestion.rs index 8559ee93..2442947b 100644 --- a/tests/color/multiline_removal_suggestion.rs +++ b/tests/color/multiline_removal_suggestion.rs @@ -88,7 +88,7 @@ fn main() {} ), Group::with_title(Level::NOTE.title("required by a bound in `flatten`")) .element( - Origin::new("/rustc/FAKE_PREFIX/library/core/src/iter/traits/iterator.rs") + Origin::path("/rustc/FAKE_PREFIX/library/core/src/iter/traits/iterator.rs") .line(1556) .char_column(4), ), diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index c9bba626..112a5c79 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -1743,7 +1743,7 @@ fn main() { Level::NOTE .title("for a trait to be dyn compatible it needs to allow building a vtable\nfor more information, visit <https://doc.rust-lang.org/reference/items/traits.html#dyn-compatibility>")) .element( - Origin::new("$SRC_DIR/core/src/cmp.rs") + Origin::path("$SRC_DIR/core/src/cmp.rs") .line(334) .char_column(14) .primary(true) From cf84eb67990cf2f01cdf3160a0094ca2f4f856a6 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 3 Jul 2025 10:00:21 -0600 Subject: [PATCH 437/455] fix: Address clippy::needless_doctest_main --- src/level.rs | 1 + src/snippet.rs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/level.rs b/src/level.rs index 6596bf8e..7aafe838 100644 --- a/src/level.rs +++ b/src/level.rs @@ -144,6 +144,7 @@ impl<'a> Level<'a> { /// # Example /// /// ```rust + /// # #[allow(clippy::needless_doctest_main)] #[doc = include_str!("../examples/custom_level.rs")] /// ``` #[doc = include_str!("../examples/custom_level.svg")] diff --git a/src/snippet.rs b/src/snippet.rs index 1d0d0e87..b140a14b 100644 --- a/src/snippet.rs +++ b/src/snippet.rs @@ -26,6 +26,7 @@ pub(crate) struct Id<'a> { /// # Example /// /// ```rust +/// # #[allow(clippy::needless_doctest_main)] #[doc = include_str!("../examples/highlight_message.rs")] /// ``` #[doc = include_str!("../examples/highlight_message.svg")] @@ -47,6 +48,7 @@ impl<'a> Group<'a> { /// # Example /// /// ```rust + /// # #[allow(clippy::needless_doctest_main)] #[doc = include_str!("../examples/elide_header.rs")] /// ``` #[doc = include_str!("../examples/elide_header.svg")] From 3a46662e04e2efb1f2f9428c0ec9b559a177a048 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 2 Jul 2025 22:37:08 -0600 Subject: [PATCH 438/455] test: Add a test for max_line_num with no fold --- tests/formatter.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/formatter.rs b/tests/formatter.rs index b8405b8d..dc661e7d 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -2574,3 +2574,41 @@ LL + break; let renderer_unicode = renderer_ascii.theme(OutputTheme::Unicode); assert_data_eq!(renderer_unicode.render(input), expected_unicode); } + +#[test] +fn max_line_num_no_fold() { + let source = r#"cargo +fuzzy +pizza +jumps +crazy +quack +zappy +"#; + + let input_new = &[Group::with_title( + Level::ERROR + .title("the size for values of type `T` cannot be known at compilation time") + .id("E0277"), + ) + .element( + Snippet::source(source) + .line_start(8) + .fold(false) + .annotation(AnnotationKind::Primary.span(6..11)), + )]; + let expected = str![[r#" +error[E0277]: the size for values of type `T` cannot be known at compilation time + | +8 | cargo +9 | fuzzy + | ^^^^^ +10| pizza +11| jumps +12| crazy +13| quack +14| zappy +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input_new), expected); +} From 9a2155aad4e28a6f7e86a4aaa1f911d3c64d0629 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 2 Jul 2025 22:38:15 -0600 Subject: [PATCH 439/455] fix: If fold is false max_line_num is last source line --- src/renderer/mod.rs | 44 ++++++++++++++++++++++++++------------------ tests/formatter.rs | 18 +++++++++--------- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 759fbfb5..83e1a7da 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -2919,26 +2919,34 @@ fn max_line_number(groups: &[Group<'_>]) -> usize { | Element::Origin(_) | Element::Padding(_) => 0, Element::Cause(cause) => { - let end = cause - .markers - .iter() - .map(|a| a.span.end) - .max() - .unwrap_or(cause.source.len()) - .min(cause.source.len()); - - cause.line_start + newline_count(&cause.source[..end]) + if cause.fold { + let end = cause + .markers + .iter() + .map(|a| a.span.end) + .max() + .unwrap_or(cause.source.len()) + .min(cause.source.len()); + + cause.line_start + newline_count(&cause.source[..end]) + } else { + cause.line_start + newline_count(&cause.source) + } } Element::Suggestion(suggestion) => { - let end = suggestion - .markers - .iter() - .map(|a| a.span.end) - .max() - .unwrap_or(suggestion.source.len()) - .min(suggestion.source.len()); - - suggestion.line_start + newline_count(&suggestion.source[..end]) + if suggestion.fold { + let end = suggestion + .markers + .iter() + .map(|a| a.span.end) + .max() + .unwrap_or(suggestion.source.len()) + .min(suggestion.source.len()); + + suggestion.line_start + newline_count(&suggestion.source[..end]) + } else { + suggestion.line_start + newline_count(&suggestion.source) + } } }) .max() diff --git a/tests/formatter.rs b/tests/formatter.rs index dc661e7d..8745e566 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -2599,15 +2599,15 @@ zappy )]; let expected = str![[r#" error[E0277]: the size for values of type `T` cannot be known at compilation time - | -8 | cargo -9 | fuzzy - | ^^^^^ -10| pizza -11| jumps -12| crazy -13| quack -14| zappy + | + 8 | cargo + 9 | fuzzy + | ^^^^^ +10 | pizza +11 | jumps +12 | crazy +13 | quack +14 | zappy "#]]; let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input_new), expected); From 510a9e69d4f9a105f17b349eb3801c6c9c2a56b3 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 1 Jul 2025 18:17:23 -0600 Subject: [PATCH 440/455] test: Add Annotating an empty span at start of line --- tests/formatter.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/formatter.rs b/tests/formatter.rs index 8745e566..eca0b826 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -2612,3 +2612,26 @@ error[E0277]: the size for values of type `T` cannot be known at compilation tim let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input_new), expected); } + +#[test] +fn empty_span_start_line() { + let source = "#: E112\nif False:\nprint()\n#: E113\nprint()\n"; + let input = &[Group::with_level(Level::ERROR).element( + Snippet::source(source) + .line_start(7) + .fold(false) + .annotation(AnnotationKind::Primary.span(18..18).label("E112")), + )]; + + let expected = str![[r#" + | + 7 | #: E112 + 8 | if False: + 9 | print() + | ^ E112 +10 | #: E113 +11 | print() +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input), expected); +} From 81012284c2732799e004dd03645c7cb94de53592 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 2 Jul 2025 23:27:24 -0600 Subject: [PATCH 441/455] test: Origin tests --- tests/rustc_tests.rs | 526 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 526 insertions(+) diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index 112a5c79..afec2712 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -2840,3 +2840,529 @@ help: or use `IntoIterator::into_iter(..)` instead of `.into_iter()` to explicit let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input), expected); } + +#[test] +fn autoderef_box_no_add() { + // tests/ui/autoref-autoderef/autoderef-box-no-add.rs + + let source = r#"//! Tests that auto-dereferencing does not allow addition of `Box<isize>` values. +//! +//! This test ensures that `Box<isize>` fields in structs (`Clam` and `Fish`) are not +//! automatically dereferenced to `isize` during addition operations, as `Box<isize>` +//! does not implement the `Add` trait. + +struct Clam { + x: Box<isize>, + y: Box<isize>, +} + +struct Fish { + a: Box<isize>, +} + +fn main() { + let a: Clam = Clam { + x: Box::new(1), + y: Box::new(2), + }; + let b: Clam = Clam { + x: Box::new(10), + y: Box::new(20), + }; + let z: isize = a.x + b.y; + //~^ ERROR cannot add `Box<isize>` to `Box<isize>` + println!("{}", z); + assert_eq!(z, 21); + let forty: Fish = Fish { a: Box::new(40) }; + let two: Fish = Fish { a: Box::new(2) }; + let answer: isize = forty.a + two.a; + //~^ ERROR cannot add `Box<isize>` to `Box<isize>` + println!("{}", answer); + assert_eq!(answer, 42); +} +"#; + let input = &[ + Group::with_title( + Level::ERROR + .title("cannot add `Box<isize>` to `Box<isize>`") + .id("E0369"), + ) + .element( + Snippet::source(source) + .path("$DIR/autoderef-box-no-add.rs") + .annotation(AnnotationKind::Context.span(583..586).label("Box<isize>")) + .annotation(AnnotationKind::Context.span(589..592).label("Box<isize>")) + .annotation(AnnotationKind::Primary.span(587..588)), + ), + Group::with_title( + Level::NOTE.title("the foreign item type `Box<isize>` doesn't implement `Add`"), + ) + .element( + Origin::path("$SRC_DIR/alloc/src/boxed.rs") + .line(231) + .char_column(0) + .primary(true), + ) + .element( + Origin::path("$SRC_DIR/alloc/src/boxed.rs") + .line(234) + .char_column(1), + ) + .element(Padding) + .element(Level::NOTE.message("not implement `Add`")), + ]; + + let expected_ascii = str![[r#" +error[E0369]: cannot add `Box<isize>` to `Box<isize>` + --> $DIR/autoderef-box-no-add.rs:25:24 + | +LL | let z: isize = a.x + b.y; + | --- ^ --- Box<isize> + | | + | Box<isize> + | +note: the foreign item type `Box<isize>` doesn't implement `Add` + --> $SRC_DIR/alloc/src/boxed.rs:231:0 + ::: $SRC_DIR/alloc/src/boxed.rs:234:1 + | + = note: not implement `Add` +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected_ascii); + + let expected_unicode = str![[r#" +error[E0369]: cannot add `Box<isize>` to `Box<isize>` + ╭▸ $DIR/autoderef-box-no-add.rs:25:24 + │ +LL │ let z: isize = a.x + b.y; + │ ┬── ━ ─── Box<isize> + │ │ + │ Box<isize> + ╰╴ +note: the foreign item type `Box<isize>` doesn't implement `Add` + ╭▸ $SRC_DIR/alloc/src/boxed.rs:231:0 + ⸬ $SRC_DIR/alloc/src/boxed.rs:234:1 + │ + ╰ note: not implement `Add` +"#]]; + let renderer = renderer.theme(OutputTheme::Unicode); + assert_data_eq!(renderer.render(input), expected_unicode); +} + +#[test] +fn dont_project_to_specializable_projection() { + // tests/ui/async-await/in-trait/dont-project-to-specializable-projection.rs + + let source = r#"//@ edition: 2021 +//@ known-bug: #108309 + +#![feature(min_specialization)] + +struct MyStruct; + +trait MyTrait<T> { + async fn foo(_: T) -> &'static str; +} + +impl<T> MyTrait<T> for MyStruct { + default async fn foo(_: T) -> &'static str { + "default" + } +} + +impl MyTrait<i32> for MyStruct { + async fn foo(_: i32) -> &'static str { + "specialized" + } +} + +async fn async_main() { + assert_eq!(MyStruct::foo(42).await, "specialized"); + assert_eq!(indirection(42).await, "specialized"); +} + +async fn indirection<T>(x: T) -> &'static str { + //explicit type coercion is currently necessary + // because of https://github.com/rust-lang/rust/issues/67918 + <MyStruct as MyTrait<T>>::foo(x).await +} + +// ------------------------------------------------------------------------- // +// Implementation Details Below... + +use std::pin::{pin, Pin}; +use std::task::*; + +fn main() { + let mut fut = pin!(async_main()); + + // Poll loop, just to test the future... + let ctx = &mut Context::from_waker(Waker::noop()); + + loop { + match fut.as_mut().poll(ctx) { + Poll::Pending => {} + Poll::Ready(()) => break, + } + } +} +"#; + + let title_0 = "no method named `poll` found for struct `Pin<&mut impl Future<Output = ()>>` in the current scope"; + let title_1 = "trait `Future` which provides `poll` is implemented but not in scope; perhaps you want to import it"; + + let input = &[ + Group::with_title(Level::ERROR.title(title_0).id("E0599")) + .element( + Snippet::source(source) + .path("$DIR/dont-project-to-specializable-projection.rs") + .annotation( + AnnotationKind::Primary + .span(1071..1075) + .label("method not found in `Pin<&mut impl Future<Output = ()>>`"), + ), + ) + .element( + Origin::path("$SRC_DIR/core/src/future/future.rs") + .line(104) + .char_column(7) + .primary(true), + ) + .element(Padding) + .element( + Level::NOTE.message( + "the method is available for `Pin<&mut impl Future<Output = ()>>` here", + ), + ) + .element(Padding) + .element( + Level::HELP.message("items from traits can only be used if the trait is in scope"), + ), + Group::with_title(Level::HELP.title(title_1)).element( + Snippet::source("struct MyStruct;\n") + .path("$DIR/dont-project-to-specializable-projection.rs") + .line_start(6) + .patch(Patch::new( + 0..0, + r#"use std::future::Future; +"#, + )), + ), + ]; + let expected_ascii = str![[r#" +error[E0599]: no method named `poll` found for struct `Pin<&mut impl Future<Output = ()>>` in the current scope + --> $DIR/dont-project-to-specializable-projection.rs:48:28 + | +LL | match fut.as_mut().poll(ctx) { + | ^^^^ method not found in `Pin<&mut impl Future<Output = ()>>` + --> $SRC_DIR/core/src/future/future.rs:104:7 + | + = note: the method is available for `Pin<&mut impl Future<Output = ()>>` here + | + = help: items from traits can only be used if the trait is in scope +help: trait `Future` which provides `poll` is implemented but not in scope; perhaps you want to import it + | +LL + use std::future::Future; + | +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected_ascii); + + let expected_unicode = str![[r#" +error[E0599]: no method named `poll` found for struct `Pin<&mut impl Future<Output = ()>>` in the current scope + ╭▸ $DIR/dont-project-to-specializable-projection.rs:48:28 + │ +LL │ match fut.as_mut().poll(ctx) { + │ ━━━━ method not found in `Pin<&mut impl Future<Output = ()>>` + ╭▸ $SRC_DIR/core/src/future/future.rs:104:7 + │ + ╰ note: the method is available for `Pin<&mut impl Future<Output = ()>>` here + │ + ╰ help: items from traits can only be used if the trait is in scope +help: trait `Future` which provides `poll` is implemented but not in scope; perhaps you want to import it + ╭╴ +LL + use std::future::Future; + ╰╴ +"#]]; + let renderer = renderer.theme(OutputTheme::Unicode); + assert_data_eq!(renderer.render(input), expected_unicode); +} + +#[test] +fn binary_op_not_allowed_issue_125631() { + // tests/ui/binop/binary-op-not-allowed-issue-125631.rs + + let source = r#"use std::io::{Error, ErrorKind}; +use std::thread; + +struct T1; +struct T2; + +fn main() { + (Error::new(ErrorKind::Other, "1"), T1, 1) == (Error::new(ErrorKind::Other, "1"), T1, 2); + //~^ERROR binary operation `==` cannot be applied to type + (Error::new(ErrorKind::Other, "2"), thread::current()) + == (Error::new(ErrorKind::Other, "2"), thread::current()); + //~^ERROR binary operation `==` cannot be applied to type + (Error::new(ErrorKind::Other, "4"), thread::current(), T1, T2) + == (Error::new(ErrorKind::Other, "4"), thread::current(), T1, T2); + //~^ERROR binary operation `==` cannot be applied to type +} +"#; + let title_0 = "binary operation `==` cannot be applied to type `(std::io::Error, Thread)`"; + let title_1 = + "the foreign item types don't implement required traits for this operation to be valid"; + + let input = &[ + Group::with_title(Level::ERROR.title(title_0).id("E0369")).element( + Snippet::source(source) + .path("$DIR/binary-op-not-allowed-issue-125631.rs") + .annotation( + AnnotationKind::Context + .span(246..300) + .label("(std::io::Error, Thread)"), + ) + .annotation( + AnnotationKind::Context + .span(312..366) + .label("(std::io::Error, Thread)"), + ) + .annotation(AnnotationKind::Primary.span(309..311)), + ), + Group::with_title(Level::NOTE.title(title_1)) + .element( + Origin::path("$SRC_DIR/std/src/io/error.rs") + .line(65) + .char_column(0) + .primary(true), + ) + .element(Padding) + .element(Level::NOTE.message("not implement `PartialEq`")) + .element( + Origin::path("$SRC_DIR/std/src/thread/mod.rs") + .line(1415) + .char_column(0) + .primary(true), + ) + .element(Padding) + .element(Level::NOTE.message("not implement `PartialEq`")), + ]; + + let expected_ascii = str![[r#" +error[E0369]: binary operation `==` cannot be applied to type `(std::io::Error, Thread)` + --> $DIR/binary-op-not-allowed-issue-125631.rs:11:9 + | +LL | (Error::new(ErrorKind::Other, "2"), thread::current()) + | ------------------------------------------------------ (std::io::Error, Thread) +LL | == (Error::new(ErrorKind::Other, "2"), thread::current()); + | ^^ ------------------------------------------------------ (std::io::Error, Thread) + | +note: the foreign item types don't implement required traits for this operation to be valid + --> $SRC_DIR/std/src/io/error.rs:65:0 + | + = note: not implement `PartialEq` + --> $SRC_DIR/std/src/thread/mod.rs:1415:0 + | + = note: not implement `PartialEq` +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected_ascii); + + let expected_unicode = str![[r#" +error[E0369]: binary operation `==` cannot be applied to type `(std::io::Error, Thread)` + ╭▸ $DIR/binary-op-not-allowed-issue-125631.rs:11:9 + │ +LL │ (Error::new(ErrorKind::Other, "2"), thread::current()) + │ ────────────────────────────────────────────────────── (std::io::Error, Thread) +LL │ == (Error::new(ErrorKind::Other, "2"), thread::current()); + │ ━━ ────────────────────────────────────────────────────── (std::io::Error, Thread) + ╰╴ +note: the foreign item types don't implement required traits for this operation to be valid + ╭▸ $SRC_DIR/std/src/io/error.rs:65:0 + │ + ╰ note: not implement `PartialEq` + ╭▸ $SRC_DIR/std/src/thread/mod.rs:1415:0 + │ + ╰ note: not implement `PartialEq` +"#]]; + let renderer = renderer.theme(OutputTheme::Unicode); + assert_data_eq!(renderer.render(input), expected_unicode); +} + +#[test] +fn deriving_meta_unknown_trait() { + // tests/ui/derives/deriving-meta-unknown-trait.rs + + let source = r#"#[derive(Eqr)] +//~^ ERROR cannot find derive macro `Eqr` in this scope +//~| ERROR cannot find derive macro `Eqr` in this scope +struct Foo; + +pub fn main() {} +"#; + + let input = + &[ + Group::with_title(Level::ERROR.title("cannot find derive macro `Eqr` in this scope")) + .element( + Snippet::source(source) + .path("$DIR/deriving-meta-unknown-trait.rs") + .annotation( + AnnotationKind::Primary + .span(9..12) + .label("help: a derive macro with a similar name exists: `Eq`"), + ), + ) + .element( + Origin::path("$SRC_DIR/core/src/cmp.rs") + .line(356) + .char_column(0) + .primary(true), + ) + .element(Padding) + .element(Level::NOTE.message("similarly named derive macro `Eq` defined here")) + .element(Padding) + .element(Level::NOTE.message( + "duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`", + )), + ]; + + let expected_ascii = str![[r#" +error: cannot find derive macro `Eqr` in this scope + --> $DIR/deriving-meta-unknown-trait.rs:1:10 + | +LL | #[derive(Eqr)] + | ^^^ help: a derive macro with a similar name exists: `Eq` + --> $SRC_DIR/core/src/cmp.rs:356:0 + | + = note: similarly named derive macro `Eq` defined here + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected_ascii); + + let expected_unicode = str![[r#" +error: cannot find derive macro `Eqr` in this scope + ╭▸ $DIR/deriving-meta-unknown-trait.rs:1:10 + │ +LL │ #[derive(Eqr)] + │ ━━━ help: a derive macro with a similar name exists: `Eq` + ╭▸ $SRC_DIR/core/src/cmp.rs:356:0 + │ + ╰ note: similarly named derive macro `Eq` defined here + │ + ╰ note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` +"#]]; + let renderer = renderer.theme(OutputTheme::Unicode); + assert_data_eq!(renderer.render(input), expected_unicode); +} + +#[test] +fn not_repeatable() { + // tests/ui/proc-macro/quote/not-repeatable.rs + + let source = r#"#![feature(proc_macro_quote)] + +extern crate proc_macro; + +use proc_macro::quote; + +struct Ipv4Addr; + +fn main() { + let ip = Ipv4Addr; + let _ = quote! { $($ip)* }; //~ ERROR the method `quote_into_iter` exists for struct `Ipv4Addr`, but its trait bounds were not satisfied +} +"#; + let label_0 = "method `quote_into_iter` not found for this struct because it doesn't satisfy `Ipv4Addr: Iterator`, `Ipv4Addr: ToTokens`, `Ipv4Addr: proc_macro::ext::RepIteratorExt` or `Ipv4Addr: proc_macro::ext::RepToTokensExt`"; + let title_0 = "the method `quote_into_iter` exists for struct `Ipv4Addr`, but its trait bounds were not satisfied"; + let title_1 = r#"the following trait bounds were not satisfied: +`Ipv4Addr: Iterator` +which is required by `Ipv4Addr: proc_macro::ext::RepIteratorExt` +`&Ipv4Addr: Iterator` +which is required by `&Ipv4Addr: proc_macro::ext::RepIteratorExt` +`Ipv4Addr: ToTokens` +which is required by `Ipv4Addr: proc_macro::ext::RepToTokensExt` +`&mut Ipv4Addr: Iterator` +which is required by `&mut Ipv4Addr: proc_macro::ext::RepIteratorExt`"#; + + let input = &[ + Group::with_title(Level::ERROR.title(title_0).id("E0599")) + .element( + Snippet::source(source) + .path("$DIR/not-repeatable.rs") + .annotation(AnnotationKind::Primary.span(146..164).label( + "method cannot be called on `Ipv4Addr` due to unsatisfied trait bounds", + )) + .annotation(AnnotationKind::Context.span(81..96).label(label_0)), + ) + .element(Level::NOTE.message(title_1)), + Group::with_title( + Level::NOTE.title("the traits `Iterator` and `ToTokens` must be implemented"), + ) + .element( + Origin::path("$SRC_DIR/proc_macro/src/to_tokens.rs") + .line(11) + .char_column(0) + .primary(true), + ) + .element( + Origin::path("$SRC_DIR/core/src/iter/traits/iterator.rs") + .line(39) + .char_column(0) + .primary(true), + ), + ]; + let expected_ascii = str![[r##" +error[E0599]: the method `quote_into_iter` exists for struct `Ipv4Addr`, but its trait bounds were not satisfied + --> $DIR/not-repeatable.rs:11:13 + | +LL | struct Ipv4Addr; + | --------------- method `quote_into_iter` not found for this struct because it doesn't satisfy `Ipv4Addr: Iterator`, `Ipv4Addr: ToTokens`, `Ipv4Addr: proc_macro::ext::RepIteratorExt` or `Ipv4Addr: proc_macro::ext::RepToTokensExt` +... +LL | let _ = quote! { $($ip)* }; //~ ERROR the method `quote_into_iter` exists for struct `Ipv4Addr`, but its trait bounds were not s... + | ^^^^^^^^^^^^^^^^^^ method cannot be called on `Ipv4Addr` due to unsatisfied trait bounds + | + = note: the following trait bounds were not satisfied: + `Ipv4Addr: Iterator` + which is required by `Ipv4Addr: proc_macro::ext::RepIteratorExt` + `&Ipv4Addr: Iterator` + which is required by `&Ipv4Addr: proc_macro::ext::RepIteratorExt` + `Ipv4Addr: ToTokens` + which is required by `Ipv4Addr: proc_macro::ext::RepToTokensExt` + `&mut Ipv4Addr: Iterator` + which is required by `&mut Ipv4Addr: proc_macro::ext::RepIteratorExt` +note: the traits `Iterator` and `ToTokens` must be implemented + --> $SRC_DIR/proc_macro/src/to_tokens.rs:11:0 + --> $SRC_DIR/core/src/iter/traits/iterator.rs:39:0 +"##]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected_ascii); + + let expected_unicode = str![[r#" +error[E0599]: the method `quote_into_iter` exists for struct `Ipv4Addr`, but its trait bounds were not satisfied + ╭▸ $DIR/not-repeatable.rs:11:13 + │ +LL │ struct Ipv4Addr; + │ ─────────────── method `quote_into_iter` not found for this struct because it doesn't satisfy `Ipv4Addr: Iterator`, `Ipv4Addr: ToTokens`, `Ipv4Addr: proc_macro::ext::RepIteratorExt` or `Ipv4Addr: proc_macro::ext::RepToTokensExt` + ‡ +LL │ let _ = quote! { $($ip)* }; //~ ERROR the method `quote_into_iter` exists for struct `Ipv4Addr`, but its trait bounds were not sat… + │ ━━━━━━━━━━━━━━━━━━ method cannot be called on `Ipv4Addr` due to unsatisfied trait bounds + │ + ╰ note: the following trait bounds were not satisfied: + `Ipv4Addr: Iterator` + which is required by `Ipv4Addr: proc_macro::ext::RepIteratorExt` + `&Ipv4Addr: Iterator` + which is required by `&Ipv4Addr: proc_macro::ext::RepIteratorExt` + `Ipv4Addr: ToTokens` + which is required by `Ipv4Addr: proc_macro::ext::RepToTokensExt` + `&mut Ipv4Addr: Iterator` + which is required by `&mut Ipv4Addr: proc_macro::ext::RepIteratorExt` +note: the traits `Iterator` and `ToTokens` must be implemented + ╭▸ $SRC_DIR/proc_macro/src/to_tokens.rs:11:0 + ╭▸ $SRC_DIR/core/src/iter/traits/iterator.rs:39:0 +"#]]; + let renderer = renderer.theme(OutputTheme::Unicode); + assert_data_eq!(renderer.render(input), expected_unicode); +} From d1c2b85f4636ecbc36728b6fa274a5340b41758e Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 2 Jul 2025 23:44:23 -0600 Subject: [PATCH 442/455] fix: Make Title followed by Padding a continuation --- src/renderer/mod.rs | 6 +++++- tests/rustc_tests.rs | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 83e1a7da..53597ef5 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -316,7 +316,11 @@ impl Renderer { title, max_line_num_len, title_style, - matches!(peek, Some(Element::Title(_) | Element::Message(_))), + matches!( + peek, + Some(Element::Title(_) | Element::Message(_)) + | Some(Element::Padding(_)) + ), buffer_msg_line_offset, ); last_was_suggestion = false; diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index afec2712..2750da11 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -3076,7 +3076,7 @@ LL │ match fut.as_mut().poll(ctx) { │ ━━━━ method not found in `Pin<&mut impl Future<Output = ()>>` ╭▸ $SRC_DIR/core/src/future/future.rs:104:7 │ - ╰ note: the method is available for `Pin<&mut impl Future<Output = ()>>` here + ├ note: the method is available for `Pin<&mut impl Future<Output = ()>>` here │ ╰ help: items from traits can only be used if the trait is in scope help: trait `Future` which provides `poll` is implemented but not in scope; perhaps you want to import it @@ -3250,7 +3250,7 @@ LL │ #[derive(Eqr)] │ ━━━ help: a derive macro with a similar name exists: `Eq` ╭▸ $SRC_DIR/core/src/cmp.rs:356:0 │ - ╰ note: similarly named derive macro `Eq` defined here + ├ note: similarly named derive macro `Eq` defined here │ ╰ note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` "#]]; From 8c46015024a1b33c763c840bba4d3e8a8bde62b5 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 2 Jul 2025 23:44:23 -0600 Subject: [PATCH 443/455] fix: Add end col seperator if Snippet is followed by a primary Origin --- src/renderer/mod.rs | 6 ++++++ tests/rustc_tests.rs | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 53597ef5..31712339 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -352,6 +352,12 @@ impl Renderer { max_line_num_len + 1, ); } + Some(Element::Origin(origin)) if origin.primary => self + .draw_col_separator_end( + &mut buffer, + current_line, + max_line_num_len + 1, + ), Some(Element::Message(level)) if level.level.name != Some(None) => diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index 2750da11..b32dd8fc 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -3055,6 +3055,7 @@ error[E0599]: no method named `poll` found for struct `Pin<&mut impl Future<Outp | LL | match fut.as_mut().poll(ctx) { | ^^^^ method not found in `Pin<&mut impl Future<Output = ()>>` + | --> $SRC_DIR/core/src/future/future.rs:104:7 | = note: the method is available for `Pin<&mut impl Future<Output = ()>>` here @@ -3074,6 +3075,7 @@ error[E0599]: no method named `poll` found for struct `Pin<&mut impl Future<Outp │ LL │ match fut.as_mut().poll(ctx) { │ ━━━━ method not found in `Pin<&mut impl Future<Output = ()>>` + ╰╴ ╭▸ $SRC_DIR/core/src/future/future.rs:104:7 │ ├ note: the method is available for `Pin<&mut impl Future<Output = ()>>` here @@ -3233,6 +3235,7 @@ error: cannot find derive macro `Eqr` in this scope | LL | #[derive(Eqr)] | ^^^ help: a derive macro with a similar name exists: `Eq` + | --> $SRC_DIR/core/src/cmp.rs:356:0 | = note: similarly named derive macro `Eq` defined here @@ -3248,6 +3251,7 @@ error: cannot find derive macro `Eqr` in this scope │ LL │ #[derive(Eqr)] │ ━━━ help: a derive macro with a similar name exists: `Eq` + ╰╴ ╭▸ $SRC_DIR/core/src/cmp.rs:356:0 │ ├ note: similarly named derive macro `Eq` defined here From b461247cc6c3ad6d3492e4d0ef42d246011a79df Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 25 Jun 2025 10:11:39 -0600 Subject: [PATCH 444/455] test: Suggestion span beyond source --- tests/formatter.rs | 137 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/tests/formatter.rs b/tests/formatter.rs index eca0b826..84ab4a52 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -2635,3 +2635,140 @@ fn empty_span_start_line() { let renderer = Renderer::plain(); assert_data_eq!(renderer.render(input), expected); } + +#[test] +fn suggestion_span_one_bigger_than_source() { + let snippet_source = r#"#![allow(unused)] +fn main() { +[1, 2, 3].into_iter().for_each(|n| { *n; }); +} +"#; + + let suggestion_source = r#"[1, 2, 3].into_iter().for_each(|n| { *n; }); +"#; + + let long_title1 ="this method call resolves to `<&[T; N] as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<[T; N] as IntoIterator>::into_iter` in Rust 2021"; + let long_title2 = "for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2021/IntoIterator-for-arrays.html>"; + let long_title3 = "or use `IntoIterator::into_iter(..)` instead of `.into_iter()` to explicitly iterate by value"; + + let input = &[ + Group::with_title(Level::WARNING.title(long_title1)) + .element( + Snippet::source(snippet_source) + .path("lint_example.rs") + .annotation(AnnotationKind::Primary.span(40..49)), + ) + .element(Level::WARNING.message("this changes meaning in Rust 2021")) + .element(Level::NOTE.message(long_title2)) + .element(Level::NOTE.message("`#[warn(array_into_iter)]` on by default")), + Group::with_title( + Level::HELP.title("use `.iter()` instead of `.into_iter()` to avoid ambiguity"), + ) + .element( + Snippet::source(suggestion_source) + .path("lint_example.rs") + .line_start(3) + .patch(Patch::new(10..19, "iter")), + ), + Group::with_title(Level::HELP.title(long_title3)).element( + Snippet::source(suggestion_source) + .path("lint_example.rs") + .line_start(3) + .patch(Patch::new( + suggestion_source.len() + 1..suggestion_source.len() + 1, + "IntoIterator::into_iter(", + )), + ), + ]; + + let expected = str![[r#" +warning: this method call resolves to `<&[T; N] as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<[T; N] as IntoIterator>::into_iter` in Rust 2021 + --> lint_example.rs:3:11 + | +3 | [1, 2, 3].into_iter().for_each(|n| { *n; }); + | ^^^^^^^^^ + | + = warning: this changes meaning in Rust 2021 + = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2021/IntoIterator-for-arrays.html> + = note: `#[warn(array_into_iter)]` on by default +help: use `.iter()` instead of `.into_iter()` to avoid ambiguity + | +3 - [1, 2, 3].into_iter().for_each(|n| { *n; }); +3 + [1, 2, 3].iter().for_each(|n| { *n; }); + | +help: or use `IntoIterator::into_iter(..)` instead of `.into_iter()` to explicitly iterate by value + | +3 | IntoIterator::into_iter( + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input), expected); +} + +#[test] +fn suggestion_span_bigger_than_source() { + let snippet_source = r#"#![allow(unused)] +fn main() { +[1, 2, 3].into_iter().for_each(|n| { *n; }); +} +"#; + let suggestion_source = r#"[1, 2, 3].into_iter().for_each(|n| { *n; }); +"#; + + let long_title1 ="this method call resolves to `<&[T; N] as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<[T; N] as IntoIterator>::into_iter` in Rust 2021"; + let long_title2 = "for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2021/IntoIterator-for-arrays.html>"; + let long_title3 = "or use `IntoIterator::into_iter(..)` instead of `.into_iter()` to explicitly iterate by value"; + + let input = &[ + Group::with_title(Level::WARNING.title(long_title1)) + .element( + Snippet::source(snippet_source) + .path("lint_example.rs") + .annotation(AnnotationKind::Primary.span(40..49)), + ) + .element(Level::WARNING.message("this changes meaning in Rust 2021")) + .element(Level::NOTE.message(long_title2)) + .element(Level::NOTE.message("`#[warn(array_into_iter)]` on by default")), + Group::with_title( + Level::HELP.title("use `.iter()` instead of `.into_iter()` to avoid ambiguity"), + ) + .element( + Snippet::source(suggestion_source) + .path("lint_example.rs") + .line_start(3) + .patch(Patch::new(10..19, "iter")), + ), + Group::with_title(Level::HELP.title(long_title3)).element( + Snippet::source(suggestion_source) + .path("lint_example.rs") + .line_start(3) + .patch(Patch::new( + suggestion_source.len() + 2..suggestion_source.len() + 2, + "IntoIterator::into_iter(", + )), + ), + ]; + + let expected = str![[r#" +warning: this method call resolves to `<&[T; N] as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<[T; N] as IntoIterator>::into_iter` in Rust 2021 + --> lint_example.rs:3:11 + | +3 | [1, 2, 3].into_iter().for_each(|n| { *n; }); + | ^^^^^^^^^ + | + = warning: this changes meaning in Rust 2021 + = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2021/IntoIterator-for-arrays.html> + = note: `#[warn(array_into_iter)]` on by default +help: use `.iter()` instead of `.into_iter()` to avoid ambiguity + | +3 - [1, 2, 3].into_iter().for_each(|n| { *n; }); +3 + [1, 2, 3].iter().for_each(|n| { *n; }); + | +help: or use `IntoIterator::into_iter(..)` instead of `.into_iter()` to explicitly iterate by value + | +3 | IntoIterator::into_iter( + | +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input), expected); +} From 83d2cbbf6ecf14926f563d39e84166809cc16b90 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Wed, 25 Jun 2025 10:14:49 -0600 Subject: [PATCH 445/455] fix!: Panic if Patch span is beyond the end of buffer --- src/renderer/source_map.rs | 13 +++++++++++++ tests/formatter.rs | 23 ++--------------------- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/src/renderer/source_map.rs b/src/renderer/source_map.rs index e2e4b61c..44b773b9 100644 --- a/src/renderer/source_map.rs +++ b/src/renderer/source_map.rs @@ -379,6 +379,19 @@ impl<'a> SourceMap<'a> { } line_count } + + let source_len = self.source.len(); + if let Some(bigger) = patches.iter().find_map(|x| { + // Allow patching one past the last character in the source. + if source_len + 1 < x.span.end { + Some(&x.span) + } else { + None + } + }) { + panic!("Patch span `{bigger:?}` is beyond the end of buffer `{source_len}`") + } + // Assumption: all spans are in the same file, and all spans // are disjoint. Sort in ascending order. patches.sort_by_key(|p| p.span.start); diff --git a/tests/formatter.rs b/tests/formatter.rs index 84ab4a52..1d21d13d 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -2706,6 +2706,7 @@ help: or use `IntoIterator::into_iter(..)` instead of `.into_iter()` to explicit } #[test] +#[should_panic = "Patch span `47..47` is beyond the end of buffer `45`"] fn suggestion_span_bigger_than_source() { let snippet_source = r#"#![allow(unused)] fn main() { @@ -2749,26 +2750,6 @@ fn main() { ), ]; - let expected = str![[r#" -warning: this method call resolves to `<&[T; N] as IntoIterator>::into_iter` (due to backwards compatibility), but will resolve to `<[T; N] as IntoIterator>::into_iter` in Rust 2021 - --> lint_example.rs:3:11 - | -3 | [1, 2, 3].into_iter().for_each(|n| { *n; }); - | ^^^^^^^^^ - | - = warning: this changes meaning in Rust 2021 - = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2021/IntoIterator-for-arrays.html> - = note: `#[warn(array_into_iter)]` on by default -help: use `.iter()` instead of `.into_iter()` to avoid ambiguity - | -3 - [1, 2, 3].into_iter().for_each(|n| { *n; }); -3 + [1, 2, 3].iter().for_each(|n| { *n; }); - | -help: or use `IntoIterator::into_iter(..)` instead of `.into_iter()` to explicitly iterate by value - | -3 | IntoIterator::into_iter( - | -"#]]; let renderer = Renderer::plain(); - assert_data_eq!(renderer.render(input), expected); + renderer.render(input); } From 35d2ef68f2ad66f6bb54a38d8d5c32227e3e53c2 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 3 Jul 2025 04:16:23 -0600 Subject: [PATCH 446/455] test: Snippet with no path --- tests/formatter.rs | 68 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/tests/formatter.rs b/tests/formatter.rs index 1d21d13d..bd64dd7b 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -2753,3 +2753,71 @@ fn main() { let renderer = Renderer::plain(); renderer.render(input); } + +#[test] +fn snippet_no_path() { + // Taken from: https://docs.python.org/3/library/typing.html#annotating-callable-objects + + let source = "def __call__(self, *vals: bytes, maxlen: int | None = None) -> list[bytes]: ..."; + let input = &[Group::with_title(Level::ERROR.title("")).element( + Snippet::source(source).annotation(AnnotationKind::Primary.span(4..12).label("annotation")), + )]; + + let expected_ascii = str![[r#" +error: + | +1 | def __call__(self, *vals: bytes, maxlen: int | None = None) -> list[bytes]: ... + | ^^^^^^^^ annotation +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input), expected_ascii); + + let expected_unicode = str![[r#" +error: + │ +1 │ def __call__(self, *vals: bytes, maxlen: int | None = None) -> list[bytes]: ... + ╰╴ ━━━━━━━━ annotation +"#]]; + let renderer = Renderer::plain().theme(OutputTheme::Unicode); + assert_data_eq!(renderer.render(input), expected_unicode); +} + +#[test] +fn multiple_snippet_no_path() { + // Taken from: https://docs.python.org/3/library/typing.html#annotating-callable-objects + + let source = "def __call__(self, *vals: bytes, maxlen: int | None = None) -> list[bytes]: ..."; + let input = &[Group::with_title(Level::ERROR.title("")) + .element( + Snippet::source(source) + .annotation(AnnotationKind::Primary.span(4..12).label("annotation")), + ) + .element( + Snippet::source(source) + .annotation(AnnotationKind::Primary.span(4..12).label("annotation")), + )]; + + let expected_ascii = str![[r#" +error: + | +1 | def __call__(self, *vals: bytes, maxlen: int | None = None) -> list[bytes]: ... + | ^^^^^^^^ annotation + | +1 | def __call__(self, *vals: bytes, maxlen: int | None = None) -> list[bytes]: ... + | ^^^^^^^^ annotation +"#]]; + let renderer = Renderer::plain(); + assert_data_eq!(renderer.render(input), expected_ascii); + + let expected_unicode = str![[r#" +error: + │ +1 │ def __call__(self, *vals: bytes, maxlen: int | None = None) -> list[bytes]: ... + │ ━━━━━━━━ annotation + │ +1 │ def __call__(self, *vals: bytes, maxlen: int | None = None) -> list[bytes]: ... + ╰╴ ━━━━━━━━ annotation +"#]]; + let renderer = Renderer::plain().theme(OutputTheme::Unicode); + assert_data_eq!(renderer.render(input), expected_unicode); +} From f1fcddaf6e3736718cbf6d729d64080f2deab3ef Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 3 Jul 2025 04:36:09 -0600 Subject: [PATCH 447/455] fix: Show Group/File start for Snippets without a path --- src/renderer/mod.rs | 63 ++++++++++++++++++++++++++++++++---- tests/color/issue_9.term.svg | 16 +++++---- tests/formatter.rs | 14 ++++---- 3 files changed, 74 insertions(+), 19 deletions(-) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 31712339..80a88110 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -288,10 +288,14 @@ impl Renderer { } let mut message_iter = group.elements.iter().enumerate().peekable(); let mut last_was_suggestion = false; + let mut first_was_title = false; while let Some((i, section)) = message_iter.next() { let peek = message_iter.peek().map(|(_, s)| s).copied(); match §ion { Element::Title(title) => { + if i == 0 { + first_was_title = true; + } let title_style = match (i == 0, g == 0) { (true, true) => TitleStyle::MainHeader, (true, false) => TitleStyle::Header, @@ -329,11 +333,13 @@ impl Renderer { if let Some((source_map, annotated_lines)) = source_map_annotated_lines.pop_front() { + let is_primary = primary_path == cause.path.as_ref() + && i == first_was_title as usize; self.render_snippet_annotations( &mut buffer, max_line_num_len, cause, - primary_path, + is_primary, &source_map, &annotated_lines, max_depth, @@ -722,7 +728,7 @@ impl Renderer { buffer: &mut StyledBuffer, max_line_num_len: usize, snippet: &Snippet<'_, Annotation<'_>>, - primary_path: Option<&Cow<'_, str>>, + is_primary: bool, sm: &SourceMap<'_>, annotated_lines: &[AnnotatedLineInfo<'_>], multiline_depth: usize, @@ -732,7 +738,7 @@ impl Renderer { let mut origin = Origin::path(path.as_ref()); // print out the span location and spacer before we print the annotated source // to do this, we need to know if this span will be primary - let is_primary = primary_path == Some(&origin.path); + //let is_primary = primary_path == Some(&origin.path); if is_primary { origin.primary = true; @@ -776,11 +782,54 @@ impl Renderer { } let buffer_msg_line_offset = buffer.num_lines(); self.render_origin(buffer, max_line_num_len, &origin, buffer_msg_line_offset); - } + // Put in the spacer between the location and annotated source + self.draw_col_separator_no_space( + buffer, + buffer_msg_line_offset + 1, + max_line_num_len + 1, + ); + } else { + let buffer_msg_line_offset = buffer.num_lines(); + if is_primary { + if self.theme == OutputTheme::Unicode { + buffer.puts( + buffer_msg_line_offset, + max_line_num_len, + self.file_start(), + ElementStyle::LineNumber, + ); + } else { + self.draw_col_separator_no_space( + buffer, + buffer_msg_line_offset, + max_line_num_len + 1, + ); + } + } else { + // Add spacing line, as shown: + // --> $DIR/file:54:15 + // | + // LL | code + // | ^^^^ + // | (<- It prints *this* line) + // ::: $DIR/other_file.rs:15:5 + // | + // LL | code + // | ---- + self.draw_col_separator_no_space( + buffer, + buffer_msg_line_offset, + max_line_num_len + 1, + ); - // Put in the spacer between the location and annotated source - let buffer_msg_line_offset = buffer.num_lines(); - self.draw_col_separator_no_space(buffer, buffer_msg_line_offset, max_line_num_len + 1); + buffer.puts( + buffer_msg_line_offset + 1, + max_line_num_len, + self.secondary_file_start(), + ElementStyle::LineNumber, + ); + } + } // Contains the vertical lines' positions for active multiline annotations let mut multilines = Vec::new(); diff --git a/tests/color/issue_9.term.svg b/tests/color/issue_9.term.svg index 5ae5da77..da58ee8b 100644 --- a/tests/color/issue_9.term.svg +++ b/tests/color/issue_9.term.svg @@ -1,4 +1,4 @@ -<svg width="911px" height="236px" xmlns="http://www.w3.org/2000/svg"> +<svg width="911px" height="272px" xmlns="http://www.w3.org/2000/svg"> <style> .fg { fill: #AAAAAA } .bg { background: #000000 } @@ -33,15 +33,19 @@ </tspan> <tspan x="10px" y="136px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="154px"><tspan class="fg-bright-blue bold">7</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let y = x;</tspan> + <tspan x="10px" y="154px"><tspan> </tspan><tspan class="fg-bright-blue bold">::: </tspan> </tspan> - <tspan x="10px" y="172px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">-</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">value moved here</tspan> + <tspan x="10px" y="172px"><tspan class="fg-bright-blue bold">7</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> let y = x;</tspan> </tspan> - <tspan x="10px" y="190px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> + <tspan x="10px" y="190px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">-</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">value moved here</tspan> </tspan> - <tspan x="10px" y="208px"><tspan class="fg-bright-blue bold">9</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> x;</tspan> + <tspan x="10px" y="208px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan> </tspan> - <tspan x="10px" y="226px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">value used here after move</tspan> + <tspan x="10px" y="226px"><tspan> </tspan><tspan class="fg-bright-blue bold">::: </tspan> +</tspan> + <tspan x="10px" y="244px"><tspan class="fg-bright-blue bold">9</tspan><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> x;</tspan> +</tspan> + <tspan x="10px" y="262px"><tspan> </tspan><tspan class="fg-bright-blue bold">|</tspan><tspan> </tspan><tspan class="fg-bright-red bold">^</tspan><tspan> </tspan><tspan class="fg-bright-red bold">value used here after move</tspan> </tspan> </text> diff --git a/tests/formatter.rs b/tests/formatter.rs index bd64dd7b..e7c08d47 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -2053,7 +2053,7 @@ error: title let expected_unicode = str![[r#" error: title - │ + ╭▸ 1 │ version = "0.1.0" 2 │ # Ensure that the spans from toml handle utf-8 correctly 3 │ authors = [ @@ -2093,7 +2093,7 @@ error: expected item, found `?` let expected_unicode = str![[r#" error: expected item, found `?` - │ + ╭▸ 1 │ … 宽的。这是宽的。这是宽的。这是宽的。这是宽的。这是宽的。*/? │ ━ expected item │ @@ -2130,7 +2130,7 @@ error: expected item, found `?` let expected_unicode = str![[r#" error: expected item, found `?` - │ + ╭▸ 1 │ … 的。这是宽的。这是宽的。这是宽的。… │ ━━ expected item │ @@ -2167,7 +2167,7 @@ error: expected item, found `?` let expected_unicode = str![[r#" error: expected item, found `?` - │ + ╭▸ 1 │ …aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/? │ ━ expected item │ @@ -2774,7 +2774,7 @@ error: let expected_unicode = str![[r#" error: - │ + ╭▸ 1 │ def __call__(self, *vals: bytes, maxlen: int | None = None) -> list[bytes]: ... ╰╴ ━━━━━━━━ annotation "#]]; @@ -2803,6 +2803,7 @@ error: 1 | def __call__(self, *vals: bytes, maxlen: int | None = None) -> list[bytes]: ... | ^^^^^^^^ annotation | + ::: 1 | def __call__(self, *vals: bytes, maxlen: int | None = None) -> list[bytes]: ... | ^^^^^^^^ annotation "#]]; @@ -2811,10 +2812,11 @@ error: let expected_unicode = str![[r#" error: - │ + ╭▸ 1 │ def __call__(self, *vals: bytes, maxlen: int | None = None) -> list[bytes]: ... │ ━━━━━━━━ annotation │ + ⸬ 1 │ def __call__(self, *vals: bytes, maxlen: int | None = None) -> list[bytes]: ... ╰╴ ━━━━━━━━ annotation "#]]; From 7a4fd3e1e9ecfa5c6b5c00f14b0c192a2785152a Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 3 Jul 2025 14:48:28 -0600 Subject: [PATCH 448/455] test: Add test for Message text ending with \n --- tests/rustc_tests.rs | 74 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index b32dd8fc..52e8bbe7 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -3370,3 +3370,77 @@ note: the traits `Iterator` and `ToTokens` must be implemented let renderer = renderer.theme(OutputTheme::Unicode); assert_data_eq!(renderer.render(input), expected_unicode); } + +#[test] +fn not_found_self_type_differs_shadowing_trait_item() { + // tests/ui/associated-inherent-types/not-found-self-type-differs-shadowing-trait-item.rs + + let source = r#"#![feature(inherent_associated_types)] +#![allow(incomplete_features)] + +// Check that it's okay to report “[inherent] associated type […] not found” for inherent associated +// type candidates that are not applicable (due to unsuitable Self type) even if there exists a +// “shadowed” associated type from a trait with the same name since its use would be ambiguous +// anyway if the IAT didn't exist. +// FIXME(inherent_associated_types): Figure out which error would be more helpful here. + +//@ revisions: shadowed uncovered + +struct S<T>(T); + +trait Tr { + type Pr; +} + +impl<T> Tr for S<T> { + type Pr = (); +} + +#[cfg(shadowed)] +impl S<()> { + type Pr = i32; +} + +fn main() { + let _: S::<bool>::Pr = (); + //[shadowed]~^ ERROR associated type `Pr` not found + //[uncovered]~^^ ERROR associated type `Pr` not found +} +"#; + + let input = &[Group::with_title( + Level::ERROR + .title("associated type `Pr` not found for `S<bool>` in the current scope") + .id("E0220"), + ) + .element( + Snippet::source(source) + .path("$DIR/not-found-self-type-differs-shadowing-trait-item.rs") + .annotation( + AnnotationKind::Primary + .span(705..707) + .label("associated item not found in `S<bool>`"), + ) + .annotation( + AnnotationKind::Context + .span(532..543) + .label("associated type `Pr` not found for this struct"), + ), + ) + .element(Level::NOTE.title("the associated type was found for\n"))]; + + let expected = str![[r#" +error[E0220]: associated type `Pr` not found for `S<bool>` in the current scope + --> $DIR/not-found-self-type-differs-shadowing-trait-item.rs:28:23 + | +LL | struct S<T>(T); + | ----------- associated type `Pr` not found for this struct +... +LL | let _: S::<bool>::Pr = (); + | ^^ associated item not found in `S<bool>` + | + = note: the associated type was found for +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected); +} From 82abe2699d655cb946bbf7684b67a5e7291051f2 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Thu, 3 Jul 2025 14:56:12 -0600 Subject: [PATCH 449/455] fix: Render newline if a Message ends with one --- src/renderer/mod.rs | 2 +- tests/rustc_tests.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 80a88110..8d3c6a2a 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -636,7 +636,7 @@ impl Renderer { } else { (normalize_whitespace(title.text()), title_element_style) }; - for (i, text) in title_str.lines().enumerate() { + for (i, text) in title_str.split('\n').enumerate() { if i != 0 { buffer.append(buffer_msg_line_offset + i, &padding, ElementStyle::NoStyle); if title_style == TitleStyle::Secondary diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index 52e8bbe7..dd5dbe82 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -3440,6 +3440,7 @@ LL | let _: S::<bool>::Pr = (); | ^^ associated item not found in `S<bool>` | = note: the associated type was found for + "#]]; let renderer = Renderer::plain().anonymized_line_numbers(true); assert_data_eq!(renderer.render(input), expected); From 43ef46a64ea53030c0691940a138162e5631bb52 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 8 Jul 2025 00:13:48 -0600 Subject: [PATCH 450/455] test: Padding last in group --- tests/formatter.rs | 131 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 130 insertions(+), 1 deletion(-) diff --git a/tests/formatter.rs b/tests/formatter.rs index e7c08d47..94f4ee7e 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -1,4 +1,6 @@ -use annotate_snippets::{Annotation, AnnotationKind, Group, Level, Patch, Renderer, Snippet}; +use annotate_snippets::{ + Annotation, AnnotationKind, Group, Level, Padding, Patch, Renderer, Snippet, +}; use annotate_snippets::renderer::OutputTheme; use snapbox::{assert_data_eq, str}; @@ -2823,3 +2825,130 @@ error: let renderer = Renderer::plain().theme(OutputTheme::Unicode); assert_data_eq!(renderer.render(input), expected_unicode); } + +#[test] +fn padding_last_in_group() { + let source = r#"// When the type of a method call's receiver is unknown, the span should point +// to the receiver (and not the entire call, as was previously the case before +// the fix of which this tests). + +fn shines_a_beacon_through_the_darkness() { + let x: Option<_> = None; //~ ERROR type annotations needed + x.unwrap().method_that_could_exist_on_some_type(); +} + +fn courier_to_des_moines_and_points_west(data: &[u32]) -> String { + data.iter() + .sum::<_>() //~ ERROR type annotations needed + .to_string() +} + +fn main() {} +"#; + + let input = &[ + Group::with_title(Level::ERROR.title("type annotations needed").id("E0282")) + .element( + Snippet::source(source) + .path("$DIR/issue-42234-unknown-receiver-type.rs") + .annotation(AnnotationKind::Primary.span(449..452).label( + "cannot infer type of the type parameter `S` declared on the method `sum`", + )), + ) + .element(Padding), + ]; + + let expected_ascii = str![[r#" +error[E0282]: type annotations needed + --> $DIR/issue-42234-unknown-receiver-type.rs:12:10 + | +LL | .sum::<_>() //~ ERROR type annotations needed + | ^^^ cannot infer type of the type parameter `S` declared on the method `sum` + | +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected_ascii); + + let expected_unicode = str![[r#" +error[E0282]: type annotations needed + ╭▸ $DIR/issue-42234-unknown-receiver-type.rs:12:10 + │ +LL │ .sum::<_>() //~ ERROR type annotations needed + │ ━━━ cannot infer type of the type parameter `S` declared on the method `sum` + │ +"#]]; + let renderer = renderer.theme(OutputTheme::Unicode); + assert_data_eq!(renderer.render(input), expected_unicode); +} + +#[test] +fn padding_last_in_group_with_group_after() { + let source = r#"// When the type of a method call's receiver is unknown, the span should point +// to the receiver (and not the entire call, as was previously the case before +// the fix of which this tests). + +fn shines_a_beacon_through_the_darkness() { + let x: Option<_> = None; //~ ERROR type annotations needed + x.unwrap().method_that_could_exist_on_some_type(); +} + +fn courier_to_des_moines_and_points_west(data: &[u32]) -> String { + data.iter() + .sum::<_>() //~ ERROR type annotations needed + .to_string() +} + +fn main() {} +"#; + + let input = &[ + Group::with_title(Level::ERROR.title("type annotations needed").id("E0282")) + .element( + Snippet::source(source) + .path("$DIR/issue-42234-unknown-receiver-type.rs") + .annotation(AnnotationKind::Primary.span(449..452).label( + "cannot infer type of the type parameter `S` declared on the method `sum`", + )), + ) + .element(Padding), + Group::with_title(Level::HELP.title("consider specifying the generic argument")).element( + Snippet::source(source) + .path("$DIR/issue-42234-unknown-receiver-type.rs") + .line_start(12) + .fold(true) + .patch(Patch::new(452..457, "::<GENERIC_ARG>")), + ), + ]; + + let expected_ascii = str![[r#" +error[E0282]: type annotations needed + --> $DIR/issue-42234-unknown-receiver-type.rs:12:10 + | +LL | .sum::<_>() //~ ERROR type annotations needed + | ^^^ cannot infer type of the type parameter `S` declared on the method `sum` + | +help: consider specifying the generic argument + | +LL - .sum::<_>() //~ ERROR type annotations needed +LL + .sum::<GENERIC_ARG>() //~ ERROR type annotations needed + | +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected_ascii); + + let expected_unicode = str![[r#" +error[E0282]: type annotations needed + ╭▸ $DIR/issue-42234-unknown-receiver-type.rs:12:10 + │ +LL │ .sum::<_>() //~ ERROR type annotations needed + │ ━━━ cannot infer type of the type parameter `S` declared on the method `sum` + │ +help: consider specifying the generic argument + ╭╴ +LL - .sum::<_>() //~ ERROR type annotations needed +LL + .sum::<GENERIC_ARG>() //~ ERROR type annotations needed + ╰╴ +"#]]; + let renderer = renderer.theme(OutputTheme::Unicode); + assert_data_eq!(renderer.render(input), expected_unicode); +} From 475dca6c838ba25599e6b0f8f97ec1684ac6b03c Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 8 Jul 2025 00:17:13 -0600 Subject: [PATCH 451/455] fix: Render Padding as end col separator if last in Group --- src/renderer/mod.rs | 18 +++++++++++++----- tests/formatter.rs | 4 ++-- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 8d3c6a2a..5754dc7c 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -413,11 +413,19 @@ impl Renderer { } Element::Padding(_) => { let current_line = buffer.num_lines(); - self.draw_col_separator_no_space( - &mut buffer, - current_line, - max_line_num_len + 1, - ); + if peek.is_none() { + self.draw_col_separator_end( + &mut buffer, + current_line, + max_line_num_len + 1, + ); + } else { + self.draw_col_separator_no_space( + &mut buffer, + current_line, + max_line_num_len + 1, + ); + } } } if g == 0 diff --git a/tests/formatter.rs b/tests/formatter.rs index 94f4ee7e..54621309 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -2875,7 +2875,7 @@ error[E0282]: type annotations needed │ LL │ .sum::<_>() //~ ERROR type annotations needed │ ━━━ cannot infer type of the type parameter `S` declared on the method `sum` - │ + ╰╴ "#]]; let renderer = renderer.theme(OutputTheme::Unicode); assert_data_eq!(renderer.render(input), expected_unicode); @@ -2942,7 +2942,7 @@ error[E0282]: type annotations needed │ LL │ .sum::<_>() //~ ERROR type annotations needed │ ━━━ cannot infer type of the type parameter `S` declared on the method `sum` - │ + ╰╴ help: consider specifying the generic argument ╭╴ LL - .sum::<_>() //~ ERROR type annotations needed From 268088f482df52200c7801c94c3085fd25f9e3dd Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 8 Jul 2025 01:32:45 -0600 Subject: [PATCH 452/455] test: Rustc annotation position overlaps --- tests/rustc_tests.rs | 246 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 245 insertions(+), 1 deletion(-) diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index dd5dbe82..51a27a6b 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -5,7 +5,7 @@ use annotate_snippets::{AnnotationKind, Group, Level, Origin, Padding, Patch, Renderer, Snippet}; use annotate_snippets::renderer::OutputTheme; -use snapbox::{assert_data_eq, str}; +use snapbox::{assert_data_eq, str, IntoData}; #[test] fn ends_on_col0() { @@ -3445,3 +3445,247 @@ LL | let _: S::<bool>::Pr = (); let renderer = Renderer::plain().anonymized_line_numbers(true); assert_data_eq!(renderer.render(input), expected); } + +#[test] +fn unsafe_extern_suggestion() { + // tests/ui/rust-2024/unsafe-extern-blocks/unsafe-extern-suggestion.rs + + let source = r#"//@ run-rustfix + +#![deny(missing_unsafe_on_extern)] +#![allow(unused)] + +extern "C" { + //~^ ERROR extern blocks should be unsafe [missing_unsafe_on_extern] + //~| WARN this is accepted in the current edition (Rust 2015) but is a hard error in Rust 2024! + static TEST1: i32; + fn test1(i: i32); +} + +unsafe extern "C" { + static TEST2: i32; + fn test2(i: i32); +} + +fn main() {} +"#; + + let title_0 = + "this is accepted in the current edition (Rust 2015) but is a hard error in Rust 2024!"; + let title_1 = "for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/unsafe-extern.html>"; + + let input = &[ + Group::with_title(Level::ERROR.title("extern blocks should be unsafe")) + .element( + Snippet::source(source) + .path("$DIR/unsafe-extern-suggestion.rs") + .annotation( + AnnotationKind::Context + .span(71..71) + .label("help: needs `unsafe` before the extern keyword: `unsafe`"), + ) + .annotation(AnnotationKind::Primary.span(71..303)), + ) + .element(Level::WARNING.message(title_0)) + .element(Level::NOTE.message(title_1)), + Group::with_title(Level::NOTE.title("the lint level is defined here")).element( + Snippet::source(source) + .path("$DIR/unsafe-extern-suggestion.rs") + .annotation(AnnotationKind::Primary.span(25..49)), + ), + ]; + + let expected = str![[r#" +error: extern blocks should be unsafe + --> $DIR/unsafe-extern-suggestion.rs:6:1 + | +LL | extern "C" { + | ^ help: needs `unsafe` before the extern keyword: `unsafe` + | _| + | | +LL | | //~^ ERROR extern blocks should be unsafe [missing_unsafe_on_extern] +LL | | //~| WARN this is accepted in the current edition (Rust 2015) but is a hard error in Rust 2024! +LL | | static TEST1: i32; +LL | | fn test1(i: i32); +LL | | } + | |_^ + | + = warning: this is accepted in the current edition (Rust 2015) but is a hard error in Rust 2024! + = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/unsafe-extern.html> +note: the lint level is defined here + --> $DIR/unsafe-extern-suggestion.rs:3:9 + | +LL | #![deny(missing_unsafe_on_extern)] + | ^^^^^^^^^^^^^^^^^^^^^^^^ +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected); +} + +#[test] +fn alloc_error_handler_bad_signature_2() { + // tests/ui/alloc-error/alloc-error-handler-bad-signature-2.rs + + let source = r#"//@ compile-flags:-C panic=abort + +#![feature(alloc_error_handler)] +#![no_std] +#![no_main] + +struct Layout; + +#[alloc_error_handler] +fn oom( + info: Layout, //~^ ERROR mismatched types +) { //~^^ ERROR mismatched types + loop {} +} + +#[panic_handler] +fn panic(_: &core::panic::PanicInfo) -> ! { loop {} } +"#; + let title_0 = + "`core::alloc::Layout` and `Layout` have similar names, but are actually distinct types"; + + let input = &[ + Group::with_title(Level::ERROR.title("mismatched types").id("E0308")) + .element( + Snippet::source(source) + .path("$DIR/alloc-error-handler-bad-signature-2.rs") + .annotation( + AnnotationKind::Primary + .span(130..230) + .label("expected `Layout`, found `core::alloc::Layout`"), + ) + .annotation( + AnnotationKind::Context + .span(130..185) + .label("arguments to this function are incorrect"), + ) + .annotation( + AnnotationKind::Context + .span(107..129) + .label("in this procedural macro expansion"), + ), + ) + .element(Level::NOTE.message(title_0)), + Group::with_title(Level::NOTE.title("`core::alloc::Layout` is defined in crate `core`")) + .element( + Origin::path("$SRC_DIR/core/src/alloc/layout.rs") + .line(40) + .char_column(0) + .primary(true), + ), + Group::with_title(Level::NOTE.title("`Layout` is defined in the current crate")).element( + Snippet::source(source) + .path("$DIR/alloc-error-handler-bad-signature-2.rs") + .annotation(AnnotationKind::Primary.span(91..104)), + ), + Group::with_title(Level::NOTE.title("function defined here")).element( + Snippet::source(source) + .path("$DIR/alloc-error-handler-bad-signature-2.rs") + .annotation(AnnotationKind::Context.span(142..154).label("")) + .annotation(AnnotationKind::Primary.span(133..136)), + ), + ]; + let expected = str![[r#" +error[E0308]: mismatched types + --> $DIR/alloc-error-handler-bad-signature-2.rs:10:1 + | +LL | #[alloc_error_handler] + | ---------------------- in this procedural macro expansion +LL | // fn oom( +LL | || info: Layout, //~^ ERROR mismatched types +LL | || ) { //~^^ ERROR mismatched types + | ||_- arguments to this function are incorrect +LL | | loop {} +LL | | } + | |__^ expected `Layout`, found `core::alloc::Layout` + | + = note: `core::alloc::Layout` and `Layout` have similar names, but are actually distinct types +note: `core::alloc::Layout` is defined in crate `core` + --> $SRC_DIR/core/src/alloc/layout.rs:40:0 +note: `Layout` is defined in the current crate + --> $DIR/alloc-error-handler-bad-signature-2.rs:7:1 + | +LL | struct Layout; + | ^^^^^^^^^^^^^ +note: function defined here + --> $DIR/alloc-error-handler-bad-signature-2.rs:10:4 + | +LL | fn oom( + | ^^^ +LL | info: Layout, //~^ ERROR mismatched types + | ------------ +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected); +} + +#[test] +fn str_escape() { + // tests/ui/str/str-escape.rs + + let source = r#"//@ check-pass +// ignore-tidy-tab +//@ edition: 2021 + +fn main() { + let s = "\ + + "; + //~^^^ WARNING multiple lines skipped by escaped newline + assert_eq!(s, ""); + + let s = c"foo\ + bar + "; + //~^^^ WARNING whitespace symbol '\u{a0}' is not skipped + assert_eq!(s, c"foo bar\n "); + + let s = "a\ + b"; + assert_eq!(s, "ab"); + + let s = "a\ + b"; + assert_eq!(s, "ab"); + + let s = b"a\ + + b"; + //~^^ WARNING whitespace symbol '\u{c}' is not skipped + // '\x0c' is ASCII whitespace, but it may not need skipped + // discussion: https://github.com/rust-lang/rust/pull/108403 + assert_eq!(s, b"a\x0cb"); +} +"#; + + let input = + &[ + Group::with_title(Level::WARNING.title(r#"whitespace symbol '\u{a0}' is not skipped"#)) + .element( + Snippet::source(source) + .path("$DIR/str-escape.rs") + .annotation( + AnnotationKind::Context + .span(203..205) + .label(r#"whitespace symbol '\u{a0}' is not skipped"#), + ) + .annotation(AnnotationKind::Primary.span(199..205)), + ), + ]; + let expected = str![[r#" +warning: whitespace symbol '\u{a0}' is not skipped + --> $DIR/str-escape.rs:12:18 + | +LL | let s = c"foo\ + | __________________^ +LL | | bar + | | ^ whitespace symbol '\u{a0}' is not skipped + | |___| + | +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected.raw()); +} From 9e2559862a6d50747f478be6797abc14d5dd2ca3 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Tue, 8 Jul 2025 01:32:45 -0600 Subject: [PATCH 453/455] fix: Match rustc's annotation overlap --- src/renderer/mod.rs | 6 ++++-- tests/rustc_tests.rs | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 5754dc7c..c347dbac 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -1167,7 +1167,7 @@ impl Renderer { // otherwise the lines would end up needing to go over a message. let mut annotations = line_info.annotations.clone(); - annotations.sort_by_key(|a| Reverse(a.start.display)); + annotations.sort_by_key(|a| Reverse((a.start.display, a.start.char))); // First, figure out where each label will be positioned. // @@ -1250,7 +1250,9 @@ impl Renderer { // If we're overlapping with an un-labelled annotation with the same span // we can just merge them in the output if next.start.display == annotation.start.display + && next.start.char == annotation.start.char && next.end.display == annotation.end.display + && next.end.char == annotation.end.char && !next.has_label() { continue; @@ -1284,7 +1286,7 @@ impl Renderer { && next.takes_space()) || (annotation.takes_space() && next.takes_space()) || (overlaps(next, annotation, l) - && next.end.display <= annotation.end.display + && (next.end.display, next.end.char) <= (annotation.end.display, annotation.end.char) && next.has_label() && p == 0) // Avoid #42595. diff --git a/tests/rustc_tests.rs b/tests/rustc_tests.rs index 51a27a6b..330e3029 100644 --- a/tests/rustc_tests.rs +++ b/tests/rustc_tests.rs @@ -3500,8 +3500,9 @@ error: extern blocks should be unsafe --> $DIR/unsafe-extern-suggestion.rs:6:1 | LL | extern "C" { - | ^ help: needs `unsafe` before the extern keyword: `unsafe` - | _| + | ^ + | | + | _help: needs `unsafe` before the extern keyword: `unsafe` | | LL | | //~^ ERROR extern blocks should be unsafe [missing_unsafe_on_extern] LL | | //~| WARN this is accepted in the current edition (Rust 2015) but is a hard error in Rust 2024! From dbf910159225429ae684e769cf64638b79200b9a Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Mon, 7 Jul 2025 23:54:52 -0600 Subject: [PATCH 454/455] test: Suggestion same as source --- tests/formatter.rs | 49 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/tests/formatter.rs b/tests/formatter.rs index 54621309..700b38aa 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -2952,3 +2952,52 @@ LL + .sum::<GENERIC_ARG>() //~ ERROR type annotations needed let renderer = renderer.theme(OutputTheme::Unicode); assert_data_eq!(renderer.render(input), expected_unicode); } + +#[test] +fn suggestion_same_as_source() { + let source = r#"// When the type of a method call's receiver is unknown, the span should point +// to the receiver (and not the entire call, as was previously the case before +// the fix of which this tests). + +fn shines_a_beacon_through_the_darkness() { + let x: Option<_> = None; //~ ERROR type annotations needed + x.unwrap().method_that_could_exist_on_some_type(); +} + +fn courier_to_des_moines_and_points_west(data: &[u32]) -> String { + data.iter() + .sum::<_>() //~ ERROR type annotations needed + .to_string() +} + +fn main() {} +"#; + + let input = &[ + Group::with_title(Level::ERROR.title("type annotations needed").id("E0282")).element( + Snippet::source(source) + .path("$DIR/issue-42234-unknown-receiver-type.rs") + .annotation(AnnotationKind::Primary.span(449..452).label( + "cannot infer type of the type parameter `S` declared on the method `sum`", + )), + ), + Group::with_title(Level::HELP.title("consider specifying the generic argument")).element( + Snippet::source(source) + .path("$DIR/issue-42234-unknown-receiver-type.rs") + .line_start(12) + .fold(true) + .patch(Patch::new(452..457, "::<_>")), + ), + ]; + let expected = str![[r#" +error[E0282]: type annotations needed + --> $DIR/issue-42234-unknown-receiver-type.rs:12:10 + | +LL | .sum::<_>() //~ ERROR type annotations needed + | ^^^ cannot infer type of the type parameter `S` declared on the method `sum` + | +help: consider specifying the generic argument +"#]]; + let renderer = Renderer::plain().anonymized_line_numbers(true); + assert_data_eq!(renderer.render(input), expected); +} From 212d62085509a0e6d96848061e31a97bb1d54641 Mon Sep 17 00:00:00 2001 From: Scott Schafer <schaferjscott@gmail.com> Date: Mon, 7 Jul 2025 23:54:52 -0600 Subject: [PATCH 455/455] fix: Show suggestion if same as source --- src/renderer/mod.rs | 13 +------------ src/renderer/source_map.rs | 16 +++++----------- tests/formatter.rs | 3 +++ 3 files changed, 9 insertions(+), 23 deletions(-) diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index c347dbac..bb39495e 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -55,7 +55,6 @@ use std::borrow::Cow; use std::cmp::{max, min, Ordering, Reverse}; use std::collections::{HashMap, VecDeque}; use std::fmt; -use std::ops::Range; use stylesheet::Stylesheet; const ANONYMIZED_LINE_NUM: &str = "LL"; @@ -1911,9 +1910,7 @@ impl Renderer { assert!(underline_start >= 0 && underline_end >= 0); let padding: usize = max_line_num_len + 3; for p in underline_start..underline_end { - if matches!(show_code_change, DisplaySuggestion::Underline) - && is_different(sm, &part.replacement, part.span.clone()) - { + if matches!(show_code_change, DisplaySuggestion::Underline) { // If this is a replacement, underline with `~`, if this is an addition // underline with `+`. buffer.putc( @@ -2955,14 +2952,6 @@ struct UnderlineParts { multiline_bottom_right_with_text: char, } -/// Whether the original and suggested code are the same. -pub(crate) fn is_different(sm: &SourceMap<'_>, suggested: &str, range: Range<usize>) -> bool { - match sm.span_to_snippet(range) { - Some(s) => s != suggested, - None => true, - } -} - #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum OutputTheme { Ascii, diff --git a/src/renderer/source_map.rs b/src/renderer/source_map.rs index 44b773b9..fdc4cd68 100644 --- a/src/renderer/source_map.rs +++ b/src/renderer/source_map.rs @@ -1,4 +1,4 @@ -use crate::renderer::{char_width, is_different, num_overlap, LineAnnotation, LineAnnotationType}; +use crate::renderer::{char_width, num_overlap, LineAnnotation, LineAnnotationType}; use crate::{Annotation, AnnotationKind, Patch}; use std::borrow::Cow; use std::cmp::{max, min}; @@ -474,16 +474,10 @@ impl<'a> SourceMap<'a> { _ => 1, }) .sum(); - if !is_different(self, &part.replacement, part.span.clone()) { - // Account for cases where we are suggesting the same code that's already - // there. This shouldn't happen often, but in some cases for multipart - // suggestions it's much easier to handle it here than in the origin. - } else { - line_highlight.push(SubstitutionHighlight { - start: (cur_lo.char as isize + acc) as usize, - end: (cur_lo.char as isize + acc + len) as usize, - }); - } + line_highlight.push(SubstitutionHighlight { + start: (cur_lo.char as isize + acc) as usize, + end: (cur_lo.char as isize + acc + len) as usize, + }); buf.push_str(&part.replacement); // Account for the difference between the width of the current code and the // snippet being suggested, so that the *later* suggestions are correctly diff --git a/tests/formatter.rs b/tests/formatter.rs index 700b38aa..6d2b895c 100644 --- a/tests/formatter.rs +++ b/tests/formatter.rs @@ -2997,6 +2997,9 @@ LL | .sum::<_>() //~ ERROR type annotations needed | ^^^ cannot infer type of the type parameter `S` declared on the method `sum` | help: consider specifying the generic argument + | +LL | .sum::<_>() //~ ERROR type annotations needed + | "#]]; let renderer = Renderer::plain().anonymized_line_numbers(true); assert_data_eq!(renderer.render(input), expected);