Skip to content

Commit 7bbdca6

Browse files
bors[bot]kiljacken
andauthored
Merge #3564
3564: Better handling of a few kinds of cargo/clippy diagnostics r=matklad a=kiljacken This was initially supposed to just be a fix for #3433, but I caught a few things that ended up being useful as well. This PR primarily makes us handle multi-edit fix suggestions properly. Instead of just applying the first fix we apply all the parts of the fix in a single action. Second up, this PR handles diagnostics with multiple primary spans, f.x. the unused import diagnostic from rustc: ![image](https://user-images.githubusercontent.com/209321/76531793-03269480-6476-11ea-9180-41c0ea705553.png) The LSP doesn't handle this too well, as it only support a single complete range for each diagnostic, so we get duplicate messages in the problem panel of VSCode: ![image](https://user-images.githubusercontent.com/209321/76531901-29e4cb00-6476-11ea-9746-cd57f8974b85.png) However, I feel like the improved visual aspect in-editor outweighs the duplication in the problem panel. I'm open to not including the second commit if anybody really doesn't like the idea of duplicate diagnostics in the problem pane. Fixes #3433 Fixes #3257 Co-authored-by: Emil Lauridsen <mine809@gmail.com>
2 parents afd64ef + 98e8ad5 commit 7bbdca6

11 files changed

+722
-458
lines changed

crates/ra_cargo_watch/src/conv.rs

