aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorEmil Lauridsen <[email protected]>2019-12-27 10:10:07 +0000
committerEmil Lauridsen <[email protected]>2019-12-27 10:10:07 +0000
commit428a6ff5b8bad2c80a3522599195bf2a393f744e (patch)
tree9707dd343b8f00f01041a7995536aac84b137291 /crates
parent0cdbd0814958e174c5481d6bf16bd2a7e53ec981 (diff)
Move cargo watch functionality to separate crate
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_cargo_watch/Cargo.toml17
-rw-r--r--crates/ra_cargo_watch/src/conv.rs280
-rw-r--r--crates/ra_cargo_watch/src/conv/snapshots/test__snap_clippy_pass_by_ref.snap (renamed from crates/ra_lsp_server/src/snapshots/test__snap_clippy_pass_by_ref.snap)2
-rw-r--r--crates/ra_cargo_watch/src/conv/snapshots/test__snap_handles_macro_location.snap (renamed from crates/ra_lsp_server/src/snapshots/test__snap_handles_macro_location.snap)2
-rw-r--r--crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_incompatible_type_for_trait.snap (renamed from crates/ra_lsp_server/src/snapshots/test__snap_rustc_incompatible_type_for_trait.snap)2
-rw-r--r--crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_mismatched_type.snap (renamed from crates/ra_lsp_server/src/snapshots/test__snap_rustc_mismatched_type.snap)2
-rw-r--r--crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_unused_variable.snap (renamed from crates/ra_lsp_server/src/snapshots/test__snap_rustc_unused_variable.snap)2
-rw-r--r--crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_wrong_number_of_parameters.snap (renamed from crates/ra_lsp_server/src/snapshots/test__snap_rustc_wrong_number_of_parameters.snap)2
-rw-r--r--crates/ra_cargo_watch/src/conv/test.rs698
-rw-r--r--crates/ra_cargo_watch/src/lib.rs345
-rw-r--r--crates/ra_lsp_server/Cargo.toml3
-rw-r--r--crates/ra_lsp_server/src/cargo_check.rs1316
-rw-r--r--crates/ra_lsp_server/src/lib.rs1
-rw-r--r--crates/ra_lsp_server/src/main_loop.rs12
-rw-r--r--crates/ra_lsp_server/src/world.rs10
15 files changed, 1358 insertions, 1336 deletions
diff --git a/crates/ra_cargo_watch/Cargo.toml b/crates/ra_cargo_watch/Cargo.toml
new file mode 100644
index 000000000..bcc4648ff
--- /dev/null
+++ b/crates/ra_cargo_watch/Cargo.toml
@@ -0,0 +1,17 @@
1[package]
2edition = "2018"
3name = "ra_cargo_watch"
4version = "0.1.0"
5authors = ["rust-analyzer developers"]
6
7[dependencies]
8crossbeam-channel = "0.4"
9lsp-types = { version = "0.67.0", features = ["proposed"] }
10log = "0.4.3"
11cargo_metadata = "0.9.1"
12jod-thread = "0.1.0"
13parking_lot = "0.10.0"
14
15[dev-dependencies]
16insta = "0.12.0"
17serde_json = "1.0" \ No newline at end of file
diff --git a/crates/ra_cargo_watch/src/conv.rs b/crates/ra_cargo_watch/src/conv.rs
new file mode 100644
index 000000000..3bd4bf7a5
--- /dev/null
+++ b/crates/ra_cargo_watch/src/conv.rs
@@ -0,0 +1,280 @@
1//! This module provides the functionality needed to convert diagnostics from
2//! `cargo check` json format to the LSP diagnostic format.
3use cargo_metadata::diagnostic::{
4 Applicability, Diagnostic as RustDiagnostic, DiagnosticLevel, DiagnosticSpan,
5 DiagnosticSpanMacroExpansion,
6};
7use lsp_types::{
8 Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, Location,
9 NumberOrString, Position, Range, Url,
10};
11use std::{fmt::Write, path::PathBuf};
12
13#[cfg(test)]
14mod test;
15
16/// Converts a Rust level string to a LSP severity
17fn map_level_to_severity(val: DiagnosticLevel) -> Option<DiagnosticSeverity> {
18 match val {
19 DiagnosticLevel::Ice => Some(DiagnosticSeverity::Error),
20 DiagnosticLevel::Error => Some(DiagnosticSeverity::Error),
21 DiagnosticLevel::Warning => Some(DiagnosticSeverity::Warning),
22 DiagnosticLevel::Note => Some(DiagnosticSeverity::Information),
23 DiagnosticLevel::Help => Some(DiagnosticSeverity::Hint),
24 DiagnosticLevel::Unknown => None,
25 }
26}
27
28/// Check whether a file name is from macro invocation
29fn is_from_macro(file_name: &str) -> bool {
30 file_name.starts_with('<') && file_name.ends_with('>')
31}
32
33/// Converts a Rust macro span to a LSP location recursively
34fn map_macro_span_to_location(
35 span_macro: &DiagnosticSpanMacroExpansion,
36 workspace_root: &PathBuf,
37) -> Option<Location> {
38 if !is_from_macro(&span_macro.span.file_name) {
39 return Some(map_span_to_location(&span_macro.span, workspace_root));
40 }
41
42 if let Some(expansion) = &span_macro.span.expansion {
43 return map_macro_span_to_location(&expansion, workspace_root);
44 }
45
46 None
47}
48
49/// Converts a Rust span to a LSP location
50fn map_span_to_location(span: &DiagnosticSpan, workspace_root: &PathBuf) -> Location {
51 if is_from_macro(&span.file_name) && span.expansion.is_some() {
52 let expansion = span.expansion.as_ref().unwrap();
53 if let Some(macro_range) = map_macro_span_to_location(&expansion, workspace_root) {
54 return macro_range;
55 }
56 }
57
58 let mut file_name = workspace_root.clone();
59 file_name.push(&span.file_name);
60 let uri = Url::from_file_path(file_name).unwrap();
61
62 let range = Range::new(
63 Position::new(span.line_start as u64 - 1, span.column_start as u64 - 1),
64 Position::new(span.line_end as u64 - 1, span.column_end as u64 - 1),
65 );
66
67 Location { uri, range }
68}
69
70/// Converts a secondary Rust span to a LSP related information
71///
72/// If the span is unlabelled this will return `None`.
73fn map_secondary_span_to_related(
74 span: &DiagnosticSpan,
75 workspace_root: &PathBuf,
76) -> Option<DiagnosticRelatedInformation> {
77 if let Some(label) = &span.label {
78 let location = map_span_to_location(span, workspace_root);
79 Some(DiagnosticRelatedInformation { location, message: label.clone() })
80 } else {
81 // Nothing to label this with
82 None
83 }
84}
85
86/// Determines if diagnostic is related to unused code
87fn is_unused_or_unnecessary(rd: &RustDiagnostic) -> bool {
88 if let Some(code) = &rd.code {
89 match code.code.as_str() {
90 "dead_code" | "unknown_lints" | "unreachable_code" | "unused_attributes"
91 | "unused_imports" | "unused_macros" | "unused_variables" => true,
92 _ => false,
93 }
94 } else {
95 false
96 }
97}
98
99/// Determines if diagnostic is related to deprecated code
100fn is_deprecated(rd: &RustDiagnostic) -> bool {
101 if let Some(code) = &rd.code {
102 match code.code.as_str() {
103 "deprecated" => true,
104 _ => false,
105 }
106 } else {
107 false
108 }
109}
110
111#[derive(Debug)]
112pub struct SuggestedFix {
113 pub title: String,
114 pub location: Location,
115 pub replacement: String,
116 pub applicability: Applicability,
117 pub diagnostics: Vec<Diagnostic>,
118}
119
120impl std::cmp::PartialEq<SuggestedFix> for SuggestedFix {
121 fn eq(&self, other: &SuggestedFix) -> bool {
122 if self.title == other.title
123 && self.location == other.location
124 && self.replacement == other.replacement
125 {
126 // Applicability doesn't impl PartialEq...
127 match (&self.applicability, &other.applicability) {
128 (Applicability::MachineApplicable, Applicability::MachineApplicable) => true,
129 (Applicability::HasPlaceholders, Applicability::HasPlaceholders) => true,
130 (Applicability::MaybeIncorrect, Applicability::MaybeIncorrect) => true,
131 (Applicability::Unspecified, Applicability::Unspecified) => true,
132 _ => false,
133 }
134 } else {
135 false
136 }
137 }
138}
139
140enum MappedRustChildDiagnostic {
141 Related(DiagnosticRelatedInformation),
142 SuggestedFix(SuggestedFix),
143 MessageLine(String),
144}
145
146fn map_rust_child_diagnostic(
147 rd: &RustDiagnostic,
148 workspace_root: &PathBuf,
149) -> MappedRustChildDiagnostic {
150 let span: &DiagnosticSpan = match rd.spans.iter().find(|s| s.is_primary) {
151 Some(span) => span,
152 None => {
153 // `rustc` uses these spanless children as a way to print multi-line
154 // messages
155 return MappedRustChildDiagnostic::MessageLine(rd.message.clone());
156 }
157 };
158
159 // If we have a primary span use its location, otherwise use the parent
160 let location = map_span_to_location(&span, workspace_root);
161
162 if let Some(suggested_replacement) = &span.suggested_replacement {
163 // Include our replacement in the title unless it's empty
164 let title = if !suggested_replacement.is_empty() {
165 format!("{}: '{}'", rd.message, suggested_replacement)
166 } else {
167 rd.message.clone()
168 };
169
170 MappedRustChildDiagnostic::SuggestedFix(SuggestedFix {
171 title,
172 location,
173 replacement: suggested_replacement.clone(),
174 applicability: span.suggestion_applicability.clone().unwrap_or(Applicability::Unknown),
175 diagnostics: vec![],
176 })
177 } else {
178 MappedRustChildDiagnostic::Related(DiagnosticRelatedInformation {
179 location,
180 message: rd.message.clone(),
181 })
182 }
183}
184
185#[derive(Debug)]
186pub(crate) struct MappedRustDiagnostic {
187 pub location: Location,
188 pub diagnostic: Diagnostic,
189 pub suggested_fixes: Vec<SuggestedFix>,
190}
191
192/// Converts a Rust root diagnostic to LSP form
193///
194/// This flattens the Rust diagnostic by:
195///
196/// 1. Creating a LSP diagnostic with the root message and primary span.
197/// 2. Adding any labelled secondary spans to `relatedInformation`
198/// 3. Categorising child diagnostics as either `SuggestedFix`es,
199/// `relatedInformation` or additional message lines.
200///
201/// If the diagnostic has no primary span this will return `None`
202pub(crate) fn map_rust_diagnostic_to_lsp(
203 rd: &RustDiagnostic,
204 workspace_root: &PathBuf,
205) -> Option<MappedRustDiagnostic> {
206 let primary_span = rd.spans.iter().find(|s| s.is_primary)?;
207
208 let location = map_span_to_location(&primary_span, workspace_root);
209
210 let severity = map_level_to_severity(rd.level);
211 let mut primary_span_label = primary_span.label.as_ref();
212
213 let mut source = String::from("rustc");
214 let mut code = rd.code.as_ref().map(|c| c.code.clone());
215 if let Some(code_val) = &code {
216 // See if this is an RFC #2103 scoped lint (e.g. from Clippy)
217 let scoped_code: Vec<&str> = code_val.split("::").collect();
218 if scoped_code.len() == 2 {
219 source = String::from(scoped_code[0]);
220 code = Some(String::from(scoped_code[1]));
221 }
222 }
223
224 let mut related_information = vec![];
225 let mut tags = vec![];
226
227 for secondary_span in rd.spans.iter().filter(|s| !s.is_primary) {
228 let related = map_secondary_span_to_related(secondary_span, workspace_root);
229 if let Some(related) = related {
230 related_information.push(related);
231 }
232 }
233
234 let mut suggested_fixes = vec![];
235 let mut message = rd.message.clone();
236 for child in &rd.children {
237 let child = map_rust_child_diagnostic(&child, workspace_root);
238 match child {
239 MappedRustChildDiagnostic::Related(related) => related_information.push(related),
240 MappedRustChildDiagnostic::SuggestedFix(suggested_fix) => {
241 suggested_fixes.push(suggested_fix)
242 }
243 MappedRustChildDiagnostic::MessageLine(message_line) => {
244 write!(&mut message, "\n{}", message_line).unwrap();
245
246 // These secondary messages usually duplicate the content of the
247 // primary span label.
248 primary_span_label = None;
249 }
250 }
251 }
252
253 if let Some(primary_span_label) = primary_span_label {
254 write!(&mut message, "\n{}", primary_span_label).unwrap();
255 }
256
257 if is_unused_or_unnecessary(rd) {
258 tags.push(DiagnosticTag::Unnecessary);
259 }
260
261 if is_deprecated(rd) {
262 tags.push(DiagnosticTag::Deprecated);
263 }
264
265 let diagnostic = Diagnostic {
266 range: location.range,
267 severity,
268 code: code.map(NumberOrString::String),
269 source: Some(source),
270 message,
271 related_information: if !related_information.is_empty() {
272 Some(related_information)
273 } else {
274 None
275 },
276 tags: if !tags.is_empty() { Some(tags) } else { None },
277 };
278
279 Some(MappedRustDiagnostic { location, diagnostic, suggested_fixes })
280}
diff --git a/crates/ra_lsp_server/src/snapshots/test__snap_clippy_pass_by_ref.snap b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_clippy_pass_by_ref.snap
index a5ce29157..cb0920914 100644
--- a/crates/ra_lsp_server/src/snapshots/test__snap_clippy_pass_by_ref.snap
+++ b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_clippy_pass_by_ref.snap
@@ -1,5 +1,5 @@
1--- 1---
2source: crates/ra_lsp_server/src/cargo_check.rs 2source: crates/ra_cargo_watch/src/conv/test.rs
3expression: diag 3expression: diag
4--- 4---
5MappedRustDiagnostic { 5MappedRustDiagnostic {
diff --git a/crates/ra_lsp_server/src/snapshots/test__snap_handles_macro_location.snap b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_handles_macro_location.snap
index 07e363ebf..19510ecc1 100644
--- a/crates/ra_lsp_server/src/snapshots/test__snap_handles_macro_location.snap
+++ b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_handles_macro_location.snap
@@ -1,5 +1,5 @@
1--- 1---
2source: crates/ra_lsp_server/src/cargo_check.rs 2source: crates/ra_cargo_watch/src/conv/test.rs
3expression: diag 3expression: diag
4--- 4---
5MappedRustDiagnostic { 5MappedRustDiagnostic {
diff --git a/crates/ra_lsp_server/src/snapshots/test__snap_rustc_incompatible_type_for_trait.snap b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_incompatible_type_for_trait.snap
index 85a87db0b..cf683e4b6 100644
--- a/crates/ra_lsp_server/src/snapshots/test__snap_rustc_incompatible_type_for_trait.snap
+++ b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_incompatible_type_for_trait.snap
@@ -1,5 +1,5 @@
1--- 1---
2source: crates/ra_lsp_server/src/cargo_check.rs 2source: crates/ra_cargo_watch/src/conv/test.rs
3expression: diag 3expression: diag
4--- 4---
5MappedRustDiagnostic { 5MappedRustDiagnostic {
diff --git a/crates/ra_lsp_server/src/snapshots/test__snap_rustc_mismatched_type.snap b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_mismatched_type.snap
index 69cb8badf..8c1483c74 100644
--- a/crates/ra_lsp_server/src/snapshots/test__snap_rustc_mismatched_type.snap
+++ b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_mismatched_type.snap
@@ -1,5 +1,5 @@
1--- 1---
2source: crates/ra_lsp_server/src/cargo_check.rs 2source: crates/ra_cargo_watch/src/conv/test.rs
3expression: diag 3expression: diag
4--- 4---
5MappedRustDiagnostic { 5MappedRustDiagnostic {
diff --git a/crates/ra_lsp_server/src/snapshots/test__snap_rustc_unused_variable.snap b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_unused_variable.snap
index 33a3e3034..eb5a2247b 100644
--- a/crates/ra_lsp_server/src/snapshots/test__snap_rustc_unused_variable.snap
+++ b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_unused_variable.snap
@@ -1,5 +1,5 @@
1--- 1---
2source: crates/ra_lsp_server/src/cargo_check.rs 2source: crates/ra_cargo_watch/src/conv/test.rs
3expression: diag 3expression: diag
4--- 4---
5MappedRustDiagnostic { 5MappedRustDiagnostic {
diff --git a/crates/ra_lsp_server/src/snapshots/test__snap_rustc_wrong_number_of_parameters.snap b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_wrong_number_of_parameters.snap
index ced6fa4df..2f4518931 100644
--- a/crates/ra_lsp_server/src/snapshots/test__snap_rustc_wrong_number_of_parameters.snap
+++ b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_wrong_number_of_parameters.snap
@@ -1,5 +1,5 @@
1--- 1---
2source: crates/ra_lsp_server/src/cargo_check.rs 2source: crates/ra_cargo_watch/src/conv/test.rs
3expression: diag 3expression: diag
4--- 4---
5MappedRustDiagnostic { 5MappedRustDiagnostic {
diff --git a/crates/ra_cargo_watch/src/conv/test.rs b/crates/ra_cargo_watch/src/conv/test.rs
new file mode 100644
index 000000000..69a07be11
--- /dev/null
+++ b/crates/ra_cargo_watch/src/conv/test.rs
@@ -0,0 +1,698 @@
1use crate::*;
2
3fn parse_diagnostic(val: &str) -> cargo_metadata::diagnostic::Diagnostic {
4 serde_json::from_str::<cargo_metadata::diagnostic::Diagnostic>(val).unwrap()
5}
6
7#[test]
8fn snap_rustc_incompatible_type_for_trait() {
9 let diag = parse_diagnostic(
10 r##"{
11 "message": "method `next` has an incompatible type for trait",
12 "code": {
13 "code": "E0053",
14 "explanation": "\nThe parameters of any trait method must match between a trait implementation\nand the trait definition.\n\nHere are a couple examples of this error:\n\n```compile_fail,E0053\ntrait Foo {\n fn foo(x: u16);\n fn bar(&self);\n}\n\nstruct Bar;\n\nimpl Foo for Bar {\n // error, expected u16, found i16\n fn foo(x: i16) { }\n\n // error, types differ in mutability\n fn bar(&mut self) { }\n}\n```\n"
15 },
16 "level": "error",
17 "spans": [
18 {
19 "file_name": "compiler/ty/list_iter.rs",
20 "byte_start": 1307,
21 "byte_end": 1350,
22 "line_start": 52,
23 "line_end": 52,
24 "column_start": 5,
25 "column_end": 48,
26 "is_primary": true,
27 "text": [
28 {
29 "text": " fn next(&self) -> Option<&'list ty::Ref<M>> {",
30 "highlight_start": 5,
31 "highlight_end": 48
32 }
33 ],
34 "label": "types differ in mutability",
35 "suggested_replacement": null,
36 "suggestion_applicability": null,
37 "expansion": null
38 }
39 ],
40 "children": [
41 {
42 "message": "expected type `fn(&mut ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&ty::Ref<M>>`\n found type `fn(&ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&'list ty::Ref<M>>`",
43 "code": null,
44 "level": "note",
45 "spans": [],
46 "children": [],
47 "rendered": null
48 }
49 ],
50 "rendered": "error[E0053]: method `next` has an incompatible type for trait\n --> compiler/ty/list_iter.rs:52:5\n |\n52 | fn next(&self) -> Option<&'list ty::Ref<M>> {\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ types differ in mutability\n |\n = note: expected type `fn(&mut ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&ty::Ref<M>>`\n found type `fn(&ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&'list ty::Ref<M>>`\n\n"
51 }
52 "##,
53 );
54
55 let workspace_root = PathBuf::from("/test/");
56 let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic");
57 insta::assert_debug_snapshot!(diag);
58}
59
60#[test]
61fn snap_rustc_unused_variable() {
62 let diag = parse_diagnostic(
63 r##"{
64"message": "unused variable: `foo`",
65"code": {
66 "code": "unused_variables",
67 "explanation": null
68},
69"level": "warning",
70"spans": [
71 {
72 "file_name": "driver/subcommand/repl.rs",
73 "byte_start": 9228,
74 "byte_end": 9231,
75 "line_start": 291,
76 "line_end": 291,
77 "column_start": 9,
78 "column_end": 12,
79 "is_primary": true,
80 "text": [
81 {
82 "text": " let foo = 42;",
83 "highlight_start": 9,
84 "highlight_end": 12
85 }
86 ],
87 "label": null,
88 "suggested_replacement": null,
89 "suggestion_applicability": null,
90 "expansion": null
91 }
92],
93"children": [
94 {
95 "message": "#[warn(unused_variables)] on by default",
96 "code": null,
97 "level": "note",
98 "spans": [],
99 "children": [],
100 "rendered": null
101 },
102 {
103 "message": "consider prefixing with an underscore",
104 "code": null,
105 "level": "help",
106 "spans": [
107 {
108 "file_name": "driver/subcommand/repl.rs",
109 "byte_start": 9228,
110 "byte_end": 9231,
111 "line_start": 291,
112 "line_end": 291,
113 "column_start": 9,
114 "column_end": 12,
115 "is_primary": true,
116 "text": [
117 {
118 "text": " let foo = 42;",
119 "highlight_start": 9,
120 "highlight_end": 12
121 }
122 ],
123 "label": null,
124 "suggested_replacement": "_foo",
125 "suggestion_applicability": "MachineApplicable",
126 "expansion": null
127 }
128 ],
129 "children": [],
130 "rendered": null
131 }
132],
133"rendered": "warning: unused variable: `foo`\n --> driver/subcommand/repl.rs:291:9\n |\n291 | let foo = 42;\n | ^^^ help: consider prefixing with an underscore: `_foo`\n |\n = note: #[warn(unused_variables)] on by default\n\n"
134}"##,
135 );
136
137 let workspace_root = PathBuf::from("/test/");
138 let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic");
139 insta::assert_debug_snapshot!(diag);
140}
141
142#[test]
143fn snap_rustc_wrong_number_of_parameters() {
144 let diag = parse_diagnostic(
145 r##"{
146"message": "this function takes 2 parameters but 3 parameters were supplied",
147"code": {
148 "code": "E0061",
149 "explanation": "\nThe number of arguments passed to a function must match the number of arguments\nspecified in the function signature.\n\nFor example, a function like:\n\n```\nfn f(a: u16, b: &str) {}\n```\n\nMust always be called with exactly two arguments, e.g., `f(2, \"test\")`.\n\nNote that Rust does not have a notion of optional function arguments or\nvariadic functions (except for its C-FFI).\n"
150},
151"level": "error",
152"spans": [
153 {
154 "file_name": "compiler/ty/select.rs",
155 "byte_start": 8787,
156 "byte_end": 9241,
157 "line_start": 219,
158 "line_end": 231,
159 "column_start": 5,
160 "column_end": 6,
161 "is_primary": false,
162 "text": [
163 {
164 "text": " pub fn add_evidence(",
165 "highlight_start": 5,
166 "highlight_end": 25
167 },
168 {
169 "text": " &mut self,",
170 "highlight_start": 1,
171 "highlight_end": 19
172 },
173 {
174 "text": " target_poly: &ty::Ref<ty::Poly>,",
175 "highlight_start": 1,
176 "highlight_end": 41
177 },
178 {
179 "text": " evidence_poly: &ty::Ref<ty::Poly>,",
180 "highlight_start": 1,
181 "highlight_end": 43
182 },
183 {
184 "text": " ) {",
185 "highlight_start": 1,
186 "highlight_end": 8
187 },
188 {
189 "text": " match target_poly {",
190 "highlight_start": 1,
191 "highlight_end": 28
192 },
193 {
194 "text": " ty::Ref::Var(tvar, _) => self.add_var_evidence(tvar, evidence_poly),",
195 "highlight_start": 1,
196 "highlight_end": 81
197 },
198 {
199 "text": " ty::Ref::Fixed(target_ty) => {",
200 "highlight_start": 1,
201 "highlight_end": 43
202 },
203 {
204 "text": " let evidence_ty = evidence_poly.resolve_to_ty();",
205 "highlight_start": 1,
206 "highlight_end": 65
207 },
208 {
209 "text": " self.add_evidence_ty(target_ty, evidence_poly, evidence_ty)",
210 "highlight_start": 1,
211 "highlight_end": 76
212 },
213 {
214 "text": " }",
215 "highlight_start": 1,
216 "highlight_end": 14
217 },
218 {
219 "text": " }",
220 "highlight_start": 1,
221 "highlight_end": 10
222 },
223 {
224 "text": " }",
225 "highlight_start": 1,
226 "highlight_end": 6
227 }
228 ],
229 "label": "defined here",
230 "suggested_replacement": null,
231 "suggestion_applicability": null,
232 "expansion": null
233 },
234 {
235 "file_name": "compiler/ty/select.rs",
236 "byte_start": 4045,
237 "byte_end": 4057,
238 "line_start": 104,
239 "line_end": 104,
240 "column_start": 18,
241 "column_end": 30,
242 "is_primary": true,
243 "text": [
244 {
245 "text": " self.add_evidence(target_fixed, evidence_fixed, false);",
246 "highlight_start": 18,
247 "highlight_end": 30
248 }
249 ],
250 "label": "expected 2 parameters",
251 "suggested_replacement": null,
252 "suggestion_applicability": null,
253 "expansion": null
254 }
255],
256"children": [],
257"rendered": "error[E0061]: this function takes 2 parameters but 3 parameters were supplied\n --> compiler/ty/select.rs:104:18\n |\n104 | self.add_evidence(target_fixed, evidence_fixed, false);\n | ^^^^^^^^^^^^ expected 2 parameters\n...\n219 | / pub fn add_evidence(\n220 | | &mut self,\n221 | | target_poly: &ty::Ref<ty::Poly>,\n222 | | evidence_poly: &ty::Ref<ty::Poly>,\n... |\n230 | | }\n231 | | }\n | |_____- defined here\n\n"
258}"##,
259 );
260
261 let workspace_root = PathBuf::from("/test/");
262 let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic");
263 insta::assert_debug_snapshot!(diag);
264}
265
266#[test]
267fn snap_clippy_pass_by_ref() {
268 let diag = parse_diagnostic(
269 r##"{
270"message": "this argument is passed by reference, but would be more efficient if passed by value",
271"code": {
272 "code": "clippy::trivially_copy_pass_by_ref",
273 "explanation": null
274},
275"level": "warning",
276"spans": [
277 {
278 "file_name": "compiler/mir/tagset.rs",
279 "byte_start": 941,
280 "byte_end": 946,
281 "line_start": 42,
282 "line_end": 42,
283 "column_start": 24,
284 "column_end": 29,
285 "is_primary": true,
286 "text": [
287 {
288 "text": " pub fn is_disjoint(&self, other: Self) -> bool {",
289 "highlight_start": 24,
290 "highlight_end": 29
291 }
292 ],
293 "label": null,
294 "suggested_replacement": null,
295 "suggestion_applicability": null,
296 "expansion": null
297 }
298],
299"children": [
300 {
301 "message": "lint level defined here",
302 "code": null,
303 "level": "note",
304 "spans": [
305 {
306 "file_name": "compiler/lib.rs",
307 "byte_start": 8,
308 "byte_end": 19,
309 "line_start": 1,
310 "line_end": 1,
311 "column_start": 9,
312 "column_end": 20,
313 "is_primary": true,
314 "text": [
315 {
316 "text": "#![warn(clippy::all)]",
317 "highlight_start": 9,
318 "highlight_end": 20
319 }
320 ],
321 "label": null,
322 "suggested_replacement": null,
323 "suggestion_applicability": null,
324 "expansion": null
325 }
326 ],
327 "children": [],
328 "rendered": null
329 },
330 {
331 "message": "#[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]",
332 "code": null,
333 "level": "note",
334 "spans": [],
335 "children": [],
336 "rendered": null
337 },
338 {
339 "message": "for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref",
340 "code": null,
341 "level": "help",
342 "spans": [],
343 "children": [],
344 "rendered": null
345 },
346 {
347 "message": "consider passing by value instead",
348 "code": null,
349 "level": "help",
350 "spans": [
351 {
352 "file_name": "compiler/mir/tagset.rs",
353 "byte_start": 941,
354 "byte_end": 946,
355 "line_start": 42,
356 "line_end": 42,
357 "column_start": 24,
358 "column_end": 29,
359 "is_primary": true,
360 "text": [
361 {
362 "text": " pub fn is_disjoint(&self, other: Self) -> bool {",
363 "highlight_start": 24,
364 "highlight_end": 29
365 }
366 ],
367 "label": null,
368 "suggested_replacement": "self",
369 "suggestion_applicability": "Unspecified",
370 "expansion": null
371 }
372 ],
373 "children": [],
374 "rendered": null
375 }
376],
377"rendered": "warning: this argument is passed by reference, but would be more efficient if passed by value\n --> compiler/mir/tagset.rs:42:24\n |\n42 | pub fn is_disjoint(&self, other: Self) -> bool {\n | ^^^^^ help: consider passing by value instead: `self`\n |\nnote: lint level defined here\n --> compiler/lib.rs:1:9\n |\n1 | #![warn(clippy::all)]\n | ^^^^^^^^^^^\n = note: #[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]\n = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref\n\n"
378}"##,
379 );
380
381 let workspace_root = PathBuf::from("/test/");
382 let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic");
383 insta::assert_debug_snapshot!(diag);
384}
385
386#[test]
387fn snap_rustc_mismatched_type() {
388 let diag = parse_diagnostic(
389 r##"{
390"message": "mismatched types",
391"code": {
392 "code": "E0308",
393 "explanation": "\nThis error occurs when the compiler was unable to infer the concrete type of a\nvariable. It can occur for several cases, the most common of which is a\nmismatch in the expected type that the compiler inferred for a variable's\ninitializing expression, and the actual type explicitly assigned to the\nvariable.\n\nFor example:\n\n```compile_fail,E0308\nlet x: i32 = \"I am not a number!\";\n// ~~~ ~~~~~~~~~~~~~~~~~~~~\n// | |\n// | initializing expression;\n// | compiler infers type `&str`\n// |\n// type `i32` assigned to variable `x`\n```\n"
394},
395"level": "error",
396"spans": [
397 {
398 "file_name": "runtime/compiler_support.rs",
399 "byte_start": 1589,
400 "byte_end": 1594,
401 "line_start": 48,
402 "line_end": 48,
403 "column_start": 65,
404 "column_end": 70,
405 "is_primary": true,
406 "text": [
407 {
408 "text": " let layout = alloc::Layout::from_size_align_unchecked(size, align);",
409 "highlight_start": 65,
410 "highlight_end": 70
411 }
412 ],
413 "label": "expected usize, found u32",
414 "suggested_replacement": null,
415 "suggestion_applicability": null,
416 "expansion": null
417 }
418],
419"children": [],
420"rendered": "error[E0308]: mismatched types\n --> runtime/compiler_support.rs:48:65\n |\n48 | let layout = alloc::Layout::from_size_align_unchecked(size, align);\n | ^^^^^ expected usize, found u32\n\n"
421}"##,
422 );
423
424 let workspace_root = PathBuf::from("/test/");
425 let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic");
426 insta::assert_debug_snapshot!(diag);
427}
428
429#[test]
430fn snap_handles_macro_location() {
431 let diag = parse_diagnostic(
432 r##"{
433"rendered": "error[E0277]: can't compare `{integer}` with `&str`\n --> src/main.rs:2:5\n |\n2 | assert_eq!(1, \"love\");\n | ^^^^^^^^^^^^^^^^^^^^^^ no implementation for `{integer} == &str`\n |\n = help: the trait `std::cmp::PartialEq<&str>` is not implemented for `{integer}`\n = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)\n\n",
434"children": [
435 {
436 "children": [],
437 "code": null,
438 "level": "help",
439 "message": "the trait `std::cmp::PartialEq<&str>` is not implemented for `{integer}`",
440 "rendered": null,
441 "spans": []
442 }
443],
444"code": {
445 "code": "E0277",
446 "explanation": "\nYou tried to use a type which doesn't implement some trait in a place which\nexpected that trait. Erroneous code example:\n\n```compile_fail,E0277\n// here we declare the Foo trait with a bar method\ntrait Foo {\n fn bar(&self);\n}\n\n// we now declare a function which takes an object implementing the Foo trait\nfn some_func<T: Foo>(foo: T) {\n foo.bar();\n}\n\nfn main() {\n // we now call the method with the i32 type, which doesn't implement\n // the Foo trait\n some_func(5i32); // error: the trait bound `i32 : Foo` is not satisfied\n}\n```\n\nIn order to fix this error, verify that the type you're using does implement\nthe trait. Example:\n\n```\ntrait Foo {\n fn bar(&self);\n}\n\nfn some_func<T: Foo>(foo: T) {\n foo.bar(); // we can now use this method since i32 implements the\n // Foo trait\n}\n\n// we implement the trait on the i32 type\nimpl Foo for i32 {\n fn bar(&self) {}\n}\n\nfn main() {\n some_func(5i32); // ok!\n}\n```\n\nOr in a generic context, an erroneous code example would look like:\n\n```compile_fail,E0277\nfn some_func<T>(foo: T) {\n println!(\"{:?}\", foo); // error: the trait `core::fmt::Debug` is not\n // implemented for the type `T`\n}\n\nfn main() {\n // We now call the method with the i32 type,\n // which *does* implement the Debug trait.\n some_func(5i32);\n}\n```\n\nNote that the error here is in the definition of the generic function: Although\nwe only call it with a parameter that does implement `Debug`, the compiler\nstill rejects the function: It must work with all possible input types. In\norder to make this example compile, we need to restrict the generic type we're\naccepting:\n\n```\nuse std::fmt;\n\n// Restrict the input type to types that implement Debug.\nfn some_func<T: fmt::Debug>(foo: T) {\n println!(\"{:?}\", foo);\n}\n\nfn main() {\n // Calling the method is still fine, as i32 implements Debug.\n some_func(5i32);\n\n // This would fail to compile now:\n // struct WithoutDebug;\n // some_func(WithoutDebug);\n}\n```\n\nRust only looks at the signature of the called function, as such it must\nalready specify all requirements that will be used for every type parameter.\n"
447},
448"level": "error",
449"message": "can't compare `{integer}` with `&str`",
450"spans": [
451 {
452 "byte_end": 155,
453 "byte_start": 153,
454 "column_end": 33,
455 "column_start": 31,
456 "expansion": {
457 "def_site_span": {
458 "byte_end": 940,
459 "byte_start": 0,
460 "column_end": 6,
461 "column_start": 1,
462 "expansion": null,
463 "file_name": "<::core::macros::assert_eq macros>",
464 "is_primary": false,
465 "label": null,
466 "line_end": 36,
467 "line_start": 1,
468 "suggested_replacement": null,
469 "suggestion_applicability": null,
470 "text": [
471 {
472 "highlight_end": 35,
473 "highlight_start": 1,
474 "text": "($ left : expr, $ right : expr) =>"
475 },
476 {
477 "highlight_end": 3,
478 "highlight_start": 1,
479 "text": "({"
480 },
481 {
482 "highlight_end": 33,
483 "highlight_start": 1,
484 "text": " match (& $ left, & $ right)"
485 },
486 {
487 "highlight_end": 7,
488 "highlight_start": 1,
489 "text": " {"
490 },
491 {
492 "highlight_end": 34,
493 "highlight_start": 1,
494 "text": " (left_val, right_val) =>"
495 },
496 {
497 "highlight_end": 11,
498 "highlight_start": 1,
499 "text": " {"
500 },
501 {
502 "highlight_end": 46,
503 "highlight_start": 1,
504 "text": " if ! (* left_val == * right_val)"
505 },
506 {
507 "highlight_end": 15,
508 "highlight_start": 1,
509 "text": " {"
510 },
511 {
512 "highlight_end": 25,
513 "highlight_start": 1,
514 "text": " panic !"
515 },
516 {
517 "highlight_end": 57,
518 "highlight_start": 1,
519 "text": " (r#\"assertion failed: `(left == right)`"
520 },
521 {
522 "highlight_end": 16,
523 "highlight_start": 1,
524 "text": " left: `{:?}`,"
525 },
526 {
527 "highlight_end": 18,
528 "highlight_start": 1,
529 "text": " right: `{:?}`\"#,"
530 },
531 {
532 "highlight_end": 47,
533 "highlight_start": 1,
534 "text": " & * left_val, & * right_val)"
535 },
536 {
537 "highlight_end": 15,
538 "highlight_start": 1,
539 "text": " }"
540 },
541 {
542 "highlight_end": 11,
543 "highlight_start": 1,
544 "text": " }"
545 },
546 {
547 "highlight_end": 7,
548 "highlight_start": 1,
549 "text": " }"
550 },
551 {
552 "highlight_end": 42,
553 "highlight_start": 1,
554 "text": " }) ; ($ left : expr, $ right : expr,) =>"
555 },
556 {
557 "highlight_end": 49,
558 "highlight_start": 1,
559 "text": "({ $ crate :: assert_eq ! ($ left, $ right) }) ;"
560 },
561 {
562 "highlight_end": 53,
563 "highlight_start": 1,
564 "text": "($ left : expr, $ right : expr, $ ($ arg : tt) +) =>"
565 },
566 {
567 "highlight_end": 3,
568 "highlight_start": 1,
569 "text": "({"
570 },
571 {
572 "highlight_end": 37,
573 "highlight_start": 1,
574 "text": " match (& ($ left), & ($ right))"
575 },
576 {
577 "highlight_end": 7,
578 "highlight_start": 1,
579 "text": " {"
580 },
581 {
582 "highlight_end": 34,
583 "highlight_start": 1,
584 "text": " (left_val, right_val) =>"
585 },
586 {
587 "highlight_end": 11,
588 "highlight_start": 1,
589 "text": " {"
590 },
591 {
592 "highlight_end": 46,
593 "highlight_start": 1,
594 "text": " if ! (* left_val == * right_val)"
595 },
596 {
597 "highlight_end": 15,
598 "highlight_start": 1,
599 "text": " {"
600 },
601 {
602 "highlight_end": 25,
603 "highlight_start": 1,
604 "text": " panic !"
605 },
606 {
607 "highlight_end": 57,
608 "highlight_start": 1,
609 "text": " (r#\"assertion failed: `(left == right)`"
610 },
611 {
612 "highlight_end": 16,
613 "highlight_start": 1,
614 "text": " left: `{:?}`,"
615 },
616 {
617 "highlight_end": 22,
618 "highlight_start": 1,
619 "text": " right: `{:?}`: {}\"#,"
620 },
621 {
622 "highlight_end": 72,
623 "highlight_start": 1,
624 "text": " & * left_val, & * right_val, $ crate :: format_args !"
625 },
626 {
627 "highlight_end": 33,
628 "highlight_start": 1,
629 "text": " ($ ($ arg) +))"
630 },
631 {
632 "highlight_end": 15,
633 "highlight_start": 1,
634 "text": " }"
635 },
636 {
637 "highlight_end": 11,
638 "highlight_start": 1,
639 "text": " }"
640 },
641 {
642 "highlight_end": 7,
643 "highlight_start": 1,
644 "text": " }"
645 },
646 {
647 "highlight_end": 6,
648 "highlight_start": 1,
649 "text": " }) ;"
650 }
651 ]
652 },
653 "macro_decl_name": "assert_eq!",
654 "span": {
655 "byte_end": 38,
656 "byte_start": 16,
657 "column_end": 27,
658 "column_start": 5,
659 "expansion": null,
660 "file_name": "src/main.rs",
661 "is_primary": false,
662 "label": null,
663 "line_end": 2,
664 "line_start": 2,
665 "suggested_replacement": null,
666 "suggestion_applicability": null,
667 "text": [
668 {
669 "highlight_end": 27,
670 "highlight_start": 5,
671 "text": " assert_eq!(1, \"love\");"
672 }
673 ]
674 }
675 },
676 "file_name": "<::core::macros::assert_eq macros>",
677 "is_primary": true,
678 "label": "no implementation for `{integer} == &str`",
679 "line_end": 7,
680 "line_start": 7,
681 "suggested_replacement": null,
682 "suggestion_applicability": null,
683 "text": [
684 {
685 "highlight_end": 33,
686 "highlight_start": 31,
687 "text": " if ! (* left_val == * right_val)"
688 }
689 ]
690 }
691]
692}"##,
693 );
694
695 let workspace_root = PathBuf::from("/test/");
696 let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic");
697 insta::assert_debug_snapshot!(diag);
698}
diff --git a/crates/ra_cargo_watch/src/lib.rs b/crates/ra_cargo_watch/src/lib.rs
new file mode 100644
index 000000000..c86386610
--- /dev/null
+++ b/crates/ra_cargo_watch/src/lib.rs
@@ -0,0 +1,345 @@
1//! cargo_check provides the functionality needed to run `cargo check` or
2//! another compatible command (f.x. clippy) in a background thread and provide
3//! LSP diagnostics based on the output of the command.
4use cargo_metadata::Message;
5use crossbeam_channel::{select, unbounded, Receiver, RecvError, Sender, TryRecvError};
6use lsp_types::{
7 Diagnostic, Url, WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressEnd,
8 WorkDoneProgressReport,
9};
10use parking_lot::RwLock;
11use std::{
12 collections::HashMap,
13 path::PathBuf,
14 process::{Command, Stdio},
15 sync::Arc,
16 thread::JoinHandle,
17 time::Instant,
18};
19
20mod conv;
21
22use crate::conv::{map_rust_diagnostic_to_lsp, MappedRustDiagnostic, SuggestedFix};
23
24#[derive(Clone, Debug)]
25pub struct CheckOptions {
26 pub enable: bool,
27 pub args: Vec<String>,
28 pub command: String,
29 pub all_targets: bool,
30}
31
32/// CheckWatcher wraps the shared state and communication machinery used for
33/// running `cargo check` (or other compatible command) and providing
34/// diagnostics based on the output.
35#[derive(Debug)]
36pub struct CheckWatcher {
37 pub task_recv: Receiver<CheckTask>,
38 pub cmd_send: Sender<CheckCommand>,
39 pub shared: Arc<RwLock<CheckWatcherSharedState>>,
40 handle: JoinHandle<()>,
41}
42
43impl CheckWatcher {
44 pub fn new(options: &CheckOptions, workspace_root: PathBuf) -> CheckWatcher {
45 let options = options.clone();
46 let shared = Arc::new(RwLock::new(CheckWatcherSharedState::new()));
47
48 let (task_send, task_recv) = unbounded::<CheckTask>();
49 let (cmd_send, cmd_recv) = unbounded::<CheckCommand>();
50 let shared_ = shared.clone();
51 let handle = std::thread::spawn(move || {
52 let mut check = CheckWatcherState::new(options, workspace_root, shared_);
53 check.run(&task_send, &cmd_recv);
54 });
55
56 CheckWatcher { task_recv, cmd_send, handle, shared }
57 }
58
59 /// Schedule a re-start of the cargo check worker.
60 pub fn update(&self) {
61 self.cmd_send.send(CheckCommand::Update).unwrap();
62 }
63}
64
65pub struct CheckWatcherState {
66 options: CheckOptions,
67 workspace_root: PathBuf,
68 running: bool,
69 watcher: WatchThread,
70 last_update_req: Option<Instant>,
71 shared: Arc<RwLock<CheckWatcherSharedState>>,
72}
73
74#[derive(Debug)]
75pub struct CheckWatcherSharedState {
76 diagnostic_collection: HashMap<Url, Vec<Diagnostic>>,
77 suggested_fix_collection: HashMap<Url, Vec<SuggestedFix>>,
78}
79
80impl CheckWatcherSharedState {
81 fn new() -> CheckWatcherSharedState {
82 CheckWatcherSharedState {
83 diagnostic_collection: HashMap::new(),
84 suggested_fix_collection: HashMap::new(),
85 }
86 }
87
88 /// Clear the cached diagnostics, and schedule updating diagnostics by the
89 /// server, to clear stale results.
90 pub fn clear(&mut self, task_send: &Sender<CheckTask>) {
91 let cleared_files: Vec<Url> = self.diagnostic_collection.keys().cloned().collect();
92
93 self.diagnostic_collection.clear();
94 self.suggested_fix_collection.clear();
95
96 for uri in cleared_files {
97 task_send.send(CheckTask::Update(uri.clone())).unwrap();
98 }
99 }
100
101 pub fn diagnostics_for(&self, uri: &Url) -> Option<&[Diagnostic]> {
102 self.diagnostic_collection.get(uri).map(|d| d.as_slice())
103 }
104
105 pub fn fixes_for(&self, uri: &Url) -> Option<&[SuggestedFix]> {
106 self.suggested_fix_collection.get(uri).map(|d| d.as_slice())
107 }
108
109 fn add_diagnostic(&mut self, file_uri: Url, diagnostic: Diagnostic) {
110 let diagnostics = self.diagnostic_collection.entry(file_uri).or_default();
111
112 // If we're building multiple targets it's possible we've already seen this diagnostic
113 let is_duplicate = diagnostics.iter().any(|d| are_diagnostics_equal(d, &diagnostic));
114 if is_duplicate {
115 return;
116 }
117
118 diagnostics.push(diagnostic);
119 }
120
121 fn add_suggested_fix_for_diagnostic(
122 &mut self,
123 mut suggested_fix: SuggestedFix,
124 diagnostic: &Diagnostic,
125 ) {
126 let file_uri = suggested_fix.location.uri.clone();
127 let file_suggestions = self.suggested_fix_collection.entry(file_uri).or_default();
128
129 let existing_suggestion: Option<&mut SuggestedFix> =
130 file_suggestions.iter_mut().find(|s| s == &&suggested_fix);
131 if let Some(existing_suggestion) = existing_suggestion {
132 // The existing suggestion also applies to this new diagnostic
133 existing_suggestion.diagnostics.push(diagnostic.clone());
134 } else {
135 // We haven't seen this suggestion before
136 suggested_fix.diagnostics.push(diagnostic.clone());
137 file_suggestions.push(suggested_fix);
138 }
139 }
140}
141
142#[derive(Debug)]
143pub enum CheckTask {
144 /// Request a update of the given files diagnostics
145 Update(Url),
146
147 /// Request check progress notification to client
148 Status(WorkDoneProgress),
149}
150
151pub enum CheckCommand {
152 /// Request re-start of check thread
153 Update,
154}
155
156impl CheckWatcherState {
157 pub fn new(
158 options: CheckOptions,
159 workspace_root: PathBuf,
160 shared: Arc<RwLock<CheckWatcherSharedState>>,
161 ) -> CheckWatcherState {
162 let watcher = WatchThread::new(&options, &workspace_root);
163 CheckWatcherState {
164 options,
165 workspace_root,
166 running: false,
167 watcher,
168 last_update_req: None,
169 shared,
170 }
171 }
172
173 pub fn run(&mut self, task_send: &Sender<CheckTask>, cmd_recv: &Receiver<CheckCommand>) {
174 self.running = true;
175 while self.running {
176 select! {
177 recv(&cmd_recv) -> cmd => match cmd {
178 Ok(cmd) => self.handle_command(cmd),
179 Err(RecvError) => {
180 // Command channel has closed, so shut down
181 self.running = false;
182 },
183 },
184 recv(self.watcher.message_recv) -> msg => match msg {
185 Ok(msg) => self.handle_message(msg, task_send),
186 Err(RecvError) => {},
187 }
188 };
189
190 if self.should_recheck() {
191 self.last_update_req.take();
192 self.shared.write().clear(task_send);
193
194 self.watcher.cancel();
195 self.watcher = WatchThread::new(&self.options, &self.workspace_root);
196 }
197 }
198 }
199
200 fn should_recheck(&mut self) -> bool {
201 if let Some(_last_update_req) = &self.last_update_req {
202 // We currently only request an update on save, as we need up to
203 // date source on disk for cargo check to do it's magic, so we
204 // don't really need to debounce the requests at this point.
205 return true;
206 }
207 false
208 }
209
210 fn handle_command(&mut self, cmd: CheckCommand) {
211 match cmd {
212 CheckCommand::Update => self.last_update_req = Some(Instant::now()),
213 }
214 }
215
216 fn handle_message(&mut self, msg: CheckEvent, task_send: &Sender<CheckTask>) {
217 match msg {
218 CheckEvent::Begin => {
219 task_send
220 .send(CheckTask::Status(WorkDoneProgress::Begin(WorkDoneProgressBegin {
221 title: "Running 'cargo check'".to_string(),
222 cancellable: Some(false),
223 message: None,
224 percentage: None,
225 })))
226 .unwrap();
227 }
228
229 CheckEvent::End => {
230 task_send
231 .send(CheckTask::Status(WorkDoneProgress::End(WorkDoneProgressEnd {
232 message: None,
233 })))
234 .unwrap();
235 }
236
237 CheckEvent::Msg(Message::CompilerArtifact(msg)) => {
238 task_send
239 .send(CheckTask::Status(WorkDoneProgress::Report(WorkDoneProgressReport {
240 cancellable: Some(false),
241 message: Some(msg.target.name),
242 percentage: None,
243 })))
244 .unwrap();
245 }
246
247 CheckEvent::Msg(Message::CompilerMessage(msg)) => {
248 let map_result =
249 match map_rust_diagnostic_to_lsp(&msg.message, &self.workspace_root) {
250 Some(map_result) => map_result,
251 None => return,
252 };
253
254 let MappedRustDiagnostic { location, diagnostic, suggested_fixes } = map_result;
255 let file_uri = location.uri.clone();
256
257 if !suggested_fixes.is_empty() {
258 for suggested_fix in suggested_fixes {
259 self.shared
260 .write()
261 .add_suggested_fix_for_diagnostic(suggested_fix, &diagnostic);
262 }
263 }
264 self.shared.write().add_diagnostic(file_uri, diagnostic);
265
266 task_send.send(CheckTask::Update(location.uri)).unwrap();
267 }
268
269 CheckEvent::Msg(Message::BuildScriptExecuted(_msg)) => {}
270 CheckEvent::Msg(Message::Unknown) => {}
271 }
272 }
273}
274
275/// WatchThread exists to wrap around the communication needed to be able to
276/// run `cargo check` without blocking. Currently the Rust standard library
277/// doesn't provide a way to read sub-process output without blocking, so we
278/// have to wrap sub-processes output handling in a thread and pass messages
279/// back over a channel.
280struct WatchThread {
281 message_recv: Receiver<CheckEvent>,
282 cancel_send: Sender<()>,
283}
284
285enum CheckEvent {
286 Begin,
287 Msg(cargo_metadata::Message),
288 End,
289}
290
291impl WatchThread {
292 fn new(options: &CheckOptions, workspace_root: &PathBuf) -> WatchThread {
293 let mut args: Vec<String> = vec![
294 options.command.clone(),
295 "--message-format=json".to_string(),
296 "--manifest-path".to_string(),
297 format!("{}/Cargo.toml", workspace_root.to_string_lossy()),
298 ];
299 if options.all_targets {
300 args.push("--all-targets".to_string());
301 }
302 args.extend(options.args.iter().cloned());
303
304 let (message_send, message_recv) = unbounded();
305 let (cancel_send, cancel_recv) = unbounded();
306 let enabled = options.enable;
307 std::thread::spawn(move || {
308 if !enabled {
309 return;
310 }
311
312 let mut command = Command::new("cargo")
313 .args(&args)
314 .stdout(Stdio::piped())
315 .stderr(Stdio::null())
316 .spawn()
317 .expect("couldn't launch cargo");
318
319 message_send.send(CheckEvent::Begin).unwrap();
320 for message in cargo_metadata::parse_messages(command.stdout.take().unwrap()) {
321 match cancel_recv.try_recv() {
322 Ok(()) | Err(TryRecvError::Disconnected) => {
323 command.kill().expect("couldn't kill command");
324 }
325 Err(TryRecvError::Empty) => (),
326 }
327
328 message_send.send(CheckEvent::Msg(message.unwrap())).unwrap();
329 }
330 message_send.send(CheckEvent::End).unwrap();
331 });
332 WatchThread { message_recv, cancel_send }
333 }
334
335 fn cancel(&self) {
336 let _ = self.cancel_send.send(());
337 }
338}
339
340fn are_diagnostics_equal(left: &Diagnostic, right: &Diagnostic) -> bool {
341 left.source == right.source
342 && left.severity == right.severity
343 && left.range == right.range
344 && left.message == right.message
345}
diff --git a/crates/ra_lsp_server/Cargo.toml b/crates/ra_lsp_server/Cargo.toml
index 54a01d7a2..9b7dcb6e9 100644
--- a/crates/ra_lsp_server/Cargo.toml
+++ b/crates/ra_lsp_server/Cargo.toml
@@ -27,10 +27,9 @@ ra_project_model = { path = "../ra_project_model" }
27ra_prof = { path = "../ra_prof" } 27ra_prof = { path = "../ra_prof" }
28ra_vfs_glob = { path = "../ra_vfs_glob" } 28ra_vfs_glob = { path = "../ra_vfs_glob" }
29env_logger = { version = "0.7.1", default-features = false, features = ["humantime"] } 29env_logger = { version = "0.7.1", default-features = false, features = ["humantime"] }
30cargo_metadata = "0.9.1" 30ra_cargo_watch = { path = "../ra_cargo_watch" }
31 31
32[dev-dependencies] 32[dev-dependencies]
33insta = "0.12.0"
34tempfile = "3" 33tempfile = "3"
35test_utils = { path = "../test_utils" } 34test_utils = { path = "../test_utils" }
36 35
diff --git a/crates/ra_lsp_server/src/cargo_check.rs b/crates/ra_lsp_server/src/cargo_check.rs
deleted file mode 100644
index 70c723b19..000000000
--- a/crates/ra_lsp_server/src/cargo_check.rs
+++ /dev/null
@@ -1,1316 +0,0 @@
1//! cargo_check provides the functionality needed to run `cargo check` or
2//! another compatible command (f.x. clippy) in a background thread and provide
3//! LSP diagnostics based on the output of the command.
4use crate::world::Options;
5use cargo_metadata::{
6 diagnostic::{
7 Applicability, Diagnostic as RustDiagnostic, DiagnosticLevel, DiagnosticSpan,
8 DiagnosticSpanMacroExpansion,
9 },
10 Message,
11};
12use crossbeam_channel::{select, unbounded, Receiver, RecvError, Sender, TryRecvError};
13use lsp_types::{
14 Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, Location,
15 NumberOrString, Position, Range, Url, WorkDoneProgress, WorkDoneProgressBegin,
16 WorkDoneProgressEnd, WorkDoneProgressReport,
17};
18use parking_lot::RwLock;
19use std::{
20 collections::HashMap,
21 fmt::Write,
22 path::PathBuf,
23 process::{Command, Stdio},
24 sync::Arc,
25 thread::JoinHandle,
26 time::Instant,
27};
28
29/// CheckWatcher wraps the shared state and communication machinery used for
30/// running `cargo check` (or other compatible command) and providing
31/// diagnostics based on the output.
32#[derive(Debug)]
33pub struct CheckWatcher {
34 pub task_recv: Receiver<CheckTask>,
35 pub cmd_send: Sender<CheckCommand>,
36 pub shared: Arc<RwLock<CheckWatcherSharedState>>,
37 handle: JoinHandle<()>,
38}
39
40impl CheckWatcher {
41 pub fn new(options: &Options, workspace_root: PathBuf) -> CheckWatcher {
42 let options = options.clone();
43 let shared = Arc::new(RwLock::new(CheckWatcherSharedState::new()));
44
45 let (task_send, task_recv) = unbounded::<CheckTask>();
46 let (cmd_send, cmd_recv) = unbounded::<CheckCommand>();
47 let shared_ = shared.clone();
48 let handle = std::thread::spawn(move || {
49 let mut check = CheckWatcherState::new(options, workspace_root, shared_);
50 check.run(&task_send, &cmd_recv);
51 });
52
53 CheckWatcher { task_recv, cmd_send, handle, shared }
54 }
55
56 /// Schedule a re-start of the cargo check worker.
57 pub fn update(&self) {
58 self.cmd_send.send(CheckCommand::Update).unwrap();
59 }
60}
61
62pub struct CheckWatcherState {
63 options: Options,
64 workspace_root: PathBuf,
65 running: bool,
66 watcher: WatchThread,
67 last_update_req: Option<Instant>,
68 shared: Arc<RwLock<CheckWatcherSharedState>>,
69}
70
71#[derive(Debug)]
72pub struct CheckWatcherSharedState {
73 diagnostic_collection: HashMap<Url, Vec<Diagnostic>>,
74 suggested_fix_collection: HashMap<Url, Vec<SuggestedFix>>,
75}
76
77impl CheckWatcherSharedState {
78 fn new() -> CheckWatcherSharedState {
79 CheckWatcherSharedState {
80 diagnostic_collection: HashMap::new(),
81 suggested_fix_collection: HashMap::new(),
82 }
83 }
84
85 /// Clear the cached diagnostics, and schedule updating diagnostics by the
86 /// server, to clear stale results.
87 pub fn clear(&mut self, task_send: &Sender<CheckTask>) {
88 let cleared_files: Vec<Url> = self.diagnostic_collection.keys().cloned().collect();
89
90 self.diagnostic_collection.clear();
91 self.suggested_fix_collection.clear();
92
93 for uri in cleared_files {
94 task_send.send(CheckTask::Update(uri.clone())).unwrap();
95 }
96 }
97
98 pub fn diagnostics_for(&self, uri: &Url) -> Option<&[Diagnostic]> {
99 self.diagnostic_collection.get(uri).map(|d| d.as_slice())
100 }
101
102 pub fn fixes_for(&self, uri: &Url) -> Option<&[SuggestedFix]> {
103 self.suggested_fix_collection.get(uri).map(|d| d.as_slice())
104 }
105
106 fn add_diagnostic(&mut self, file_uri: Url, diagnostic: Diagnostic) {
107 let diagnostics = self.diagnostic_collection.entry(file_uri).or_default();
108
109 // If we're building multiple targets it's possible we've already seen this diagnostic
110 let is_duplicate = diagnostics.iter().any(|d| are_diagnostics_equal(d, &diagnostic));
111 if is_duplicate {
112 return;
113 }
114
115 diagnostics.push(diagnostic);
116 }
117
118 fn add_suggested_fix_for_diagnostic(
119 &mut self,
120 mut suggested_fix: SuggestedFix,
121 diagnostic: &Diagnostic,
122 ) {
123 let file_uri = suggested_fix.location.uri.clone();
124 let file_suggestions = self.suggested_fix_collection.entry(file_uri).or_default();
125
126 let existing_suggestion: Option<&mut SuggestedFix> =
127 file_suggestions.iter_mut().find(|s| s == &&suggested_fix);
128 if let Some(existing_suggestion) = existing_suggestion {
129 // The existing suggestion also applies to this new diagnostic
130 existing_suggestion.diagnostics.push(diagnostic.clone());
131 } else {
132 // We haven't seen this suggestion before
133 suggested_fix.diagnostics.push(diagnostic.clone());
134 file_suggestions.push(suggested_fix);
135 }
136 }
137}
138
139#[derive(Debug)]
140pub enum CheckTask {
141 /// Request a update of the given files diagnostics
142 Update(Url),
143
144 /// Request check progress notification to client
145 Status(WorkDoneProgress),
146}
147
148pub enum CheckCommand {
149 /// Request re-start of check thread
150 Update,
151}
152
153impl CheckWatcherState {
154 pub fn new(
155 options: Options,
156 workspace_root: PathBuf,
157 shared: Arc<RwLock<CheckWatcherSharedState>>,
158 ) -> CheckWatcherState {
159 let watcher = WatchThread::new(&options, &workspace_root);
160 CheckWatcherState {
161 options,
162 workspace_root,
163 running: false,
164 watcher,
165 last_update_req: None,
166 shared,
167 }
168 }
169
170 pub fn run(&mut self, task_send: &Sender<CheckTask>, cmd_recv: &Receiver<CheckCommand>) {
171 self.running = true;
172 while self.running {
173 select! {
174 recv(&cmd_recv) -> cmd => match cmd {
175 Ok(cmd) => self.handle_command(cmd),
176 Err(RecvError) => {
177 // Command channel has closed, so shut down
178 self.running = false;
179 },
180 },
181 recv(self.watcher.message_recv) -> msg => match msg {
182 Ok(msg) => self.handle_message(msg, task_send),
183 Err(RecvError) => {},
184 }
185 };
186
187 if self.should_recheck() {
188 self.last_update_req.take();
189 self.shared.write().clear(task_send);
190
191 self.watcher.cancel();
192 self.watcher = WatchThread::new(&self.options, &self.workspace_root);
193 }
194 }
195 }
196
197 fn should_recheck(&mut self) -> bool {
198 if let Some(_last_update_req) = &self.last_update_req {
199 // We currently only request an update on save, as we need up to
200 // date source on disk for cargo check to do it's magic, so we
201 // don't really need to debounce the requests at this point.
202 return true;
203 }
204 false
205 }
206
207 fn handle_command(&mut self, cmd: CheckCommand) {
208 match cmd {
209 CheckCommand::Update => self.last_update_req = Some(Instant::now()),
210 }
211 }
212
213 fn handle_message(&mut self, msg: CheckEvent, task_send: &Sender<CheckTask>) {
214 match msg {
215 CheckEvent::Begin => {
216 task_send
217 .send(CheckTask::Status(WorkDoneProgress::Begin(WorkDoneProgressBegin {
218 title: "Running 'cargo check'".to_string(),
219 cancellable: Some(false),
220 message: None,
221 percentage: None,
222 })))
223 .unwrap();
224 }
225
226 CheckEvent::End => {
227 task_send
228 .send(CheckTask::Status(WorkDoneProgress::End(WorkDoneProgressEnd {
229 message: None,
230 })))
231 .unwrap();
232 }
233
234 CheckEvent::Msg(Message::CompilerArtifact(msg)) => {
235 task_send
236 .send(CheckTask::Status(WorkDoneProgress::Report(WorkDoneProgressReport {
237 cancellable: Some(false),
238 message: Some(msg.target.name),
239 percentage: None,
240 })))
241 .unwrap();
242 }
243
244 CheckEvent::Msg(Message::CompilerMessage(msg)) => {
245 let map_result =
246 match map_rust_diagnostic_to_lsp(&msg.message, &self.workspace_root) {
247 Some(map_result) => map_result,
248 None => return,
249 };
250
251 let MappedRustDiagnostic { location, diagnostic, suggested_fixes } = map_result;
252 let file_uri = location.uri.clone();
253
254 if !suggested_fixes.is_empty() {
255 for suggested_fix in suggested_fixes {
256 self.shared
257 .write()
258 .add_suggested_fix_for_diagnostic(suggested_fix, &diagnostic);
259 }
260 }
261 self.shared.write().add_diagnostic(file_uri, diagnostic);
262
263 task_send.send(CheckTask::Update(location.uri)).unwrap();
264 }
265
266 CheckEvent::Msg(Message::BuildScriptExecuted(_msg)) => {}
267 CheckEvent::Msg(Message::Unknown) => {}
268 }
269 }
270}
271
272/// WatchThread exists to wrap around the communication needed to be able to
273/// run `cargo check` without blocking. Currently the Rust standard library
274/// doesn't provide a way to read sub-process output without blocking, so we
275/// have to wrap sub-processes output handling in a thread and pass messages
276/// back over a channel.
277struct WatchThread {
278 message_recv: Receiver<CheckEvent>,
279 cancel_send: Sender<()>,
280}
281
282enum CheckEvent {
283 Begin,
284 Msg(cargo_metadata::Message),
285 End,
286}
287
288impl WatchThread {
289 fn new(options: &Options, workspace_root: &PathBuf) -> WatchThread {
290 let mut args: Vec<String> = vec![
291 options.cargo_watch_command.clone(),
292 "--message-format=json".to_string(),
293 "--manifest-path".to_string(),
294 format!("{}/Cargo.toml", workspace_root.to_string_lossy()),
295 ];
296 if options.cargo_watch_all_targets {
297 args.push("--all-targets".to_string());
298 }
299 args.extend(options.cargo_watch_args.iter().cloned());
300
301 let (message_send, message_recv) = unbounded();
302 let (cancel_send, cancel_recv) = unbounded();
303 let enabled = options.cargo_watch_enable;
304 std::thread::spawn(move || {
305 if !enabled {
306 return;
307 }
308
309 let mut command = Command::new("cargo")
310 .args(&args)
311 .stdout(Stdio::piped())
312 .stderr(Stdio::null())
313 .spawn()
314 .expect("couldn't launch cargo");
315
316 message_send.send(CheckEvent::Begin).unwrap();
317 for message in cargo_metadata::parse_messages(command.stdout.take().unwrap()) {
318 match cancel_recv.try_recv() {
319 Ok(()) | Err(TryRecvError::Disconnected) => {
320 command.kill().expect("couldn't kill command");
321 }
322 Err(TryRecvError::Empty) => (),
323 }
324
325 message_send.send(CheckEvent::Msg(message.unwrap())).unwrap();
326 }
327 message_send.send(CheckEvent::End).unwrap();
328 });
329 WatchThread { message_recv, cancel_send }
330 }
331
332 fn cancel(&self) {
333 let _ = self.cancel_send.send(());
334 }
335}
336
337/// Converts a Rust level string to a LSP severity
338fn map_level_to_severity(val: DiagnosticLevel) -> Option<DiagnosticSeverity> {
339 match val {
340 DiagnosticLevel::Ice => Some(DiagnosticSeverity::Error),
341 DiagnosticLevel::Error => Some(DiagnosticSeverity::Error),
342 DiagnosticLevel::Warning => Some(DiagnosticSeverity::Warning),
343 DiagnosticLevel::Note => Some(DiagnosticSeverity::Information),
344 DiagnosticLevel::Help => Some(DiagnosticSeverity::Hint),
345 DiagnosticLevel::Unknown => None,
346 }
347}
348
349/// Check whether a file name is from macro invocation
350fn is_from_macro(file_name: &str) -> bool {
351 file_name.starts_with('<') && file_name.ends_with('>')
352}
353
354/// Converts a Rust macro span to a LSP location recursively
355fn map_macro_span_to_location(
356 span_macro: &DiagnosticSpanMacroExpansion,
357 workspace_root: &PathBuf,
358) -> Option<Location> {
359 if !is_from_macro(&span_macro.span.file_name) {
360 return Some(map_span_to_location(&span_macro.span, workspace_root));
361 }
362
363 if let Some(expansion) = &span_macro.span.expansion {
364 return map_macro_span_to_location(&expansion, workspace_root);
365 }
366
367 None
368}
369
370/// Converts a Rust span to a LSP location
371fn map_span_to_location(span: &DiagnosticSpan, workspace_root: &PathBuf) -> Location {
372 if is_from_macro(&span.file_name) && span.expansion.is_some() {
373 let expansion = span.expansion.as_ref().unwrap();
374 if let Some(macro_range) = map_macro_span_to_location(&expansion, workspace_root) {
375 return macro_range;
376 }
377 }
378
379 let mut file_name = workspace_root.clone();
380 file_name.push(&span.file_name);
381 let uri = Url::from_file_path(file_name).unwrap();
382
383 let range = Range::new(
384 Position::new(span.line_start as u64 - 1, span.column_start as u64 - 1),
385 Position::new(span.line_end as u64 - 1, span.column_end as u64 - 1),
386 );
387
388 Location { uri, range }
389}
390
391/// Converts a secondary Rust span to a LSP related information
392///
393/// If the span is unlabelled this will return `None`.
394fn map_secondary_span_to_related(
395 span: &DiagnosticSpan,
396 workspace_root: &PathBuf,
397) -> Option<DiagnosticRelatedInformation> {
398 if let Some(label) = &span.label {
399 let location = map_span_to_location(span, workspace_root);
400 Some(DiagnosticRelatedInformation { location, message: label.clone() })
401 } else {
402 // Nothing to label this with
403 None
404 }
405}
406
407/// Determines if diagnostic is related to unused code
408fn is_unused_or_unnecessary(rd: &RustDiagnostic) -> bool {
409 if let Some(code) = &rd.code {
410 match code.code.as_str() {
411 "dead_code" | "unknown_lints" | "unreachable_code" | "unused_attributes"
412 | "unused_imports" | "unused_macros" | "unused_variables" => true,
413 _ => false,
414 }
415 } else {
416 false
417 }
418}
419
420/// Determines if diagnostic is related to deprecated code
421fn is_deprecated(rd: &RustDiagnostic) -> bool {
422 if let Some(code) = &rd.code {
423 match code.code.as_str() {
424 "deprecated" => true,
425 _ => false,
426 }
427 } else {
428 false
429 }
430}
431
432#[derive(Debug)]
433pub struct SuggestedFix {
434 pub title: String,
435 pub location: Location,
436 pub replacement: String,
437 pub applicability: Applicability,
438 pub diagnostics: Vec<Diagnostic>,
439}
440
441impl std::cmp::PartialEq<SuggestedFix> for SuggestedFix {
442 fn eq(&self, other: &SuggestedFix) -> bool {
443 if self.title == other.title
444 && self.location == other.location
445 && self.replacement == other.replacement
446 {
447 // Applicability doesn't impl PartialEq...
448 match (&self.applicability, &other.applicability) {
449 (Applicability::MachineApplicable, Applicability::MachineApplicable) => true,
450 (Applicability::HasPlaceholders, Applicability::HasPlaceholders) => true,
451 (Applicability::MaybeIncorrect, Applicability::MaybeIncorrect) => true,
452 (Applicability::Unspecified, Applicability::Unspecified) => true,
453 _ => false,
454 }
455 } else {
456 false
457 }
458 }
459}
460
461enum MappedRustChildDiagnostic {
462 Related(DiagnosticRelatedInformation),
463 SuggestedFix(SuggestedFix),
464 MessageLine(String),
465}
466
467fn map_rust_child_diagnostic(
468 rd: &RustDiagnostic,
469 workspace_root: &PathBuf,
470) -> MappedRustChildDiagnostic {
471 let span: &DiagnosticSpan = match rd.spans.iter().find(|s| s.is_primary) {
472 Some(span) => span,
473 None => {
474 // `rustc` uses these spanless children as a way to print multi-line
475 // messages
476 return MappedRustChildDiagnostic::MessageLine(rd.message.clone());
477 }
478 };
479
480 // If we have a primary span use its location, otherwise use the parent
481 let location = map_span_to_location(&span, workspace_root);
482
483 if let Some(suggested_replacement) = &span.suggested_replacement {
484 // Include our replacement in the title unless it's empty
485 let title = if !suggested_replacement.is_empty() {
486 format!("{}: '{}'", rd.message, suggested_replacement)
487 } else {
488 rd.message.clone()
489 };
490
491 MappedRustChildDiagnostic::SuggestedFix(SuggestedFix {
492 title,
493 location,
494 replacement: suggested_replacement.clone(),
495 applicability: span.suggestion_applicability.clone().unwrap_or(Applicability::Unknown),
496 diagnostics: vec![],
497 })
498 } else {
499 MappedRustChildDiagnostic::Related(DiagnosticRelatedInformation {
500 location,
501 message: rd.message.clone(),
502 })
503 }
504}
505
506#[derive(Debug)]
507struct MappedRustDiagnostic {
508 location: Location,
509 diagnostic: Diagnostic,
510 suggested_fixes: Vec<SuggestedFix>,
511}
512
513/// Converts a Rust root diagnostic to LSP form
514///
515/// This flattens the Rust diagnostic by:
516///
517/// 1. Creating a LSP diagnostic with the root message and primary span.
518/// 2. Adding any labelled secondary spans to `relatedInformation`
519/// 3. Categorising child diagnostics as either `SuggestedFix`es,
520/// `relatedInformation` or additional message lines.
521///
522/// If the diagnostic has no primary span this will return `None`
523fn map_rust_diagnostic_to_lsp(
524 rd: &RustDiagnostic,
525 workspace_root: &PathBuf,
526) -> Option<MappedRustDiagnostic> {
527 let primary_span = rd.spans.iter().find(|s| s.is_primary)?;
528
529 let location = map_span_to_location(&primary_span, workspace_root);
530
531 let severity = map_level_to_severity(rd.level);
532 let mut primary_span_label = primary_span.label.as_ref();
533
534 let mut source = String::from("rustc");
535 let mut code = rd.code.as_ref().map(|c| c.code.clone());
536 if let Some(code_val) = &code {
537 // See if this is an RFC #2103 scoped lint (e.g. from Clippy)
538 let scoped_code: Vec<&str> = code_val.split("::").collect();
539 if scoped_code.len() == 2 {
540 source = String::from(scoped_code[0]);
541 code = Some(String::from(scoped_code[1]));
542 }
543 }
544
545 let mut related_information = vec![];
546 let mut tags = vec![];
547
548 for secondary_span in rd.spans.iter().filter(|s| !s.is_primary) {
549 let related = map_secondary_span_to_related(secondary_span, workspace_root);
550 if let Some(related) = related {
551 related_information.push(related);
552 }
553 }
554
555 let mut suggested_fixes = vec![];
556 let mut message = rd.message.clone();
557 for child in &rd.children {
558 let child = map_rust_child_diagnostic(&child, workspace_root);
559 match child {
560 MappedRustChildDiagnostic::Related(related) => related_information.push(related),
561 MappedRustChildDiagnostic::SuggestedFix(suggested_fix) => {
562 suggested_fixes.push(suggested_fix)
563 }
564 MappedRustChildDiagnostic::MessageLine(message_line) => {
565 write!(&mut message, "\n{}", message_line).unwrap();
566
567 // These secondary messages usually duplicate the content of the
568 // primary span label.
569 primary_span_label = None;
570 }
571 }
572 }
573
574 if let Some(primary_span_label) = primary_span_label {
575 write!(&mut message, "\n{}", primary_span_label).unwrap();
576 }
577
578 if is_unused_or_unnecessary(rd) {
579 tags.push(DiagnosticTag::Unnecessary);
580 }
581
582 if is_deprecated(rd) {
583 tags.push(DiagnosticTag::Deprecated);
584 }
585
586 let diagnostic = Diagnostic {
587 range: location.range,
588 severity,
589 code: code.map(NumberOrString::String),
590 source: Some(source),
591 message,
592 related_information: if !related_information.is_empty() {
593 Some(related_information)
594 } else {
595 None
596 },
597 tags: if !tags.is_empty() { Some(tags) } else { None },
598 };
599
600 Some(MappedRustDiagnostic { location, diagnostic, suggested_fixes })
601}
602
603fn are_diagnostics_equal(left: &Diagnostic, right: &Diagnostic) -> bool {
604 left.source == right.source
605 && left.severity == right.severity
606 && left.range == right.range
607 && left.message == right.message
608}
609
610#[cfg(test)]
611mod test {
612 use super::*;
613
614 fn parse_diagnostic(val: &str) -> cargo_metadata::diagnostic::Diagnostic {
615 serde_json::from_str::<cargo_metadata::diagnostic::Diagnostic>(val).unwrap()
616 }
617
618 #[test]
619 fn snap_rustc_incompatible_type_for_trait() {
620 let diag = parse_diagnostic(
621 r##"{
622 "message": "method `next` has an incompatible type for trait",
623 "code": {
624 "code": "E0053",
625 "explanation": "\nThe parameters of any trait method must match between a trait implementation\nand the trait definition.\n\nHere are a couple examples of this error:\n\n```compile_fail,E0053\ntrait Foo {\n fn foo(x: u16);\n fn bar(&self);\n}\n\nstruct Bar;\n\nimpl Foo for Bar {\n // error, expected u16, found i16\n fn foo(x: i16) { }\n\n // error, types differ in mutability\n fn bar(&mut self) { }\n}\n```\n"
626 },
627 "level": "error",
628 "spans": [
629 {
630 "file_name": "compiler/ty/list_iter.rs",
631 "byte_start": 1307,
632 "byte_end": 1350,
633 "line_start": 52,
634 "line_end": 52,
635 "column_start": 5,
636 "column_end": 48,
637 "is_primary": true,
638 "text": [
639 {
640 "text": " fn next(&self) -> Option<&'list ty::Ref<M>> {",
641 "highlight_start": 5,
642 "highlight_end": 48
643 }
644 ],
645 "label": "types differ in mutability",
646 "suggested_replacement": null,
647 "suggestion_applicability": null,
648 "expansion": null
649 }
650 ],
651 "children": [
652 {
653 "message": "expected type `fn(&mut ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&ty::Ref<M>>`\n found type `fn(&ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&'list ty::Ref<M>>`",
654 "code": null,
655 "level": "note",
656 "spans": [],
657 "children": [],
658 "rendered": null
659 }
660 ],
661 "rendered": "error[E0053]: method `next` has an incompatible type for trait\n --> compiler/ty/list_iter.rs:52:5\n |\n52 | fn next(&self) -> Option<&'list ty::Ref<M>> {\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ types differ in mutability\n |\n = note: expected type `fn(&mut ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&ty::Ref<M>>`\n found type `fn(&ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&'list ty::Ref<M>>`\n\n"
662 }
663 "##,
664 );
665
666 let workspace_root = PathBuf::from("/test/");
667 let diag =
668 map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic");
669 insta::assert_debug_snapshot!(diag);
670 }
671
672 #[test]
673 fn snap_rustc_unused_variable() {
674 let diag = parse_diagnostic(
675 r##"{
676 "message": "unused variable: `foo`",
677 "code": {
678 "code": "unused_variables",
679 "explanation": null
680 },
681 "level": "warning",
682 "spans": [
683 {
684 "file_name": "driver/subcommand/repl.rs",
685 "byte_start": 9228,
686 "byte_end": 9231,
687 "line_start": 291,
688 "line_end": 291,
689 "column_start": 9,
690 "column_end": 12,
691 "is_primary": true,
692 "text": [
693 {
694 "text": " let foo = 42;",
695 "highlight_start": 9,
696 "highlight_end": 12
697 }
698 ],
699 "label": null,
700 "suggested_replacement": null,
701 "suggestion_applicability": null,
702 "expansion": null
703 }
704 ],
705 "children": [
706 {
707 "message": "#[warn(unused_variables)] on by default",
708 "code": null,
709 "level": "note",
710 "spans": [],
711 "children": [],
712 "rendered": null
713 },
714 {
715 "message": "consider prefixing with an underscore",
716 "code": null,
717 "level": "help",
718 "spans": [
719 {
720 "file_name": "driver/subcommand/repl.rs",
721 "byte_start": 9228,
722 "byte_end": 9231,
723 "line_start": 291,
724 "line_end": 291,
725 "column_start": 9,
726 "column_end": 12,
727 "is_primary": true,
728 "text": [
729 {
730 "text": " let foo = 42;",
731 "highlight_start": 9,
732 "highlight_end": 12
733 }
734 ],
735 "label": null,
736 "suggested_replacement": "_foo",
737 "suggestion_applicability": "MachineApplicable",
738 "expansion": null
739 }
740 ],
741 "children": [],
742 "rendered": null
743 }
744 ],
745 "rendered": "warning: unused variable: `foo`\n --> driver/subcommand/repl.rs:291:9\n |\n291 | let foo = 42;\n | ^^^ help: consider prefixing with an underscore: `_foo`\n |\n = note: #[warn(unused_variables)] on by default\n\n"
746}"##,
747 );
748
749 let workspace_root = PathBuf::from("/test/");
750 let diag =
751 map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic");
752 insta::assert_debug_snapshot!(diag);
753 }
754
755 #[test]
756 fn snap_rustc_wrong_number_of_parameters() {
757 let diag = parse_diagnostic(
758 r##"{
759 "message": "this function takes 2 parameters but 3 parameters were supplied",
760 "code": {
761 "code": "E0061",
762 "explanation": "\nThe number of arguments passed to a function must match the number of arguments\nspecified in the function signature.\n\nFor example, a function like:\n\n```\nfn f(a: u16, b: &str) {}\n```\n\nMust always be called with exactly two arguments, e.g., `f(2, \"test\")`.\n\nNote that Rust does not have a notion of optional function arguments or\nvariadic functions (except for its C-FFI).\n"
763 },
764 "level": "error",
765 "spans": [
766 {
767 "file_name": "compiler/ty/select.rs",
768 "byte_start": 8787,
769 "byte_end": 9241,
770 "line_start": 219,
771 "line_end": 231,
772 "column_start": 5,
773 "column_end": 6,
774 "is_primary": false,
775 "text": [
776 {
777 "text": " pub fn add_evidence(",
778 "highlight_start": 5,
779 "highlight_end": 25
780 },
781 {
782 "text": " &mut self,",
783 "highlight_start": 1,
784 "highlight_end": 19
785 },
786 {
787 "text": " target_poly: &ty::Ref<ty::Poly>,",
788 "highlight_start": 1,
789 "highlight_end": 41
790 },
791 {
792 "text": " evidence_poly: &ty::Ref<ty::Poly>,",
793 "highlight_start": 1,
794 "highlight_end": 43
795 },
796 {
797 "text": " ) {",
798 "highlight_start": 1,
799 "highlight_end": 8
800 },
801 {
802 "text": " match target_poly {",
803 "highlight_start": 1,
804 "highlight_end": 28
805 },
806 {
807 "text": " ty::Ref::Var(tvar, _) => self.add_var_evidence(tvar, evidence_poly),",
808 "highlight_start": 1,
809 "highlight_end": 81
810 },
811 {
812 "text": " ty::Ref::Fixed(target_ty) => {",
813 "highlight_start": 1,
814 "highlight_end": 43
815 },
816 {
817 "text": " let evidence_ty = evidence_poly.resolve_to_ty();",
818 "highlight_start": 1,
819 "highlight_end": 65
820 },
821 {
822 "text": " self.add_evidence_ty(target_ty, evidence_poly, evidence_ty)",
823 "highlight_start": 1,
824 "highlight_end": 76
825 },
826 {
827 "text": " }",
828 "highlight_start": 1,
829 "highlight_end": 14
830 },
831 {
832 "text": " }",
833 "highlight_start": 1,
834 "highlight_end": 10
835 },
836 {
837 "text": " }",
838 "highlight_start": 1,
839 "highlight_end": 6
840 }
841 ],
842 "label": "defined here",
843 "suggested_replacement": null,
844 "suggestion_applicability": null,
845 "expansion": null
846 },
847 {
848 "file_name": "compiler/ty/select.rs",
849 "byte_start": 4045,
850 "byte_end": 4057,
851 "line_start": 104,
852 "line_end": 104,
853 "column_start": 18,
854 "column_end": 30,
855 "is_primary": true,
856 "text": [
857 {
858 "text": " self.add_evidence(target_fixed, evidence_fixed, false);",
859 "highlight_start": 18,
860 "highlight_end": 30
861 }
862 ],
863 "label": "expected 2 parameters",
864 "suggested_replacement": null,
865 "suggestion_applicability": null,
866 "expansion": null
867 }
868 ],
869 "children": [],
870 "rendered": "error[E0061]: this function takes 2 parameters but 3 parameters were supplied\n --> compiler/ty/select.rs:104:18\n |\n104 | self.add_evidence(target_fixed, evidence_fixed, false);\n | ^^^^^^^^^^^^ expected 2 parameters\n...\n219 | / pub fn add_evidence(\n220 | | &mut self,\n221 | | target_poly: &ty::Ref<ty::Poly>,\n222 | | evidence_poly: &ty::Ref<ty::Poly>,\n... |\n230 | | }\n231 | | }\n | |_____- defined here\n\n"
871}"##,
872 );
873
874 let workspace_root = PathBuf::from("/test/");
875 let diag =
876 map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic");
877 insta::assert_debug_snapshot!(diag);
878 }
879
880 #[test]
881 fn snap_clippy_pass_by_ref() {
882 let diag = parse_diagnostic(
883 r##"{
884 "message": "this argument is passed by reference, but would be more efficient if passed by value",
885 "code": {
886 "code": "clippy::trivially_copy_pass_by_ref",
887 "explanation": null
888 },
889 "level": "warning",
890 "spans": [
891 {
892 "file_name": "compiler/mir/tagset.rs",
893 "byte_start": 941,
894 "byte_end": 946,
895 "line_start": 42,
896 "line_end": 42,
897 "column_start": 24,
898 "column_end": 29,
899 "is_primary": true,
900 "text": [
901 {
902 "text": " pub fn is_disjoint(&self, other: Self) -> bool {",
903 "highlight_start": 24,
904 "highlight_end": 29
905 }
906 ],
907 "label": null,
908 "suggested_replacement": null,
909 "suggestion_applicability": null,
910 "expansion": null
911 }
912 ],
913 "children": [
914 {
915 "message": "lint level defined here",
916 "code": null,
917 "level": "note",
918 "spans": [
919 {
920 "file_name": "compiler/lib.rs",
921 "byte_start": 8,
922 "byte_end": 19,
923 "line_start": 1,
924 "line_end": 1,
925 "column_start": 9,
926 "column_end": 20,
927 "is_primary": true,
928 "text": [
929 {
930 "text": "#![warn(clippy::all)]",
931 "highlight_start": 9,
932 "highlight_end": 20
933 }
934 ],
935 "label": null,
936 "suggested_replacement": null,
937 "suggestion_applicability": null,
938 "expansion": null
939 }
940 ],
941 "children": [],
942 "rendered": null
943 },
944 {
945 "message": "#[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]",
946 "code": null,
947 "level": "note",
948 "spans": [],
949 "children": [],
950 "rendered": null
951 },
952 {
953 "message": "for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref",
954 "code": null,
955 "level": "help",
956 "spans": [],
957 "children": [],
958 "rendered": null
959 },
960 {
961 "message": "consider passing by value instead",
962 "code": null,
963 "level": "help",
964 "spans": [
965 {
966 "file_name": "compiler/mir/tagset.rs",
967 "byte_start": 941,
968 "byte_end": 946,
969 "line_start": 42,
970 "line_end": 42,
971 "column_start": 24,
972 "column_end": 29,
973 "is_primary": true,
974 "text": [
975 {
976 "text": " pub fn is_disjoint(&self, other: Self) -> bool {",
977 "highlight_start": 24,
978 "highlight_end": 29
979 }
980 ],
981 "label": null,
982 "suggested_replacement": "self",
983 "suggestion_applicability": "Unspecified",
984 "expansion": null
985 }
986 ],
987 "children": [],
988 "rendered": null
989 }
990 ],
991 "rendered": "warning: this argument is passed by reference, but would be more efficient if passed by value\n --> compiler/mir/tagset.rs:42:24\n |\n42 | pub fn is_disjoint(&self, other: Self) -> bool {\n | ^^^^^ help: consider passing by value instead: `self`\n |\nnote: lint level defined here\n --> compiler/lib.rs:1:9\n |\n1 | #![warn(clippy::all)]\n | ^^^^^^^^^^^\n = note: #[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]\n = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref\n\n"
992}"##,
993 );
994
995 let workspace_root = PathBuf::from("/test/");
996 let diag =
997 map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic");
998 insta::assert_debug_snapshot!(diag);
999 }
1000
1001 #[test]
1002 fn snap_rustc_mismatched_type() {
1003 let diag = parse_diagnostic(
1004 r##"{
1005 "message": "mismatched types",
1006 "code": {
1007 "code": "E0308",
1008 "explanation": "\nThis error occurs when the compiler was unable to infer the concrete type of a\nvariable. It can occur for several cases, the most common of which is a\nmismatch in the expected type that the compiler inferred for a variable's\ninitializing expression, and the actual type explicitly assigned to the\nvariable.\n\nFor example:\n\n```compile_fail,E0308\nlet x: i32 = \"I am not a number!\";\n// ~~~ ~~~~~~~~~~~~~~~~~~~~\n// | |\n// | initializing expression;\n// | compiler infers type `&str`\n// |\n// type `i32` assigned to variable `x`\n```\n"
1009 },
1010 "level": "error",
1011 "spans": [
1012 {
1013 "file_name": "runtime/compiler_support.rs",
1014 "byte_start": 1589,
1015 "byte_end": 1594,
1016 "line_start": 48,
1017 "line_end": 48,
1018 "column_start": 65,
1019 "column_end": 70,
1020 "is_primary": true,
1021 "text": [
1022 {
1023 "text": " let layout = alloc::Layout::from_size_align_unchecked(size, align);",
1024 "highlight_start": 65,
1025 "highlight_end": 70
1026 }
1027 ],
1028 "label": "expected usize, found u32",
1029 "suggested_replacement": null,
1030 "suggestion_applicability": null,
1031 "expansion": null
1032 }
1033 ],
1034 "children": [],
1035 "rendered": "error[E0308]: mismatched types\n --> runtime/compiler_support.rs:48:65\n |\n48 | let layout = alloc::Layout::from_size_align_unchecked(size, align);\n | ^^^^^ expected usize, found u32\n\n"
1036}"##,
1037 );
1038
1039 let workspace_root = PathBuf::from("/test/");
1040 let diag =
1041 map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic");
1042 insta::assert_debug_snapshot!(diag);
1043 }
1044
1045 #[test]
1046 fn snap_handles_macro_location() {
1047 let diag = parse_diagnostic(
1048 r##"{
1049 "rendered": "error[E0277]: can't compare `{integer}` with `&str`\n --> src/main.rs:2:5\n |\n2 | assert_eq!(1, \"love\");\n | ^^^^^^^^^^^^^^^^^^^^^^ no implementation for `{integer} == &str`\n |\n = help: the trait `std::cmp::PartialEq<&str>` is not implemented for `{integer}`\n = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)\n\n",
1050 "children": [
1051 {
1052 "children": [],
1053 "code": null,
1054 "level": "help",
1055 "message": "the trait `std::cmp::PartialEq<&str>` is not implemented for `{integer}`",
1056 "rendered": null,
1057 "spans": []
1058 }
1059 ],
1060 "code": {
1061 "code": "E0277",
1062 "explanation": "\nYou tried to use a type which doesn't implement some trait in a place which\nexpected that trait. Erroneous code example:\n\n```compile_fail,E0277\n// here we declare the Foo trait with a bar method\ntrait Foo {\n fn bar(&self);\n}\n\n// we now declare a function which takes an object implementing the Foo trait\nfn some_func<T: Foo>(foo: T) {\n foo.bar();\n}\n\nfn main() {\n // we now call the method with the i32 type, which doesn't implement\n // the Foo trait\n some_func(5i32); // error: the trait bound `i32 : Foo` is not satisfied\n}\n```\n\nIn order to fix this error, verify that the type you're using does implement\nthe trait. Example:\n\n```\ntrait Foo {\n fn bar(&self);\n}\n\nfn some_func<T: Foo>(foo: T) {\n foo.bar(); // we can now use this method since i32 implements the\n // Foo trait\n}\n\n// we implement the trait on the i32 type\nimpl Foo for i32 {\n fn bar(&self) {}\n}\n\nfn main() {\n some_func(5i32); // ok!\n}\n```\n\nOr in a generic context, an erroneous code example would look like:\n\n```compile_fail,E0277\nfn some_func<T>(foo: T) {\n println!(\"{:?}\", foo); // error: the trait `core::fmt::Debug` is not\n // implemented for the type `T`\n}\n\nfn main() {\n // We now call the method with the i32 type,\n // which *does* implement the Debug trait.\n some_func(5i32);\n}\n```\n\nNote that the error here is in the definition of the generic function: Although\nwe only call it with a parameter that does implement `Debug`, the compiler\nstill rejects the function: It must work with all possible input types. In\norder to make this example compile, we need to restrict the generic type we're\naccepting:\n\n```\nuse std::fmt;\n\n// Restrict the input type to types that implement Debug.\nfn some_func<T: fmt::Debug>(foo: T) {\n println!(\"{:?}\", foo);\n}\n\nfn main() {\n // Calling the method is still fine, as i32 implements Debug.\n some_func(5i32);\n\n // This would fail to compile now:\n // struct WithoutDebug;\n // some_func(WithoutDebug);\n}\n```\n\nRust only looks at the signature of the called function, as such it must\nalready specify all requirements that will be used for every type parameter.\n"
1063 },
1064 "level": "error",
1065 "message": "can't compare `{integer}` with `&str`",
1066 "spans": [
1067 {
1068 "byte_end": 155,
1069 "byte_start": 153,
1070 "column_end": 33,
1071 "column_start": 31,
1072 "expansion": {
1073 "def_site_span": {
1074 "byte_end": 940,
1075 "byte_start": 0,
1076 "column_end": 6,
1077 "column_start": 1,
1078 "expansion": null,
1079 "file_name": "<::core::macros::assert_eq macros>",
1080 "is_primary": false,
1081 "label": null,
1082 "line_end": 36,
1083 "line_start": 1,
1084 "suggested_replacement": null,
1085 "suggestion_applicability": null,
1086 "text": [
1087 {
1088 "highlight_end": 35,
1089 "highlight_start": 1,
1090 "text": "($ left : expr, $ right : expr) =>"
1091 },
1092 {
1093 "highlight_end": 3,
1094 "highlight_start": 1,
1095 "text": "({"
1096 },
1097 {
1098 "highlight_end": 33,
1099 "highlight_start": 1,
1100 "text": " match (& $ left, & $ right)"
1101 },
1102 {
1103 "highlight_end": 7,
1104 "highlight_start": 1,
1105 "text": " {"
1106 },
1107 {
1108 "highlight_end": 34,
1109 "highlight_start": 1,
1110 "text": " (left_val, right_val) =>"
1111 },
1112 {
1113 "highlight_end": 11,
1114 "highlight_start": 1,
1115 "text": " {"
1116 },
1117 {
1118 "highlight_end": 46,
1119 "highlight_start": 1,
1120 "text": " if ! (* left_val == * right_val)"
1121 },
1122 {
1123 "highlight_end": 15,
1124 "highlight_start": 1,
1125 "text": " {"
1126 },
1127 {
1128 "highlight_end": 25,
1129 "highlight_start": 1,
1130 "text": " panic !"
1131 },
1132 {
1133 "highlight_end": 57,
1134 "highlight_start": 1,
1135 "text": " (r#\"assertion failed: `(left == right)`"
1136 },
1137 {
1138 "highlight_end": 16,
1139 "highlight_start": 1,
1140 "text": " left: `{:?}`,"
1141 },
1142 {
1143 "highlight_end": 18,
1144 "highlight_start": 1,
1145 "text": " right: `{:?}`\"#,"
1146 },
1147 {
1148 "highlight_end": 47,
1149 "highlight_start": 1,
1150 "text": " & * left_val, & * right_val)"
1151 },
1152 {
1153 "highlight_end": 15,
1154 "highlight_start": 1,
1155 "text": " }"
1156 },
1157 {
1158 "highlight_end": 11,
1159 "highlight_start": 1,
1160 "text": " }"
1161 },
1162 {
1163 "highlight_end": 7,
1164 "highlight_start": 1,
1165 "text": " }"
1166 },
1167 {
1168 "highlight_end": 42,
1169 "highlight_start": 1,
1170 "text": " }) ; ($ left : expr, $ right : expr,) =>"
1171 },
1172 {
1173 "highlight_end": 49,
1174 "highlight_start": 1,
1175 "text": "({ $ crate :: assert_eq ! ($ left, $ right) }) ;"
1176 },
1177 {
1178 "highlight_end": 53,
1179 "highlight_start": 1,
1180 "text": "($ left : expr, $ right : expr, $ ($ arg : tt) +) =>"
1181 },
1182 {
1183 "highlight_end": 3,
1184 "highlight_start": 1,
1185 "text": "({"
1186 },
1187 {
1188 "highlight_end": 37,
1189 "highlight_start": 1,
1190 "text": " match (& ($ left), & ($ right))"
1191 },
1192 {
1193 "highlight_end": 7,
1194 "highlight_start": 1,
1195 "text": " {"
1196 },
1197 {
1198 "highlight_end": 34,
1199 "highlight_start": 1,
1200 "text": " (left_val, right_val) =>"
1201 },
1202 {
1203 "highlight_end": 11,
1204 "highlight_start": 1,
1205 "text": " {"
1206 },
1207 {
1208 "highlight_end": 46,
1209 "highlight_start": 1,
1210 "text": " if ! (* left_val == * right_val)"
1211 },
1212 {
1213 "highlight_end": 15,
1214 "highlight_start": 1,
1215 "text": " {"
1216 },
1217 {
1218 "highlight_end": 25,
1219 "highlight_start": 1,
1220 "text": " panic !"
1221 },
1222 {
1223 "highlight_end": 57,
1224 "highlight_start": 1,
1225 "text": " (r#\"assertion failed: `(left == right)`"
1226 },
1227 {
1228 "highlight_end": 16,
1229 "highlight_start": 1,
1230 "text": " left: `{:?}`,"
1231 },
1232 {
1233 "highlight_end": 22,
1234 "highlight_start": 1,
1235 "text": " right: `{:?}`: {}\"#,"
1236 },
1237 {
1238 "highlight_end": 72,
1239 "highlight_start": 1,
1240 "text": " & * left_val, & * right_val, $ crate :: format_args !"
1241 },
1242 {
1243 "highlight_end": 33,
1244 "highlight_start": 1,
1245 "text": " ($ ($ arg) +))"
1246 },
1247 {
1248 "highlight_end": 15,
1249 "highlight_start": 1,
1250 "text": " }"
1251 },
1252 {
1253 "highlight_end": 11,
1254 "highlight_start": 1,
1255 "text": " }"
1256 },
1257 {
1258 "highlight_end": 7,
1259 "highlight_start": 1,
1260 "text": " }"
1261 },
1262 {
1263 "highlight_end": 6,
1264 "highlight_start": 1,
1265 "text": " }) ;"
1266 }
1267 ]
1268 },
1269 "macro_decl_name": "assert_eq!",
1270 "span": {
1271 "byte_end": 38,
1272 "byte_start": 16,
1273 "column_end": 27,
1274 "column_start": 5,
1275 "expansion": null,
1276 "file_name": "src/main.rs",
1277 "is_primary": false,
1278 "label": null,
1279 "line_end": 2,
1280 "line_start": 2,
1281 "suggested_replacement": null,
1282 "suggestion_applicability": null,
1283 "text": [
1284 {
1285 "highlight_end": 27,
1286 "highlight_start": 5,
1287 "text": " assert_eq!(1, \"love\");"
1288 }
1289 ]
1290 }
1291 },
1292 "file_name": "<::core::macros::assert_eq macros>",
1293 "is_primary": true,
1294 "label": "no implementation for `{integer} == &str`",
1295 "line_end": 7,
1296 "line_start": 7,
1297 "suggested_replacement": null,
1298 "suggestion_applicability": null,
1299 "text": [
1300 {
1301 "highlight_end": 33,
1302 "highlight_start": 31,
1303 "text": " if ! (* left_val == * right_val)"
1304 }
1305 ]
1306 }
1307 ]
1308}"##,
1309 );
1310
1311 let workspace_root = PathBuf::from("/test/");
1312 let diag =
1313 map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic");
1314 insta::assert_debug_snapshot!(diag);
1315 }
1316}
diff --git a/crates/ra_lsp_server/src/lib.rs b/crates/ra_lsp_server/src/lib.rs
index 2811231fa..2ca149fd5 100644
--- a/crates/ra_lsp_server/src/lib.rs
+++ b/crates/ra_lsp_server/src/lib.rs
@@ -22,7 +22,6 @@ macro_rules! print {
22} 22}
23 23
24mod caps; 24mod caps;
25mod cargo_check;
26mod cargo_target_spec; 25mod cargo_target_spec;
27mod conv; 26mod conv;
28mod main_loop; 27mod main_loop;
diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs
index c58af7e47..e66b8f9eb 100644
--- a/crates/ra_lsp_server/src/main_loop.rs
+++ b/crates/ra_lsp_server/src/main_loop.rs
@@ -10,6 +10,7 @@ use std::{error::Error, fmt, panic, path::PathBuf, sync::Arc, time::Instant};
10use crossbeam_channel::{select, unbounded, RecvError, Sender}; 10use crossbeam_channel::{select, unbounded, RecvError, Sender};
11use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; 11use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response};
12use lsp_types::{ClientCapabilities, NumberOrString}; 12use lsp_types::{ClientCapabilities, NumberOrString};
13use ra_cargo_watch::{CheckOptions, CheckTask};
13use ra_ide::{Canceled, FeatureFlags, FileId, LibraryData, SourceRootId}; 14use ra_ide::{Canceled, FeatureFlags, FileId, LibraryData, SourceRootId};
14use ra_prof::profile; 15use ra_prof::profile;
15use ra_vfs::{VfsTask, Watch}; 16use ra_vfs::{VfsTask, Watch};
@@ -19,7 +20,6 @@ use serde::{de::DeserializeOwned, Serialize};
19use threadpool::ThreadPool; 20use threadpool::ThreadPool;
20 21
21use crate::{ 22use crate::{
22 cargo_check::CheckTask,
23 main_loop::{ 23 main_loop::{
24 pending_requests::{PendingRequest, PendingRequests}, 24 pending_requests::{PendingRequest, PendingRequests},
25 subscriptions::Subscriptions, 25 subscriptions::Subscriptions,
@@ -127,10 +127,12 @@ pub fn main_loop(
127 .and_then(|it| it.line_folding_only) 127 .and_then(|it| it.line_folding_only)
128 .unwrap_or(false), 128 .unwrap_or(false),
129 max_inlay_hint_length: config.max_inlay_hint_length, 129 max_inlay_hint_length: config.max_inlay_hint_length,
130 cargo_watch_enable: config.cargo_watch_enable, 130 cargo_watch: CheckOptions {
131 cargo_watch_args: config.cargo_watch_args, 131 enable: config.cargo_watch_enable,
132 cargo_watch_command: config.cargo_watch_command, 132 args: config.cargo_watch_args,
133 cargo_watch_all_targets: config.cargo_watch_all_targets, 133 command: config.cargo_watch_command,
134 all_targets: config.cargo_watch_all_targets,
135 },
134 } 136 }
135 }; 137 };
136 138
diff --git a/crates/ra_lsp_server/src/world.rs b/crates/ra_lsp_server/src/world.rs
index 39a07c01a..4b3959e38 100644
--- a/crates/ra_lsp_server/src/world.rs
+++ b/crates/ra_lsp_server/src/world.rs
@@ -12,6 +12,7 @@ use crossbeam_channel::{unbounded, Receiver};
12use lsp_server::ErrorCode; 12use lsp_server::ErrorCode;
13use lsp_types::Url; 13use lsp_types::Url;
14use parking_lot::RwLock; 14use parking_lot::RwLock;
15use ra_cargo_watch::{CheckOptions, CheckWatcher, CheckWatcherSharedState};
15use ra_ide::{ 16use ra_ide::{
16 Analysis, AnalysisChange, AnalysisHost, CrateGraph, FeatureFlags, FileId, LibraryData, 17 Analysis, AnalysisChange, AnalysisHost, CrateGraph, FeatureFlags, FileId, LibraryData,
17 SourceRootId, 18 SourceRootId,
@@ -23,7 +24,6 @@ use relative_path::RelativePathBuf;
23use std::path::{Component, Prefix}; 24use std::path::{Component, Prefix};
24 25
25use crate::{ 26use crate::{
26 cargo_check::{CheckWatcher, CheckWatcherSharedState},
27 main_loop::pending_requests::{CompletedRequest, LatestRequests}, 27 main_loop::pending_requests::{CompletedRequest, LatestRequests},
28 LspError, Result, 28 LspError, Result,
29}; 29};
@@ -35,10 +35,7 @@ pub struct Options {
35 pub supports_location_link: bool, 35 pub supports_location_link: bool,
36 pub line_folding_only: bool, 36 pub line_folding_only: bool,
37 pub max_inlay_hint_length: Option<usize>, 37 pub max_inlay_hint_length: Option<usize>,
38 pub cargo_watch_enable: bool, 38 pub cargo_watch: CheckOptions,
39 pub cargo_watch_args: Vec<String>,
40 pub cargo_watch_command: String,
41 pub cargo_watch_all_targets: bool,
42} 39}
43 40
44/// `WorldState` is the primary mutable state of the language server 41/// `WorldState` is the primary mutable state of the language server
@@ -135,7 +132,8 @@ impl WorldState {
135 change.set_crate_graph(crate_graph); 132 change.set_crate_graph(crate_graph);
136 133
137 // FIXME: Figure out the multi-workspace situation 134 // FIXME: Figure out the multi-workspace situation
138 let check_watcher = CheckWatcher::new(&options, folder_roots.first().cloned().unwrap()); 135 let check_watcher =
136 CheckWatcher::new(&options.cargo_watch, folder_roots.first().cloned().unwrap());
139 137
140 let mut analysis_host = AnalysisHost::new(lru_capacity, feature_flags); 138 let mut analysis_host = AnalysisHost::new(lru_capacity, feature_flags);
141 analysis_host.apply_change(change); 139 analysis_host.apply_change(change);