aboutsummaryrefslogtreecommitdiff
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
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]>
-rw-r--r--Cargo.lock15
-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
-rw-r--r--editors/code/package.json52
-rw-r--r--editors/code/src/commands/cargo_watch.ts264
-rw-r--r--editors/code/src/commands/runnables.ts91
-rw-r--r--editors/code/src/commands/watch_status.ts42
-rw-r--r--editors/code/src/config.ts40
-rw-r--r--editors/code/src/extension.ts35
-rw-r--r--editors/code/src/server.ts5
-rw-r--r--editors/code/src/test/fixtures/rust-diagnostics/clippy/trivially_copy_pass_by_ref.json110
-rw-r--r--editors/code/src/test/fixtures/rust-diagnostics/error/E0053.json42
-rw-r--r--editors/code/src/test/fixtures/rust-diagnostics/error/E0061.json114
-rw-r--r--editors/code/src/test/fixtures/rust-diagnostics/error/E0277.json261
-rw-r--r--editors/code/src/test/fixtures/rust-diagnostics/error/E0308.json33
-rw-r--r--editors/code/src/test/fixtures/rust-diagnostics/warning/unused_variables.json72
-rw-r--r--editors/code/src/test/utils/diagnotics/SuggestedFix.test.ts134
-rw-r--r--editors/code/src/test/utils/diagnotics/SuggestedFixCollection.test.ts127
-rw-r--r--editors/code/src/test/utils/diagnotics/rust.test.ts236
-rw-r--r--editors/code/src/test/utils/diagnotics/vscode.test.ts98
-rw-r--r--editors/code/src/utils/diagnostics/SuggestedFix.ts67
-rw-r--r--editors/code/src/utils/diagnostics/SuggestedFixCollection.ts77
-rw-r--r--editors/code/src/utils/diagnostics/rust.ts299
-rw-r--r--editors/code/src/utils/diagnostics/vscode.ts14
-rw-r--r--editors/code/src/utils/processes.ts51
-rw-r--r--editors/code/src/utils/terminateProcess.sh12
41 files changed, 1933 insertions, 2209 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 3314ef70f..b63b5ae13 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -899,6 +899,20 @@ dependencies = [
899] 899]
900 900
901[[package]] 901[[package]]
902name = "ra_cargo_watch"
903version = "0.1.0"
904dependencies = [
905 "cargo_metadata 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
906 "crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
907 "insta 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
908 "jod-thread 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
909 "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
910 "lsp-types 0.67.1 (registry+https://github.com/rust-lang/crates.io-index)",
911 "parking_lot 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
912 "serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)",
913]
914
915[[package]]
902name = "ra_cfg" 916name = "ra_cfg"
903version = "0.1.0" 917version = "0.1.0"
904dependencies = [ 918dependencies = [
@@ -1059,6 +1073,7 @@ dependencies = [
1059 "lsp-server 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 1073 "lsp-server 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
1060 "lsp-types 0.67.1 (registry+https://github.com/rust-lang/crates.io-index)", 1074 "lsp-types 0.67.1 (registry+https://github.com/rust-lang/crates.io-index)",
1061 "parking_lot 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", 1075 "parking_lot 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
1076 "ra_cargo_watch 0.1.0",
1062 "ra_ide 0.1.0", 1077 "ra_ide 0.1.0",
1063 "ra_prof 0.1.0", 1078 "ra_prof 0.1.0",
1064 "ra_project_model 0.1.0", 1079 "ra_project_model 0.1.0",
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
diff --git a/editors/code/package.json b/editors/code/package.json
index f75fafeb9..69298e917 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -18,7 +18,7 @@
18 "scripts": { 18 "scripts": {
19 "vscode:prepublish": "npm run compile", 19 "vscode:prepublish": "npm run compile",
20 "package": "vsce package", 20 "package": "vsce package",
21 "compile": "rollup -c && shx cp src/utils/terminateProcess.sh bundle/terminateProcess.sh", 21 "compile": "rollup -c",
22 "watch": "tsc -watch -p ./", 22 "watch": "tsc -watch -p ./",
23 "fix": "prettier **/*.{json,ts} --write && tslint --project . --fix", 23 "fix": "prettier **/*.{json,ts} --write && tslint --project . --fix",
24 "lint": "tslint --project .", 24 "lint": "tslint --project .",
@@ -133,16 +133,6 @@
133 "command": "rust-analyzer.reload", 133 "command": "rust-analyzer.reload",
134 "title": "Restart server", 134 "title": "Restart server",
135 "category": "Rust Analyzer" 135 "category": "Rust Analyzer"
136 },
137 {
138 "command": "rust-analyzer.startCargoWatch",
139 "title": "Start Cargo Watch",
140 "category": "Rust Analyzer"
141 },
142 {
143 "command": "rust-analyzer.stopCargoWatch",
144 "title": "Stop Cargo Watch",
145 "category": "Rust Analyzer"
146 } 136 }
147 ], 137 ],
148 "keybindings": [ 138 "keybindings": [
@@ -198,21 +188,6 @@
198 "default": "ra_lsp_server", 188 "default": "ra_lsp_server",
199 "description": "Path to ra_lsp_server executable" 189 "description": "Path to ra_lsp_server executable"
200 }, 190 },
201 "rust-analyzer.enableCargoWatchOnStartup": {
202 "type": "string",
203 "default": "ask",
204 "enum": [
205 "ask",
206 "enabled",
207 "disabled"
208 ],
209 "enumDescriptions": [
210 "Asks each time whether to run `cargo watch`",
211 "`cargo watch` is always started",
212 "Don't start `cargo watch`"
213 ],
214 "description": "Whether to run `cargo watch` on startup"
215 },
216 "rust-analyzer.excludeGlobs": { 191 "rust-analyzer.excludeGlobs": {
217 "type": "array", 192 "type": "array",
218 "default": [], 193 "default": [],
@@ -223,21 +198,21 @@
223 "default": true, 198 "default": true,
224 "description": "client provided file watching instead of notify watching." 199 "description": "client provided file watching instead of notify watching."
225 }, 200 },
201 "rust-analyzer.cargo-watch.enable": {
202 "type": "boolean",
203 "default": true,
204 "description": "Run `cargo check` for diagnostics on save"
205 },
226 "rust-analyzer.cargo-watch.arguments": { 206 "rust-analyzer.cargo-watch.arguments": {
227 "type": "string", 207 "type": "array",
228 "description": "`cargo-watch` arguments. (e.g: `--features=\"shumway,pdf\"` will run as `cargo watch -x \"check --features=\"shumway,pdf\"\"` )", 208 "description": "`cargo-watch` arguments. (e.g: `--features=\"shumway,pdf\"` will run as `cargo watch -x \"check --features=\"shumway,pdf\"\"` )",
229 "default": "" 209 "default": []
230 }, 210 },
231 "rust-analyzer.cargo-watch.command": { 211 "rust-analyzer.cargo-watch.command": {
232 "type": "string", 212 "type": "string",
233 "description": "`cargo-watch` command. (e.g: `clippy` will run as `cargo watch -x clippy` )", 213 "description": "`cargo-watch` command. (e.g: `clippy` will run as `cargo watch -x clippy` )",
234 "default": "check" 214 "default": "check"
235 }, 215 },
236 "rust-analyzer.cargo-watch.ignore": {
237 "type": "array",
238 "description": "A list of patterns for cargo-watch to ignore (will be passed as `--ignore`)",
239 "default": []
240 },
241 "rust-analyzer.cargo-watch.allTargets": { 216 "rust-analyzer.cargo-watch.allTargets": {
242 "type": "boolean", 217 "type": "boolean",
243 "description": "Check all targets and tests (will be passed as `--all-targets`)", 218 "description": "Check all targets and tests (will be passed as `--all-targets`)",
@@ -259,17 +234,6 @@
259 "default": "off", 234 "default": "off",
260 "description": "Trace requests to the ra_lsp_server" 235 "description": "Trace requests to the ra_lsp_server"
261 }, 236 },
262 "rust-analyzer.trace.cargo-watch": {
263 "type": "string",
264 "scope": "window",
265 "enum": [
266 "off",
267 "error",
268 "verbose"
269 ],
270 "default": "off",
271 "description": "Trace output of cargo-watch"
272 },
273 "rust-analyzer.lruCapacity": { 237 "rust-analyzer.lruCapacity": {
274 "type": "number", 238 "type": "number",
275 "default": null, 239 "default": null,
diff --git a/editors/code/src/commands/cargo_watch.ts b/editors/code/src/commands/cargo_watch.ts
deleted file mode 100644
index ac62bdd48..000000000
--- a/editors/code/src/commands/cargo_watch.ts
+++ /dev/null
@@ -1,264 +0,0 @@
1import * as child_process from 'child_process';
2import * as path from 'path';
3import * as vscode from 'vscode';
4
5import { Server } from '../server';
6import { terminate } from '../utils/processes';
7import { LineBuffer } from './line_buffer';
8import { StatusDisplay } from './watch_status';
9
10import {
11 mapRustDiagnosticToVsCode,
12 RustDiagnostic,
13} from '../utils/diagnostics/rust';
14import SuggestedFixCollection from '../utils/diagnostics/SuggestedFixCollection';
15import { areDiagnosticsEqual } from '../utils/diagnostics/vscode';
16
17export async function registerCargoWatchProvider(
18 subscriptions: vscode.Disposable[],
19): Promise<CargoWatchProvider | undefined> {
20 let cargoExists = false;
21
22 // Check if the working directory is valid cargo root path
23 const cargoTomlPath = path.join(vscode.workspace.rootPath!, 'Cargo.toml');
24 const cargoTomlUri = vscode.Uri.file(cargoTomlPath);
25 const cargoTomlFileInfo = await vscode.workspace.fs.stat(cargoTomlUri);
26
27 if (cargoTomlFileInfo) {
28 cargoExists = true;
29 }
30
31 if (!cargoExists) {
32 vscode.window.showErrorMessage(
33 `Couldn\'t find \'Cargo.toml\' at ${cargoTomlPath}`,
34 );
35 return;
36 }
37
38 const provider = new CargoWatchProvider();
39 subscriptions.push(provider);
40 return provider;
41}
42
43export class CargoWatchProvider implements vscode.Disposable {
44 private readonly diagnosticCollection: vscode.DiagnosticCollection;
45 private readonly statusDisplay: StatusDisplay;
46 private readonly outputChannel: vscode.OutputChannel;
47
48 private suggestedFixCollection: SuggestedFixCollection;
49 private codeActionDispose: vscode.Disposable;
50
51 private cargoProcess?: child_process.ChildProcess;
52
53 constructor() {
54 this.diagnosticCollection = vscode.languages.createDiagnosticCollection(
55 'rustc',
56 );
57 this.statusDisplay = new StatusDisplay(
58 Server.config.cargoWatchOptions.command,
59 );
60 this.outputChannel = vscode.window.createOutputChannel(
61 'Cargo Watch Trace',
62 );
63
64 // Track `rustc`'s suggested fixes so we can convert them to code actions
65 this.suggestedFixCollection = new SuggestedFixCollection();
66 this.codeActionDispose = vscode.languages.registerCodeActionsProvider(
67 [{ scheme: 'file', language: 'rust' }],
68 this.suggestedFixCollection,
69 {
70 providedCodeActionKinds:
71 SuggestedFixCollection.PROVIDED_CODE_ACTION_KINDS,
72 },
73 );
74 }
75
76 public start() {
77 if (this.cargoProcess) {
78 vscode.window.showInformationMessage(
79 'Cargo Watch is already running',
80 );
81 return;
82 }
83
84 let args =
85 Server.config.cargoWatchOptions.command + ' --message-format json';
86 if (Server.config.cargoWatchOptions.allTargets) {
87 args += ' --all-targets';
88 }
89 if (Server.config.cargoWatchOptions.command.length > 0) {
90 // Excape the double quote string:
91 args += ' ' + Server.config.cargoWatchOptions.arguments;
92 }
93 // Windows handles arguments differently than the unix-likes, so we need to wrap the args in double quotes
94 if (process.platform === 'win32') {
95 args = '"' + args + '"';
96 }
97
98 const ignoreFlags = Server.config.cargoWatchOptions.ignore.reduce(
99 (flags, pattern) => [...flags, '--ignore', pattern],
100 [] as string[],
101 );
102
103 // Start the cargo watch with json message
104 this.cargoProcess = child_process.spawn(
105 'cargo',
106 ['watch', '-x', args, ...ignoreFlags],
107 {
108 stdio: ['ignore', 'pipe', 'pipe'],
109 cwd: vscode.workspace.rootPath,
110 windowsVerbatimArguments: true,
111 },
112 );
113
114 if (!this.cargoProcess) {
115 vscode.window.showErrorMessage('Cargo Watch failed to start');
116 return;
117 }
118
119 const stdoutData = new LineBuffer();
120 this.cargoProcess.stdout?.on('data', (s: string) => {
121 stdoutData.processOutput(s, line => {
122 this.logInfo(line);
123 try {
124 this.parseLine(line);
125 } catch (err) {
126 this.logError(`Failed to parse: ${err}, content : ${line}`);
127 }
128 });
129 });
130
131 const stderrData = new LineBuffer();
132 this.cargoProcess.stderr?.on('data', (s: string) => {
133 stderrData.processOutput(s, line => {
134 this.logError('Error on cargo-watch : {\n' + line + '}\n');
135 });
136 });
137
138 this.cargoProcess.on('error', (err: Error) => {
139 this.logError(
140 'Error on cargo-watch process : {\n' + err.message + '}\n',
141 );
142 });
143
144 this.logInfo('cargo-watch started.');
145 }
146
147 public stop() {
148 if (this.cargoProcess) {
149 this.cargoProcess.kill();
150 terminate(this.cargoProcess);
151 this.cargoProcess = undefined;
152 } else {
153 vscode.window.showInformationMessage('Cargo Watch is not running');
154 }
155 }
156
157 public dispose(): void {
158 this.stop();
159
160 this.diagnosticCollection.clear();
161 this.diagnosticCollection.dispose();
162 this.outputChannel.dispose();
163 this.statusDisplay.dispose();
164 this.codeActionDispose.dispose();
165 }
166
167 private logInfo(line: string) {
168 if (Server.config.cargoWatchOptions.trace === 'verbose') {
169 this.outputChannel.append(line);
170 }
171 }
172
173 private logError(line: string) {
174 if (
175 Server.config.cargoWatchOptions.trace === 'error' ||
176 Server.config.cargoWatchOptions.trace === 'verbose'
177 ) {
178 this.outputChannel.append(line);
179 }
180 }
181
182 private parseLine(line: string) {
183 if (line.startsWith('[Running')) {
184 this.diagnosticCollection.clear();
185 this.suggestedFixCollection.clear();
186 this.statusDisplay.show();
187 }
188
189 if (line.startsWith('[Finished running')) {
190 this.statusDisplay.hide();
191 }
192
193 interface CargoArtifact {
194 reason: string;
195 package_id: string;
196 }
197
198 // https://github.com/rust-lang/cargo/blob/master/src/cargo/util/machine_message.rs
199 interface CargoMessage {
200 reason: string;
201 package_id: string;
202 message: RustDiagnostic;
203 }
204
205 // cargo-watch itself output non json format
206 // Ignore these lines
207 let data: CargoMessage;
208 try {
209 data = JSON.parse(line.trim());
210 } catch (error) {
211 this.logError(`Fail to parse to json : { ${error} }`);
212 return;
213 }
214
215 if (data.reason === 'compiler-artifact') {
216 const msg = data as CargoArtifact;
217
218 // The format of the package_id is "{name} {version} ({source_id})",
219 // https://github.com/rust-lang/cargo/blob/37ad03f86e895bb80b474c1c088322634f4725f5/src/cargo/core/package_id.rs#L53
220 this.statusDisplay.packageName = msg.package_id.split(' ')[0];
221 } else if (data.reason === 'compiler-message') {
222 const msg = data.message as RustDiagnostic;
223
224 const mapResult = mapRustDiagnosticToVsCode(msg);
225 if (!mapResult) {
226 return;
227 }
228
229 const { location, diagnostic, suggestedFixes } = mapResult;
230 const fileUri = location.uri;
231
232 const diagnostics: vscode.Diagnostic[] = [
233 ...(this.diagnosticCollection!.get(fileUri) || []),
234 ];
235
236 // If we're building multiple targets it's possible we've already seen this diagnostic
237 const isDuplicate = diagnostics.some(d =>
238 areDiagnosticsEqual(d, diagnostic),
239 );
240 if (isDuplicate) {
241 return;
242 }
243
244 diagnostics.push(diagnostic);
245 this.diagnosticCollection!.set(fileUri, diagnostics);
246
247 if (suggestedFixes.length) {
248 for (const suggestedFix of suggestedFixes) {
249 this.suggestedFixCollection.addSuggestedFixForDiagnostic(
250 suggestedFix,
251 diagnostic,
252 );
253 }
254
255 // Have VsCode query us for the code actions
256 vscode.commands.executeCommand(
257 'vscode.executeCodeActionProvider',
258 fileUri,
259 diagnostic.range,
260 );
261 }
262 }
263 }
264}
diff --git a/editors/code/src/commands/runnables.ts b/editors/code/src/commands/runnables.ts
index cf980e257..7728541de 100644
--- a/editors/code/src/commands/runnables.ts
+++ b/editors/code/src/commands/runnables.ts
@@ -1,11 +1,7 @@
1import * as child_process from 'child_process';
2
3import * as util from 'util';
4import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
5import * as lc from 'vscode-languageclient'; 2import * as lc from 'vscode-languageclient';
6 3
7import { Server } from '../server'; 4import { Server } from '../server';
8import { CargoWatchProvider, registerCargoWatchProvider } from './cargo_watch';
9 5
10interface RunnablesParams { 6interface RunnablesParams {
11 textDocument: lc.TextDocumentIdentifier; 7 textDocument: lc.TextDocumentIdentifier;
@@ -131,90 +127,3 @@ export async function handleSingle(runnable: Runnable) {
131 127
132 return vscode.tasks.executeTask(task); 128 return vscode.tasks.executeTask(task);
133} 129}
134
135/**
136 * Interactively asks the user whether we should run `cargo check` in order to
137 * provide inline diagnostics; the user is met with a series of dialog boxes
138 * that, when accepted, allow us to `cargo install cargo-watch` and then run it.
139 */
140export async function interactivelyStartCargoWatch(
141 context: vscode.ExtensionContext,
142): Promise<CargoWatchProvider | undefined> {
143 if (Server.config.cargoWatchOptions.enableOnStartup === 'disabled') {
144 return;
145 }
146
147 if (Server.config.cargoWatchOptions.enableOnStartup === 'ask') {
148 const watch = await vscode.window.showInformationMessage(
149 'Start watching changes with cargo? (Executes `cargo watch`, provides inline diagnostics)',
150 'yes',
151 'no',
152 );
153 if (watch !== 'yes') {
154 return;
155 }
156 }
157
158 return startCargoWatch(context);
159}
160
161export async function startCargoWatch(
162 context: vscode.ExtensionContext,
163): Promise<CargoWatchProvider | undefined> {
164 const execPromise = util.promisify(child_process.exec);
165
166 const { stderr, code = 0 } = await execPromise(
167 'cargo watch --version',
168 ).catch(e => e);
169
170 if (stderr.includes('no such subcommand: `watch`')) {
171 const msg =
172 'The `cargo-watch` subcommand is not installed. Install? (takes ~1-2 minutes)';
173 const install = await vscode.window.showInformationMessage(
174 msg,
175 'yes',
176 'no',
177 );
178 if (install !== 'yes') {
179 return;
180 }
181
182 const label = 'install-cargo-watch';
183 const taskFinished = new Promise((resolve, _reject) => {
184 const disposable = vscode.tasks.onDidEndTask(({ execution }) => {
185 if (execution.task.name === label) {
186 disposable.dispose();
187 resolve();
188 }
189 });
190 });
191
192 vscode.tasks.executeTask(
193 createTask({
194 label,
195 bin: 'cargo',
196 args: ['install', 'cargo-watch'],
197 env: {},
198 }),
199 );
200 await taskFinished;
201 const output = await execPromise('cargo watch --version').catch(e => e);
202 if (output.stderr !== '') {
203 vscode.window.showErrorMessage(
204 `Couldn't install \`cargo-\`watch: ${output.stderr}`,
205 );
206 return;
207 }
208 } else if (code !== 0) {
209 vscode.window.showErrorMessage(
210 `\`cargo watch\` failed with ${code}: ${stderr}`,
211 );
212 return;
213 }
214
215 const provider = await registerCargoWatchProvider(context.subscriptions);
216 if (provider) {
217 provider.start();
218 }
219 return provider;
220}
diff --git a/editors/code/src/commands/watch_status.ts b/editors/code/src/commands/watch_status.ts
index 8d64394c7..10787b510 100644
--- a/editors/code/src/commands/watch_status.ts
+++ b/editors/code/src/commands/watch_status.ts
@@ -57,7 +57,49 @@ export class StatusDisplay implements vscode.Disposable {
57 this.statusBarItem.dispose(); 57 this.statusBarItem.dispose();
58 } 58 }
59 59
60 public handleProgressNotification(params: ProgressParams) {
61 const { token, value } = params;
62 if (token !== 'rustAnalyzer/cargoWatcher') {
63 return;
64 }
65
66 switch (value.kind) {
67 case 'begin':
68 this.show();
69 break;
70
71 case 'report':
72 if (value.message) {
73 this.packageName = value.message;
74 }
75 break;
76
77 case 'end':
78 this.hide();
79 break;
80 }
81 }
82
60 private frame() { 83 private frame() {
61 return spinnerFrames[(this.i = ++this.i % spinnerFrames.length)]; 84 return spinnerFrames[(this.i = ++this.i % spinnerFrames.length)];
62 } 85 }
63} 86}
87
88// FIXME: Replace this once vscode-languageclient is updated to LSP 3.15
89interface ProgressParams {
90 token: string;
91 value: WorkDoneProgress;
92}
93
94enum WorkDoneProgressKind {
95 Begin = 'begin',
96 Report = 'report',
97 End = 'end',
98}
99
100interface WorkDoneProgress {
101 kind: WorkDoneProgressKind;
102 message?: string;
103 cancelable?: boolean;
104 percentage?: string;
105}
diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts
index e131f09df..4b388b80c 100644
--- a/editors/code/src/config.ts
+++ b/editors/code/src/config.ts
@@ -4,15 +4,10 @@ import { Server } from './server';
4 4
5const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG; 5const RA_LSP_DEBUG = process.env.__RA_LSP_SERVER_DEBUG;
6 6
7export type CargoWatchStartupOptions = 'ask' | 'enabled' | 'disabled';
8export type CargoWatchTraceOptions = 'off' | 'error' | 'verbose';
9
10export interface CargoWatchOptions { 7export interface CargoWatchOptions {
11 enableOnStartup: CargoWatchStartupOptions; 8 enable: boolean;
12 arguments: string; 9 arguments: string[];
13 command: string; 10 command: string;
14 trace: CargoWatchTraceOptions;
15 ignore: string[];
16 allTargets: boolean; 11 allTargets: boolean;
17} 12}
18 13
@@ -36,11 +31,9 @@ export class Config {
36 // for internal use 31 // for internal use
37 public withSysroot: null | boolean = null; 32 public withSysroot: null | boolean = null;
38 public cargoWatchOptions: CargoWatchOptions = { 33 public cargoWatchOptions: CargoWatchOptions = {
39 enableOnStartup: 'ask', 34 enable: true,
40 trace: 'off', 35 arguments: [],
41 arguments: '',
42 command: '', 36 command: '',
43 ignore: [],
44 allTargets: true, 37 allTargets: true,
45 }; 38 };
46 public cargoFeatures: CargoFeatures = { 39 public cargoFeatures: CargoFeatures = {
@@ -100,23 +93,17 @@ export class Config {
100 RA_LSP_DEBUG || (config.get('raLspServerPath') as string); 93 RA_LSP_DEBUG || (config.get('raLspServerPath') as string);
101 } 94 }
102 95
103 if (config.has('enableCargoWatchOnStartup')) { 96 if (config.has('cargo-watch.enable')) {
104 this.cargoWatchOptions.enableOnStartup = config.get< 97 this.cargoWatchOptions.enable = config.get<boolean>(
105 CargoWatchStartupOptions 98 'cargo-watch.enable',
106 >('enableCargoWatchOnStartup', 'ask'); 99 true,
107 }
108
109 if (config.has('trace.cargo-watch')) {
110 this.cargoWatchOptions.trace = config.get<CargoWatchTraceOptions>(
111 'trace.cargo-watch',
112 'off',
113 ); 100 );
114 } 101 }
115 102
116 if (config.has('cargo-watch.arguments')) { 103 if (config.has('cargo-watch.arguments')) {
117 this.cargoWatchOptions.arguments = config.get<string>( 104 this.cargoWatchOptions.arguments = config.get<string[]>(
118 'cargo-watch.arguments', 105 'cargo-watch.arguments',
119 '', 106 [],
120 ); 107 );
121 } 108 }
122 109
@@ -127,13 +114,6 @@ export class Config {
127 ); 114 );
128 } 115 }
129 116
130 if (config.has('cargo-watch.ignore')) {
131 this.cargoWatchOptions.ignore = config.get<string[]>(
132 'cargo-watch.ignore',
133 [],
134 );
135 }
136
137 if (config.has('cargo-watch.allTargets')) { 117 if (config.has('cargo-watch.allTargets')) {
138 this.cargoWatchOptions.allTargets = config.get<boolean>( 118 this.cargoWatchOptions.allTargets = config.get<boolean>(
139 'cargo-watch.allTargets', 119 'cargo-watch.allTargets',
diff --git a/editors/code/src/extension.ts b/editors/code/src/extension.ts
index 815f3692c..1da10ebd0 100644
--- a/editors/code/src/extension.ts
+++ b/editors/code/src/extension.ts
@@ -2,14 +2,10 @@ import * as vscode from 'vscode';
2import * as lc from 'vscode-languageclient'; 2import * as lc from 'vscode-languageclient';
3 3
4import * as commands from './commands'; 4import * as commands from './commands';
5import { CargoWatchProvider } from './commands/cargo_watch';
6import { ExpandMacroContentProvider } from './commands/expand_macro'; 5import { ExpandMacroContentProvider } from './commands/expand_macro';
7import { HintsUpdater } from './commands/inlay_hints'; 6import { HintsUpdater } from './commands/inlay_hints';
8import {
9 interactivelyStartCargoWatch,
10 startCargoWatch,
11} from './commands/runnables';
12import { SyntaxTreeContentProvider } from './commands/syntaxTree'; 7import { SyntaxTreeContentProvider } from './commands/syntaxTree';
8import { StatusDisplay } from './commands/watch_status';
13import * as events from './events'; 9import * as events from './events';
14import * as notifications from './notifications'; 10import * as notifications from './notifications';
15import { Server } from './server'; 11import { Server } from './server';
@@ -88,6 +84,11 @@ export async function activate(context: vscode.ExtensionContext) {
88 overrideCommand('type', commands.onEnter.handle); 84 overrideCommand('type', commands.onEnter.handle);
89 } 85 }
90 86
87 const watchStatus = new StatusDisplay(
88 Server.config.cargoWatchOptions.command,
89 );
90 disposeOnDeactivation(watchStatus);
91
91 // Notifications are events triggered by the language server 92 // Notifications are events triggered by the language server
92 const allNotifications: Iterable<[ 93 const allNotifications: Iterable<[
93 string, 94 string,
@@ -97,6 +98,10 @@ export async function activate(context: vscode.ExtensionContext) {
97 'rust-analyzer/publishDecorations', 98 'rust-analyzer/publishDecorations',
98 notifications.publishDecorations.handle, 99 notifications.publishDecorations.handle,
99 ], 100 ],
101 [
102 '$/progress',
103 params => watchStatus.handleProgressNotification(params),
104 ],
100 ]; 105 ];
101 const syntaxTreeContentProvider = new SyntaxTreeContentProvider(); 106 const syntaxTreeContentProvider = new SyntaxTreeContentProvider();
102 const expandMacroContentProvider = new ExpandMacroContentProvider(); 107 const expandMacroContentProvider = new ExpandMacroContentProvider();
@@ -139,26 +144,6 @@ export async function activate(context: vscode.ExtensionContext) {
139 144
140 vscode.commands.registerCommand('rust-analyzer.reload', reloadCommand); 145 vscode.commands.registerCommand('rust-analyzer.reload', reloadCommand);
141 146
142 // Executing `cargo watch` provides us with inline diagnostics on save
143 let provider: CargoWatchProvider | undefined;
144 interactivelyStartCargoWatch(context).then(p => {
145 provider = p;
146 });
147 registerCommand('rust-analyzer.startCargoWatch', () => {
148 if (provider) {
149 provider.start();
150 } else {
151 startCargoWatch(context).then(p => {
152 provider = p;
153 });
154 }
155 });
156 registerCommand('rust-analyzer.stopCargoWatch', () => {
157 if (provider) {
158 provider.stop();
159 }
160 });
161
162 // Start the language server, finally! 147 // Start the language server, finally!
163 try { 148 try {
164 await startServer(); 149 await startServer();
diff --git a/editors/code/src/server.ts b/editors/code/src/server.ts
index 5ace1d0fa..ae81af848 100644
--- a/editors/code/src/server.ts
+++ b/editors/code/src/server.ts
@@ -55,6 +55,11 @@ export class Server {
55 publishDecorations: true, 55 publishDecorations: true,
56 lruCapacity: Server.config.lruCapacity, 56 lruCapacity: Server.config.lruCapacity,
57 maxInlayHintLength: Server.config.maxInlayHintLength, 57 maxInlayHintLength: Server.config.maxInlayHintLength,
58 cargoWatchEnable: Server.config.cargoWatchOptions.enable,
59 cargoWatchArgumets: Server.config.cargoWatchOptions.arguments,
60 cargoWatchCommand: Server.config.cargoWatchOptions.command,
61 cargoWatchAllTargets:
62 Server.config.cargoWatchOptions.allTargets,
58 excludeGlobs: Server.config.excludeGlobs, 63 excludeGlobs: Server.config.excludeGlobs,
59 useClientWatching: Server.config.useClientWatching, 64 useClientWatching: Server.config.useClientWatching,
60 featureFlags: Server.config.featureFlags, 65 featureFlags: Server.config.featureFlags,
diff --git a/editors/code/src/test/fixtures/rust-diagnostics/clippy/trivially_copy_pass_by_ref.json b/editors/code/src/test/fixtures/rust-diagnostics/clippy/trivially_copy_pass_by_ref.json
deleted file mode 100644
index d874e99bc..000000000
--- a/editors/code/src/test/fixtures/rust-diagnostics/clippy/trivially_copy_pass_by_ref.json
+++ /dev/null
@@ -1,110 +0,0 @@
1{
2 "message": "this argument is passed by reference, but would be more efficient if passed by value",
3 "code": {
4 "code": "clippy::trivially_copy_pass_by_ref",
5 "explanation": null
6 },
7 "level": "warning",
8 "spans": [
9 {
10 "file_name": "compiler/mir/tagset.rs",
11 "byte_start": 941,
12 "byte_end": 946,
13 "line_start": 42,
14 "line_end": 42,
15 "column_start": 24,
16 "column_end": 29,
17 "is_primary": true,
18 "text": [
19 {
20 "text": " pub fn is_disjoint(&self, other: Self) -> bool {",
21 "highlight_start": 24,
22 "highlight_end": 29
23 }
24 ],
25 "label": null,
26 "suggested_replacement": null,
27 "suggestion_applicability": null,
28 "expansion": null
29 }
30 ],
31 "children": [
32 {
33 "message": "lint level defined here",
34 "code": null,
35 "level": "note",
36 "spans": [
37 {
38 "file_name": "compiler/lib.rs",
39 "byte_start": 8,
40 "byte_end": 19,
41 "line_start": 1,
42 "line_end": 1,
43 "column_start": 9,
44 "column_end": 20,
45 "is_primary": true,
46 "text": [
47 {
48 "text": "#![warn(clippy::all)]",
49 "highlight_start": 9,
50 "highlight_end": 20
51 }
52 ],
53 "label": null,
54 "suggested_replacement": null,
55 "suggestion_applicability": null,
56 "expansion": null
57 }
58 ],
59 "children": [],
60 "rendered": null
61 },
62 {
63 "message": "#[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]",
64 "code": null,
65 "level": "note",
66 "spans": [],
67 "children": [],
68 "rendered": null
69 },
70 {
71 "message": "for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref",
72 "code": null,
73 "level": "help",
74 "spans": [],
75 "children": [],
76 "rendered": null
77 },
78 {
79 "message": "consider passing by value instead",
80 "code": null,
81 "level": "help",
82 "spans": [
83 {
84 "file_name": "compiler/mir/tagset.rs",
85 "byte_start": 941,
86 "byte_end": 946,
87 "line_start": 42,
88 "line_end": 42,
89 "column_start": 24,
90 "column_end": 29,
91 "is_primary": true,
92 "text": [
93 {
94 "text": " pub fn is_disjoint(&self, other: Self) -> bool {",
95 "highlight_start": 24,
96 "highlight_end": 29
97 }
98 ],
99 "label": null,
100 "suggested_replacement": "self",
101 "suggestion_applicability": "Unspecified",
102 "expansion": null
103 }
104 ],
105 "children": [],
106 "rendered": null
107 }
108 ],
109 "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"
110}
diff --git a/editors/code/src/test/fixtures/rust-diagnostics/error/E0053.json b/editors/code/src/test/fixtures/rust-diagnostics/error/E0053.json
deleted file mode 100644
index ea5c976d1..000000000
--- a/editors/code/src/test/fixtures/rust-diagnostics/error/E0053.json
+++ /dev/null
@@ -1,42 +0,0 @@
1{
2 "message": "method `next` has an incompatible type for trait",
3 "code": {
4 "code": "E0053",
5 "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"
6 },
7 "level": "error",
8 "spans": [
9 {
10 "file_name": "compiler/ty/list_iter.rs",
11 "byte_start": 1307,
12 "byte_end": 1350,
13 "line_start": 52,
14 "line_end": 52,
15 "column_start": 5,
16 "column_end": 48,
17 "is_primary": true,
18 "text": [
19 {
20 "text": " fn next(&self) -> Option<&'list ty::Ref<M>> {",
21 "highlight_start": 5,
22 "highlight_end": 48
23 }
24 ],
25 "label": "types differ in mutability",
26 "suggested_replacement": null,
27 "suggestion_applicability": null,
28 "expansion": null
29 }
30 ],
31 "children": [
32 {
33 "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>>`",
34 "code": null,
35 "level": "note",
36 "spans": [],
37 "children": [],
38 "rendered": null
39 }
40 ],
41 "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"
42}
diff --git a/editors/code/src/test/fixtures/rust-diagnostics/error/E0061.json b/editors/code/src/test/fixtures/rust-diagnostics/error/E0061.json
deleted file mode 100644
index 3154d1098..000000000
--- a/editors/code/src/test/fixtures/rust-diagnostics/error/E0061.json
+++ /dev/null
@@ -1,114 +0,0 @@
1{
2 "message": "this function takes 2 parameters but 3 parameters were supplied",
3 "code": {
4 "code": "E0061",
5 "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"
6 },
7 "level": "error",
8 "spans": [
9 {
10 "file_name": "compiler/ty/select.rs",
11 "byte_start": 8787,
12 "byte_end": 9241,
13 "line_start": 219,
14 "line_end": 231,
15 "column_start": 5,
16 "column_end": 6,
17 "is_primary": false,
18 "text": [
19 {
20 "text": " pub fn add_evidence(",
21 "highlight_start": 5,
22 "highlight_end": 25
23 },
24 {
25 "text": " &mut self,",
26 "highlight_start": 1,
27 "highlight_end": 19
28 },
29 {
30 "text": " target_poly: &ty::Ref<ty::Poly>,",
31 "highlight_start": 1,
32 "highlight_end": 41
33 },
34 {
35 "text": " evidence_poly: &ty::Ref<ty::Poly>,",
36 "highlight_start": 1,
37 "highlight_end": 43
38 },
39 {
40 "text": " ) {",
41 "highlight_start": 1,
42 "highlight_end": 8
43 },
44 {
45 "text": " match target_poly {",
46 "highlight_start": 1,
47 "highlight_end": 28
48 },
49 {
50 "text": " ty::Ref::Var(tvar, _) => self.add_var_evidence(tvar, evidence_poly),",
51 "highlight_start": 1,
52 "highlight_end": 81
53 },
54 {
55 "text": " ty::Ref::Fixed(target_ty) => {",
56 "highlight_start": 1,
57 "highlight_end": 43
58 },
59 {
60 "text": " let evidence_ty = evidence_poly.resolve_to_ty();",
61 "highlight_start": 1,
62 "highlight_end": 65
63 },
64 {
65 "text": " self.add_evidence_ty(target_ty, evidence_poly, evidence_ty)",
66 "highlight_start": 1,
67 "highlight_end": 76
68 },
69 {
70 "text": " }",
71 "highlight_start": 1,
72 "highlight_end": 14
73 },
74 {
75 "text": " }",
76 "highlight_start": 1,
77 "highlight_end": 10
78 },
79 {
80 "text": " }",
81 "highlight_start": 1,
82 "highlight_end": 6
83 }
84 ],
85 "label": "defined here",
86 "suggested_replacement": null,
87 "suggestion_applicability": null,
88 "expansion": null
89 },
90 {
91 "file_name": "compiler/ty/select.rs",
92 "byte_start": 4045,
93 "byte_end": 4057,
94 "line_start": 104,
95 "line_end": 104,
96 "column_start": 18,
97 "column_end": 30,
98 "is_primary": true,
99 "text": [
100 {
101 "text": " self.add_evidence(target_fixed, evidence_fixed, false);",
102 "highlight_start": 18,
103 "highlight_end": 30
104 }
105 ],
106 "label": "expected 2 parameters",
107 "suggested_replacement": null,
108 "suggestion_applicability": null,
109 "expansion": null
110 }
111 ],
112 "children": [],
113 "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"
114}
diff --git a/editors/code/src/test/fixtures/rust-diagnostics/error/E0277.json b/editors/code/src/test/fixtures/rust-diagnostics/error/E0277.json
deleted file mode 100644
index bfef33c7d..000000000
--- a/editors/code/src/test/fixtures/rust-diagnostics/error/E0277.json
+++ /dev/null
@@ -1,261 +0,0 @@
1{
2 "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",
3 "children": [
4 {
5 "children": [],
6 "code": null,
7 "level": "help",
8 "message": "the trait `std::cmp::PartialEq<&str>` is not implemented for `{integer}`",
9 "rendered": null,
10 "spans": []
11 }
12 ],
13 "code": {
14 "code": "E0277",
15 "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"
16 },
17 "level": "error",
18 "message": "can't compare `{integer}` with `&str`",
19 "spans": [
20 {
21 "byte_end": 155,
22 "byte_start": 153,
23 "column_end": 33,
24 "column_start": 31,
25 "expansion": {
26 "def_site_span": {
27 "byte_end": 940,
28 "byte_start": 0,
29 "column_end": 6,
30 "column_start": 1,
31 "expansion": null,
32 "file_name": "<::core::macros::assert_eq macros>",
33 "is_primary": false,
34 "label": null,
35 "line_end": 36,
36 "line_start": 1,
37 "suggested_replacement": null,
38 "suggestion_applicability": null,
39 "text": [
40 {
41 "highlight_end": 35,
42 "highlight_start": 1,
43 "text": "($ left : expr, $ right : expr) =>"
44 },
45 {
46 "highlight_end": 3,
47 "highlight_start": 1,
48 "text": "({"
49 },
50 {
51 "highlight_end": 33,
52 "highlight_start": 1,
53 "text": " match (& $ left, & $ right)"
54 },
55 {
56 "highlight_end": 7,
57 "highlight_start": 1,
58 "text": " {"
59 },
60 {
61 "highlight_end": 34,
62 "highlight_start": 1,
63 "text": " (left_val, right_val) =>"
64 },
65 {
66 "highlight_end": 11,
67 "highlight_start": 1,
68 "text": " {"
69 },
70 {
71 "highlight_end": 46,
72 "highlight_start": 1,
73 "text": " if ! (* left_val == * right_val)"
74 },
75 {
76 "highlight_end": 15,
77 "highlight_start": 1,
78 "text": " {"
79 },
80 {
81 "highlight_end": 25,
82 "highlight_start": 1,
83 "text": " panic !"
84 },
85 {
86 "highlight_end": 57,
87 "highlight_start": 1,
88 "text": " (r#\"assertion failed: `(left == right)`"
89 },
90 {
91 "highlight_end": 16,
92 "highlight_start": 1,
93 "text": " left: `{:?}`,"
94 },
95 {
96 "highlight_end": 18,
97 "highlight_start": 1,
98 "text": " right: `{:?}`\"#,"
99 },
100 {
101 "highlight_end": 47,
102 "highlight_start": 1,
103 "text": " & * left_val, & * right_val)"
104 },
105 {
106 "highlight_end": 15,
107 "highlight_start": 1,
108 "text": " }"
109 },
110 {
111 "highlight_end": 11,
112 "highlight_start": 1,
113 "text": " }"
114 },
115 {
116 "highlight_end": 7,
117 "highlight_start": 1,
118 "text": " }"
119 },
120 {
121 "highlight_end": 42,
122 "highlight_start": 1,
123 "text": " }) ; ($ left : expr, $ right : expr,) =>"
124 },
125 {
126 "highlight_end": 49,
127 "highlight_start": 1,
128 "text": "({ $ crate :: assert_eq ! ($ left, $ right) }) ;"
129 },
130 {
131 "highlight_end": 53,
132 "highlight_start": 1,
133 "text": "($ left : expr, $ right : expr, $ ($ arg : tt) +) =>"
134 },
135 {
136 "highlight_end": 3,
137 "highlight_start": 1,
138 "text": "({"
139 },
140 {
141 "highlight_end": 37,
142 "highlight_start": 1,
143 "text": " match (& ($ left), & ($ right))"
144 },
145 {
146 "highlight_end": 7,
147 "highlight_start": 1,
148 "text": " {"
149 },
150 {
151 "highlight_end": 34,
152 "highlight_start": 1,
153 "text": " (left_val, right_val) =>"
154 },
155 {
156 "highlight_end": 11,
157 "highlight_start": 1,
158 "text": " {"
159 },
160 {
161 "highlight_end": 46,
162 "highlight_start": 1,
163 "text": " if ! (* left_val == * right_val)"
164 },
165 {
166 "highlight_end": 15,
167 "highlight_start": 1,
168 "text": " {"
169 },
170 {
171 "highlight_end": 25,
172 "highlight_start": 1,
173 "text": " panic !"
174 },
175 {
176 "highlight_end": 57,
177 "highlight_start": 1,
178 "text": " (r#\"assertion failed: `(left == right)`"
179 },
180 {
181 "highlight_end": 16,
182 "highlight_start": 1,
183 "text": " left: `{:?}`,"
184 },
185 {
186 "highlight_end": 22,
187 "highlight_start": 1,
188 "text": " right: `{:?}`: {}\"#,"
189 },
190 {
191 "highlight_end": 72,
192 "highlight_start": 1,
193 "text": " & * left_val, & * right_val, $ crate :: format_args !"
194 },
195 {
196 "highlight_end": 33,
197 "highlight_start": 1,
198 "text": " ($ ($ arg) +))"
199 },
200 {
201 "highlight_end": 15,
202 "highlight_start": 1,
203 "text": " }"
204 },
205 {
206 "highlight_end": 11,
207 "highlight_start": 1,
208 "text": " }"
209 },
210 {
211 "highlight_end": 7,
212 "highlight_start": 1,
213 "text": " }"
214 },
215 {
216 "highlight_end": 6,
217 "highlight_start": 1,
218 "text": " }) ;"
219 }
220 ]
221 },
222 "macro_decl_name": "assert_eq!",
223 "span": {
224 "byte_end": 38,
225 "byte_start": 16,
226 "column_end": 27,
227 "column_start": 5,
228 "expansion": null,
229 "file_name": "src/main.rs",
230 "is_primary": false,
231 "label": null,
232 "line_end": 2,
233 "line_start": 2,
234 "suggested_replacement": null,
235 "suggestion_applicability": null,
236 "text": [
237 {
238 "highlight_end": 27,
239 "highlight_start": 5,
240 "text": " assert_eq!(1, \"love\");"
241 }
242 ]
243 }
244 },
245 "file_name": "<::core::macros::assert_eq macros>",
246 "is_primary": true,
247 "label": "no implementation for `{integer} == &str`",
248 "line_end": 7,
249 "line_start": 7,
250 "suggested_replacement": null,
251 "suggestion_applicability": null,
252 "text": [
253 {
254 "highlight_end": 33,
255 "highlight_start": 31,
256 "text": " if ! (* left_val == * right_val)"
257 }
258 ]
259 }
260 ]
261}
diff --git a/editors/code/src/test/fixtures/rust-diagnostics/error/E0308.json b/editors/code/src/test/fixtures/rust-diagnostics/error/E0308.json
deleted file mode 100644
index fb23824a3..000000000
--- a/editors/code/src/test/fixtures/rust-diagnostics/error/E0308.json
+++ /dev/null
@@ -1,33 +0,0 @@
1{
2 "message": "mismatched types",
3 "code": {
4 "code": "E0308",
5 "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"
6 },
7 "level": "error",
8 "spans": [
9 {
10 "file_name": "runtime/compiler_support.rs",
11 "byte_start": 1589,
12 "byte_end": 1594,
13 "line_start": 48,
14 "line_end": 48,
15 "column_start": 65,
16 "column_end": 70,
17 "is_primary": true,
18 "text": [
19 {
20 "text": " let layout = alloc::Layout::from_size_align_unchecked(size, align);",
21 "highlight_start": 65,
22 "highlight_end": 70
23 }
24 ],
25 "label": "expected usize, found u32",
26 "suggested_replacement": null,
27 "suggestion_applicability": null,
28 "expansion": null
29 }
30 ],
31 "children": [],
32 "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"
33}
diff --git a/editors/code/src/test/fixtures/rust-diagnostics/warning/unused_variables.json b/editors/code/src/test/fixtures/rust-diagnostics/warning/unused_variables.json
deleted file mode 100644
index d1e2be722..000000000
--- a/editors/code/src/test/fixtures/rust-diagnostics/warning/unused_variables.json
+++ /dev/null
@@ -1,72 +0,0 @@
1{
2 "message": "unused variable: `foo`",
3 "code": {
4 "code": "unused_variables",
5 "explanation": null
6 },
7 "level": "warning",
8 "spans": [
9 {
10 "file_name": "driver/subcommand/repl.rs",
11 "byte_start": 9228,
12 "byte_end": 9231,
13 "line_start": 291,
14 "line_end": 291,
15 "column_start": 9,
16 "column_end": 12,
17 "is_primary": true,
18 "text": [
19 {
20 "text": " let foo = 42;",
21 "highlight_start": 9,
22 "highlight_end": 12
23 }
24 ],
25 "label": null,
26 "suggested_replacement": null,
27 "suggestion_applicability": null,
28 "expansion": null
29 }
30 ],
31 "children": [
32 {
33 "message": "#[warn(unused_variables)] on by default",
34 "code": null,
35 "level": "note",
36 "spans": [],
37 "children": [],
38 "rendered": null
39 },
40 {
41 "message": "consider prefixing with an underscore",
42 "code": null,
43 "level": "help",
44 "spans": [
45 {
46 "file_name": "driver/subcommand/repl.rs",
47 "byte_start": 9228,
48 "byte_end": 9231,
49 "line_start": 291,
50 "line_end": 291,
51 "column_start": 9,
52 "column_end": 12,
53 "is_primary": true,
54 "text": [
55 {
56 "text": " let foo = 42;",
57 "highlight_start": 9,
58 "highlight_end": 12
59 }
60 ],
61 "label": null,
62 "suggested_replacement": "_foo",
63 "suggestion_applicability": "MachineApplicable",
64 "expansion": null
65 }
66 ],
67 "children": [],
68 "rendered": null
69 }
70 ],
71 "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"
72}
diff --git a/editors/code/src/test/utils/diagnotics/SuggestedFix.test.ts b/editors/code/src/test/utils/diagnotics/SuggestedFix.test.ts
deleted file mode 100644
index 2b25eb705..000000000
--- a/editors/code/src/test/utils/diagnotics/SuggestedFix.test.ts
+++ /dev/null
@@ -1,134 +0,0 @@
1import * as assert from 'assert';
2import * as vscode from 'vscode';
3
4import { SuggestionApplicability } from '../../../utils/diagnostics/rust';
5import SuggestedFix from '../../../utils/diagnostics/SuggestedFix';
6
7const location1 = new vscode.Location(
8 vscode.Uri.file('/file/1'),
9 new vscode.Range(new vscode.Position(1, 2), new vscode.Position(3, 4)),
10);
11
12const location2 = new vscode.Location(
13 vscode.Uri.file('/file/2'),
14 new vscode.Range(new vscode.Position(5, 6), new vscode.Position(7, 8)),
15);
16
17describe('SuggestedFix', () => {
18 describe('isEqual', () => {
19 it('should treat identical instances as equal', () => {
20 const suggestion1 = new SuggestedFix(
21 'Replace me!',
22 location1,
23 'With this!',
24 );
25
26 const suggestion2 = new SuggestedFix(
27 'Replace me!',
28 location1,
29 'With this!',
30 );
31
32 assert(suggestion1.isEqual(suggestion2));
33 });
34
35 it('should treat instances with different titles as inequal', () => {
36 const suggestion1 = new SuggestedFix(
37 'Replace me!',
38 location1,
39 'With this!',
40 );
41
42 const suggestion2 = new SuggestedFix(
43 'Not the same title!',
44 location1,
45 'With this!',
46 );
47
48 assert(!suggestion1.isEqual(suggestion2));
49 });
50
51 it('should treat instances with different replacements as inequal', () => {
52 const suggestion1 = new SuggestedFix(
53 'Replace me!',
54 location1,
55 'With this!',
56 );
57
58 const suggestion2 = new SuggestedFix(
59 'Replace me!',
60 location1,
61 'With something else!',
62 );
63
64 assert(!suggestion1.isEqual(suggestion2));
65 });
66
67 it('should treat instances with different locations as inequal', () => {
68 const suggestion1 = new SuggestedFix(
69 'Replace me!',
70 location1,
71 'With this!',
72 );
73
74 const suggestion2 = new SuggestedFix(
75 'Replace me!',
76 location2,
77 'With this!',
78 );
79
80 assert(!suggestion1.isEqual(suggestion2));
81 });
82
83 it('should treat instances with different applicability as inequal', () => {
84 const suggestion1 = new SuggestedFix(
85 'Replace me!',
86 location1,
87 'With this!',
88 SuggestionApplicability.MachineApplicable,
89 );
90
91 const suggestion2 = new SuggestedFix(
92 'Replace me!',
93 location2,
94 'With this!',
95 SuggestionApplicability.HasPlaceholders,
96 );
97
98 assert(!suggestion1.isEqual(suggestion2));
99 });
100 });
101
102 describe('toCodeAction', () => {
103 it('should map a simple suggestion', () => {
104 const suggestion = new SuggestedFix(
105 'Replace me!',
106 location1,
107 'With this!',
108 );
109
110 const codeAction = suggestion.toCodeAction();
111 assert.strictEqual(codeAction.kind, vscode.CodeActionKind.QuickFix);
112 assert.strictEqual(codeAction.title, 'Replace me!');
113 assert.strictEqual(codeAction.isPreferred, false);
114
115 const edit = codeAction.edit;
116 if (!edit) {
117 assert.fail('Code Action edit unexpectedly missing');
118 return;
119 }
120
121 const editEntries = edit.entries();
122 assert.strictEqual(editEntries.length, 1);
123
124 const [[editUri, textEdits]] = editEntries;
125 assert.strictEqual(editUri.toString(), location1.uri.toString());
126
127 assert.strictEqual(textEdits.length, 1);
128 const [textEdit] = textEdits;
129
130 assert(textEdit.range.isEqual(location1.range));
131 assert.strictEqual(textEdit.newText, 'With this!');
132 });
133 });
134});
diff --git a/editors/code/src/test/utils/diagnotics/SuggestedFixCollection.test.ts b/editors/code/src/test/utils/diagnotics/SuggestedFixCollection.test.ts
deleted file mode 100644
index ef09013f4..000000000
--- a/editors/code/src/test/utils/diagnotics/SuggestedFixCollection.test.ts
+++ /dev/null
@@ -1,127 +0,0 @@
1import * as assert from 'assert';
2import * as vscode from 'vscode';
3
4import SuggestedFix from '../../../utils/diagnostics/SuggestedFix';
5import SuggestedFixCollection from '../../../utils/diagnostics/SuggestedFixCollection';
6
7const uri1 = vscode.Uri.file('/file/1');
8const uri2 = vscode.Uri.file('/file/2');
9
10const mockDocument1 = ({
11 uri: uri1,
12} as unknown) as vscode.TextDocument;
13
14const mockDocument2 = ({
15 uri: uri2,
16} as unknown) as vscode.TextDocument;
17
18const range1 = new vscode.Range(
19 new vscode.Position(1, 2),
20 new vscode.Position(3, 4),
21);
22const range2 = new vscode.Range(
23 new vscode.Position(5, 6),
24 new vscode.Position(7, 8),
25);
26
27const diagnostic1 = new vscode.Diagnostic(range1, 'First diagnostic');
28const diagnostic2 = new vscode.Diagnostic(range2, 'Second diagnostic');
29
30// This is a mutable object so return a fresh instance every time
31function suggestion1(): SuggestedFix {
32 return new SuggestedFix(
33 'Replace me!',
34 new vscode.Location(uri1, range1),
35 'With this!',
36 );
37}
38
39describe('SuggestedFixCollection', () => {
40 it('should add a suggestion then return it as a code action', () => {
41 const suggestedFixes = new SuggestedFixCollection();
42 suggestedFixes.addSuggestedFixForDiagnostic(suggestion1(), diagnostic1);
43
44 // Specify the document and range that exactly matches
45 const codeActions = suggestedFixes.provideCodeActions(
46 mockDocument1,
47 range1,
48 );
49
50 assert.strictEqual(codeActions.length, 1);
51 const [codeAction] = codeActions;
52 assert.strictEqual(codeAction.title, suggestion1().title);
53
54 const { diagnostics } = codeAction;
55 if (!diagnostics) {
56 assert.fail('Diagnostics unexpectedly missing');
57 return;
58 }
59
60 assert.strictEqual(diagnostics.length, 1);
61 assert.strictEqual(diagnostics[0], diagnostic1);
62 });
63
64 it('should not return code actions for different ranges', () => {
65 const suggestedFixes = new SuggestedFixCollection();
66 suggestedFixes.addSuggestedFixForDiagnostic(suggestion1(), diagnostic1);
67
68 const codeActions = suggestedFixes.provideCodeActions(
69 mockDocument1,
70 range2,
71 );
72
73 assert(!codeActions || codeActions.length === 0);
74 });
75
76 it('should not return code actions for different documents', () => {
77 const suggestedFixes = new SuggestedFixCollection();
78 suggestedFixes.addSuggestedFixForDiagnostic(suggestion1(), diagnostic1);
79
80 const codeActions = suggestedFixes.provideCodeActions(
81 mockDocument2,
82 range1,
83 );
84
85 assert(!codeActions || codeActions.length === 0);
86 });
87
88 it('should not return code actions that have been cleared', () => {
89 const suggestedFixes = new SuggestedFixCollection();
90 suggestedFixes.addSuggestedFixForDiagnostic(suggestion1(), diagnostic1);
91 suggestedFixes.clear();
92
93 const codeActions = suggestedFixes.provideCodeActions(
94 mockDocument1,
95 range1,
96 );
97
98 assert(!codeActions || codeActions.length === 0);
99 });
100
101 it('should merge identical suggestions together', () => {
102 const suggestedFixes = new SuggestedFixCollection();
103
104 // Add the same suggestion for two diagnostics
105 suggestedFixes.addSuggestedFixForDiagnostic(suggestion1(), diagnostic1);
106 suggestedFixes.addSuggestedFixForDiagnostic(suggestion1(), diagnostic2);
107
108 const codeActions = suggestedFixes.provideCodeActions(
109 mockDocument1,
110 range1,
111 );
112
113 assert.strictEqual(codeActions.length, 1);
114 const [codeAction] = codeActions;
115 const { diagnostics } = codeAction;
116
117 if (!diagnostics) {
118 assert.fail('Diagnostics unexpectedly missing');
119 return;
120 }
121
122 // We should be associated with both diagnostics
123 assert.strictEqual(diagnostics.length, 2);
124 assert.strictEqual(diagnostics[0], diagnostic1);
125 assert.strictEqual(diagnostics[1], diagnostic2);
126 });
127});
diff --git a/editors/code/src/test/utils/diagnotics/rust.test.ts b/editors/code/src/test/utils/diagnotics/rust.test.ts
deleted file mode 100644
index 358325cc8..000000000
--- a/editors/code/src/test/utils/diagnotics/rust.test.ts
+++ /dev/null
@@ -1,236 +0,0 @@
1import * as assert from 'assert';
2import * as fs from 'fs';
3import * as vscode from 'vscode';
4
5import {
6 MappedRustDiagnostic,
7 mapRustDiagnosticToVsCode,
8 RustDiagnostic,
9 SuggestionApplicability,
10} from '../../../utils/diagnostics/rust';
11
12function loadDiagnosticFixture(name: string): RustDiagnostic {
13 const jsonText = fs
14 .readFileSync(
15 // We're actually in our JavaScript output directory, climb out
16 `${__dirname}/../../../../src/test/fixtures/rust-diagnostics/${name}.json`,
17 )
18 .toString();
19
20 return JSON.parse(jsonText);
21}
22
23function mapFixtureToVsCode(name: string): MappedRustDiagnostic {
24 const rd = loadDiagnosticFixture(name);
25 const mapResult = mapRustDiagnosticToVsCode(rd);
26
27 if (!mapResult) {
28 return assert.fail('Mapping unexpectedly failed');
29 }
30 return mapResult;
31}
32
33describe('mapRustDiagnosticToVsCode', () => {
34 it('should map an incompatible type for trait error', () => {
35 const { diagnostic, suggestedFixes } = mapFixtureToVsCode(
36 'error/E0053',
37 );
38
39 assert.strictEqual(
40 diagnostic.severity,
41 vscode.DiagnosticSeverity.Error,
42 );
43 assert.strictEqual(diagnostic.source, 'rustc');
44 assert.strictEqual(
45 diagnostic.message,
46 [
47 `method \`next\` has an incompatible type for trait`,
48 `expected type \`fn(&mut ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&ty::Ref<M>>\``,
49 ` found type \`fn(&ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&'list ty::Ref<M>>\``,
50 ].join('\n'),
51 );
52 assert.strictEqual(diagnostic.code, 'E0053');
53 assert.deepStrictEqual(diagnostic.tags, []);
54
55 // No related information
56 assert.deepStrictEqual(diagnostic.relatedInformation, []);
57
58 // There are no suggested fixes
59 assert.strictEqual(suggestedFixes.length, 0);
60 });
61
62 it('should map an unused variable warning', () => {
63 const { diagnostic, suggestedFixes } = mapFixtureToVsCode(
64 'warning/unused_variables',
65 );
66
67 assert.strictEqual(
68 diagnostic.severity,
69 vscode.DiagnosticSeverity.Warning,
70 );
71 assert.strictEqual(
72 diagnostic.message,
73 [
74 'unused variable: `foo`',
75 '#[warn(unused_variables)] on by default',
76 ].join('\n'),
77 );
78 assert.strictEqual(diagnostic.code, 'unused_variables');
79 assert.strictEqual(diagnostic.source, 'rustc');
80 assert.deepStrictEqual(diagnostic.tags, [
81 vscode.DiagnosticTag.Unnecessary,
82 ]);
83
84 // No related information
85 assert.deepStrictEqual(diagnostic.relatedInformation, []);
86
87 // One suggested fix available to prefix the variable
88 assert.strictEqual(suggestedFixes.length, 1);
89 const [suggestedFix] = suggestedFixes;
90 assert.strictEqual(
91 suggestedFix.title,
92 'consider prefixing with an underscore: `_foo`',
93 );
94 assert.strictEqual(
95 suggestedFix.applicability,
96 SuggestionApplicability.MachineApplicable,
97 );
98 });
99
100 it('should map a wrong number of parameters error', () => {
101 const { diagnostic, suggestedFixes } = mapFixtureToVsCode(
102 'error/E0061',
103 );
104
105 assert.strictEqual(
106 diagnostic.severity,
107 vscode.DiagnosticSeverity.Error,
108 );
109 assert.strictEqual(
110 diagnostic.message,
111 [
112 'this function takes 2 parameters but 3 parameters were supplied',
113 'expected 2 parameters',
114 ].join('\n'),
115 );
116 assert.strictEqual(diagnostic.code, 'E0061');
117 assert.strictEqual(diagnostic.source, 'rustc');
118 assert.deepStrictEqual(diagnostic.tags, []);
119
120 // One related information for the original definition
121 const relatedInformation = diagnostic.relatedInformation;
122 if (!relatedInformation) {
123 assert.fail('Related information unexpectedly undefined');
124 return;
125 }
126 assert.strictEqual(relatedInformation.length, 1);
127 const [related] = relatedInformation;
128 assert.strictEqual(related.message, 'defined here');
129
130 // There are no suggested fixes
131 assert.strictEqual(suggestedFixes.length, 0);
132 });
133
134 it('should map a Clippy copy pass by ref warning', () => {
135 const { diagnostic, suggestedFixes } = mapFixtureToVsCode(
136 'clippy/trivially_copy_pass_by_ref',
137 );
138
139 assert.strictEqual(
140 diagnostic.severity,
141 vscode.DiagnosticSeverity.Warning,
142 );
143 assert.strictEqual(diagnostic.source, 'clippy');
144 assert.strictEqual(
145 diagnostic.message,
146 [
147 'this argument is passed by reference, but would be more efficient if passed by value',
148 '#[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]',
149 'for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref',
150 ].join('\n'),
151 );
152 assert.strictEqual(diagnostic.code, 'trivially_copy_pass_by_ref');
153 assert.deepStrictEqual(diagnostic.tags, []);
154
155 // One related information for the lint definition
156 const relatedInformation = diagnostic.relatedInformation;
157 if (!relatedInformation) {
158 assert.fail('Related information unexpectedly undefined');
159 return;
160 }
161 assert.strictEqual(relatedInformation.length, 1);
162 const [related] = relatedInformation;
163 assert.strictEqual(related.message, 'lint level defined here');
164
165 // One suggested fix to pass by value
166 assert.strictEqual(suggestedFixes.length, 1);
167 const [suggestedFix] = suggestedFixes;
168 assert.strictEqual(
169 suggestedFix.title,
170 'consider passing by value instead: `self`',
171 );
172 // Clippy does not mark this with any applicability
173 assert.strictEqual(
174 suggestedFix.applicability,
175 SuggestionApplicability.Unspecified,
176 );
177 });
178
179 it('should map a mismatched type error', () => {
180 const { diagnostic, suggestedFixes } = mapFixtureToVsCode(
181 'error/E0308',
182 );
183
184 assert.strictEqual(
185 diagnostic.severity,
186 vscode.DiagnosticSeverity.Error,
187 );
188 assert.strictEqual(
189 diagnostic.message,
190 ['mismatched types', 'expected usize, found u32'].join('\n'),
191 );
192 assert.strictEqual(diagnostic.code, 'E0308');
193 assert.strictEqual(diagnostic.source, 'rustc');
194 assert.deepStrictEqual(diagnostic.tags, []);
195
196 // No related information
197 assert.deepStrictEqual(diagnostic.relatedInformation, []);
198
199 // There are no suggested fixes
200 assert.strictEqual(suggestedFixes.length, 0);
201 });
202
203 it('should map a macro invocation location to normal file path', () => {
204 const { location, diagnostic, suggestedFixes } = mapFixtureToVsCode(
205 'error/E0277',
206 );
207
208 assert.strictEqual(
209 diagnostic.severity,
210 vscode.DiagnosticSeverity.Error,
211 );
212 assert.strictEqual(
213 diagnostic.message,
214 [
215 "can't compare `{integer}` with `&str`",
216 'the trait `std::cmp::PartialEq<&str>` is not implemented for `{integer}`',
217 ].join('\n'),
218 );
219 assert.strictEqual(diagnostic.code, 'E0277');
220 assert.strictEqual(diagnostic.source, 'rustc');
221 assert.deepStrictEqual(diagnostic.tags, []);
222
223 // No related information
224 assert.deepStrictEqual(diagnostic.relatedInformation, []);
225
226 // There are no suggested fixes
227 assert.strictEqual(suggestedFixes.length, 0);
228
229 // The file url should be normal file
230 // Ignore the first part because it depends on vs workspace location
231 assert.strictEqual(
232 location.uri.path.substr(-'src/main.rs'.length),
233 'src/main.rs',
234 );
235 });
236});
diff --git a/editors/code/src/test/utils/diagnotics/vscode.test.ts b/editors/code/src/test/utils/diagnotics/vscode.test.ts
deleted file mode 100644
index 4944dd032..000000000
--- a/editors/code/src/test/utils/diagnotics/vscode.test.ts
+++ /dev/null
@@ -1,98 +0,0 @@
1import * as assert from 'assert';
2import * as vscode from 'vscode';
3
4import { areDiagnosticsEqual } from '../../../utils/diagnostics/vscode';
5
6const range1 = new vscode.Range(
7 new vscode.Position(1, 2),
8 new vscode.Position(3, 4),
9);
10
11const range2 = new vscode.Range(
12 new vscode.Position(5, 6),
13 new vscode.Position(7, 8),
14);
15
16describe('areDiagnosticsEqual', () => {
17 it('should treat identical diagnostics as equal', () => {
18 const diagnostic1 = new vscode.Diagnostic(
19 range1,
20 'Hello, world!',
21 vscode.DiagnosticSeverity.Error,
22 );
23
24 const diagnostic2 = new vscode.Diagnostic(
25 range1,
26 'Hello, world!',
27 vscode.DiagnosticSeverity.Error,
28 );
29
30 assert(areDiagnosticsEqual(diagnostic1, diagnostic2));
31 });
32
33 it('should treat diagnostics with different sources as inequal', () => {
34 const diagnostic1 = new vscode.Diagnostic(
35 range1,
36 'Hello, world!',
37 vscode.DiagnosticSeverity.Error,
38 );
39 diagnostic1.source = 'rustc';
40
41 const diagnostic2 = new vscode.Diagnostic(
42 range1,
43 'Hello, world!',
44 vscode.DiagnosticSeverity.Error,
45 );
46 diagnostic2.source = 'clippy';
47
48 assert(!areDiagnosticsEqual(diagnostic1, diagnostic2));
49 });
50
51 it('should treat diagnostics with different ranges as inequal', () => {
52 const diagnostic1 = new vscode.Diagnostic(
53 range1,
54 'Hello, world!',
55 vscode.DiagnosticSeverity.Error,
56 );
57
58 const diagnostic2 = new vscode.Diagnostic(
59 range2,
60 'Hello, world!',
61 vscode.DiagnosticSeverity.Error,
62 );
63
64 assert(!areDiagnosticsEqual(diagnostic1, diagnostic2));
65 });
66
67 it('should treat diagnostics with different messages as inequal', () => {
68 const diagnostic1 = new vscode.Diagnostic(
69 range1,
70 'Hello, world!',
71 vscode.DiagnosticSeverity.Error,
72 );
73
74 const diagnostic2 = new vscode.Diagnostic(
75 range1,
76 'Goodbye!, world!',
77 vscode.DiagnosticSeverity.Error,
78 );
79
80 assert(!areDiagnosticsEqual(diagnostic1, diagnostic2));
81 });
82
83 it('should treat diagnostics with different severities as inequal', () => {
84 const diagnostic1 = new vscode.Diagnostic(
85 range1,
86 'Hello, world!',
87 vscode.DiagnosticSeverity.Warning,
88 );
89
90 const diagnostic2 = new vscode.Diagnostic(
91 range1,
92 'Hello, world!',
93 vscode.DiagnosticSeverity.Error,
94 );
95
96 assert(!areDiagnosticsEqual(diagnostic1, diagnostic2));
97 });
98});
diff --git a/editors/code/src/utils/diagnostics/SuggestedFix.ts b/editors/code/src/utils/diagnostics/SuggestedFix.ts
deleted file mode 100644
index 6e660bb61..000000000
--- a/editors/code/src/utils/diagnostics/SuggestedFix.ts
+++ /dev/null
@@ -1,67 +0,0 @@
1import * as vscode from 'vscode';
2
3import { SuggestionApplicability } from './rust';
4
5/**
6 * Model object for text replacements suggested by the Rust compiler
7 *
8 * This is an intermediate form between the raw `rustc` JSON and a
9 * `vscode.CodeAction`. It's optimised for the use-cases of
10 * `SuggestedFixCollection`.
11 */
12export default class SuggestedFix {
13 public readonly title: string;
14 public readonly location: vscode.Location;
15 public readonly replacement: string;
16 public readonly applicability: SuggestionApplicability;
17
18 /**
19 * Diagnostics this suggested fix could resolve
20 */
21 public diagnostics: vscode.Diagnostic[];
22
23 constructor(
24 title: string,
25 location: vscode.Location,
26 replacement: string,
27 applicability: SuggestionApplicability = SuggestionApplicability.Unspecified,
28 ) {
29 this.title = title;
30 this.location = location;
31 this.replacement = replacement;
32 this.applicability = applicability;
33 this.diagnostics = [];
34 }
35
36 /**
37 * Determines if this suggested fix is equivalent to another instance
38 */
39 public isEqual(other: SuggestedFix): boolean {
40 return (
41 this.title === other.title &&
42 this.location.range.isEqual(other.location.range) &&
43 this.replacement === other.replacement &&
44 this.applicability === other.applicability
45 );
46 }
47
48 /**
49 * Converts this suggested fix to a VS Code Quick Fix code action
50 */
51 public toCodeAction(): vscode.CodeAction {
52 const codeAction = new vscode.CodeAction(
53 this.title,
54 vscode.CodeActionKind.QuickFix,
55 );
56
57 const edit = new vscode.WorkspaceEdit();
58 edit.replace(this.location.uri, this.location.range, this.replacement);
59 codeAction.edit = edit;
60
61 codeAction.isPreferred =
62 this.applicability === SuggestionApplicability.MachineApplicable;
63
64 codeAction.diagnostics = [...this.diagnostics];
65 return codeAction;
66 }
67}
diff --git a/editors/code/src/utils/diagnostics/SuggestedFixCollection.ts b/editors/code/src/utils/diagnostics/SuggestedFixCollection.ts
deleted file mode 100644
index 57c9856cf..000000000
--- a/editors/code/src/utils/diagnostics/SuggestedFixCollection.ts
+++ /dev/null
@@ -1,77 +0,0 @@
1import * as vscode from 'vscode';
2import SuggestedFix from './SuggestedFix';
3
4/**
5 * Collection of suggested fixes across multiple documents
6 *
7 * This stores `SuggestedFix` model objects and returns them via the
8 * `vscode.CodeActionProvider` interface.
9 */
10export default class SuggestedFixCollection
11 implements vscode.CodeActionProvider {
12 public static PROVIDED_CODE_ACTION_KINDS = [vscode.CodeActionKind.QuickFix];
13
14 /**
15 * Map of document URI strings to suggested fixes
16 */
17 private suggestedFixes: Map<string, SuggestedFix[]>;
18
19 constructor() {
20 this.suggestedFixes = new Map();
21 }
22
23 /**
24 * Clears all suggested fixes across all documents
25 */
26 public clear(): void {
27 this.suggestedFixes = new Map();
28 }
29
30 /**
31 * Adds a suggested fix for the given diagnostic
32 *
33 * Some suggested fixes will appear in multiple diagnostics. For example,
34 * forgetting a `mut` on a variable will suggest changing the delaration on
35 * every mutable usage site. If the suggested fix has already been added
36 * this method will instead associate the existing fix with the new
37 * diagnostic.
38 */
39 public addSuggestedFixForDiagnostic(
40 suggestedFix: SuggestedFix,
41 diagnostic: vscode.Diagnostic,
42 ): void {
43 const fileUriString = suggestedFix.location.uri.toString();
44 const fileSuggestions = this.suggestedFixes.get(fileUriString) || [];
45
46 const existingSuggestion = fileSuggestions.find(s =>
47 s.isEqual(suggestedFix),
48 );
49
50 if (existingSuggestion) {
51 // The existing suggestion also applies to this new diagnostic
52 existingSuggestion.diagnostics.push(diagnostic);
53 } else {
54 // We haven't seen this suggestion before
55 suggestedFix.diagnostics.push(diagnostic);
56 fileSuggestions.push(suggestedFix);
57 }
58
59 this.suggestedFixes.set(fileUriString, fileSuggestions);
60 }
61
62 /**
63 * Filters suggested fixes by their document and range and converts them to
64 * code actions
65 */
66 public provideCodeActions(
67 document: vscode.TextDocument,
68 range: vscode.Range,
69 ): vscode.CodeAction[] {
70 const documentUriString = document.uri.toString();
71
72 const suggestedFixes = this.suggestedFixes.get(documentUriString);
73 return (suggestedFixes || [])
74 .filter(({ location }) => location.range.intersection(range))
75 .map(suggestedEdit => suggestedEdit.toCodeAction());
76 }
77}
diff --git a/editors/code/src/utils/diagnostics/rust.ts b/editors/code/src/utils/diagnostics/rust.ts
deleted file mode 100644
index 1f0c0d3e4..000000000
--- a/editors/code/src/utils/diagnostics/rust.ts
+++ /dev/null
@@ -1,299 +0,0 @@
1import * as path from 'path';
2import * as vscode from 'vscode';
3
4import SuggestedFix from './SuggestedFix';
5
6export enum SuggestionApplicability {
7 MachineApplicable = 'MachineApplicable',
8 HasPlaceholders = 'HasPlaceholders',
9 MaybeIncorrect = 'MaybeIncorrect',
10 Unspecified = 'Unspecified',
11}
12
13export interface RustDiagnosticSpanMacroExpansion {
14 span: RustDiagnosticSpan;
15 macro_decl_name: string;
16 def_site_span?: RustDiagnosticSpan;
17}
18
19// Reference:
20// https://github.com/rust-lang/rust/blob/master/src/libsyntax/json.rs
21export interface RustDiagnosticSpan {
22 line_start: number;
23 line_end: number;
24 column_start: number;
25 column_end: number;
26 is_primary: boolean;
27 file_name: string;
28 label?: string;
29 expansion?: RustDiagnosticSpanMacroExpansion;
30 suggested_replacement?: string;
31 suggestion_applicability?: SuggestionApplicability;
32}
33
34export interface RustDiagnostic {
35 spans: RustDiagnosticSpan[];
36 rendered: string;
37 message: string;
38 level: string;
39 code?: {
40 code: string;
41 };
42 children: RustDiagnostic[];
43}
44
45export interface MappedRustDiagnostic {
46 location: vscode.Location;
47 diagnostic: vscode.Diagnostic;
48 suggestedFixes: SuggestedFix[];
49}
50
51interface MappedRustChildDiagnostic {
52 related?: vscode.DiagnosticRelatedInformation;
53 suggestedFix?: SuggestedFix;
54 messageLine?: string;
55}
56
57/**
58 * Converts a Rust level string to a VsCode severity
59 */
60function mapLevelToSeverity(s: string): vscode.DiagnosticSeverity {
61 if (s === 'error') {
62 return vscode.DiagnosticSeverity.Error;
63 }
64 if (s.startsWith('warn')) {
65 return vscode.DiagnosticSeverity.Warning;
66 }
67 return vscode.DiagnosticSeverity.Information;
68}
69
70/**
71 * Check whether a file name is from macro invocation
72 */
73function isFromMacro(fileName: string): boolean {
74 return fileName.startsWith('<') && fileName.endsWith('>');
75}
76
77/**
78 * Converts a Rust macro span to a VsCode location recursively
79 */
80function mapMacroSpanToLocation(
81 spanMacro: RustDiagnosticSpanMacroExpansion,
82): vscode.Location | undefined {
83 if (!isFromMacro(spanMacro.span.file_name)) {
84 return mapSpanToLocation(spanMacro.span);
85 }
86
87 if (spanMacro.span.expansion) {
88 return mapMacroSpanToLocation(spanMacro.span.expansion);
89 }
90
91 return;
92}
93
94/**
95 * Converts a Rust span to a VsCode location
96 */
97function mapSpanToLocation(span: RustDiagnosticSpan): vscode.Location {
98 if (isFromMacro(span.file_name) && span.expansion) {
99 const macroLoc = mapMacroSpanToLocation(span.expansion);
100 if (macroLoc) {
101 return macroLoc;
102 }
103 }
104
105 const fileName = path.join(vscode.workspace.rootPath || '', span.file_name);
106 const fileUri = vscode.Uri.file(fileName);
107
108 const range = new vscode.Range(
109 new vscode.Position(span.line_start - 1, span.column_start - 1),
110 new vscode.Position(span.line_end - 1, span.column_end - 1),
111 );
112
113 return new vscode.Location(fileUri, range);
114}
115
116/**
117 * Converts a secondary Rust span to a VsCode related information
118 *
119 * If the span is unlabelled this will return `undefined`.
120 */
121function mapSecondarySpanToRelated(
122 span: RustDiagnosticSpan,
123): vscode.DiagnosticRelatedInformation | undefined {
124 if (!span.label) {
125 // Nothing to label this with
126 return;
127 }
128
129 const location = mapSpanToLocation(span);
130 return new vscode.DiagnosticRelatedInformation(location, span.label);
131}
132
133/**
134 * Determines if diagnostic is related to unused code
135 */
136function isUnusedOrUnnecessary(rd: RustDiagnostic): boolean {
137 if (!rd.code) {
138 return false;
139 }
140
141 return [
142 'dead_code',
143 'unknown_lints',
144 'unreachable_code',
145 'unused_attributes',
146 'unused_imports',
147 'unused_macros',
148 'unused_variables',
149 ].includes(rd.code.code);
150}
151
152/**
153 * Determines if diagnostic is related to deprecated code
154 */
155function isDeprecated(rd: RustDiagnostic): boolean {
156 if (!rd.code) {
157 return false;
158 }
159
160 return ['deprecated'].includes(rd.code.code);
161}
162
163/**
164 * Converts a Rust child diagnostic to a VsCode related information
165 *
166 * This can have three outcomes:
167 *
168 * 1. If this is no primary span this will return a `noteLine`
169 * 2. If there is a primary span with a suggested replacement it will return a
170 * `codeAction`.
171 * 3. If there is a primary span without a suggested replacement it will return
172 * a `related`.
173 */
174function mapRustChildDiagnostic(rd: RustDiagnostic): MappedRustChildDiagnostic {
175 const span = rd.spans.find(s => s.is_primary);
176
177 if (!span) {
178 // `rustc` uses these spanless children as a way to print multi-line
179 // messages
180 return { messageLine: rd.message };
181 }
182
183 // If we have a primary span use its location, otherwise use the parent
184 const location = mapSpanToLocation(span);
185
186 // We need to distinguish `null` from an empty string
187 if (span && typeof span.suggested_replacement === 'string') {
188 // Include our replacement in the title unless it's empty
189 const title = span.suggested_replacement
190 ? `${rd.message}: \`${span.suggested_replacement}\``
191 : rd.message;
192
193 return {
194 suggestedFix: new SuggestedFix(
195 title,
196 location,
197 span.suggested_replacement,
198 span.suggestion_applicability,
199 ),
200 };
201 } else {
202 const related = new vscode.DiagnosticRelatedInformation(
203 location,
204 rd.message,
205 );
206
207 return { related };
208 }
209}
210
211/**
212 * Converts a Rust root diagnostic to VsCode form
213 *
214 * This flattens the Rust diagnostic by:
215 *
216 * 1. Creating a `vscode.Diagnostic` with the root message and primary span.
217 * 2. Adding any labelled secondary spans to `relatedInformation`
218 * 3. Categorising child diagnostics as either `SuggestedFix`es,
219 * `relatedInformation` or additional message lines.
220 *
221 * If the diagnostic has no primary span this will return `undefined`
222 */
223export function mapRustDiagnosticToVsCode(
224 rd: RustDiagnostic,
225): MappedRustDiagnostic | undefined {
226 const primarySpan = rd.spans.find(s => s.is_primary);
227 if (!primarySpan) {
228 return;
229 }
230
231 const location = mapSpanToLocation(primarySpan);
232 const secondarySpans = rd.spans.filter(s => !s.is_primary);
233
234 const severity = mapLevelToSeverity(rd.level);
235 let primarySpanLabel = primarySpan.label;
236
237 const vd = new vscode.Diagnostic(location.range, rd.message, severity);
238
239 let source = 'rustc';
240 let code = rd.code && rd.code.code;
241 if (code) {
242 // See if this is an RFC #2103 scoped lint (e.g. from Clippy)
243 const scopedCode = code.split('::');
244 if (scopedCode.length === 2) {
245 [source, code] = scopedCode;
246 }
247 }
248
249 vd.source = source;
250 vd.code = code;
251 vd.relatedInformation = [];
252 vd.tags = [];
253
254 for (const secondarySpan of secondarySpans) {
255 const related = mapSecondarySpanToRelated(secondarySpan);
256 if (related) {
257 vd.relatedInformation.push(related);
258 }
259 }
260
261 const suggestedFixes = [];
262 for (const child of rd.children) {
263 const { related, suggestedFix, messageLine } = mapRustChildDiagnostic(
264 child,
265 );
266
267 if (related) {
268 vd.relatedInformation.push(related);
269 }
270 if (suggestedFix) {
271 suggestedFixes.push(suggestedFix);
272 }
273 if (messageLine) {
274 vd.message += `\n${messageLine}`;
275
276 // These secondary messages usually duplicate the content of the
277 // primary span label.
278 primarySpanLabel = undefined;
279 }
280 }
281
282 if (primarySpanLabel) {
283 vd.message += `\n${primarySpanLabel}`;
284 }
285
286 if (isUnusedOrUnnecessary(rd)) {
287 vd.tags.push(vscode.DiagnosticTag.Unnecessary);
288 }
289
290 if (isDeprecated(rd)) {
291 vd.tags.push(vscode.DiagnosticTag.Deprecated);
292 }
293
294 return {
295 location,
296 diagnostic: vd,
297 suggestedFixes,
298 };
299}
diff --git a/editors/code/src/utils/diagnostics/vscode.ts b/editors/code/src/utils/diagnostics/vscode.ts
deleted file mode 100644
index f4a5450e2..000000000
--- a/editors/code/src/utils/diagnostics/vscode.ts
+++ /dev/null
@@ -1,14 +0,0 @@
1import * as vscode from 'vscode';
2
3/** Compares two `vscode.Diagnostic`s for equality */
4export function areDiagnosticsEqual(
5 left: vscode.Diagnostic,
6 right: vscode.Diagnostic,
7): boolean {
8 return (
9 left.source === right.source &&
10 left.severity === right.severity &&
11 left.range.isEqual(right.range) &&
12 left.message === right.message
13 );
14}
diff --git a/editors/code/src/utils/processes.ts b/editors/code/src/utils/processes.ts
deleted file mode 100644
index a1d6b7eaf..000000000
--- a/editors/code/src/utils/processes.ts
+++ /dev/null
@@ -1,51 +0,0 @@
1'use strict';
2
3import * as cp from 'child_process';
4import ChildProcess = cp.ChildProcess;
5
6import { join } from 'path';
7
8const isWindows = process.platform === 'win32';
9const isMacintosh = process.platform === 'darwin';
10const isLinux = process.platform === 'linux';
11
12// this is very complex, but is basically copy-pased from VSCode implementation here:
13// https://github.com/Microsoft/vscode-languageserver-node/blob/dbfd37e35953ad0ee14c4eeced8cfbc41697b47e/client/src/utils/processes.ts#L15
14
15// And see discussion at
16// https://github.com/rust-analyzer/rust-analyzer/pull/1079#issuecomment-478908109
17
18export function terminate(process: ChildProcess, cwd?: string): boolean {
19 if (isWindows) {
20 try {
21 // This we run in Atom execFileSync is available.
22 // Ignore stderr since this is otherwise piped to parent.stderr
23 // which might be already closed.
24 const options: any = {
25 stdio: ['pipe', 'pipe', 'ignore'],
26 };
27 if (cwd) {
28 options.cwd = cwd;
29 }
30 cp.execFileSync(
31 'taskkill',
32 ['/T', '/F', '/PID', process.pid.toString()],
33 options,
34 );
35 return true;
36 } catch (err) {
37 return false;
38 }
39 } else if (isLinux || isMacintosh) {
40 try {
41 const cmd = join(__dirname, 'terminateProcess.sh');
42 const result = cp.spawnSync(cmd, [process.pid.toString()]);
43 return result.error ? false : true;
44 } catch (err) {
45 return false;
46 }
47 } else {
48 process.kill('SIGKILL');
49 return true;
50 }
51}
diff --git a/editors/code/src/utils/terminateProcess.sh b/editors/code/src/utils/terminateProcess.sh
deleted file mode 100644
index 2ec9e1c2e..000000000
--- a/editors/code/src/utils/terminateProcess.sh
+++ /dev/null
@@ -1,12 +0,0 @@
1#!/bin/bash
2
3terminateTree() {
4 for cpid in $(pgrep -P $1); do
5 terminateTree $cpid
6 done
7 kill -9 $1 > /dev/null 2>&1
8}
9
10for pid in $*; do
11 terminateTree $pid
12done \ No newline at end of file