mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-09 22:25:20 +00:00

We do this because `...` is valid Python, which makes it pretty likely that some line trimming will lead to ambiguous output. So we add support for overriding the cut indicator. This also requires changing some of the alignment math, which was previously tightly coupled to `...`. For Ruff, we go with `…` (`U+2026 HORIZONTAL ELLIPSIS`) for our cut indicator. For more details, see the patch sent to upstream: https://github.com/rust-lang/annotate-snippets-rs/pull/172
1031 lines
26 KiB
Rust
1031 lines
26 KiB
Rust
// Since this is a vendored copy of `annotate-snippets`, we squash Clippy
|
||
// warnings from upstream in order to the reduce the diff. If our copy drifts
|
||
// far from upstream such that patches become impractical to apply in both
|
||
// places, then we can get rid of these suppressions and fix the lints.
|
||
#![allow(clippy::redundant_clone, clippy::should_panic_without_expect)]
|
||
|
||
use ruff_annotate_snippets::{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 expected = str![[r#"
|
||
error: oops
|
||
--> <current file>:2:8
|
||
|
|
||
2 | Second oops line
|
||
| ^^^^ oops
|
||
|
|
||
"#]];
|
||
|
||
let renderer = Renderer::plain();
|
||
assert_data_eq!(renderer.render(snippets).to_string(), expected);
|
||
}
|
||
|
||
#[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 expected = str![[r#"
|
||
error
|
||
--> <current file>:1:7
|
||
|
|
||
1 | こんにちは、世界
|
||
| ^^^^ world
|
||
|
|
||
"#]];
|
||
|
||
let renderer = Renderer::plain();
|
||
assert_data_eq!(renderer.render(snippets).to_string(), expected);
|
||
}
|
||
|
||
#[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 expected = str![[r#"
|
||
error
|
||
--> <current file>:1:3
|
||
|
|
||
1 | おはよう
|
||
| _____^
|
||
2 | | ございます
|
||
| |______^ Good morning
|
||
|
|
||
"#]];
|
||
|
||
let renderer = Renderer::plain();
|
||
assert_data_eq!(renderer.render(snippets).to_string(), expected);
|
||
}
|
||
|
||
#[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 expected = str![[r#"
|
||
error
|
||
--> <current file>:1:1
|
||
|
|
||
1 | お寿司
|
||
| ^^^^^^ Sushi1
|
||
2 | 食べたい🍣
|
||
| ---- note: Sushi2
|
||
|
|
||
"#]];
|
||
|
||
let renderer = Renderer::plain();
|
||
assert_data_eq!(renderer.render(snippets).to_string(), expected);
|
||
}
|
||
|
||
#[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 expected = str![[r#"
|
||
error
|
||
--> <current file>:1:7
|
||
|
|
||
1 | こんにちは、新しいWorld!
|
||
| ^^^^^^^^^^^ New world
|
||
|
|
||
"#]];
|
||
|
||
let renderer = Renderer::plain();
|
||
assert_data_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 = str![r#"error[E0001]: This is a title"#];
|
||
let renderer = Renderer::plain();
|
||
assert_data_eq!(renderer.render(input).to_string(), expected);
|
||
}
|
||
|
||
/// Tests that we can format a message *without* a header.
|
||
///
|
||
/// This uses `Level::None`, which is somewhat of a hacky API addition I made
|
||
/// to our vendored copy of `annotate-snippets` in order to do exactly what
|
||
/// this test asserts: skip the header.
|
||
#[test]
|
||
fn test_format_skip_title() {
|
||
let source =
|
||
"# Docstring followed by a newline\n\ndef foobar(foot, bar={}):\n \"\"\"\n \"\"\"\n";
|
||
let src_annotation = Level::Error.span(56..58).label("B006");
|
||
let snippet = Snippet::source(source)
|
||
.line_start(1)
|
||
.annotation(src_annotation)
|
||
.fold(false);
|
||
let message = Level::None.title("").snippet(snippet);
|
||
|
||
let expected = str![[r#"
|
||
|
|
||
1 | # Docstring followed by a newline
|
||
2 |
|
||
3 | def foobar(foot, bar={}):
|
||
| ^^ B006
|
||
4 | """
|
||
5 | """
|
||
|
|
||
"#]];
|
||
assert_data_eq!(Renderer::plain().render(message).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 = str![[r#"
|
||
error
|
||
|
|
||
5402 | This is line 1
|
||
5403 | This is line 2
|
||
|
|
||
"#]];
|
||
let renderer = Renderer::plain();
|
||
assert_data_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 = str![[r#"
|
||
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);
|
||
}
|
||
|
||
#[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 = str![[r#"
|
||
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);
|
||
}
|
||
|
||
#[test]
|
||
fn test_format_footer_title() {
|
||
let input = Level::Error
|
||
.title("")
|
||
.footer(Level::Error.title("This __is__ a title"));
|
||
let expected = str![[r#"
|
||
error
|
||
= error: This __is__ a title
|
||
"#]];
|
||
let renderer = Renderer::plain();
|
||
assert_data_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 = str![[r#"
|
||
error
|
||
|
|
||
56 | This is an example
|
||
57 | of content lines
|
||
|
|
||
"#]];
|
||
let renderer = Renderer::plain();
|
||
assert_data_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 = str![[r#"
|
||
error
|
||
|
|
||
1 | tests
|
||
| ----- help: Example string
|
||
|
|
||
"#]];
|
||
let renderer = Renderer::plain();
|
||
assert_data_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 = str![[r#"
|
||
error
|
||
|
|
||
1 | tests
|
||
| -----
|
||
| |
|
||
| help: Example string
|
||
| help: Second line
|
||
|
|
||
"#]];
|
||
let renderer = Renderer::plain();
|
||
assert_data_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 = str![[r#"
|
||
error
|
||
--> file.rs
|
||
|
|
||
|
|
||
"#]];
|
||
let renderer = Renderer::plain();
|
||
assert_data_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 = str![[r#"
|
||
error
|
||
|
|
||
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);
|
||
}
|
||
|
||
#[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);
|
||
}
|
||
|
||
#[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 }
|
||
| ^^^ --------------- 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);
|
||
}
|
||
|
||
#[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);
|
||
}
|
||
|
||
#[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:3:1
|
||
|
|
||
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);
|
||
}
|
||
|
||
#[test]
|
||
fn long_line_cut() {
|
||
let source = "abcd abcd abcd abcd abcd abcd abcd";
|
||
let input = Level::Error.title("").snippet(
|
||
Snippet::source(source)
|
||
.line_start(1)
|
||
.annotation(Level::Error.span(0..4)),
|
||
);
|
||
let expected = str![[r#"
|
||
error
|
||
|
|
||
1 | abcd abcd a...
|
||
| ^^^^
|
||
|
|
||
"#]];
|
||
let renderer = Renderer::plain().term_width(18);
|
||
assert_data_eq!(renderer.render(input).to_string(), expected);
|
||
}
|
||
|
||
#[test]
|
||
fn long_line_cut_custom() {
|
||
let source = "abcd abcd abcd abcd abcd abcd abcd";
|
||
let input = Level::Error.title("").snippet(
|
||
Snippet::source(source)
|
||
.line_start(1)
|
||
.annotation(Level::Error.span(0..4)),
|
||
);
|
||
// This trims a little less because `…` is visually smaller than `...`.
|
||
let expected = str![[r#"
|
||
error
|
||
|
|
||
1 | abcd abcd abc…
|
||
| ^^^^
|
||
|
|
||
"#]];
|
||
let renderer = Renderer::plain().term_width(18).cut_indicator("…");
|
||
assert_data_eq!(renderer.render(input).to_string(), expected);
|
||
}
|