From 5cc5a3c8905e8a29f5a7c59408057f7484ccb324 Mon Sep 17 00:00:00 2001 From: Scott Schafer Date: Tue, 3 Jun 2025 09:45:06 -0600 Subject: [PATCH 1/2] 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 cf33969..d013aa3 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 Date: Tue, 3 Jun 2025 09:45:06 -0600 Subject: [PATCH 2/2] 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 eca057e..403c1c6 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 24827f6..2f7eb90 100644 --- a/tests/color/ann_multiline2.term.svg +++ b/tests/color/ann_multiline2.term.svg @@ -1,4 +1,4 @@ - +