aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2019-12-29 12:57:24 +0000
committerGitHub <[email protected]>2019-12-29 12:57:24 +0000
commitdc48f89581843248660ceb755bb20469ab6ac0c9 (patch)
tree05a7e47d1c68c250025be1ce6492f56c15353749 /crates
parentcdcb3d3833d3d5b37b2cd4dac91a6e9366f20aea (diff)
parent899dbebd02b41b12d89c9f485e85208b39b81932 (diff)
Merge #2668
2668: In-server cargo check watching r=matklad a=kiljacken Opening a draft now so people can follow the progress, and comment if they spot something stupid. Things that need doing: - [x] Running cargo check on save - [x] Pipe through configuration options from client - [x] Tests for parsing behavior - [x] Remove existing cargo watch support from VSCode extension - [x] Progress notification in VSCode extension using LSP 3.15 `$/progress` notification - [ ] ~~Rework ra-ide diagnostics to support secondary messages~~ - [ ] ~~Make cargo-check watcher use ra-ide diagnostics~~ ~~I'd love some input on whether to try to keep the status bar progress thingy for VSCode? It will require some plumbing, and maintaining yet another rust-analyzer specific LSP notification, which I'm not sure we want to.~~ Fixes #1894 Co-authored-by: Emil Lauridsen <[email protected]>
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.snap85
-rw-r--r--crates/ra_cargo_watch/src/conv/snapshots/test__snap_handles_macro_location.snap46
-rw-r--r--crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_incompatible_type_for_trait.snap46
-rw-r--r--crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_mismatched_type.snap46
-rw-r--r--crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_unused_variable.snap70
-rw-r--r--crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_wrong_number_of_parameters.snap65
-rw-r--r--crates/ra_cargo_watch/src/conv/test.rs700
-rw-r--r--crates/ra_cargo_watch/src/lib.rs392
-rw-r--r--crates/ra_lsp_server/Cargo.toml1
-rw-r--r--crates/ra_lsp_server/src/caps.rs4
-rw-r--r--crates/ra_lsp_server/src/config.rs9
-rw-r--r--crates/ra_lsp_server/src/main_loop.rs44
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs28
-rw-r--r--crates/ra_lsp_server/src/req.rs8
-rw-r--r--crates/ra_lsp_server/src/world.rs10
17 files changed, 1843 insertions, 8 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_cargo_watch/src/conv/snapshots/test__snap_clippy_pass_by_ref.snap b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_clippy_pass_by_ref.snap
new file mode 100644
index 000000000..cb0920914
--- /dev/null
+++ b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_clippy_pass_by_ref.snap
@@ -0,0 +1,85 @@
1---
2source: crates/ra_cargo_watch/src/conv/test.rs
3expression: diag
4---
5MappedRustDiagnostic {
6 location: Location {
7 uri: "file:///test/compiler/mir/tagset.rs",
8 range: Range {
9 start: Position {
10 line: 41,
11 character: 23,
12 },
13 end: Position {
14 line: 41,
15 character: 28,
16 },
17 },
18 },
19 diagnostic: Diagnostic {
20 range: Range {
21 start: Position {
22 line: 41,
23 character: 23,
24 },
25 end: Position {
26 line: 41,
27 character: 28,
28 },
29 },
30 severity: Some(
31 Warning,
32 ),
33 code: Some(
34 String(
35 "trivially_copy_pass_by_ref",
36 ),
37 ),
38 source: Some(
39 "clippy",
40 ),
41 message: "this argument is passed by reference, but would be more efficient if passed by value\n#[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]\nfor further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref",
42 related_information: Some(
43 [
44 DiagnosticRelatedInformation {
45 location: Location {
46 uri: "file:///test/compiler/lib.rs",
47 range: Range {
48 start: Position {
49 line: 0,
50 character: 8,
51 },
52 end: Position {
53 line: 0,
54 character: 19,
55 },
56 },
57 },
58 message: "lint level defined here",
59 },
60 ],
61 ),
62 tags: None,
63 },
64 suggested_fixes: [
65 SuggestedFix {
66 title: "consider passing by value instead: \'self\'",
67 location: Location {
68 uri: "file:///test/compiler/mir/tagset.rs",
69 range: Range {
70 start: Position {
71 line: 41,
72 character: 23,
73 },
74 end: Position {
75 line: 41,
76 character: 28,
77 },
78 },
79 },
80 replacement: "self",
81 applicability: Unspecified,
82 diagnostics: [],
83 },
84 ],
85}
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/test__snap_handles_macro_location.snap b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_handles_macro_location.snap
new file mode 100644
index 000000000..19510ecc1
--- /dev/null
+++ b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_handles_macro_location.snap
@@ -0,0 +1,46 @@
1---
2source: crates/ra_cargo_watch/src/conv/test.rs
3expression: diag
4---
5MappedRustDiagnostic {
6 location: Location {
7 uri: "file:///test/src/main.rs",
8 range: Range {
9 start: Position {
10 line: 1,
11 character: 4,
12 },
13 end: Position {
14 line: 1,
15 character: 26,
16 },
17 },
18 },
19 diagnostic: Diagnostic {
20 range: Range {
21 start: Position {
22 line: 1,
23 character: 4,
24 },
25 end: Position {
26 line: 1,
27 character: 26,
28 },
29 },
30 severity: Some(
31 Error,
32 ),
33 code: Some(
34 String(
35 "E0277",
36 ),
37 ),
38 source: Some(
39 "rustc",
40 ),
41 message: "can\'t compare `{integer}` with `&str`\nthe trait `std::cmp::PartialEq<&str>` is not implemented for `{integer}`",
42 related_information: None,
43 tags: None,
44 },
45 suggested_fixes: [],
46}
diff --git a/crates/ra_cargo_watch/src/conv/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
new file mode 100644
index 000000000..cf683e4b6
--- /dev/null
+++ b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_incompatible_type_for_trait.snap
@@ -0,0 +1,46 @@
1---
2source: crates/ra_cargo_watch/src/conv/test.rs
3expression: diag
4---
5MappedRustDiagnostic {
6 location: Location {
7 uri: "file:///test/compiler/ty/list_iter.rs",
8 range: Range {
9 start: Position {
10 line: 51,
11 character: 4,
12 },
13 end: Position {
14 line: 51,
15 character: 47,
16 },
17 },
18 },
19 diagnostic: Diagnostic {
20 range: Range {
21 start: Position {
22 line: 51,
23 character: 4,
24 },
25 end: Position {
26 line: 51,
27 character: 47,
28 },
29 },
30 severity: Some(
31 Error,
32 ),
33 code: Some(
34 String(
35 "E0053",
36 ),
37 ),
38 source: Some(
39 "rustc",
40 ),
41 message: "method `next` has an incompatible type for trait\nexpected 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>>`",
42 related_information: None,
43 tags: None,
44 },
45 suggested_fixes: [],
46}
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_mismatched_type.snap b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_mismatched_type.snap
new file mode 100644
index 000000000..8c1483c74
--- /dev/null
+++ b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_mismatched_type.snap
@@ -0,0 +1,46 @@
1---
2source: crates/ra_cargo_watch/src/conv/test.rs
3expression: diag
4---
5MappedRustDiagnostic {
6 location: Location {
7 uri: "file:///test/runtime/compiler_support.rs",
8 range: Range {
9 start: Position {
10 line: 47,
11 character: 64,
12 },
13 end: Position {
14 line: 47,
15 character: 69,
16 },
17 },
18 },
19 diagnostic: Diagnostic {
20 range: Range {
21 start: Position {
22 line: 47,
23 character: 64,
24 },
25 end: Position {
26 line: 47,
27 character: 69,
28 },
29 },
30 severity: Some(
31 Error,
32 ),
33 code: Some(
34 String(
35 "E0308",
36 ),
37 ),
38 source: Some(
39 "rustc",
40 ),
41 message: "mismatched types\nexpected usize, found u32",
42 related_information: None,
43 tags: None,
44 },
45 suggested_fixes: [],
46}
diff --git a/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_unused_variable.snap b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_unused_variable.snap
new file mode 100644
index 000000000..eb5a2247b
--- /dev/null
+++ b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_unused_variable.snap
@@ -0,0 +1,70 @@
1---
2source: crates/ra_cargo_watch/src/conv/test.rs
3expression: diag
4---
5MappedRustDiagnostic {
6 location: Location {
7 uri: "file:///test/driver/subcommand/repl.rs",
8 range: Range {
9 start: Position {
10 line: 290,
11 character: 8,
12 },
13 end: Position {
14 line: 290,
15 character: 11,
16 },
17 },
18 },
19 diagnostic: Diagnostic {
20 range: Range {
21 start: Position {
22 line: 290,
23 character: 8,
24 },
25 end: Position {
26 line: 290,
27 character: 11,
28 },
29 },
30 severity: Some(
31 Warning,
32 ),
33 code: Some(
34 String(
35 "unused_variables",
36 ),
37 ),
38 source: Some(
39 "rustc",
40 ),
41 message: "unused variable: `foo`\n#[warn(unused_variables)] on by default",
42 related_information: None,
43 tags: Some(
44 [
45 Unnecessary,
46 ],
47 ),
48 },
49 suggested_fixes: [
50 SuggestedFix {
51 title: "consider prefixing with an underscore: \'_foo\'",
52 location: Location {
53 uri: "file:///test/driver/subcommand/repl.rs",
54 range: Range {
55 start: Position {
56 line: 290,
57 character: 8,
58 },
59 end: Position {
60 line: 290,
61 character: 11,
62 },
63 },
64 },
65 replacement: "_foo",
66 applicability: MachineApplicable,
67 diagnostics: [],
68 },
69 ],
70}
diff --git a/crates/ra_cargo_watch/src/conv/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
new file mode 100644
index 000000000..2f4518931
--- /dev/null
+++ b/crates/ra_cargo_watch/src/conv/snapshots/test__snap_rustc_wrong_number_of_parameters.snap
@@ -0,0 +1,65 @@
1---
2source: crates/ra_cargo_watch/src/conv/test.rs
3expression: diag
4---
5MappedRustDiagnostic {
6 location: Location {
7 uri: "file:///test/compiler/ty/select.rs",
8 range: Range {
9 start: Position {
10 line: 103,
11 character: 17,
12 },
13 end: Position {
14 line: 103,
15 character: 29,
16 },
17 },
18 },
19 diagnostic: Diagnostic {
20 range: Range {
21 start: Position {
22 line: 103,
23 character: 17,
24 },
25 end: Position {
26 line: 103,
27 character: 29,
28 },
29 },
30 severity: Some(
31 Error,
32 ),
33 code: Some(
34 String(
35 "E0061",
36 ),
37 ),
38 source: Some(
39 "rustc",
40 ),
41 message: "this function takes 2 parameters but 3 parameters were supplied\nexpected 2 parameters",
42 related_information: Some(
43 [
44 DiagnosticRelatedInformation {
45 location: Location {
46 uri: "file:///test/compiler/ty/select.rs",
47 range: Range {
48 start: Position {
49 line: 218,
50 character: 4,
51 },
52 end: Position {
53 line: 230,
54 character: 5,
55 },
56 },
57 },
58 message: "defined here",
59 },
60 ],
61 ),
62 tags: None,
63 },
64 suggested_fixes: [],
65}
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..6817245c2
--- /dev/null
+++ b/crates/ra_cargo_watch/src/conv/test.rs
@@ -0,0 +1,700 @@
1//! This module contains the large and verbose snapshot tests for the
2//! conversions between `cargo check` json and LSP diagnostics.
3use crate::*;
4
5fn parse_diagnostic(val: &str) -> cargo_metadata::diagnostic::Diagnostic {
6 serde_json::from_str::<cargo_metadata::diagnostic::Diagnostic>(val).unwrap()
7}
8
9#[test]
10fn snap_rustc_incompatible_type_for_trait() {
11 let diag = parse_diagnostic(
12 r##"{
13 "message": "method `next` has an incompatible type for trait",
14 "code": {
15 "code": "E0053",
16 "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"
17 },
18 "level": "error",
19 "spans": [
20 {
21 "file_name": "compiler/ty/list_iter.rs",
22 "byte_start": 1307,
23 "byte_end": 1350,
24 "line_start": 52,
25 "line_end": 52,
26 "column_start": 5,
27 "column_end": 48,
28 "is_primary": true,
29 "text": [
30 {
31 "text": " fn next(&self) -> Option<&'list ty::Ref<M>> {",
32 "highlight_start": 5,
33 "highlight_end": 48
34 }
35 ],
36 "label": "types differ in mutability",
37 "suggested_replacement": null,
38 "suggestion_applicability": null,
39 "expansion": null
40 }
41 ],
42 "children": [
43 {
44 "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>>`",
45 "code": null,
46 "level": "note",
47 "spans": [],
48 "children": [],
49 "rendered": null
50 }
51 ],
52 "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"
53 }
54 "##,
55 );
56
57 let workspace_root = PathBuf::from("/test/");
58 let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic");
59 insta::assert_debug_snapshot!(diag);
60}
61
62#[test]
63fn snap_rustc_unused_variable() {
64 let diag = parse_diagnostic(
65 r##"{
66"message": "unused variable: `foo`",
67"code": {
68 "code": "unused_variables",
69 "explanation": null
70},
71"level": "warning",
72"spans": [
73 {
74 "file_name": "driver/subcommand/repl.rs",
75 "byte_start": 9228,
76 "byte_end": 9231,
77 "line_start": 291,
78 "line_end": 291,
79 "column_start": 9,
80 "column_end": 12,
81 "is_primary": true,
82 "text": [
83 {
84 "text": " let foo = 42;",
85 "highlight_start": 9,
86 "highlight_end": 12
87 }
88 ],
89 "label": null,
90 "suggested_replacement": null,
91 "suggestion_applicability": null,
92 "expansion": null
93 }
94],
95"children": [
96 {
97 "message": "#[warn(unused_variables)] on by default",
98 "code": null,
99 "level": "note",
100 "spans": [],
101 "children": [],
102 "rendered": null
103 },
104 {
105 "message": "consider prefixing with an underscore",
106 "code": null,
107 "level": "help",
108 "spans": [
109 {
110 "file_name": "driver/subcommand/repl.rs",
111 "byte_start": 9228,
112 "byte_end": 9231,
113 "line_start": 291,
114 "line_end": 291,
115 "column_start": 9,
116 "column_end": 12,
117 "is_primary": true,
118 "text": [
119 {
120 "text": " let foo = 42;",
121 "highlight_start": 9,
122 "highlight_end": 12
123 }
124 ],
125 "label": null,
126 "suggested_replacement": "_foo",
127 "suggestion_applicability": "MachineApplicable",
128 "expansion": null
129 }
130 ],
131 "children": [],
132 "rendered": null
133 }
134],
135"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"
136}"##,
137 );
138
139 let workspace_root = PathBuf::from("/test/");
140 let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic");
141 insta::assert_debug_snapshot!(diag);
142}
143
144#[test]
145fn snap_rustc_wrong_number_of_parameters() {
146 let diag = parse_diagnostic(
147 r##"{
148"message": "this function takes 2 parameters but 3 parameters were supplied",
149"code": {
150 "code": "E0061",
151 "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"
152},
153"level": "error",
154"spans": [
155 {
156 "file_name": "compiler/ty/select.rs",
157 "byte_start": 8787,
158 "byte_end": 9241,
159 "line_start": 219,
160 "line_end": 231,
161 "column_start": 5,
162 "column_end": 6,
163 "is_primary": false,
164 "text": [
165 {
166 "text": " pub fn add_evidence(",
167 "highlight_start": 5,
168 "highlight_end": 25
169 },
170 {
171 "text": " &mut self,",
172 "highlight_start": 1,
173 "highlight_end": 19
174 },
175 {
176 "text": " target_poly: &ty::Ref<ty::Poly>,",
177 "highlight_start": 1,
178 "highlight_end": 41
179 },
180 {
181 "text": " evidence_poly: &ty::Ref<ty::Poly>,",
182 "highlight_start": 1,
183 "highlight_end": 43
184 },
185 {
186 "text": " ) {",
187 "highlight_start": 1,
188 "highlight_end": 8
189 },
190 {
191 "text": " match target_poly {",
192 "highlight_start": 1,
193 "highlight_end": 28
194 },
195 {
196 "text": " ty::Ref::Var(tvar, _) => self.add_var_evidence(tvar, evidence_poly),",
197 "highlight_start": 1,
198 "highlight_end": 81
199 },
200 {
201 "text": " ty::Ref::Fixed(target_ty) => {",
202 "highlight_start": 1,
203 "highlight_end": 43
204 },
205 {
206 "text": " let evidence_ty = evidence_poly.resolve_to_ty();",
207 "highlight_start": 1,
208 "highlight_end": 65
209 },
210 {
211 "text": " self.add_evidence_ty(target_ty, evidence_poly, evidence_ty)",
212 "highlight_start": 1,
213 "highlight_end": 76
214 },
215 {
216 "text": " }",
217 "highlight_start": 1,
218 "highlight_end": 14
219 },
220 {
221 "text": " }",
222 "highlight_start": 1,
223 "highlight_end": 10
224 },
225 {
226 "text": " }",
227 "highlight_start": 1,
228 "highlight_end": 6
229 }
230 ],
231 "label": "defined here",
232 "suggested_replacement": null,
233 "suggestion_applicability": null,
234 "expansion": null
235 },
236 {
237 "file_name": "compiler/ty/select.rs",
238 "byte_start": 4045,
239 "byte_end": 4057,
240 "line_start": 104,
241 "line_end": 104,
242 "column_start": 18,
243 "column_end": 30,
244 "is_primary": true,
245 "text": [
246 {
247 "text": " self.add_evidence(target_fixed, evidence_fixed, false);",
248 "highlight_start": 18,
249 "highlight_end": 30
250 }
251 ],
252 "label": "expected 2 parameters",
253 "suggested_replacement": null,
254 "suggestion_applicability": null,
255 "expansion": null
256 }
257],
258"children": [],
259"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"
260}"##,
261 );
262
263 let workspace_root = PathBuf::from("/test/");
264 let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic");
265 insta::assert_debug_snapshot!(diag);
266}
267
268#[test]
269fn snap_clippy_pass_by_ref() {
270 let diag = parse_diagnostic(
271 r##"{
272"message": "this argument is passed by reference, but would be more efficient if passed by value",
273"code": {
274 "code": "clippy::trivially_copy_pass_by_ref",
275 "explanation": null
276},
277"level": "warning",
278"spans": [
279 {
280 "file_name": "compiler/mir/tagset.rs",
281 "byte_start": 941,
282 "byte_end": 946,
283 "line_start": 42,
284 "line_end": 42,
285 "column_start": 24,
286 "column_end": 29,
287 "is_primary": true,
288 "text": [
289 {
290 "text": " pub fn is_disjoint(&self, other: Self) -> bool {",
291 "highlight_start": 24,
292 "highlight_end": 29
293 }
294 ],
295 "label": null,
296 "suggested_replacement": null,
297 "suggestion_applicability": null,
298 "expansion": null
299 }
300],
301"children": [
302 {
303 "message": "lint level defined here",
304 "code": null,
305 "level": "note",
306 "spans": [
307 {
308 "file_name": "compiler/lib.rs",
309 "byte_start": 8,
310 "byte_end": 19,
311 "line_start": 1,
312 "line_end": 1,
313 "column_start": 9,
314 "column_end": 20,
315 "is_primary": true,
316 "text": [
317 {
318 "text": "#![warn(clippy::all)]",
319 "highlight_start": 9,
320 "highlight_end": 20
321 }
322 ],
323 "label": null,
324 "suggested_replacement": null,
325 "suggestion_applicability": null,
326 "expansion": null
327 }
328 ],
329 "children": [],
330 "rendered": null
331 },
332 {
333 "message": "#[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]",
334 "code": null,
335 "level": "note",
336 "spans": [],
337 "children": [],
338 "rendered": null
339 },
340 {
341 "message": "for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref",
342 "code": null,
343 "level": "help",
344 "spans": [],
345 "children": [],
346 "rendered": null
347 },
348 {
349 "message": "consider passing by value instead",
350 "code": null,
351 "level": "help",
352 "spans": [
353 {
354 "file_name": "compiler/mir/tagset.rs",
355 "byte_start": 941,
356 "byte_end": 946,
357 "line_start": 42,
358 "line_end": 42,
359 "column_start": 24,
360 "column_end": 29,
361 "is_primary": true,
362 "text": [
363 {
364 "text": " pub fn is_disjoint(&self, other: Self) -> bool {",
365 "highlight_start": 24,
366 "highlight_end": 29
367 }
368 ],
369 "label": null,
370 "suggested_replacement": "self",
371 "suggestion_applicability": "Unspecified",
372 "expansion": null
373 }
374 ],
375 "children": [],
376 "rendered": null
377 }
378],
379"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"
380}"##,
381 );
382
383 let workspace_root = PathBuf::from("/test/");
384 let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic");
385 insta::assert_debug_snapshot!(diag);
386}
387
388#[test]
389fn snap_rustc_mismatched_type() {
390 let diag = parse_diagnostic(
391 r##"{
392"message": "mismatched types",
393"code": {
394 "code": "E0308",
395 "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"
396},
397"level": "error",
398"spans": [
399 {
400 "file_name": "runtime/compiler_support.rs",
401 "byte_start": 1589,
402 "byte_end": 1594,
403 "line_start": 48,
404 "line_end": 48,
405 "column_start": 65,
406 "column_end": 70,
407 "is_primary": true,
408 "text": [
409 {
410 "text": " let layout = alloc::Layout::from_size_align_unchecked(size, align);",
411 "highlight_start": 65,
412 "highlight_end": 70
413 }
414 ],
415 "label": "expected usize, found u32",
416 "suggested_replacement": null,
417 "suggestion_applicability": null,
418 "expansion": null
419 }
420],
421"children": [],
422"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"
423}"##,
424 );
425
426 let workspace_root = PathBuf::from("/test/");
427 let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic");
428 insta::assert_debug_snapshot!(diag);
429}
430
431#[test]
432fn snap_handles_macro_location() {
433 let diag = parse_diagnostic(
434 r##"{
435"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",
436"children": [
437 {
438 "children": [],
439 "code": null,
440 "level": "help",
441 "message": "the trait `std::cmp::PartialEq<&str>` is not implemented for `{integer}`",
442 "rendered": null,
443 "spans": []
444 }
445],
446"code": {
447 "code": "E0277",
448 "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"
449},
450"level": "error",
451"message": "can't compare `{integer}` with `&str`",
452"spans": [
453 {
454 "byte_end": 155,
455 "byte_start": 153,
456 "column_end": 33,
457 "column_start": 31,
458 "expansion": {
459 "def_site_span": {
460 "byte_end": 940,
461 "byte_start": 0,
462 "column_end": 6,
463 "column_start": 1,
464 "expansion": null,
465 "file_name": "<::core::macros::assert_eq macros>",
466 "is_primary": false,
467 "label": null,
468 "line_end": 36,
469 "line_start": 1,
470 "suggested_replacement": null,
471 "suggestion_applicability": null,
472 "text": [
473 {
474 "highlight_end": 35,
475 "highlight_start": 1,
476 "text": "($ left : expr, $ right : expr) =>"
477 },
478 {
479 "highlight_end": 3,
480 "highlight_start": 1,
481 "text": "({"
482 },
483 {
484 "highlight_end": 33,
485 "highlight_start": 1,
486 "text": " match (& $ left, & $ right)"
487 },
488 {
489 "highlight_end": 7,
490 "highlight_start": 1,
491 "text": " {"
492 },
493 {
494 "highlight_end": 34,
495 "highlight_start": 1,
496 "text": " (left_val, right_val) =>"
497 },
498 {
499 "highlight_end": 11,
500 "highlight_start": 1,
501 "text": " {"
502 },
503 {
504 "highlight_end": 46,
505 "highlight_start": 1,
506 "text": " if ! (* left_val == * right_val)"
507 },
508 {
509 "highlight_end": 15,
510 "highlight_start": 1,
511 "text": " {"
512 },
513 {
514 "highlight_end": 25,
515 "highlight_start": 1,
516 "text": " panic !"
517 },
518 {
519 "highlight_end": 57,
520 "highlight_start": 1,
521 "text": " (r#\"assertion failed: `(left == right)`"
522 },
523 {
524 "highlight_end": 16,
525 "highlight_start": 1,
526 "text": " left: `{:?}`,"
527 },
528 {
529 "highlight_end": 18,
530 "highlight_start": 1,
531 "text": " right: `{:?}`\"#,"
532 },
533 {
534 "highlight_end": 47,
535 "highlight_start": 1,
536 "text": " & * left_val, & * right_val)"
537 },
538 {
539 "highlight_end": 15,
540 "highlight_start": 1,
541 "text": " }"
542 },
543 {
544 "highlight_end": 11,
545 "highlight_start": 1,
546 "text": " }"
547 },
548 {
549 "highlight_end": 7,
550 "highlight_start": 1,
551 "text": " }"
552 },
553 {
554 "highlight_end": 42,
555 "highlight_start": 1,
556 "text": " }) ; ($ left : expr, $ right : expr,) =>"
557 },
558 {
559 "highlight_end": 49,
560 "highlight_start": 1,
561 "text": "({ $ crate :: assert_eq ! ($ left, $ right) }) ;"
562 },
563 {
564 "highlight_end": 53,
565 "highlight_start": 1,
566 "text": "($ left : expr, $ right : expr, $ ($ arg : tt) +) =>"
567 },
568 {
569 "highlight_end": 3,
570 "highlight_start": 1,
571 "text": "({"
572 },
573 {
574 "highlight_end": 37,
575 "highlight_start": 1,
576 "text": " match (& ($ left), & ($ right))"
577 },
578 {
579 "highlight_end": 7,
580 "highlight_start": 1,
581 "text": " {"
582 },
583 {
584 "highlight_end": 34,
585 "highlight_start": 1,
586 "text": " (left_val, right_val) =>"
587 },
588 {
589 "highlight_end": 11,
590 "highlight_start": 1,
591 "text": " {"
592 },
593 {
594 "highlight_end": 46,
595 "highlight_start": 1,
596 "text": " if ! (* left_val == * right_val)"
597 },
598 {
599 "highlight_end": 15,
600 "highlight_start": 1,
601 "text": " {"
602 },
603 {
604 "highlight_end": 25,
605 "highlight_start": 1,
606 "text": " panic !"
607 },
608 {
609 "highlight_end": 57,
610 "highlight_start": 1,
611 "text": " (r#\"assertion failed: `(left == right)`"
612 },
613 {
614 "highlight_end": 16,
615 "highlight_start": 1,
616 "text": " left: `{:?}`,"
617 },
618 {
619 "highlight_end": 22,
620 "highlight_start": 1,
621 "text": " right: `{:?}`: {}\"#,"
622 },
623 {
624 "highlight_end": 72,
625 "highlight_start": 1,
626 "text": " & * left_val, & * right_val, $ crate :: format_args !"
627 },
628 {
629 "highlight_end": 33,
630 "highlight_start": 1,
631 "text": " ($ ($ arg) +))"
632 },
633 {
634 "highlight_end": 15,
635 "highlight_start": 1,
636 "text": " }"
637 },
638 {
639 "highlight_end": 11,
640 "highlight_start": 1,
641 "text": " }"
642 },
643 {
644 "highlight_end": 7,
645 "highlight_start": 1,
646 "text": " }"
647 },
648 {
649 "highlight_end": 6,
650 "highlight_start": 1,
651 "text": " }) ;"
652 }
653 ]
654 },
655 "macro_decl_name": "assert_eq!",
656 "span": {
657 "byte_end": 38,
658 "byte_start": 16,
659 "column_end": 27,
660 "column_start": 5,
661 "expansion": null,
662 "file_name": "src/main.rs",
663 "is_primary": false,
664 "label": null,
665 "line_end": 2,
666 "line_start": 2,
667 "suggested_replacement": null,
668 "suggestion_applicability": null,
669 "text": [
670 {
671 "highlight_end": 27,
672 "highlight_start": 5,
673 "text": " assert_eq!(1, \"love\");"
674 }
675 ]
676 }
677 },
678 "file_name": "<::core::macros::assert_eq macros>",
679 "is_primary": true,
680 "label": "no implementation for `{integer} == &str`",
681 "line_end": 7,
682 "line_start": 7,
683 "suggested_replacement": null,
684 "suggestion_applicability": null,
685 "text": [
686 {
687 "highlight_end": 33,
688 "highlight_start": 31,
689 "text": " if ! (* left_val == * right_val)"
690 }
691 ]
692 }
693]
694}"##,
695 );
696
697 let workspace_root = PathBuf::from("/test/");
698 let diag = map_rust_diagnostic_to_lsp(&diag, &workspace_root).expect("couldn't map diagnostic");
699 insta::assert_debug_snapshot!(diag);
700}
diff --git a/crates/ra_cargo_watch/src/lib.rs b/crates/ra_cargo_watch/src/lib.rs
new file mode 100644
index 000000000..78250f910
--- /dev/null
+++ b/crates/ra_cargo_watch/src/lib.rs
@@ -0,0 +1,392 @@
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::{never, select, unbounded, Receiver, RecvError, Sender};
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/// The spawned thread is shut down when this struct is dropped.
36#[derive(Debug)]
37pub struct CheckWatcher {
38 pub task_recv: Receiver<CheckTask>,
39 pub cmd_send: Option<Sender<CheckCommand>>,
40 pub shared: Arc<RwLock<CheckWatcherSharedState>>,
41 handle: Option<JoinHandle<()>>,
42}
43
44impl CheckWatcher {
45 pub fn new(options: &CheckOptions, workspace_root: PathBuf) -> CheckWatcher {
46 let options = options.clone();
47 let shared = Arc::new(RwLock::new(CheckWatcherSharedState::new()));
48
49 let (task_send, task_recv) = unbounded::<CheckTask>();
50 let (cmd_send, cmd_recv) = unbounded::<CheckCommand>();
51 let shared_ = shared.clone();
52 let handle = std::thread::spawn(move || {
53 let mut check = CheckWatcherState::new(options, workspace_root, shared_);
54 check.run(&task_send, &cmd_recv);
55 });
56 CheckWatcher { task_recv, cmd_send: Some(cmd_send), handle: Some(handle), shared }
57 }
58
59 /// Schedule a re-start of the cargo check worker.
60 pub fn update(&self) {
61 if let Some(cmd_send) = &self.cmd_send {
62 cmd_send.send(CheckCommand::Update).unwrap();
63 }
64 }
65}
66
67impl std::ops::Drop for CheckWatcher {
68 fn drop(&mut self) {
69 if let Some(handle) = self.handle.take() {
70 // Take the sender out of the option
71 let recv = self.cmd_send.take();
72
73 // Dropping the sender finishes the thread loop
74 drop(recv);
75
76 // Join the thread, it should finish shortly. We don't really care
77 // whether it panicked, so it is safe to ignore the result
78 let _ = handle.join();
79 }
80 }
81}
82
83#[derive(Debug)]
84pub struct CheckWatcherSharedState {
85 diagnostic_collection: HashMap<Url, Vec<Diagnostic>>,
86 suggested_fix_collection: HashMap<Url, Vec<SuggestedFix>>,
87}
88
89impl CheckWatcherSharedState {
90 fn new() -> CheckWatcherSharedState {
91 CheckWatcherSharedState {
92 diagnostic_collection: HashMap::new(),
93 suggested_fix_collection: HashMap::new(),
94 }
95 }
96
97 /// Clear the cached diagnostics, and schedule updating diagnostics by the
98 /// server, to clear stale results.
99 pub fn clear(&mut self, task_send: &Sender<CheckTask>) {
100 let cleared_files: Vec<Url> = self.diagnostic_collection.keys().cloned().collect();
101
102 self.diagnostic_collection.clear();
103 self.suggested_fix_collection.clear();
104
105 for uri in cleared_files {
106 task_send.send(CheckTask::Update(uri.clone())).unwrap();
107 }
108 }
109
110 pub fn diagnostics_for(&self, uri: &Url) -> Option<&[Diagnostic]> {
111 self.diagnostic_collection.get(uri).map(|d| d.as_slice())
112 }
113
114 pub fn fixes_for(&self, uri: &Url) -> Option<&[SuggestedFix]> {
115 self.suggested_fix_collection.get(uri).map(|d| d.as_slice())
116 }
117
118 fn add_diagnostic(&mut self, file_uri: Url, diagnostic: Diagnostic) {
119 let diagnostics = self.diagnostic_collection.entry(file_uri).or_default();
120
121 // If we're building multiple targets it's possible we've already seen this diagnostic
122 let is_duplicate = diagnostics.iter().any(|d| are_diagnostics_equal(d, &diagnostic));
123 if is_duplicate {
124 return;
125 }
126
127 diagnostics.push(diagnostic);
128 }
129
130 fn add_suggested_fix_for_diagnostic(
131 &mut self,
132 mut suggested_fix: SuggestedFix,
133 diagnostic: &Diagnostic,
134 ) {
135 let file_uri = suggested_fix.location.uri.clone();
136 let file_suggestions = self.suggested_fix_collection.entry(file_uri).or_default();
137
138 let existing_suggestion: Option<&mut SuggestedFix> =
139 file_suggestions.iter_mut().find(|s| s == &&suggested_fix);
140 if let Some(existing_suggestion) = existing_suggestion {
141 // The existing suggestion also applies to this new diagnostic
142 existing_suggestion.diagnostics.push(diagnostic.clone());
143 } else {
144 // We haven't seen this suggestion before
145 suggested_fix.diagnostics.push(diagnostic.clone());
146 file_suggestions.push(suggested_fix);
147 }
148 }
149}
150
151#[derive(Debug)]
152pub enum CheckTask {
153 /// Request a update of the given files diagnostics
154 Update(Url),
155
156 /// Request check progress notification to client
157 Status(WorkDoneProgress),
158}
159
160pub enum CheckCommand {
161 /// Request re-start of check thread
162 Update,
163}
164
165struct CheckWatcherState {
166 options: CheckOptions,
167 workspace_root: PathBuf,
168 watcher: WatchThread,
169 last_update_req: Option<Instant>,
170 shared: Arc<RwLock<CheckWatcherSharedState>>,
171}
172
173impl CheckWatcherState {
174 pub fn new(
175 options: CheckOptions,
176 workspace_root: PathBuf,
177 shared: Arc<RwLock<CheckWatcherSharedState>>,
178 ) -> CheckWatcherState {
179 let watcher = WatchThread::new(&options, &workspace_root);
180 CheckWatcherState { options, workspace_root, watcher, last_update_req: None, shared }
181 }
182
183 pub fn run(&mut self, task_send: &Sender<CheckTask>, cmd_recv: &Receiver<CheckCommand>) {
184 loop {
185 select! {
186 recv(&cmd_recv) -> cmd => match cmd {
187 Ok(cmd) => self.handle_command(cmd),
188 Err(RecvError) => {
189 // Command channel has closed, so shut down
190 break;
191 },
192 },
193 recv(self.watcher.message_recv) -> msg => match msg {
194 Ok(msg) => self.handle_message(msg, task_send),
195 Err(RecvError) => {
196 // Watcher finished, replace it with a never channel to
197 // avoid busy-waiting.
198 std::mem::replace(&mut self.watcher.message_recv, never());
199 },
200 }
201 };
202
203 if self.should_recheck() {
204 self.last_update_req.take();
205 self.shared.write().clear(task_send);
206
207 // By replacing the watcher, we drop the previous one which
208 // causes it to shut down automatically.
209 self.watcher = WatchThread::new(&self.options, &self.workspace_root);
210 }
211 }
212 }
213
214 fn should_recheck(&mut self) -> bool {
215 if let Some(_last_update_req) = &self.last_update_req {
216 // We currently only request an update on save, as we need up to
217 // date source on disk for cargo check to do it's magic, so we
218 // don't really need to debounce the requests at this point.
219 return true;
220 }
221 false
222 }
223
224 fn handle_command(&mut self, cmd: CheckCommand) {
225 match cmd {
226 CheckCommand::Update => self.last_update_req = Some(Instant::now()),
227 }
228 }
229
230 fn handle_message(&mut self, msg: CheckEvent, task_send: &Sender<CheckTask>) {
231 match msg {
232 CheckEvent::Begin => {
233 task_send
234 .send(CheckTask::Status(WorkDoneProgress::Begin(WorkDoneProgressBegin {
235 title: "Running 'cargo check'".to_string(),
236 cancellable: Some(false),
237 message: None,
238 percentage: None,
239 })))
240 .unwrap();
241 }
242
243 CheckEvent::End => {
244 task_send
245 .send(CheckTask::Status(WorkDoneProgress::End(WorkDoneProgressEnd {
246 message: None,
247 })))
248 .unwrap();
249 }
250
251 CheckEvent::Msg(Message::CompilerArtifact(msg)) => {
252 task_send
253 .send(CheckTask::Status(WorkDoneProgress::Report(WorkDoneProgressReport {
254 cancellable: Some(false),
255 message: Some(msg.target.name),
256 percentage: None,
257 })))
258 .unwrap();
259 }
260
261 CheckEvent::Msg(Message::CompilerMessage(msg)) => {
262 let map_result =
263 match map_rust_diagnostic_to_lsp(&msg.message, &self.workspace_root) {
264 Some(map_result) => map_result,
265 None => return,
266 };
267
268 let MappedRustDiagnostic { location, diagnostic, suggested_fixes } = map_result;
269 let file_uri = location.uri.clone();
270
271 if !suggested_fixes.is_empty() {
272 for suggested_fix in suggested_fixes {
273 self.shared
274 .write()
275 .add_suggested_fix_for_diagnostic(suggested_fix, &diagnostic);
276 }
277 }
278 self.shared.write().add_diagnostic(file_uri, diagnostic);
279
280 task_send.send(CheckTask::Update(location.uri)).unwrap();
281 }
282
283 CheckEvent::Msg(Message::BuildScriptExecuted(_msg)) => {}
284 CheckEvent::Msg(Message::Unknown) => {}
285 }
286 }
287}
288
289/// WatchThread exists to wrap around the communication needed to be able to
290/// run `cargo check` without blocking. Currently the Rust standard library
291/// doesn't provide a way to read sub-process output without blocking, so we
292/// have to wrap sub-processes output handling in a thread and pass messages
293/// back over a channel.
294/// The correct way to dispose of the thread is to drop it, on which the
295/// sub-process will be killed, and the thread will be joined.
296struct WatchThread {
297 handle: Option<JoinHandle<()>>,
298 message_recv: Receiver<CheckEvent>,
299}
300
301enum CheckEvent {
302 Begin,
303 Msg(cargo_metadata::Message),
304 End,
305}
306
307impl WatchThread {
308 fn new(options: &CheckOptions, workspace_root: &PathBuf) -> WatchThread {
309 let mut args: Vec<String> = vec![
310 options.command.clone(),
311 "--message-format=json".to_string(),
312 "--manifest-path".to_string(),
313 format!("{}/Cargo.toml", workspace_root.to_string_lossy()),
314 ];
315 if options.all_targets {
316 args.push("--all-targets".to_string());
317 }
318 args.extend(options.args.iter().cloned());
319
320 let (message_send, message_recv) = unbounded();
321 let enabled = options.enable;
322 let handle = std::thread::spawn(move || {
323 if !enabled {
324 return;
325 }
326
327 let mut command = Command::new("cargo")
328 .args(&args)
329 .stdout(Stdio::piped())
330 .stderr(Stdio::null())
331 .spawn()
332 .expect("couldn't launch cargo");
333
334 // If we trigger an error here, we will do so in the loop instead,
335 // which will break out of the loop, and continue the shutdown
336 let _ = message_send.send(CheckEvent::Begin);
337
338 for message in cargo_metadata::parse_messages(command.stdout.take().unwrap()) {
339 let message = match message {
340 Ok(message) => message,
341 Err(err) => {
342 log::error!("Invalid json from cargo check, ignoring: {}", err);
343 continue;
344 }
345 };
346
347 match message_send.send(CheckEvent::Msg(message)) {
348 Ok(()) => {}
349 Err(_err) => {
350 // The send channel was closed, so we want to shutdown
351 break;
352 }
353 }
354 }
355
356 // We can ignore any error here, as we are already in the progress
357 // of shutting down.
358 let _ = message_send.send(CheckEvent::End);
359
360 // It is okay to ignore the result, as it only errors if the process is already dead
361 let _ = command.kill();
362
363 // Again, we don't care about the exit status so just ignore the result
364 let _ = command.wait();
365 });
366 WatchThread { handle: Some(handle), message_recv }
367 }
368}
369
370impl std::ops::Drop for WatchThread {
371 fn drop(&mut self) {
372 if let Some(handle) = self.handle.take() {
373 // Replace our reciever with dummy one, so we can drop and close the
374 // one actually communicating with the thread
375 let recv = std::mem::replace(&mut self.message_recv, never());
376
377 // Dropping the original reciever initiates thread sub-process shutdown
378 drop(recv);
379
380 // Join the thread, it should finish shortly. We don't really care
381 // whether it panicked, so it is safe to ignore the result
382 let _ = handle.join();
383 }
384 }
385}
386
387fn are_diagnostics_equal(left: &Diagnostic, right: &Diagnostic) -> bool {
388 left.source == right.source
389 && left.severity == right.severity
390 && left.range == right.range
391 && left.message == right.message
392}
diff --git a/crates/ra_lsp_server/Cargo.toml b/crates/ra_lsp_server/Cargo.toml
index 030e9033c..9b7dcb6e9 100644
--- a/crates/ra_lsp_server/Cargo.toml
+++ b/crates/ra_lsp_server/Cargo.toml
@@ -27,6 +27,7 @@ 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"] }
30ra_cargo_watch = { path = "../ra_cargo_watch" }
30 31
31[dev-dependencies] 32[dev-dependencies]
32tempfile = "3" 33tempfile = "3"
diff --git a/crates/ra_lsp_server/src/caps.rs b/crates/ra_lsp_server/src/caps.rs
index ceb4c4259..0dee1f6fe 100644
--- a/crates/ra_lsp_server/src/caps.rs
+++ b/crates/ra_lsp_server/src/caps.rs
@@ -3,7 +3,7 @@
3use lsp_types::{ 3use lsp_types::{
4 CodeActionProviderCapability, CodeLensOptions, CompletionOptions, 4 CodeActionProviderCapability, CodeLensOptions, CompletionOptions,
5 DocumentOnTypeFormattingOptions, FoldingRangeProviderCapability, 5 DocumentOnTypeFormattingOptions, FoldingRangeProviderCapability,
6 ImplementationProviderCapability, RenameOptions, RenameProviderCapability, 6 ImplementationProviderCapability, RenameOptions, RenameProviderCapability, SaveOptions,
7 SelectionRangeProviderCapability, ServerCapabilities, SignatureHelpOptions, 7 SelectionRangeProviderCapability, ServerCapabilities, SignatureHelpOptions,
8 TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, 8 TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions,
9 TypeDefinitionProviderCapability, WorkDoneProgressOptions, 9 TypeDefinitionProviderCapability, WorkDoneProgressOptions,
@@ -16,7 +16,7 @@ pub fn server_capabilities() -> ServerCapabilities {
16 change: Some(TextDocumentSyncKind::Full), 16 change: Some(TextDocumentSyncKind::Full),
17 will_save: None, 17 will_save: None,
18 will_save_wait_until: None, 18 will_save_wait_until: None,
19 save: None, 19 save: Some(SaveOptions::default()),
20 })), 20 })),
21 hover_provider: Some(true), 21 hover_provider: Some(true),
22 completion_provider: Some(CompletionOptions { 22 completion_provider: Some(CompletionOptions {
diff --git a/crates/ra_lsp_server/src/config.rs b/crates/ra_lsp_server/src/config.rs
index 67942aa41..2d7948d74 100644
--- a/crates/ra_lsp_server/src/config.rs
+++ b/crates/ra_lsp_server/src/config.rs
@@ -32,6 +32,11 @@ pub struct ServerConfig {
32 32
33 pub max_inlay_hint_length: Option<usize>, 33 pub max_inlay_hint_length: Option<usize>,
34 34
35 pub cargo_watch_enable: bool,
36 pub cargo_watch_args: Vec<String>,
37 pub cargo_watch_command: String,
38 pub cargo_watch_all_targets: bool,
39
35 /// For internal usage to make integrated tests faster. 40 /// For internal usage to make integrated tests faster.
36 #[serde(deserialize_with = "nullable_bool_true")] 41 #[serde(deserialize_with = "nullable_bool_true")]
37 pub with_sysroot: bool, 42 pub with_sysroot: bool,
@@ -51,6 +56,10 @@ impl Default for ServerConfig {
51 use_client_watching: false, 56 use_client_watching: false,
52 lru_capacity: None, 57 lru_capacity: None,
53 max_inlay_hint_length: None, 58 max_inlay_hint_length: None,
59 cargo_watch_enable: true,
60 cargo_watch_args: Vec::new(),
61 cargo_watch_command: "check".to_string(),
62 cargo_watch_all_targets: true,
54 with_sysroot: true, 63 with_sysroot: true,
55 feature_flags: FxHashMap::default(), 64 feature_flags: FxHashMap::default(),
56 cargo_features: Default::default(), 65 cargo_features: Default::default(),
diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs
index dda318e43..af1a487de 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};
@@ -126,6 +127,12 @@ pub fn main_loop(
126 .and_then(|it| it.line_folding_only) 127 .and_then(|it| it.line_folding_only)
127 .unwrap_or(false), 128 .unwrap_or(false),
128 max_inlay_hint_length: config.max_inlay_hint_length, 129 max_inlay_hint_length: config.max_inlay_hint_length,
130 cargo_watch: CheckOptions {
131 enable: config.cargo_watch_enable,
132 args: config.cargo_watch_args,
133 command: config.cargo_watch_command,
134 all_targets: config.cargo_watch_all_targets,
135 },
129 } 136 }
130 }; 137 };
131 138
@@ -176,7 +183,11 @@ pub fn main_loop(
176 Ok(task) => Event::Vfs(task), 183 Ok(task) => Event::Vfs(task),
177 Err(RecvError) => Err("vfs died")?, 184 Err(RecvError) => Err("vfs died")?,
178 }, 185 },
179 recv(libdata_receiver) -> data => Event::Lib(data.unwrap()) 186 recv(libdata_receiver) -> data => Event::Lib(data.unwrap()),
187 recv(world_state.check_watcher.task_recv) -> task => match task {
188 Ok(task) => Event::CheckWatcher(task),
189 Err(RecvError) => Err("check watcher died")?,
190 }
180 }; 191 };
181 if let Event::Msg(Message::Request(req)) = &event { 192 if let Event::Msg(Message::Request(req)) = &event {
182 if connection.handle_shutdown(&req)? { 193 if connection.handle_shutdown(&req)? {
@@ -222,6 +233,7 @@ enum Event {
222 Task(Task), 233 Task(Task),
223 Vfs(VfsTask), 234 Vfs(VfsTask),
224 Lib(LibraryData), 235 Lib(LibraryData),
236 CheckWatcher(CheckTask),
225} 237}
226 238
227impl fmt::Debug for Event { 239impl fmt::Debug for Event {
@@ -259,6 +271,7 @@ impl fmt::Debug for Event {
259 Event::Task(it) => fmt::Debug::fmt(it, f), 271 Event::Task(it) => fmt::Debug::fmt(it, f),
260 Event::Vfs(it) => fmt::Debug::fmt(it, f), 272 Event::Vfs(it) => fmt::Debug::fmt(it, f),
261 Event::Lib(it) => fmt::Debug::fmt(it, f), 273 Event::Lib(it) => fmt::Debug::fmt(it, f),
274 Event::CheckWatcher(it) => fmt::Debug::fmt(it, f),
262 } 275 }
263 } 276 }
264} 277}
@@ -318,6 +331,28 @@ fn loop_turn(
318 world_state.maybe_collect_garbage(); 331 world_state.maybe_collect_garbage();
319 loop_state.in_flight_libraries -= 1; 332 loop_state.in_flight_libraries -= 1;
320 } 333 }
334 Event::CheckWatcher(task) => match task {
335 CheckTask::Update(uri) => {
336 // We manually send a diagnostic update when the watcher asks
337 // us to, to avoid the issue of having to change the file to
338 // receive updated diagnostics.
339 let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?;
340 if let Some(file_id) = world_state.vfs.read().path2file(&path) {
341 let params =
342 handlers::publish_diagnostics(&world_state.snapshot(), FileId(file_id.0))?;
343 let not = notification_new::<req::PublishDiagnostics>(params);
344 task_sender.send(Task::Notify(not)).unwrap();
345 }
346 }
347 CheckTask::Status(progress) => {
348 let params = req::ProgressParams {
349 token: req::ProgressToken::String("rustAnalyzer/cargoWatcher".to_string()),
350 value: req::ProgressParamsValue::WorkDone(progress),
351 };
352 let not = notification_new::<req::Progress>(params);
353 task_sender.send(Task::Notify(not)).unwrap();
354 }
355 },
321 Event::Msg(msg) => match msg { 356 Event::Msg(msg) => match msg {
322 Message::Request(req) => on_request( 357 Message::Request(req) => on_request(
323 world_state, 358 world_state,
@@ -517,6 +552,13 @@ fn on_notification(
517 } 552 }
518 Err(not) => not, 553 Err(not) => not,
519 }; 554 };
555 let not = match notification_cast::<req::DidSaveTextDocument>(not) {
556 Ok(_params) => {
557 state.check_watcher.update();
558 return Ok(());
559 }
560 Err(not) => not,
561 };
520 let not = match notification_cast::<req::DidCloseTextDocument>(not) { 562 let not = match notification_cast::<req::DidCloseTextDocument>(not) {
521 Ok(params) => { 563 Ok(params) => {
522 let uri = params.text_document.uri; 564 let uri = params.text_document.uri;
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index 39eb3df3e..331beab13 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -654,6 +654,29 @@ pub fn handle_code_action(
654 res.push(action.into()); 654 res.push(action.into());
655 } 655 }
656 656
657 for fix in world.check_watcher.read().fixes_for(&params.text_document.uri).into_iter().flatten()
658 {
659 let fix_range = fix.location.range.conv_with(&line_index);
660 if fix_range.intersection(&range).is_none() {
661 continue;
662 }
663
664 let edits = vec![TextEdit::new(fix.location.range, fix.replacement.clone())];
665 let mut edit_map = std::collections::HashMap::new();
666 edit_map.insert(fix.location.uri.clone(), edits);
667 let edit = WorkspaceEdit::new(edit_map);
668
669 let action = CodeAction {
670 title: fix.title.clone(),
671 kind: Some("quickfix".to_string()),
672 diagnostics: Some(fix.diagnostics.clone()),
673 edit: Some(edit),
674 command: None,
675 is_preferred: None,
676 };
677 res.push(action.into());
678 }
679
657 for assist in assists { 680 for assist in assists {
658 let title = assist.change.label.clone(); 681 let title = assist.change.label.clone();
659 let edit = assist.change.try_conv_with(&world)?; 682 let edit = assist.change.try_conv_with(&world)?;
@@ -820,7 +843,7 @@ pub fn publish_diagnostics(
820 let _p = profile("publish_diagnostics"); 843 let _p = profile("publish_diagnostics");
821 let uri = world.file_id_to_uri(file_id)?; 844 let uri = world.file_id_to_uri(file_id)?;
822 let line_index = world.analysis().file_line_index(file_id)?; 845 let line_index = world.analysis().file_line_index(file_id)?;
823 let diagnostics = world 846 let mut diagnostics: Vec<Diagnostic> = world
824 .analysis() 847 .analysis()
825 .diagnostics(file_id)? 848 .diagnostics(file_id)?
826 .into_iter() 849 .into_iter()
@@ -834,6 +857,9 @@ pub fn publish_diagnostics(
834 tags: None, 857 tags: None,
835 }) 858 })
836 .collect(); 859 .collect();
860 if let Some(check_diags) = world.check_watcher.read().diagnostics_for(&uri) {
861 diagnostics.extend(check_diags.iter().cloned());
862 }
837 Ok(req::PublishDiagnosticsParams { uri, diagnostics, version: None }) 863 Ok(req::PublishDiagnosticsParams { uri, diagnostics, version: None })
838} 864}
839 865
diff --git a/crates/ra_lsp_server/src/req.rs b/crates/ra_lsp_server/src/req.rs
index b34e6f9b8..40edaf677 100644
--- a/crates/ra_lsp_server/src/req.rs
+++ b/crates/ra_lsp_server/src/req.rs
@@ -9,10 +9,10 @@ pub use lsp_types::{
9 CodeLensParams, CompletionParams, CompletionResponse, DidChangeConfigurationParams, 9 CodeLensParams, CompletionParams, CompletionResponse, DidChangeConfigurationParams,
10 DidChangeWatchedFilesParams, DidChangeWatchedFilesRegistrationOptions, 10 DidChangeWatchedFilesParams, DidChangeWatchedFilesRegistrationOptions,
11 DocumentOnTypeFormattingParams, DocumentSymbolParams, DocumentSymbolResponse, 11 DocumentOnTypeFormattingParams, DocumentSymbolParams, DocumentSymbolResponse,
12 FileSystemWatcher, Hover, InitializeResult, MessageType, PublishDiagnosticsParams, 12 FileSystemWatcher, Hover, InitializeResult, MessageType, ProgressParams, ProgressParamsValue,
13 ReferenceParams, Registration, RegistrationParams, SelectionRange, SelectionRangeParams, 13 ProgressToken, PublishDiagnosticsParams, ReferenceParams, Registration, RegistrationParams,
14 ShowMessageParams, SignatureHelp, TextDocumentEdit, TextDocumentPositionParams, TextEdit, 14 SelectionRange, SelectionRangeParams, ShowMessageParams, SignatureHelp, TextDocumentEdit,
15 WorkspaceEdit, WorkspaceSymbolParams, 15 TextDocumentPositionParams, TextEdit, WorkspaceEdit, WorkspaceSymbolParams,
16}; 16};
17 17
18pub enum AnalyzerStatus {} 18pub enum AnalyzerStatus {}
diff --git a/crates/ra_lsp_server/src/world.rs b/crates/ra_lsp_server/src/world.rs
index 79431e7e6..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,
@@ -34,6 +35,7 @@ pub struct Options {
34 pub supports_location_link: bool, 35 pub supports_location_link: bool,
35 pub line_folding_only: bool, 36 pub line_folding_only: bool,
36 pub max_inlay_hint_length: Option<usize>, 37 pub max_inlay_hint_length: Option<usize>,
38 pub cargo_watch: CheckOptions,
37} 39}
38 40
39/// `WorldState` is the primary mutable state of the language server 41/// `WorldState` is the primary mutable state of the language server
@@ -52,6 +54,7 @@ pub struct WorldState {
52 pub vfs: Arc<RwLock<Vfs>>, 54 pub vfs: Arc<RwLock<Vfs>>,
53 pub task_receiver: Receiver<VfsTask>, 55 pub task_receiver: Receiver<VfsTask>,
54 pub latest_requests: Arc<RwLock<LatestRequests>>, 56 pub latest_requests: Arc<RwLock<LatestRequests>>,
57 pub check_watcher: CheckWatcher,
55} 58}
56 59
57/// An immutable snapshot of the world's state at a point in time. 60/// An immutable snapshot of the world's state at a point in time.
@@ -61,6 +64,7 @@ pub struct WorldSnapshot {
61 pub analysis: Analysis, 64 pub analysis: Analysis,
62 pub vfs: Arc<RwLock<Vfs>>, 65 pub vfs: Arc<RwLock<Vfs>>,
63 pub latest_requests: Arc<RwLock<LatestRequests>>, 66 pub latest_requests: Arc<RwLock<LatestRequests>>,
67 pub check_watcher: Arc<RwLock<CheckWatcherSharedState>>,
64} 68}
65 69
66impl WorldState { 70impl WorldState {
@@ -127,6 +131,10 @@ impl WorldState {
127 } 131 }
128 change.set_crate_graph(crate_graph); 132 change.set_crate_graph(crate_graph);
129 133
134 // FIXME: Figure out the multi-workspace situation
135 let check_watcher =
136 CheckWatcher::new(&options.cargo_watch, folder_roots.first().cloned().unwrap());
137
130 let mut analysis_host = AnalysisHost::new(lru_capacity, feature_flags); 138 let mut analysis_host = AnalysisHost::new(lru_capacity, feature_flags);
131 analysis_host.apply_change(change); 139 analysis_host.apply_change(change);
132 WorldState { 140 WorldState {
@@ -138,6 +146,7 @@ impl WorldState {
138 vfs: Arc::new(RwLock::new(vfs)), 146 vfs: Arc::new(RwLock::new(vfs)),
139 task_receiver, 147 task_receiver,
140 latest_requests: Default::default(), 148 latest_requests: Default::default(),
149 check_watcher,
141 } 150 }
142 } 151 }
143 152
@@ -199,6 +208,7 @@ impl WorldState {
199 analysis: self.analysis_host.analysis(), 208 analysis: self.analysis_host.analysis(),
200 vfs: Arc::clone(&self.vfs), 209 vfs: Arc::clone(&self.vfs),
201 latest_requests: Arc::clone(&self.latest_requests), 210 latest_requests: Arc::clone(&self.latest_requests),
211 check_watcher: self.check_watcher.shared.clone(),
202 } 212 }
203 } 213 }
204 214