diff options
-rw-r--r-- | Cargo.lock | 1 | ||||
-rw-r--r-- | crates/flycheck/src/lib.rs | 3 | ||||
-rw-r--r-- | crates/rust-analyzer/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/rust-analyzer/src/diagnostics/to_proto.rs | 173 |
4 files changed, 75 insertions, 103 deletions
diff --git a/Cargo.lock b/Cargo.lock index 84ca3344c..edf4749de 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -1435,6 +1435,7 @@ dependencies = [ | |||
1435 | "anyhow", | 1435 | "anyhow", |
1436 | "crossbeam-channel", | 1436 | "crossbeam-channel", |
1437 | "env_logger", | 1437 | "env_logger", |
1438 | "expect", | ||
1438 | "flycheck", | 1439 | "flycheck", |
1439 | "globset", | 1440 | "globset", |
1440 | "insta", | 1441 | "insta", |
diff --git a/crates/flycheck/src/lib.rs b/crates/flycheck/src/lib.rs index 844b093d4..6804d9bda 100644 --- a/crates/flycheck/src/lib.rs +++ b/crates/flycheck/src/lib.rs | |||
@@ -14,7 +14,8 @@ use std::{ | |||
14 | use crossbeam_channel::{never, select, unbounded, Receiver, Sender}; | 14 | use crossbeam_channel::{never, select, unbounded, Receiver, Sender}; |
15 | 15 | ||
16 | pub use cargo_metadata::diagnostic::{ | 16 | pub use cargo_metadata::diagnostic::{ |
17 | Applicability, Diagnostic, DiagnosticLevel, DiagnosticSpan, DiagnosticSpanMacroExpansion, | 17 | Applicability, Diagnostic, DiagnosticCode, DiagnosticLevel, DiagnosticSpan, |
18 | DiagnosticSpanMacroExpansion, | ||
18 | }; | 19 | }; |
19 | 20 | ||
20 | #[derive(Clone, Debug, PartialEq, Eq)] | 21 | #[derive(Clone, Debug, PartialEq, Eq)] |
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml index 837b6714d..0519884fd 100644 --- a/crates/rust-analyzer/Cargo.toml +++ b/crates/rust-analyzer/Cargo.toml | |||
@@ -59,6 +59,7 @@ winapi = "0.3.8" | |||
59 | [dev-dependencies] | 59 | [dev-dependencies] |
60 | tempfile = "3.1.0" | 60 | tempfile = "3.1.0" |
61 | insta = "0.16.0" | 61 | insta = "0.16.0" |
62 | expect = { path = "../expect" } | ||
62 | test_utils = { path = "../test_utils" } | 63 | test_utils = { path = "../test_utils" } |
63 | mbe = { path = "../ra_mbe", package = "ra_mbe" } | 64 | mbe = { path = "../ra_mbe", package = "ra_mbe" } |
64 | tt = { path = "../ra_tt", package = "ra_tt" } | 65 | tt = { path = "../ra_tt", package = "ra_tt" } |
diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs index 3eed118a9..f3a22885e 100644 --- a/crates/rust-analyzer/src/diagnostics/to_proto.rs +++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs | |||
@@ -2,11 +2,7 @@ | |||
2 | //! `cargo check` json format to the LSP diagnostic format. | 2 | //! `cargo check` json format to the LSP diagnostic format. |
3 | use std::{collections::HashMap, path::Path}; | 3 | use std::{collections::HashMap, path::Path}; |
4 | 4 | ||
5 | use flycheck::{Applicability, DiagnosticLevel, DiagnosticSpan, DiagnosticSpanMacroExpansion}; | 5 | use flycheck::{Applicability, DiagnosticLevel, DiagnosticSpan}; |
6 | use lsp_types::{ | ||
7 | Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, Location, | ||
8 | NumberOrString, Position, Range, TextEdit, Url, | ||
9 | }; | ||
10 | use stdx::format_to; | 6 | use stdx::format_to; |
11 | 7 | ||
12 | use crate::{lsp_ext, to_proto::url_from_abs_path}; | 8 | use crate::{lsp_ext, to_proto::url_from_abs_path}; |
@@ -14,22 +10,25 @@ use crate::{lsp_ext, to_proto::url_from_abs_path}; | |||
14 | use super::DiagnosticsConfig; | 10 | use super::DiagnosticsConfig; |
15 | 11 | ||
16 | /// Determines the LSP severity from a diagnostic | 12 | /// Determines the LSP severity from a diagnostic |
17 | fn map_diagnostic_to_severity( | 13 | fn diagnostic_severity( |
18 | config: &DiagnosticsConfig, | 14 | config: &DiagnosticsConfig, |
19 | val: &flycheck::Diagnostic, | 15 | level: flycheck::DiagnosticLevel, |
20 | ) -> Option<DiagnosticSeverity> { | 16 | code: Option<flycheck::DiagnosticCode>, |
21 | let res = match val.level { | 17 | ) -> Option<lsp_types::DiagnosticSeverity> { |
22 | DiagnosticLevel::Ice => DiagnosticSeverity::Error, | 18 | let res = match level { |
23 | DiagnosticLevel::Error => DiagnosticSeverity::Error, | 19 | DiagnosticLevel::Ice => lsp_types::DiagnosticSeverity::Error, |
24 | DiagnosticLevel::Warning => match &val.code { | 20 | DiagnosticLevel::Error => lsp_types::DiagnosticSeverity::Error, |
25 | Some(code) if config.warnings_as_hint.contains(&code.code) => DiagnosticSeverity::Hint, | 21 | DiagnosticLevel::Warning => match &code { |
22 | Some(code) if config.warnings_as_hint.contains(&code.code) => { | ||
23 | lsp_types::DiagnosticSeverity::Hint | ||
24 | } | ||
26 | Some(code) if config.warnings_as_info.contains(&code.code) => { | 25 | Some(code) if config.warnings_as_info.contains(&code.code) => { |
27 | DiagnosticSeverity::Information | 26 | lsp_types::DiagnosticSeverity::Information |
28 | } | 27 | } |
29 | _ => DiagnosticSeverity::Warning, | 28 | _ => lsp_types::DiagnosticSeverity::Warning, |
30 | }, | 29 | }, |
31 | DiagnosticLevel::Note => DiagnosticSeverity::Information, | 30 | DiagnosticLevel::Note => lsp_types::DiagnosticSeverity::Information, |
32 | DiagnosticLevel::Help => DiagnosticSeverity::Hint, | 31 | DiagnosticLevel::Help => lsp_types::DiagnosticSeverity::Hint, |
33 | DiagnosticLevel::Unknown => return None, | 32 | DiagnosticLevel::Unknown => return None, |
34 | }; | 33 | }; |
35 | Some(res) | 34 | Some(res) |
@@ -40,90 +39,50 @@ fn is_from_macro(file_name: &str) -> bool { | |||
40 | file_name.starts_with('<') && file_name.ends_with('>') | 39 | file_name.starts_with('<') && file_name.ends_with('>') |
41 | } | 40 | } |
42 | 41 | ||
43 | /// Converts a Rust macro span to a LSP location recursively | ||
44 | fn map_macro_span_to_location( | ||
45 | span_macro: &DiagnosticSpanMacroExpansion, | ||
46 | workspace_root: &Path, | ||
47 | ) -> Option<Location> { | ||
48 | if !is_from_macro(&span_macro.span.file_name) { | ||
49 | return Some(map_span_to_location(&span_macro.span, workspace_root)); | ||
50 | } | ||
51 | |||
52 | if let Some(expansion) = &span_macro.span.expansion { | ||
53 | return map_macro_span_to_location(&expansion, workspace_root); | ||
54 | } | ||
55 | |||
56 | None | ||
57 | } | ||
58 | |||
59 | /// Converts a Rust span to a LSP location, resolving macro expansion site if neccesary | 42 | /// Converts a Rust span to a LSP location, resolving macro expansion site if neccesary |
60 | fn map_span_to_location(span: &DiagnosticSpan, workspace_root: &Path) -> Location { | 43 | fn location(workspace_root: &Path, span: &DiagnosticSpan) -> lsp_types::Location { |
61 | if span.expansion.is_some() { | 44 | let mut span = span.clone(); |
62 | let expansion = span.expansion.as_ref().unwrap(); | 45 | while let Some(expansion) = span.expansion { |
63 | if let Some(macro_range) = map_macro_span_to_location(&expansion, workspace_root) { | 46 | span = expansion.span; |
64 | return macro_range; | ||
65 | } | ||
66 | } | 47 | } |
67 | 48 | return location_naive(workspace_root, &span); | |
68 | map_span_to_location_naive(span, workspace_root) | ||
69 | } | 49 | } |
70 | 50 | ||
71 | /// Converts a Rust span to a LSP location | 51 | /// Converts a Rust span to a LSP location |
72 | fn map_span_to_location_naive(span: &DiagnosticSpan, workspace_root: &Path) -> Location { | 52 | fn location_naive(workspace_root: &Path, span: &DiagnosticSpan) -> lsp_types::Location { |
73 | let mut file_name = workspace_root.to_path_buf(); | 53 | let file_name = workspace_root.join(&span.file_name); |
74 | file_name.push(&span.file_name); | ||
75 | let uri = url_from_abs_path(&file_name); | 54 | let uri = url_from_abs_path(&file_name); |
76 | 55 | ||
77 | // FIXME: this doesn't handle UTF16 offsets correctly | 56 | // FIXME: this doesn't handle UTF16 offsets correctly |
78 | let range = Range::new( | 57 | let range = lsp_types::Range::new( |
79 | Position::new(span.line_start as u64 - 1, span.column_start as u64 - 1), | 58 | lsp_types::Position::new(span.line_start as u64 - 1, span.column_start as u64 - 1), |
80 | Position::new(span.line_end as u64 - 1, span.column_end as u64 - 1), | 59 | lsp_types::Position::new(span.line_end as u64 - 1, span.column_end as u64 - 1), |
81 | ); | 60 | ); |
82 | 61 | ||
83 | Location { uri, range } | 62 | lsp_types::Location { uri, range } |
84 | } | 63 | } |
85 | 64 | ||
86 | /// Converts a secondary Rust span to a LSP related information | 65 | /// Converts a secondary Rust span to a LSP related inflocation(ormation |
87 | /// | 66 | /// |
88 | /// If the span is unlabelled this will return `None`. | 67 | /// If the span is unlabelled this will return `None`. |
89 | fn map_secondary_span_to_related( | 68 | fn diagnostic_related_information( |
90 | span: &DiagnosticSpan, | ||
91 | workspace_root: &Path, | 69 | workspace_root: &Path, |
92 | ) -> Option<DiagnosticRelatedInformation> { | 70 | span: &DiagnosticSpan, |
71 | ) -> Option<lsp_types::DiagnosticRelatedInformation> { | ||
93 | let message = span.label.clone()?; | 72 | let message = span.label.clone()?; |
94 | let location = map_span_to_location(span, workspace_root); | 73 | let location = location(workspace_root, span); |
95 | Some(DiagnosticRelatedInformation { location, message }) | 74 | Some(lsp_types::DiagnosticRelatedInformation { location, message }) |
96 | } | ||
97 | |||
98 | /// Determines if diagnostic is related to unused code | ||
99 | fn is_unused_or_unnecessary(rd: &flycheck::Diagnostic) -> bool { | ||
100 | match &rd.code { | ||
101 | Some(code) => match code.code.as_str() { | ||
102 | "dead_code" | "unknown_lints" | "unreachable_code" | "unused_attributes" | ||
103 | | "unused_imports" | "unused_macros" | "unused_variables" => true, | ||
104 | _ => false, | ||
105 | }, | ||
106 | None => false, | ||
107 | } | ||
108 | } | ||
109 | |||
110 | /// Determines if diagnostic is related to deprecated code | ||
111 | fn is_deprecated(rd: &flycheck::Diagnostic) -> bool { | ||
112 | match &rd.code { | ||
113 | Some(code) => code.code.as_str() == "deprecated", | ||
114 | None => false, | ||
115 | } | ||
116 | } | 75 | } |
117 | 76 | ||
118 | enum MappedRustChildDiagnostic { | 77 | enum MappedRustChildDiagnostic { |
119 | Related(DiagnosticRelatedInformation), | 78 | Related(lsp_types::DiagnosticRelatedInformation), |
120 | SuggestedFix(lsp_ext::CodeAction), | 79 | SuggestedFix(lsp_ext::CodeAction), |
121 | MessageLine(String), | 80 | MessageLine(String), |
122 | } | 81 | } |
123 | 82 | ||
124 | fn map_rust_child_diagnostic( | 83 | fn map_rust_child_diagnostic( |
125 | rd: &flycheck::Diagnostic, | ||
126 | workspace_root: &Path, | 84 | workspace_root: &Path, |
85 | rd: &flycheck::Diagnostic, | ||
127 | ) -> MappedRustChildDiagnostic { | 86 | ) -> MappedRustChildDiagnostic { |
128 | let spans: Vec<&DiagnosticSpan> = rd.spans.iter().filter(|s| s.is_primary).collect(); | 87 | let spans: Vec<&DiagnosticSpan> = rd.spans.iter().filter(|s| s.is_primary).collect(); |
129 | if spans.is_empty() { | 88 | if spans.is_empty() { |
@@ -132,21 +91,20 @@ fn map_rust_child_diagnostic( | |||
132 | return MappedRustChildDiagnostic::MessageLine(rd.message.clone()); | 91 | return MappedRustChildDiagnostic::MessageLine(rd.message.clone()); |
133 | } | 92 | } |
134 | 93 | ||
135 | let mut edit_map: HashMap<Url, Vec<TextEdit>> = HashMap::new(); | 94 | let mut edit_map: HashMap<lsp_types::Url, Vec<lsp_types::TextEdit>> = HashMap::new(); |
136 | for &span in &spans { | 95 | for &span in &spans { |
137 | match (&span.suggestion_applicability, &span.suggested_replacement) { | 96 | if let (Some(Applicability::MachineApplicable), Some(suggested_replacement)) = |
138 | (Some(Applicability::MachineApplicable), Some(suggested_replacement)) => { | 97 | (&span.suggestion_applicability, &span.suggested_replacement) |
139 | let location = map_span_to_location(span, workspace_root); | 98 | { |
140 | let edit = TextEdit::new(location.range, suggested_replacement.clone()); | 99 | let location = location(workspace_root, span); |
141 | edit_map.entry(location.uri).or_default().push(edit); | 100 | let edit = lsp_types::TextEdit::new(location.range, suggested_replacement.clone()); |
142 | } | 101 | edit_map.entry(location.uri).or_default().push(edit); |
143 | _ => {} | ||
144 | } | 102 | } |
145 | } | 103 | } |
146 | 104 | ||
147 | if edit_map.is_empty() { | 105 | if edit_map.is_empty() { |
148 | MappedRustChildDiagnostic::Related(DiagnosticRelatedInformation { | 106 | MappedRustChildDiagnostic::Related(lsp_types::DiagnosticRelatedInformation { |
149 | location: map_span_to_location(spans[0], workspace_root), | 107 | location: location(workspace_root, spans[0]), |
150 | message: rd.message.clone(), | 108 | message: rd.message.clone(), |
151 | }) | 109 | }) |
152 | } else { | 110 | } else { |
@@ -167,8 +125,8 @@ fn map_rust_child_diagnostic( | |||
167 | 125 | ||
168 | #[derive(Debug)] | 126 | #[derive(Debug)] |
169 | pub(crate) struct MappedRustDiagnostic { | 127 | pub(crate) struct MappedRustDiagnostic { |
170 | pub(crate) location: Location, | 128 | pub(crate) location: lsp_types::Location, |
171 | pub(crate) diagnostic: Diagnostic, | 129 | pub(crate) diagnostic: lsp_types::Diagnostic, |
172 | pub(crate) fixes: Vec<lsp_ext::CodeAction>, | 130 | pub(crate) fixes: Vec<lsp_ext::CodeAction>, |
173 | } | 131 | } |
174 | 132 | ||
@@ -192,7 +150,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp( | |||
192 | return Vec::new(); | 150 | return Vec::new(); |
193 | } | 151 | } |
194 | 152 | ||
195 | let severity = map_diagnostic_to_severity(config, rd); | 153 | let severity = diagnostic_severity(config, rd.level.clone(), rd.code.clone()); |
196 | 154 | ||
197 | let mut source = String::from("rustc"); | 155 | let mut source = String::from("rustc"); |
198 | let mut code = rd.code.as_ref().map(|c| c.code.clone()); | 156 | let mut code = rd.code.as_ref().map(|c| c.code.clone()); |
@@ -210,7 +168,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp( | |||
210 | let mut tags = Vec::new(); | 168 | let mut tags = Vec::new(); |
211 | 169 | ||
212 | for secondary_span in rd.spans.iter().filter(|s| !s.is_primary) { | 170 | for secondary_span in rd.spans.iter().filter(|s| !s.is_primary) { |
213 | let related = map_secondary_span_to_related(secondary_span, workspace_root); | 171 | let related = diagnostic_related_information(workspace_root, secondary_span); |
214 | if let Some(related) = related { | 172 | if let Some(related) = related { |
215 | related_information.push(related); | 173 | related_information.push(related); |
216 | } | 174 | } |
@@ -219,7 +177,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp( | |||
219 | let mut fixes = Vec::new(); | 177 | let mut fixes = Vec::new(); |
220 | let mut message = rd.message.clone(); | 178 | let mut message = rd.message.clone(); |
221 | for child in &rd.children { | 179 | for child in &rd.children { |
222 | let child = map_rust_child_diagnostic(&child, workspace_root); | 180 | let child = map_rust_child_diagnostic(workspace_root, &child); |
223 | match child { | 181 | match child { |
224 | MappedRustChildDiagnostic::Related(related) => related_information.push(related), | 182 | MappedRustChildDiagnostic::Related(related) => related_information.push(related), |
225 | MappedRustChildDiagnostic::SuggestedFix(code_action) => fixes.push(code_action), | 183 | MappedRustChildDiagnostic::SuggestedFix(code_action) => fixes.push(code_action), |
@@ -233,18 +191,30 @@ pub(crate) fn map_rust_diagnostic_to_lsp( | |||
233 | } | 191 | } |
234 | } | 192 | } |
235 | 193 | ||
236 | if is_unused_or_unnecessary(rd) { | 194 | if let Some(code) = &rd.code { |
237 | tags.push(DiagnosticTag::Unnecessary); | 195 | let code = code.code.as_str(); |
238 | } | 196 | if matches!( |
197 | code, | ||
198 | "dead_code" | ||
199 | | "unknown_lints" | ||
200 | | "unreachable_code" | ||
201 | | "unused_attributes" | ||
202 | | "unused_imports" | ||
203 | | "unused_macros" | ||
204 | | "unused_variables" | ||
205 | ) { | ||
206 | tags.push(lsp_types::DiagnosticTag::Unnecessary); | ||
207 | } | ||
239 | 208 | ||
240 | if is_deprecated(rd) { | 209 | if matches!(code, "deprecated") { |
241 | tags.push(DiagnosticTag::Deprecated); | 210 | tags.push(lsp_types::DiagnosticTag::Deprecated); |
211 | } | ||
242 | } | 212 | } |
243 | 213 | ||
244 | primary_spans | 214 | primary_spans |
245 | .iter() | 215 | .iter() |
246 | .map(|primary_span| { | 216 | .map(|primary_span| { |
247 | let location = map_span_to_location(&primary_span, workspace_root); | 217 | let location = location(workspace_root, &primary_span); |
248 | 218 | ||
249 | let mut message = message.clone(); | 219 | let mut message = message.clone(); |
250 | if needs_primary_span_label { | 220 | if needs_primary_span_label { |
@@ -256,17 +226,16 @@ pub(crate) fn map_rust_diagnostic_to_lsp( | |||
256 | // If error occurs from macro expansion, add related info pointing to | 226 | // If error occurs from macro expansion, add related info pointing to |
257 | // where the error originated | 227 | // where the error originated |
258 | if !is_from_macro(&primary_span.file_name) && primary_span.expansion.is_some() { | 228 | if !is_from_macro(&primary_span.file_name) && primary_span.expansion.is_some() { |
259 | let def_loc = map_span_to_location_naive(&primary_span, workspace_root); | 229 | related_information.push(lsp_types::DiagnosticRelatedInformation { |
260 | related_information.push(DiagnosticRelatedInformation { | 230 | location: location_naive(workspace_root, &primary_span), |
261 | location: def_loc, | ||
262 | message: "Error originated from macro here".to_string(), | 231 | message: "Error originated from macro here".to_string(), |
263 | }); | 232 | }); |
264 | } | 233 | } |
265 | 234 | ||
266 | let diagnostic = Diagnostic { | 235 | let diagnostic = lsp_types::Diagnostic { |
267 | range: location.range, | 236 | range: location.range, |
268 | severity, | 237 | severity, |
269 | code: code.clone().map(NumberOrString::String), | 238 | code: code.clone().map(lsp_types::NumberOrString::String), |
270 | source: Some(source.clone()), | 239 | source: Some(source.clone()), |
271 | message, | 240 | message, |
272 | related_information: if related_information.is_empty() { | 241 | related_information: if related_information.is_empty() { |