aboutsummaryrefslogtreecommitdiff
path: root/editors
diff options
context:
space:
mode:
Diffstat (limited to 'editors')
-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
14 files changed, 0 insertions, 1684 deletions
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}