Lines changed: 65 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use lsp_types::{
88
Location, NumberOrString, Position, Range, TextEdit, Url, WorkspaceEdit,
99
};
1010
use std::{
11+
collections::HashMap,
1112
fmt::Write,
1213
path::{Component, Path, PathBuf, Prefix},
1314
str::FromStr,
@@ -126,44 +127,34 @@ fn map_rust_child_diagnostic(
126127
rd: &RustDiagnostic,
127128
workspace_root: &PathBuf,
128129
) -> MappedRustChildDiagnostic {
129-
let span: &DiagnosticSpan = match rd.spans.iter().find(|s| s.is_primary) {
130-
Some(span) => span,
131-
None => {
132-
// `rustc` uses these spanless children as a way to print multi-line
133-
// messages
134-
return MappedRustChildDiagnostic::MessageLine(rd.message.clone());
130+
let spans: Vec<&DiagnosticSpan> = rd.spans.iter().filter(|s| s.is_primary).collect();
131+
if spans.is_empty() {
132+
// `rustc` uses these spanless children as a way to print multi-line
133+
// messages
134+
return MappedRustChildDiagnostic::MessageLine(rd.message.clone());
135+
}
136+
137+
let mut edit_map: HashMap<Url, Vec<TextEdit>> = HashMap::new();
138+
for &span in &spans {
139+
if let Some(suggested_replacement) = &span.suggested_replacement {
140+
let location = map_span_to_location(span, workspace_root);
141+
let edit = TextEdit::new(location.range, suggested_replacement.clone());
142+
edit_map.entry(location.uri).or_default().push(edit);
135143
}
136-
};
137-
138-
// If we have a primary span use its location, otherwise use the parent
139-
let location = map_span_to_location(&span, workspace_root);
140-
141-
if let Some(suggested_replacement) = &span.suggested_replacement {
142-
// Include our replacement in the title unless it's empty
143-
let title = if !suggested_replacement.is_empty() {
144-
format!("{}: '{}'", rd.message, suggested_replacement)
145-
} else {
146-
rd.message.clone()
147-
};
148-
149-
let edit = {
150-
let edits = vec![TextEdit::new(location.range, suggested_replacement.clone())];
151-
let mut edit_map = std::collections::HashMap::new();
152-
edit_map.insert(location.uri, edits);
153-
WorkspaceEdit::new(edit_map)
154-
};
144+
}
155145

146+
if !edit_map.is_empty() {
156147
MappedRustChildDiagnostic::SuggestedFix(CodeAction {
157-
title,
148+
title: rd.message.clone(),
158149
kind: Some("quickfix".to_string()),
159150
diagnostics: None,
160-
edit: Some(edit),
151+
edit: Some(WorkspaceEdit::new(edit_map)),
161152
command: None,
162153
is_preferred: None,
163154
})
164155
} else {
165156
MappedRustChildDiagnostic::Related(DiagnosticRelatedInformation {
166-
location,
157+
location: map_span_to_location(spans[0], workspace_root),
167158
message: rd.message.clone(),
168159
})
169160
}
@@ -189,13 +180,13 @@ pub(crate) struct MappedRustDiagnostic {
189180
pub(crate) fn map_rust_diagnostic_to_lsp(
190181
rd: &RustDiagnostic,
191182
workspace_root: &PathBuf,
192-
) -> Option<MappedRustDiagnostic> {
193-
let primary_span = rd.spans.iter().find(|s| s.is_primary)?;
194-
195-
let location = map_span_to_location(&primary_span, workspace_root);
183+
) -> Vec<MappedRustDiagnostic> {
184+
let primary_spans: Vec<&DiagnosticSpan> = rd.spans.iter().filter(|s| s.is_primary).collect();
185+
if primary_spans.is_empty() {
186+
return vec![];
187+
}
196188

197189
let severity = map_level_to_severity(rd.level);
198-
let mut primary_span_label = primary_span.label.as_ref();
199190

200191
let mut source = String::from("rustc");
201192
let mut code = rd.code.as_ref().map(|c| c.code.clone());
@@ -208,19 +199,10 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
208199
}
209200
}
210201

202+
let mut needs_primary_span_label = true;
211203
let mut related_information = vec![];
212204
let mut tags = vec![];
213205

214-
// If error occurs from macro expansion, add related info pointing to
215-
// where the error originated
216-
if !is_from_macro(&primary_span.file_name) && primary_span.expansion.is_some() {
217-
let def_loc = map_span_to_location_naive(&primary_span, workspace_root);
218-
related_information.push(DiagnosticRelatedInformation {
219-
location: def_loc,
220-
message: "Error originated from macro here".to_string(),
221-
});
222-
}
223-
224206
for secondary_span in rd.spans.iter().filter(|s| !s.is_primary) {
225207
let related = map_secondary_span_to_related(secondary_span, workspace_root);
226208
if let Some(related) = related {
@@ -240,15 +222,11 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
240222

241223
// These secondary messages usually duplicate the content of the
242224
// primary span label.
243-
primary_span_label = None;
225+
needs_primary_span_label = false;
244226
}
245227
}
246228
}
247229

248-
if let Some(primary_span_label) = primary_span_label {
249-
write!(&mut message, "\n{}", primary_span_label).unwrap();
250-
}
251-
252230
if is_unused_or_unnecessary(rd) {
253231
tags.push(DiagnosticTag::Unnecessary);
254232
}
@@ -257,21 +235,45 @@ pub(crate) fn map_rust_diagnostic_to_lsp(
257235
tags.push(DiagnosticTag::Deprecated);
258236
}
259237

260-
let diagnostic = Diagnostic {
261-
range: location.range,
262-
severity,
263-
code: code.map(NumberOrString::String),
264-
source: Some(source),
265-
message,
266-
related_information: if !related_information.is_empty() {
267-
Some(related_information)
268-
} else {
269-
None
270-
},
271-
tags: if !tags.is_empty() { Some(tags) } else { None },
272-
};
273-
274-
Some(MappedRustDiagnostic { location, diagnostic, fixes })
238+
primary_spans
239+
.iter()
240+
.map(|primary_span| {
241+
let location = map_span_to_location(&primary_span, workspace_root);
242+
243+
let mut message = message.clone();
244+
if needs_primary_span_label {
245+
if let Some(primary_span_label) = &primary_span.label {
246+
write!(&mut message, "\n{}", primary_span_label).unwrap();
247+
}
248+
}
249+
250+
// If error occurs from macro expansion, add related info pointing to
251+
// where the error originated
252+
if !is_from_macro(&primary_span.file_name) && primary_span.expansion.is_some() {
253+
let def_loc = map_span_to_location_naive(&primary_span, workspace_root);
254+
related_information.push(DiagnosticRelatedInformation {
255+
location: def_loc,
256+
message: "Error originated from macro here".to_string(),
257+
});
258+
}
259+
260+
let diagnostic = Diagnostic {
261+
range: location.range,
262+
severity,
263+
code: code.clone().map(NumberOrString::String),
264+
source: Some(source.clone()),
265+
message,
266+
related_information: if !related_information.is_empty() {
267+
Some(related_information.clone())
268+
} else {
269+
None
270+
},
271+
tags: if !tags.is_empty() { Some(tags.clone()) } else { None },
272+
};
273+
274+
MappedRustDiagnostic { location, diagnostic, fixes: fixes.clone() }
275+
})
276+
.collect()
275277
}
276278

277279
/// Returns a `Url` object from a given path, will lowercase drive letters if present.

crates/ra_cargo_watch/src/conv/snapshots/ra_cargo_watch__conv__test__snap_clippy_pass_by_ref.snap

Lines changed: 87 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -2,98 +2,100 @@
22
source: crates/ra_cargo_watch/src/conv/test.rs
33
expression: diag
44
---
5-
MappedRustDiagnostic {
6-
location: Location {
7-
uri: "file:///test/compiler/mir/tagset.rs",
8-
range: Range {
9-
start: Position {
10-
line: 41,
11-
character: 23,
12-
},
13-
end: Position {
14-
line: 41,
15-
character: 28,
5+
[
6+
MappedRustDiagnostic {
7+
location: Location {
8+
uri: "file:///test/compiler/mir/tagset.rs",
9+
range: Range {
10+
start: Position {
11+
line: 41,
12+
character: 23,
13+
},
14+
end: Position {
15+
line: 41,
16+
character: 28,
17+
},
1618
},
1719
},
18-
},
19-
diagnostic: Diagnostic {
20-
range: Range {
21-
start: Position {
22-
line: 41,
23-
character: 23,
24-
},
25-
end: Position {
26-
line: 41,
27-
character: 28,
20+
diagnostic: Diagnostic {
21+
range: Range {
22+
start: Position {
23+
line: 41,
24+
character: 23,
25+
},
26+
end: Position {
27+
line: 41,
28+
character: 28,
29+
},
2830
},
29-
},
30-
severity: Some(
31-
Warning,
32-
),
33-
code: Some(
34-
String(
35-
"trivially_copy_pass_by_ref",
31+
severity: Some(
32+
Warning,
3633
),
37-
),
38-
source: Some(
39-
"clippy",
40-
),
41-
message: "this argument is passed by reference, but would be more efficient if passed by value\n#[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]\nfor further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref",
42-
related_information: Some(
43-
[
44-
DiagnosticRelatedInformation {
45-
location: Location {
46-
uri: "file:///test/compiler/lib.rs",
47-
range: Range {
48-
start: Position {
49-
line: 0,
50-
character: 8,
51-
},
52-
end: Position {
53-
line: 0,
54-
character: 19,
34+
code: Some(
35+
String(
36+
"trivially_copy_pass_by_ref",
37+
),
38+
),
39+
source: Some(
40+
"clippy",
41+
),
42+
message: "this argument is passed by reference, but would be more efficient if passed by value\n#[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]\nfor further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref",
43+
related_information: Some(
44+
[
45+
DiagnosticRelatedInformation {
46+
location: Location {
47+
uri: "file:///test/compiler/lib.rs",
48+
range: Range {
49+
start: Position {
50+
line: 0,
51+
character: 8,
52+
},
53+
end: Position {
54+
line: 0,
55+
character: 19,
56+
},
5557
},
5658
},
59+
message: "lint level defined here",
5760
},
58-
message: "lint level defined here",
59-
},
60-
],
61-
),
62-
tags: None,
63-
},
64-
fixes: [
65-
CodeAction {
66-
title: "consider passing by value instead: \'self\'",
67-
kind: Some(
68-
"quickfix",
61+
],
6962
),
70-
diagnostics: None,
71-
edit: Some(
72-
WorkspaceEdit {
73-
changes: Some(
74-
{
75-
"file:///test/compiler/mir/tagset.rs": [
76-
TextEdit {
77-
range: Range {
78-
start: Position {
79-
line: 41,
80-
character: 23,
81-
},
82-
end: Position {
83-
line: 41,
84-
character: 28,
63+
tags: None,
64+
},
65+
fixes: [
66+
CodeAction {
67+
title: "consider passing by value instead",
68+
kind: Some(
69+
"quickfix",
70+
),
71+
diagnostics: None,
72+
edit: Some(
73+
WorkspaceEdit {
74+
changes: Some(
75+
{
76+
"file:///test/compiler/mir/tagset.rs": [
77+
TextEdit {
78+
range: Range {
79+
start: Position {
80+
line: 41,
81+
character: 23,
82+
},
83+
end: Position {
84+
line: 41,
85+
character: 28,
86+
},
8587
},
88+
new_text: "self",
8689
},
87-
new_text: "self",
88-
},
89-
],
90-
},
91-
),
92-
document_changes: None,
93-
},
94-
),
95-
command: None,
96-
is_preferred: None,
97-
},
98-
],
99-
}
90+
],
91+
},
92+
),
93+
document_changes: None,
94+
},
95+
),
96+
command: None,
97+
is_preferred: None,
98+
},
99+
],
100+
},
101+
]

0 commit comments

Comments
 (0)