From dab58dde2e44fa68746d932d95f0b916066320fe Mon Sep 17 00:00:00 2001 From: Scott Schafer Date: Mon, 22 Sep 2025 05:19:43 -0600 Subject: [PATCH 1/3] test: Add regression test for multiple multiline removal --- tests/color/main.rs | 1 + .../multiple_multiline_removal.ascii.term.svg | 114 ++++++++++++++++++ tests/color/multiple_multiline_removal.rs | 93 ++++++++++++++ ...ultiple_multiline_removal.unicode.term.svg | 114 ++++++++++++++++++ 4 files changed, 322 insertions(+) create mode 100644 tests/color/multiple_multiline_removal.ascii.term.svg create mode 100644 tests/color/multiple_multiline_removal.rs create mode 100644 tests/color/multiple_multiline_removal.unicode.term.svg diff --git a/tests/color/main.rs b/tests/color/main.rs index f61e113..001b6b0 100644 --- a/tests/color/main.rs +++ b/tests/color/main.rs @@ -13,6 +13,7 @@ mod highlight_source; mod issue_9; mod multiline_removal_suggestion; mod multiple_annotations; +mod multiple_multiline_removal; mod primary_title_second_group; mod simple; mod strip_line; diff --git a/tests/color/multiple_multiline_removal.ascii.term.svg b/tests/color/multiple_multiline_removal.ascii.term.svg new file mode 100644 index 0000000..ac3afa1 --- /dev/null +++ b/tests/color/multiple_multiline_removal.ascii.term.svg @@ -0,0 +1,114 @@ + + + + + + + error[E0423]: cannot initialize a tuple struct which contains private fields + + --> $DIR/suggest-box-new.rs:8:19 + + | + + 8 | wtf: Some(Box(U { + + | ^^^ + + | + + note: constructor is not visible here due to private fields + + --> $SRC_DIR/alloc/src/boxed.rs:234:2 + + | + + = note: private field + + | + + = note: private field + + help: you might have meant to use an associated function to build this type + + | + + 8 - wtf: Some(Box(U { + + 9 - wtf: None, + + 10 - x: (), + + 11 - })), + + 8 + wtf: Some(Box::new(_)), + + | + + 8 - wtf: Some(Box(U { + + 9 - wtf: None, + + 10 - x: (), + + 11 - })), + + 8 + wtf: Some(Box::new_uninit()), + + | + + 8 - wtf: Some(Box(U { + + 9 - wtf: None, + + 10 - x: (), + + 11 - })), + + 8 + wtf: Some(Box::new_zeroed()), + + | + + 8 - wtf: Some(Box(U { + + 9 - wtf: None, + + 10 - x: (), + + 11 - })), + + 8 + wtf: Some(Box::new_in(_, _)), + + | + + = and 12 other candidates + + help: consider using the `Default` trait + + | + + 8 - wtf: Some(Box(U { + + 8 + wtf: Some(<Box as std::default::Default>::default()), + + | + + + + diff --git a/tests/color/multiple_multiline_removal.rs b/tests/color/multiple_multiline_removal.rs new file mode 100644 index 0000000..ea38a36 --- /dev/null +++ b/tests/color/multiple_multiline_removal.rs @@ -0,0 +1,93 @@ +use annotate_snippets::{ + renderer::DecorStyle, AnnotationKind, Level, Origin, Padding, Patch, Renderer, Snippet, +}; + +use snapbox::{assert_data_eq, file}; + +#[test] +fn case() { + // https://github.com/rust-lang/rust/blob/4b94758d2ba7d0ef71ccf5fde29ce4bc5d6fe2a4/tests/ui/suggestions/multi-suggestion.rs + + let source = r#"#![allow(dead_code)] +struct U { + wtf: Option>>, + x: T, +} +fn main() { + U { + wtf: Some(Box(U { + wtf: None, + x: (), + })), + x: () + }; + let _ = std::collections::HashMap(); + let _ = std::collections::HashMap {}; + let _ = Box {}; +} +"#; + let path = "$DIR/suggest-box-new.rs"; + let secondary_path = "$SRC_DIR/alloc/src/boxed.rs"; + + let report = &[ + Level::ERROR + .primary_title("cannot initialize a tuple struct which contains private fields") + .id("E0423") + .element( + Snippet::source(source) + .path(path) + .annotation(AnnotationKind::Primary.span(114..117)), + ), + Level::NOTE + .secondary_title("constructor is not visible here due to private fields") + .element(Origin::path(secondary_path).line(234).char_column(2)) + .element(Padding) + .element(Level::NOTE.message("private field")) + .element(Padding) + .element(Level::NOTE.message("private field")), + Level::HELP + .secondary_title( + "you might have meant to use an associated function to build this type", + ) + .element( + Snippet::source(source) + .path(path) + .patch(Patch::new(117..174, "::new(_)")), + ) + .element( + Snippet::source(source) + .path(path) + .patch(Patch::new(117..174, "::new_uninit()")), + ) + .element( + Snippet::source(source) + .path(path) + .patch(Patch::new(117..174, "::new_zeroed()")), + ) + .element( + Snippet::source(source) + .path(path) + .patch(Patch::new(117..174, "::new_in(_, _)")), + ) + .element(Level::NOTE.no_name().message("and 12 other candidates")), + Level::HELP + .secondary_title("consider using the `Default` trait") + .element( + Snippet::source(source) + .path(path) + .patch(Patch::new(114..114, "<")) + .patch(Patch::new( + 117..174, + " as std::default::Default>::default()", + )), + ), + ]; + + let expected_ascii = file!["multiple_multiline_removal.ascii.term.svg": TermSvg]; + let renderer = Renderer::styled(); + assert_data_eq!(renderer.render(report), expected_ascii); + + let expected_unicode = file!["multiple_multiline_removal.unicode.term.svg": TermSvg]; + let renderer = renderer.decor_style(DecorStyle::Unicode); + assert_data_eq!(renderer.render(report), expected_unicode); +} diff --git a/tests/color/multiple_multiline_removal.unicode.term.svg b/tests/color/multiple_multiline_removal.unicode.term.svg new file mode 100644 index 0000000..ce8306d --- /dev/null +++ b/tests/color/multiple_multiline_removal.unicode.term.svg @@ -0,0 +1,114 @@ + + + + + + + error[E0423]: cannot initialize a tuple struct which contains private fields + + ╭▸ $DIR/suggest-box-new.rs:8:19 + + + + 8 wtf: Some(Box(U { + + ━━━ + + ╰╴ + + note: constructor is not visible here due to private fields + + ╭▸ $SRC_DIR/alloc/src/boxed.rs:234:2 + + + + note: private field + + + + note: private field + + help: you might have meant to use an associated function to build this type + + ╭╴ + + 8 - wtf: Some(Box(U { + + 9 - wtf: None, + + 10 - x: (), + + 11 - })), + + 8 + wtf: Some(Box::new(_)), + + ├╴ + + 8 - wtf: Some(Box(U { + + 9 - wtf: None, + + 10 - x: (), + + 11 - })), + + 8 + wtf: Some(Box::new_uninit()), + + ├╴ + + 8 - wtf: Some(Box(U { + + 9 - wtf: None, + + 10 - x: (), + + 11 - })), + + 8 + wtf: Some(Box::new_zeroed()), + + ├╴ + + 8 - wtf: Some(Box(U { + + 9 - wtf: None, + + 10 - x: (), + + 11 - })), + + 8 + wtf: Some(Box::new_in(_, _)), + + + + and 12 other candidates + + help: consider using the `Default` trait + + ╭╴ + + 8 - wtf: Some(Box(U { + + 8 + wtf: Some(<Box as std::default::Default>::default()), + + ╰╴ + + + + From cdbf04ed6e9e3c9a2140b5a55713a6b0fee73802 Mon Sep 17 00:00:00 2001 From: Scott Schafer Date: Mon, 22 Sep 2025 05:19:43 -0600 Subject: [PATCH 2/3] test: Add tests for highlight duplicated diff lines --- ...light_duplicated_diff_lines.ascii.term.svg | 62 +++++++++++++ .../color/highlight_duplicated_diff_lines.rs | 75 +++++++++++++++ ...ght_duplicated_diff_lines.unicode.term.svg | 62 +++++++++++++ tests/color/main.rs | 2 + ...ltiple_highlight_duplicated.ascii.term.svg | 78 ++++++++++++++++ tests/color/multiple_highlight_duplicated.rs | 93 +++++++++++++++++++ ...iple_highlight_duplicated.unicode.term.svg | 78 ++++++++++++++++ typos.toml | 2 + 8 files changed, 452 insertions(+) create mode 100644 tests/color/highlight_duplicated_diff_lines.ascii.term.svg create mode 100644 tests/color/highlight_duplicated_diff_lines.rs create mode 100644 tests/color/highlight_duplicated_diff_lines.unicode.term.svg create mode 100644 tests/color/multiple_highlight_duplicated.ascii.term.svg create mode 100644 tests/color/multiple_highlight_duplicated.rs create mode 100644 tests/color/multiple_highlight_duplicated.unicode.term.svg create mode 100644 typos.toml diff --git a/tests/color/highlight_duplicated_diff_lines.ascii.term.svg b/tests/color/highlight_duplicated_diff_lines.ascii.term.svg new file mode 100644 index 0000000..e3713b7 --- /dev/null +++ b/tests/color/highlight_duplicated_diff_lines.ascii.term.svg @@ -0,0 +1,62 @@ + + + + + + + error[E0061]: this function takes 6 arguments but 7 arguments were supplied + + --> $DIR/wrong-highlight-span-extra-arguments-147070.rs:17:15 + + | + + 17 | let foo = Thingie::new( + + | ^^^^^^^^^^^^ + + ... + + 24 | String::from(""), + + | ---------------- unexpected argument #7 of type `String` + + | + + note: associated function defined here + + --> $DIR/wrong-highlight-span-extra-arguments-147070.rs:4:19 + + | + + 4 | pub(crate) fn new( + + | ^^^ + + help: remove the extra argument + + | + + 23 - String::from(""), + + | + + + + diff --git a/tests/color/highlight_duplicated_diff_lines.rs b/tests/color/highlight_duplicated_diff_lines.rs new file mode 100644 index 0000000..4f26e8c --- /dev/null +++ b/tests/color/highlight_duplicated_diff_lines.rs @@ -0,0 +1,75 @@ +use annotate_snippets::{renderer::DecorStyle, AnnotationKind, Level, Patch, Renderer, Snippet}; + +use snapbox::{assert_data_eq, file}; + +#[test] +fn case() { + // https://github.com/rust-lang/rust/blob/4b94758d2ba7d0ef71ccf5fde29ce4bc5d6fe2a4/tests/ui/argument-suggestions/wrong-highlight-span-extra-arguments-147070.rs + + let source = r#"struct Thingie; + +impl Thingie { + pub(crate) fn new( + _a: String, + _b: String, + _c: String, + _d: String, + _e: String, + _f: String, + ) -> Self { + unimplemented!() + } +} + +fn main() { + let foo = Thingie::new( + String::from(""), + String::from(""), + String::from(""), + String::from(""), + String::from(""), + String::from(""), + String::from(""), + ); +}"#; + + let path = "$DIR/wrong-highlight-span-extra-arguments-147070.rs"; + + let report = &[ + Level::ERROR + .primary_title("this function takes 6 arguments but 7 arguments were supplied") + .id("E0061") + .element( + Snippet::source(source) + .path(path) + .annotation( + AnnotationKind::Context + .span(429..445) + .label("unexpected argument #7 of type `String`"), + ) + .annotation(AnnotationKind::Primary.span(251..263)), + ), + Level::NOTE + .secondary_title("associated function defined here") + .element( + Snippet::source(source) + .path(path) + .annotation(AnnotationKind::Primary.span(50..53)), + ), + Level::HELP + .secondary_title("remove the extra argument") + .element( + Snippet::source(source) + .path(path) + .patch(Patch::new(419..445, "")), + ), + ]; + + let expected_ascii = file!["highlight_duplicated_diff_lines.ascii.term.svg": TermSvg]; + let renderer = Renderer::styled(); + assert_data_eq!(renderer.render(report), expected_ascii); + + let expected_unicode = file!["highlight_duplicated_diff_lines.unicode.term.svg": TermSvg]; + let renderer = renderer.decor_style(DecorStyle::Unicode); + assert_data_eq!(renderer.render(report), expected_unicode); +} diff --git a/tests/color/highlight_duplicated_diff_lines.unicode.term.svg b/tests/color/highlight_duplicated_diff_lines.unicode.term.svg new file mode 100644 index 0000000..6c365f0 --- /dev/null +++ b/tests/color/highlight_duplicated_diff_lines.unicode.term.svg @@ -0,0 +1,62 @@ + + + + + + + error[E0061]: this function takes 6 arguments but 7 arguments were supplied + + ╭▸ $DIR/wrong-highlight-span-extra-arguments-147070.rs:17:15 + + + + 17 let foo = Thingie::new( + + ━━━━━━━━━━━━ + + + + 24 String::from(""), + + ──────────────── unexpected argument #7 of type `String` + + ╰╴ + + note: associated function defined here + + ╭▸ $DIR/wrong-highlight-span-extra-arguments-147070.rs:4:19 + + + + 4 pub(crate) fn new( + + ╰╴ ━━━ + + help: remove the extra argument + + ╭╴ + + 23 - String::from(""), + + ╰╴ + + + + diff --git a/tests/color/main.rs b/tests/color/main.rs index 001b6b0..7d78f4b 100644 --- a/tests/color/main.rs +++ b/tests/color/main.rs @@ -9,10 +9,12 @@ mod fold_ann_multiline; mod fold_bad_origin_line; mod fold_leading; mod fold_trailing; +mod highlight_duplicated_diff_lines; mod highlight_source; mod issue_9; mod multiline_removal_suggestion; mod multiple_annotations; +mod multiple_highlight_duplicated; mod multiple_multiline_removal; mod primary_title_second_group; mod simple; diff --git a/tests/color/multiple_highlight_duplicated.ascii.term.svg b/tests/color/multiple_highlight_duplicated.ascii.term.svg new file mode 100644 index 0000000..3af7c18 --- /dev/null +++ b/tests/color/multiple_highlight_duplicated.ascii.term.svg @@ -0,0 +1,78 @@ + + + + + + + error[E0061]: this function takes 6 arguments but 7 arguments were supplied + + --> $DIR/wrong-highlight-span-extra-arguments-147070.rs:17:15 + + | + + 17 | let foo = Thingie::new( + + | ^^^^^^^^^^^^ + + ... + + 24 | String::from(""), + + | ---------------- unexpected argument #7 of type `String` + + | + + note: associated function defined here + + --> $DIR/wrong-highlight-span-extra-arguments-147070.rs:4:19 + + | + + 4 | pub(crate) fn new( + + | ^^^ + + help: remove the extra argument + + | + + 23 - String::from(""), + + | + + 18 - String::from(""), + + | + + 18 - String::from(""), + + | + + 23 - String::from(""), + + | + + 23 - String::from(""), + + | + + + + diff --git a/tests/color/multiple_highlight_duplicated.rs b/tests/color/multiple_highlight_duplicated.rs new file mode 100644 index 0000000..625584d --- /dev/null +++ b/tests/color/multiple_highlight_duplicated.rs @@ -0,0 +1,93 @@ +use annotate_snippets::{renderer::DecorStyle, AnnotationKind, Level, Patch, Renderer, Snippet}; + +use snapbox::{assert_data_eq, file}; + +#[test] +fn case() { + let source = r#"struct Thingie; + +impl Thingie { + pub(crate) fn new( + _a: String, + _b: String, + _c: String, + _d: String, + _e: String, + _f: String, + ) -> Self { + unimplemented!() + } +} + +fn main() { + let foo = Thingie::new( + String::from(""), + String::from(""), + String::from(""), + String::from(""), + String::from(""), + String::from(""), + String::from(""), + ); +}"#; + + let path = "$DIR/wrong-highlight-span-extra-arguments-147070.rs"; + + let report = &[ + Level::ERROR + .primary_title("this function takes 6 arguments but 7 arguments were supplied") + .id("E0061") + .element( + Snippet::source(source) + .path(path) + .annotation( + AnnotationKind::Context + .span(429..445) + .label("unexpected argument #7 of type `String`"), + ) + .annotation(AnnotationKind::Primary.span(251..263)), + ), + Level::NOTE + .secondary_title("associated function defined here") + .element( + Snippet::source(source) + .path(path) + .annotation(AnnotationKind::Primary.span(50..53)), + ), + Level::HELP + .secondary_title("remove the extra argument") + .element( + Snippet::source(source) + .path(path) + .patch(Patch::new(419..445, "")), + ) + .element( + Snippet::source(source) + .path(path) + .patch(Patch::new(266..292, "")), + ) + .element( + Snippet::source(source) + .path(path) + .patch(Patch::new(289..315, "")), + ) + .element( + Snippet::source(source) + .path(path) + .patch(Patch::new(403..420, "")), + ) + .element( + Snippet::source(source) + .path(path) + .patch(Patch::new(419..445, "")), + ), + ]; + + let expected_ascii = file!["multiple_highlight_duplicated.ascii.term.svg": TermSvg]; + let renderer = Renderer::styled(); + assert_data_eq!(renderer.render(report), expected_ascii); + + let expected_unicode = file!["multiple_highlight_duplicated.unicode.term.svg": TermSvg]; + let renderer = renderer.decor_style(DecorStyle::Unicode); + assert_data_eq!(renderer.render(report), expected_unicode); +} diff --git a/tests/color/multiple_highlight_duplicated.unicode.term.svg b/tests/color/multiple_highlight_duplicated.unicode.term.svg new file mode 100644 index 0000000..63c039a --- /dev/null +++ b/tests/color/multiple_highlight_duplicated.unicode.term.svg @@ -0,0 +1,78 @@ + + + + + + + error[E0061]: this function takes 6 arguments but 7 arguments were supplied + + ╭▸ $DIR/wrong-highlight-span-extra-arguments-147070.rs:17:15 + + + + 17 let foo = Thingie::new( + + ━━━━━━━━━━━━ + + + + 24 String::from(""), + + ──────────────── unexpected argument #7 of type `String` + + ╰╴ + + note: associated function defined here + + ╭▸ $DIR/wrong-highlight-span-extra-arguments-147070.rs:4:19 + + + + 4 pub(crate) fn new( + + ╰╴ ━━━ + + help: remove the extra argument + + ╭╴ + + 23 - String::from(""), + + ├╴ + + 18 - String::from(""), + + ├╴ + + 18 - String::from(""), + + ├╴ + + 23 - String::from(""), + + ├╴ + + 23 - String::from(""), + + ╰╴ + + + + diff --git a/typos.toml b/typos.toml new file mode 100644 index 0000000..708a9e1 --- /dev/null +++ b/typos.toml @@ -0,0 +1,2 @@ +[default.extend-words] +Thingie = "Thingie" From 474d3a5d07b67681b952bf10259a966bce2cb3c4 Mon Sep 17 00:00:00 2001 From: Scott Schafer Date: Mon, 22 Sep 2025 05:19:43 -0600 Subject: [PATCH 3/3] fix: Correctly calculate row for diff suggestion --- src/renderer/render.rs | 5 ++++- .../highlight_duplicated_diff_lines.ascii.term.svg | 4 ++-- .../highlight_duplicated_diff_lines.unicode.term.svg | 4 ++-- .../multiple_highlight_duplicated.ascii.term.svg | 12 ++++++------ .../multiple_highlight_duplicated.unicode.term.svg | 12 ++++++------ 5 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/renderer/render.rs b/src/renderer/render.rs index e8cb5fa..1f5bfc5 100644 --- a/src/renderer/render.rs +++ b/src/renderer/render.rs @@ -1774,7 +1774,10 @@ fn emit_suggestion_default( // 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); + // Going lower than buffer_offset (+ 1) would mean + // overwriting existing content in the buffer + let min_row = buffer_offset + usize::from(!matches_previous_suggestion); + let row = (row_num - 2 - (newlines - i - 1)).max(min_row); // 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 diff --git a/tests/color/highlight_duplicated_diff_lines.ascii.term.svg b/tests/color/highlight_duplicated_diff_lines.ascii.term.svg index e3713b7..cd3db7b 100644 --- a/tests/color/highlight_duplicated_diff_lines.ascii.term.svg +++ b/tests/color/highlight_duplicated_diff_lines.ascii.term.svg @@ -49,11 +49,11 @@ | ^^^ - help: remove the extra argument + help: remove the extra argument | - 23 - String::from(""), + 23 - String::from(""), | diff --git a/tests/color/highlight_duplicated_diff_lines.unicode.term.svg b/tests/color/highlight_duplicated_diff_lines.unicode.term.svg index 6c365f0..5ae70d1 100644 --- a/tests/color/highlight_duplicated_diff_lines.unicode.term.svg +++ b/tests/color/highlight_duplicated_diff_lines.unicode.term.svg @@ -49,11 +49,11 @@ ╰╴ ━━━ - help: remove the extra argument + help: remove the extra argument ╭╴ - 23 - String::from(""), + 23 - String::from(""), ╰╴ diff --git a/tests/color/multiple_highlight_duplicated.ascii.term.svg b/tests/color/multiple_highlight_duplicated.ascii.term.svg index 3af7c18..a38c0fa 100644 --- a/tests/color/multiple_highlight_duplicated.ascii.term.svg +++ b/tests/color/multiple_highlight_duplicated.ascii.term.svg @@ -49,27 +49,27 @@ | ^^^ - help: remove the extra argument + help: remove the extra argument | - 23 - String::from(""), + 23 - String::from(""), | - 18 - String::from(""), + 18 - String::from(""), | - 18 - String::from(""), + 18 - String::from(""), | - 23 - String::from(""), + 23 - String::from(""), | - 23 - String::from(""), + 23 - String::from(""), | diff --git a/tests/color/multiple_highlight_duplicated.unicode.term.svg b/tests/color/multiple_highlight_duplicated.unicode.term.svg index 63c039a..8bcafa2 100644 --- a/tests/color/multiple_highlight_duplicated.unicode.term.svg +++ b/tests/color/multiple_highlight_duplicated.unicode.term.svg @@ -49,27 +49,27 @@ ╰╴ ━━━ - help: remove the extra argument + help: remove the extra argument ╭╴ - 23 - String::from(""), + 23 - String::from(""), ├╴ - 18 - String::from(""), + 18 - String::from(""), ├╴ - 18 - String::from(""), + 18 - String::from(""), ├╴ - 23 - String::from(""), + 23 - String::from(""), ├╴ - 23 - String::from(""), + 23 - String::from(""), ╰╴