Skip to content

Commit dea51de

Browse files
authored
Merge pull request #353 from Muscraft/fix-multiple-line-removals
fix: Show multiple line removals in Diff format
2 parents e4c7105 + e164aa4 commit dea51de

File tree

5 files changed

+267
-10
lines changed

5 files changed

+267
-10
lines changed

src/renderer/render.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1479,7 +1479,10 @@ fn emit_suggestion_default(
14791479
row_num += 1;
14801480
}
14811481

1482-
let file_lines = sm.span_to_lines(parts[0].span.clone());
1482+
let lo = parts.iter().map(|p| p.span.start).min().unwrap();
1483+
let hi = parts.iter().map(|p| p.span.end).max().unwrap();
1484+
1485+
let file_lines = sm.span_to_lines(lo..hi);
14831486
let (line_start, line_end) = if suggestion.fold {
14841487
// We use the original span to get original line_start
14851488
sm.span_to_locations(parts[0].original_span.clone())
@@ -1613,6 +1616,7 @@ fn emit_suggestion_default(
16131616
if let DisplaySuggestion::Diff | DisplaySuggestion::Underline | DisplaySuggestion::Add =
16141617
show_code_change
16151618
{
1619+
let mut prev_lines: Option<(usize, usize)> = None;
16161620
for part in parts {
16171621
let snippet = sm.span_to_snippet(part.span.clone()).unwrap_or_default();
16181622
let (span_start, span_end) = sm.span_to_locations(part.span.clone());
@@ -1702,6 +1706,12 @@ fn emit_suggestion_default(
17021706

17031707
let newlines = snippet.lines().count();
17041708
if newlines > 0 && row_num > newlines {
1709+
let offset = match prev_lines {
1710+
Some((start, end)) => {
1711+
file_lines.len().saturating_sub(end.saturating_sub(start))
1712+
}
1713+
None => file_lines.len(),
1714+
};
17051715
// Account for removals where the part being removed spans multiple
17061716
// lines.
17071717
// FIXME: We check the number of rows because in some cases, like in
@@ -1715,7 +1725,7 @@ fn emit_suggestion_default(
17151725
// Going lower than buffer_offset (+ 1) would mean
17161726
// overwriting existing content in the buffer
17171727
let min_row = buffer_offset + usize::from(!matches_previous_suggestion);
1718-
let row = (row_num - 2 - (newlines - i - 1)).max(min_row);
1728+
let row = (row_num - 2 - (offset - i - 1)).max(min_row);
17191729
// On the first line, we highlight between the start of the part
17201730
// span, and the end of that line.
17211731
// On the last line, we highlight between the start of the line, and
@@ -1746,6 +1756,7 @@ fn emit_suggestion_default(
17461756
true,
17471757
);
17481758
}
1759+
prev_lines = Some((span_start.line, span_end.line));
17491760
}
17501761

17511762
// length of the code after substitution

tests/color/multiple_multiline_removal.ascii.term.svg

Lines changed: 10 additions & 4 deletions
Loading

tests/color/multiple_multiline_removal.unicode.term.svg

Lines changed: 10 additions & 4 deletions
Loading

tests/formatter.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2371,6 +2371,9 @@ help: consider removing the `?Sized` bound to make the type parameter `Sized`
23712371
9 - fuzzy
23722372
10 - pizza
23732373
11 - jumps
2374+
12 - crazy
2375+
13 - quack
2376+
14 - zappy
23742377
8 + campy
23752378
|
23762379
"#]];
@@ -2386,6 +2389,9 @@ help: consider removing the `?Sized` bound to make the type parameter `Sized`
23862389
9 - fuzzy
23872390
10 - pizza
23882391
11 - jumps
2392+
12 - crazy
2393+
13 - quack
2394+
14 - zappy
23892395
8 + campy
23902396
╰╴
23912397
"#]];

tests/rustc_tests.rs

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5325,6 +5325,9 @@ help: you might have meant to use an associated function to build this type
53255325
help: consider using the `Default` trait
53265326
|
53275327
11 - wtf: Some(Box(U {
5328+
12 - wtf: None,
5329+
13 - x: (),
5330+
14 - })),
53285331
11 + wtf: Some(<Box as std::default::Default>::default()),
53295332
|
53305333
"#]];
@@ -5374,6 +5377,9 @@ help: you might have meant to use an associated function to build this type
53745377
help: consider using the `Default` trait
53755378
╭╴
53765379
11 - wtf: Some(Box(U {
5380+
12 - wtf: None,
5381+
13 - x: (),
5382+
14 - })),
53775383
11 + wtf: Some(<Box as std::default::Default>::default()),
53785384
╰╴
53795385
"#]];
@@ -5679,3 +5685,225 @@ help: Unicode character ' ' (No-Break Space) looks like ' ' (Space), but it is
56795685
let renderer_unicode = renderer_ascii.decor_style(DecorStyle::Unicode);
56805686
assert_data_eq!(renderer_unicode.render(report), expected_unicode);
56815687
}
5688+
5689+
#[test]
5690+
fn issue_109854() {
5691+
// tests/ui/suggestions/issue-109854.rs
5692+
let source_0 = r##" String::with_capacity(
5693+
//~^ ERROR this function takes 1 argument but 3 arguments were supplied
5694+
generate_setter,
5695+
r#"
5696+
pub(crate) struct Person<T: Clone> {}
5697+
"#,
5698+
r#""#,
5699+
"##;
5700+
let source_1 = r#" generate_setter,
5701+
"#;
5702+
let title_0 = "expected type `usize`
5703+
found fn item `fn() {generate_setter}`";
5704+
let source_2 = r##" generate_setter,
5705+
r#"
5706+
pub(crate) struct Person<T: Clone> {}
5707+
"#,
5708+
r#""#,
5709+
"##;
5710+
5711+
let report = &[
5712+
Level::ERROR
5713+
.primary_title("this function takes 1 argument but 3 arguments were supplied")
5714+
.id("E0061")
5715+
.element(
5716+
Snippet::source(source_0)
5717+
.path("$DIR/issue-109854.rs")
5718+
.line_start(2)
5719+
.annotation(AnnotationKind::Primary.span(4..25))
5720+
.annotation(
5721+
AnnotationKind::Context
5722+
.span(128..172)
5723+
.label("unexpected argument #2 of type `&'static str`"),
5724+
)
5725+
.annotation(
5726+
AnnotationKind::Context
5727+
.span(179..184)
5728+
.label("unexpected argument #3 of type `&'static str`"),
5729+
),
5730+
),
5731+
Level::NOTE
5732+
.secondary_title("expected `usize`, found fn item")
5733+
.element(
5734+
Snippet::source(source_1)
5735+
.path("$DIR/issue-109854.rs")
5736+
.line_start(4)
5737+
.annotation(AnnotationKind::Primary.span(4..19)),
5738+
)
5739+
.element(Level::NOTE.message(title_0)),
5740+
Level::NOTE
5741+
.secondary_title("associated function defined here")
5742+
.element(
5743+
Origin::path("$SRC_DIR/alloc/src/string.rs")
5744+
.line(480)
5745+
.char_column(11),
5746+
),
5747+
Level::HELP
5748+
.secondary_title("remove the extra arguments")
5749+
.element(
5750+
Snippet::source(source_2)
5751+
.path("$DIR/issue-109854.rs")
5752+
.line_start(4)
5753+
.patch(Patch::new(4..19, "/* usize */"))
5754+
.patch(Patch::new(19..69, ""))
5755+
.patch(Patch::new(69..81, "")),
5756+
),
5757+
];
5758+
let expected_ascii = str![[r##"
5759+
error[E0061]: this function takes 1 argument but 3 arguments were supplied
5760+
--> $DIR/issue-109854.rs:2:5
5761+
|
5762+
2 | String::with_capacity(
5763+
| ^^^^^^^^^^^^^^^^^^^^^
5764+
...
5765+
5 | / r#"
5766+
6 | | pub(crate) struct Person<T: Clone> {}
5767+
7 | | "#,
5768+
| |__- unexpected argument #2 of type `&'static str`
5769+
8 | r#""#,
5770+
| ----- unexpected argument #3 of type `&'static str`
5771+
|
5772+
note: expected `usize`, found fn item
5773+
--> $DIR/issue-109854.rs:4:5
5774+
|
5775+
4 | generate_setter,
5776+
| ^^^^^^^^^^^^^^^
5777+
= note: expected type `usize`
5778+
found fn item `fn() {generate_setter}`
5779+
note: associated function defined here
5780+
--> $SRC_DIR/alloc/src/string.rs:480:11
5781+
help: remove the extra arguments
5782+
|
5783+
4 - generate_setter,
5784+
5 - r#"
5785+
6 - pub(crate) struct Person<T: Clone> {}
5786+
7 - "#,
5787+
8 - r#""#,
5788+
4 + /* usize */,
5789+
|
5790+
"##]];
5791+
let renderer_ascii = Renderer::plain();
5792+
assert_data_eq!(renderer_ascii.render(report), expected_ascii);
5793+
5794+
let expected_unicode = str![[r##"
5795+
error[E0061]: this function takes 1 argument but 3 arguments were supplied
5796+
╭▸ $DIR/issue-109854.rs:2:5
5797+
5798+
2 │ String::with_capacity(
5799+
│ ━━━━━━━━━━━━━━━━━━━━━
5800+
5801+
5 │ ┌ r#"
5802+
6 │ │ pub(crate) struct Person<T: Clone> {}
5803+
7 │ │ "#,
5804+
│ └──┘ unexpected argument #2 of type `&'static str`
5805+
8 │ r#""#,
5806+
│ ───── unexpected argument #3 of type `&'static str`
5807+
╰╴
5808+
note: expected `usize`, found fn item
5809+
╭▸ $DIR/issue-109854.rs:4:5
5810+
5811+
4 │ generate_setter,
5812+
│ ━━━━━━━━━━━━━━━
5813+
╰ note: expected type `usize`
5814+
found fn item `fn() {generate_setter}`
5815+
note: associated function defined here
5816+
─▸ $SRC_DIR/alloc/src/string.rs:480:11
5817+
help: remove the extra arguments
5818+
╭╴
5819+
4 - generate_setter,
5820+
5 - r#"
5821+
6 - pub(crate) struct Person<T: Clone> {}
5822+
7 - "#,
5823+
8 - r#""#,
5824+
4 + /* usize */,
5825+
╰╴
5826+
"##]];
5827+
let renderer_unicode = renderer_ascii.decor_style(DecorStyle::Unicode);
5828+
assert_data_eq!(renderer_unicode.render(report), expected_unicode);
5829+
}
5830+
5831+
#[test]
5832+
fn match_same_arms() {
5833+
// src/tools/clippy/tests/ui/match_same_arms.rs
5834+
let source = r#" 2 => 'b',
5835+
3 => 'b',
5836+
_ => 'b',
5837+
"#;
5838+
5839+
let report = &[
5840+
Level::ERROR
5841+
.primary_title("these match arms have identical bodies")
5842+
.element(
5843+
Snippet::source(source)
5844+
.path("tests/ui/match_same_arms.rs")
5845+
.line_start(20)
5846+
.annotation(AnnotationKind::Primary.span(8..16))
5847+
.annotation(AnnotationKind::Primary.span(26..34))
5848+
.annotation(
5849+
AnnotationKind::Primary
5850+
.span(44..52)
5851+
.label("the wildcard arm"),
5852+
),
5853+
)
5854+
.element(
5855+
Level::HELP
5856+
.message("if this is unintentional make the arms return different values"),
5857+
),
5858+
Level::HELP
5859+
.secondary_title("otherwise remove the non-wildcard arms")
5860+
.element(
5861+
Snippet::source(source)
5862+
.path("tests/ui/match_same_arms.rs")
5863+
.line_start(20)
5864+
.patch(Patch::new(8..26, ""))
5865+
.patch(Patch::new(26..44, "")),
5866+
),
5867+
];
5868+
let expected_ascii = str![[r#"
5869+
error: these match arms have identical bodies
5870+
--> tests/ui/match_same_arms.rs:20:9
5871+
|
5872+
20 | 2 => 'b',
5873+
| ^^^^^^^^
5874+
21 | 3 => 'b',
5875+
| ^^^^^^^^
5876+
22 | _ => 'b',
5877+
| ^^^^^^^^ the wildcard arm
5878+
|
5879+
= help: if this is unintentional make the arms return different values
5880+
help: otherwise remove the non-wildcard arms
5881+
|
5882+
20 - 2 => 'b',
5883+
21 - 3 => 'b',
5884+
|
5885+
"#]];
5886+
let renderer_ascii = Renderer::plain();
5887+
assert_data_eq!(renderer_ascii.render(report), expected_ascii);
5888+
5889+
let expected_unicode = str![[r#"
5890+
error: these match arms have identical bodies
5891+
╭▸ tests/ui/match_same_arms.rs:20:9
5892+
5893+
20 │ 2 => 'b',
5894+
│ ━━━━━━━━
5895+
21 │ 3 => 'b',
5896+
│ ━━━━━━━━
5897+
22 │ _ => 'b',
5898+
│ ━━━━━━━━ the wildcard arm
5899+
5900+
╰ help: if this is unintentional make the arms return different values
5901+
help: otherwise remove the non-wildcard arms
5902+
╭╴
5903+
20 - 2 => 'b',
5904+
21 - 3 => 'b',
5905+
╰╴
5906+
"#]];
5907+
let renderer_unicode = renderer_ascii.decor_style(DecorStyle::Unicode);
5908+
assert_data_eq!(renderer_unicode.render(report), expected_unicode);
5909+
}

0 commit comments

Comments
 (0)