aboutsummaryrefslogtreecommitdiff
path: root/editors/code
diff options
context:
space:
mode:
Diffstat (limited to 'editors/code')
-rw-r--r--editors/code/.vscode/launch.json1
-rw-r--r--editors/code/package.json1
-rw-r--r--editors/code/src/commands/cargo_watch.ts66
-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/warning/unused_variables.json72
-rw-r--r--editors/code/src/test/index.ts22
-rw-r--r--editors/code/src/test/rust_diagnostics.test.ts162
-rw-r--r--editors/code/src/test/vscode_diagnostics.test.ts182
-rw-r--r--editors/code/src/utils/rust_diagnostics.ts14
-rw-r--r--editors/code/src/utils/vscode_diagnostics.ts73
12 files changed, 796 insertions, 63 deletions
diff --git a/editors/code/.vscode/launch.json b/editors/code/.vscode/launch.json
index b9d14dddd..c3578f476 100644
--- a/editors/code/.vscode/launch.json
+++ b/editors/code/.vscode/launch.json
@@ -20,6 +20,7 @@
20 "request": "launch", 20 "request": "launch",
21 "runtimeExecutable": "${execPath}", 21 "runtimeExecutable": "${execPath}",
22 "args": [ 22 "args": [
23 "${workspaceFolder}/src/test/",
23 "--extensionDevelopmentPath=${workspaceFolder}", 24 "--extensionDevelopmentPath=${workspaceFolder}",
24 "--extensionTestsPath=${workspaceFolder}/out/test" 25 "--extensionTestsPath=${workspaceFolder}/out/test"
25 ], 26 ],
diff --git a/editors/code/package.json b/editors/code/package.json
index ac2ba82e3..6e2dd0494 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -23,6 +23,7 @@
23 "postinstall": "node ./node_modules/vscode/bin/install", 23 "postinstall": "node ./node_modules/vscode/bin/install",
24 "fix": "prettier **/*.{json,ts} --write && tslint --project . --fix", 24 "fix": "prettier **/*.{json,ts} --write && tslint --project . --fix",
25 "lint": "tslint --project .", 25 "lint": "tslint --project .",
26 "test": "node node_modules/vscode/bin/test",
26 "prettier": "prettier **/*.{json,ts}", 27 "prettier": "prettier **/*.{json,ts}",
27 "travis": "npm run compile && npm run lint && npm run prettier -- --list-different" 28 "travis": "npm run compile && npm run lint && npm run prettier -- --list-different"
28 }, 29 },
diff --git a/editors/code/src/commands/cargo_watch.ts b/editors/code/src/commands/cargo_watch.ts
index 126a8b1b3..1ec5f8d5f 100644
--- a/editors/code/src/commands/cargo_watch.ts
+++ b/editors/code/src/commands/cargo_watch.ts
@@ -2,12 +2,17 @@ import * as child_process from 'child_process';
2import * as fs from 'fs'; 2import * as fs from 'fs';
3import * as path from 'path'; 3import * as path from 'path';
4import * as vscode from 'vscode'; 4import * as vscode from 'vscode';
5
5import { Server } from '../server'; 6import { Server } from '../server';
6import { terminate } from '../utils/processes'; 7import { terminate } from '../utils/processes';
7import { 8import {
8 mapRustDiagnosticToVsCode, 9 mapRustDiagnosticToVsCode,
9 RustDiagnostic 10 RustDiagnostic
10} from '../utils/rust_diagnostics'; 11} from '../utils/rust_diagnostics';
12import {
13 areCodeActionsEqual,
14 areDiagnosticsEqual
15} from '../utils/vscode_diagnostics';
11import { LineBuffer } from './line_buffer'; 16import { LineBuffer } from './line_buffer';
12import { StatusDisplay } from './watch_status'; 17import { StatusDisplay } from './watch_status';
13 18
@@ -184,67 +189,6 @@ export class CargoWatchProvider
184 this.statusDisplay.hide(); 189 this.statusDisplay.hide();
185 } 190 }
186 191
187 function areDiagnosticsEqual(
188 left: vscode.Diagnostic,
189 right: vscode.Diagnostic
190 ): boolean {
191 return (
192 left.source === right.source &&
193 left.severity === right.severity &&
194 left.range.isEqual(right.range) &&
195 left.message === right.message
196 );
197 }
198
199 function areCodeActionsEqual(
200 left: vscode.CodeAction,
201 right: vscode.CodeAction
202 ): boolean {
203 if (
204 left.kind !== right.kind ||
205 left.title !== right.title ||
206 !left.edit ||
207 !right.edit
208 ) {
209 return false;
210 }
211
212 const leftEditEntries = left.edit.entries();
213 const rightEditEntries = right.edit.entries();
214
215 if (leftEditEntries.length !== rightEditEntries.length) {
216 return false;
217 }
218
219 for (let i = 0; i < leftEditEntries.length; i++) {
220 const [leftUri, leftEdits] = leftEditEntries[i];
221 const [rightUri, rightEdits] = rightEditEntries[i];
222
223 if (leftUri.toString() !== rightUri.toString()) {
224 return false;
225 }
226
227 if (leftEdits.length !== rightEdits.length) {
228 return false;
229 }
230
231 for (let j = 0; j < leftEdits.length; j++) {
232 const leftEdit = leftEdits[j];
233 const rightEdit = rightEdits[j];
234
235 if (!leftEdit.range.isEqual(rightEdit.range)) {
236 return false;
237 }
238
239 if (leftEdit.newText !== rightEdit.newText) {
240 return false;
241 }
242 }
243 }
244
245 return true;
246 }
247
248 interface CargoArtifact { 192 interface CargoArtifact {
249 reason: string; 193 reason: string;
250 package_id: string; 194 package_id: string;
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
new file mode 100644
index 000000000..d874e99bc
--- /dev/null
+++ b/editors/code/src/test/fixtures/rust-diagnostics/clippy/trivially_copy_pass_by_ref.json
@@ -0,0 +1,110 @@
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
new file mode 100644
index 000000000..ea5c976d1
--- /dev/null
+++ b/editors/code/src/test/fixtures/rust-diagnostics/error/E0053.json
@@ -0,0 +1,42 @@
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
new file mode 100644
index 000000000..3154d1098
--- /dev/null
+++ b/editors/code/src/test/fixtures/rust-diagnostics/error/E0061.json
@@ -0,0 +1,114 @@
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/warning/unused_variables.json b/editors/code/src/test/fixtures/rust-diagnostics/warning/unused_variables.json
new file mode 100644
index 000000000..d1e2be722
--- /dev/null
+++ b/editors/code/src/test/fixtures/rust-diagnostics/warning/unused_variables.json
@@ -0,0 +1,72 @@
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/index.ts b/editors/code/src/test/index.ts
new file mode 100644
index 000000000..6e565c254
--- /dev/null
+++ b/editors/code/src/test/index.ts
@@ -0,0 +1,22 @@
1//
2// PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING
3//
4// This file is providing the test runner to use when running extension tests.
5// By default the test runner in use is Mocha based.
6//
7// You can provide your own test runner if you want to override it by exporting
8// a function run(testRoot: string, clb: (error:Error) => void) that the extension
9// host can call to run the tests. The test runner is expected to use console.log
10// to report the results back to the caller. When the tests are finished, return
11// a possible error to the callback or null if none.
12
13import * as testRunner from 'vscode/lib/testrunner';
14
15// You can directly control Mocha options by uncommenting the following lines
16// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info
17testRunner.configure({
18 ui: 'bdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.)
19 useColors: true // colored output from test results
20});
21
22module.exports = testRunner;
diff --git a/editors/code/src/test/rust_diagnostics.test.ts b/editors/code/src/test/rust_diagnostics.test.ts
new file mode 100644
index 000000000..f27c58fe2
--- /dev/null
+++ b/editors/code/src/test/rust_diagnostics.test.ts
@@ -0,0 +1,162 @@
1import * as assert from 'assert';
2import * as fs from 'fs';
3import * as vscode from 'vscode';
4
5import {
6 MappedRustDiagnostic,
7 mapRustDiagnosticToVsCode,
8 RustDiagnostic
9} from '../utils/rust_diagnostics';
10
11function loadDiagnosticFixture(name: string): RustDiagnostic {
12 const jsonText = fs
13 .readFileSync(
14 // We're actually in our JavaScript output directory, climb out
15 `${__dirname}/../../src/test/fixtures/rust-diagnostics/${name}.json`
16 )
17 .toString();
18
19 return JSON.parse(jsonText);
20}
21
22function mapFixtureToVsCode(name: string): MappedRustDiagnostic {
23 const rd = loadDiagnosticFixture(name);
24 const mapResult = mapRustDiagnosticToVsCode(rd);
25
26 if (!mapResult) {
27 return assert.fail('Mapping unexpectedly failed');
28 }
29 return mapResult;
30}
31
32describe('mapRustDiagnosticToVsCode', () => {
33 it('should map an incompatible type for trait error', () => {
34 const { diagnostic, codeActions } = mapFixtureToVsCode('error/E0053');
35
36 assert.strictEqual(
37 diagnostic.severity,
38 vscode.DiagnosticSeverity.Error
39 );
40 assert.strictEqual(diagnostic.source, 'rustc');
41 assert.strictEqual(
42 diagnostic.message,
43 [
44 `method \`next\` has an incompatible type for trait`,
45 `expected type \`fn(&mut ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&ty::Ref<M>>\``,
46 ` found type \`fn(&ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&'list ty::Ref<M>>\``
47 ].join('\n')
48 );
49 assert.strictEqual(diagnostic.code, 'E0053');
50 assert.strictEqual(diagnostic.tags, undefined);
51
52 // No related information
53 assert.deepStrictEqual(diagnostic.relatedInformation, []);
54
55 // There are no code actions available
56 assert.strictEqual(codeActions.length, 0);
57 });
58
59 it('should map an unused variable warning', () => {
60 const { diagnostic, codeActions } = mapFixtureToVsCode(
61 'warning/unused_variables'
62 );
63
64 assert.strictEqual(
65 diagnostic.severity,
66 vscode.DiagnosticSeverity.Warning
67 );
68 assert.strictEqual(
69 diagnostic.message,
70 [
71 'unused variable: `foo`',
72 '#[warn(unused_variables)] on by default'
73 ].join('\n')
74 );
75 assert.strictEqual(diagnostic.code, 'unused_variables');
76 assert.strictEqual(diagnostic.source, 'rustc');
77 assert.deepStrictEqual(diagnostic.tags, [
78 vscode.DiagnosticTag.Unnecessary
79 ]);
80
81 // No related information
82 assert.deepStrictEqual(diagnostic.relatedInformation, []);
83
84 // One code action available to prefix the variable
85 assert.strictEqual(codeActions.length, 1);
86 const [codeAction] = codeActions;
87 assert.strictEqual(
88 codeAction.title,
89 'consider prefixing with an underscore: `_foo`'
90 );
91 assert(codeAction.isPreferred);
92 });
93
94 it('should map a wrong number of parameters error', () => {
95 const { diagnostic, codeActions } = mapFixtureToVsCode('error/E0061');
96
97 assert.strictEqual(
98 diagnostic.severity,
99 vscode.DiagnosticSeverity.Error
100 );
101 assert.strictEqual(
102 diagnostic.message,
103 'this function takes 2 parameters but 3 parameters were supplied'
104 );
105 assert.strictEqual(diagnostic.code, 'E0061');
106 assert.strictEqual(diagnostic.source, 'rustc');
107 assert.strictEqual(diagnostic.tags, undefined);
108
109 // One related information for the original definition
110 const relatedInformation = diagnostic.relatedInformation;
111 if (!relatedInformation) {
112 return assert.fail('Related information unexpectedly undefined');
113 }
114 assert.strictEqual(relatedInformation.length, 1);
115 const [related] = relatedInformation;
116 assert.strictEqual(related.message, 'defined here');
117
118 // There are no actions available
119 assert.strictEqual(codeActions.length, 0);
120 });
121
122 it('should map a Clippy copy pass by ref warning', () => {
123 const { diagnostic, codeActions } = mapFixtureToVsCode(
124 'clippy/trivially_copy_pass_by_ref'
125 );
126
127 assert.strictEqual(
128 diagnostic.severity,
129 vscode.DiagnosticSeverity.Warning
130 );
131 assert.strictEqual(diagnostic.source, 'clippy');
132 assert.strictEqual(
133 diagnostic.message,
134 [
135 'this argument is passed by reference, but would be more efficient if passed by value',
136 '#[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]',
137 'for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref'
138 ].join('\n')
139 );
140 assert.strictEqual(diagnostic.code, 'trivially_copy_pass_by_ref');
141 assert.strictEqual(diagnostic.tags, undefined);
142
143 // One related information for the lint definition
144 const relatedInformation = diagnostic.relatedInformation;
145 if (!relatedInformation) {
146 return assert.fail('Related information unexpectedly undefined');
147 }
148 assert.strictEqual(relatedInformation.length, 1);
149 const [related] = relatedInformation;
150 assert.strictEqual(related.message, 'lint level defined here');
151
152 // One code action available to pass by value
153 assert.strictEqual(codeActions.length, 1);
154 const [codeAction] = codeActions;
155 assert.strictEqual(
156 codeAction.title,
157 'consider passing by value instead: `self`'
158 );
159 // Clippy does not mark this as machine applicable
160 assert.strictEqual(codeAction.isPreferred, false);
161 });
162});
diff --git a/editors/code/src/test/vscode_diagnostics.test.ts b/editors/code/src/test/vscode_diagnostics.test.ts
new file mode 100644
index 000000000..9c5d812fa
--- /dev/null
+++ b/editors/code/src/test/vscode_diagnostics.test.ts
@@ -0,0 +1,182 @@
1import * as assert from 'assert';
2import * as vscode from 'vscode';
3
4import {
5 areCodeActionsEqual,
6 areDiagnosticsEqual
7} from '../utils/vscode_diagnostics';
8
9const uri = vscode.Uri.file('/file/1');
10
11const range1 = new vscode.Range(
12 new vscode.Position(1, 2),
13 new vscode.Position(3, 4)
14);
15
16const range2 = new vscode.Range(
17 new vscode.Position(5, 6),
18 new vscode.Position(7, 8)
19);
20
21describe('areDiagnosticsEqual', () => {
22 it('should treat identical diagnostics as equal', () => {
23 const diagnostic1 = new vscode.Diagnostic(
24 range1,
25 'Hello, world!',
26 vscode.DiagnosticSeverity.Error
27 );
28
29 const diagnostic2 = new vscode.Diagnostic(
30 range1,
31 'Hello, world!',
32 vscode.DiagnosticSeverity.Error
33 );
34
35 assert(areDiagnosticsEqual(diagnostic1, diagnostic2));
36 });
37
38 it('should treat diagnostics with different sources as inequal', () => {
39 const diagnostic1 = new vscode.Diagnostic(
40 range1,
41 'Hello, world!',
42 vscode.DiagnosticSeverity.Error
43 );
44 diagnostic1.source = 'rustc';
45
46 const diagnostic2 = new vscode.Diagnostic(
47 range1,
48 'Hello, world!',
49 vscode.DiagnosticSeverity.Error
50 );
51 diagnostic2.source = 'clippy';
52
53 assert(!areDiagnosticsEqual(diagnostic1, diagnostic2));
54 });
55
56 it('should treat diagnostics with different ranges as inequal', () => {
57 const diagnostic1 = new vscode.Diagnostic(
58 range1,
59 'Hello, world!',
60 vscode.DiagnosticSeverity.Error
61 );
62
63 const diagnostic2 = new vscode.Diagnostic(
64 range2,
65 'Hello, world!',
66 vscode.DiagnosticSeverity.Error
67 );
68
69 assert(!areDiagnosticsEqual(diagnostic1, diagnostic2));
70 });
71
72 it('should treat diagnostics with different messages as inequal', () => {
73 const diagnostic1 = new vscode.Diagnostic(
74 range1,
75 'Hello, world!',
76 vscode.DiagnosticSeverity.Error
77 );
78
79 const diagnostic2 = new vscode.Diagnostic(
80 range1,
81 'Goodbye!, world!',
82 vscode.DiagnosticSeverity.Error
83 );
84
85 assert(!areDiagnosticsEqual(diagnostic1, diagnostic2));
86 });
87
88 it('should treat diagnostics with different severities as inequal', () => {
89 const diagnostic1 = new vscode.Diagnostic(
90 range1,
91 'Hello, world!',
92 vscode.DiagnosticSeverity.Warning
93 );
94
95 const diagnostic2 = new vscode.Diagnostic(
96 range1,
97 'Hello, world!',
98 vscode.DiagnosticSeverity.Error
99 );
100
101 assert(!areDiagnosticsEqual(diagnostic1, diagnostic2));
102 });
103});
104
105describe('areCodeActionsEqual', () => {
106 it('should treat identical actions as equal', () => {
107 const codeAction1 = new vscode.CodeAction(
108 'Fix me!',
109 vscode.CodeActionKind.QuickFix
110 );
111
112 const codeAction2 = new vscode.CodeAction(
113 'Fix me!',
114 vscode.CodeActionKind.QuickFix
115 );
116
117 const edit = new vscode.WorkspaceEdit();
118 edit.replace(uri, range1, 'Replace with this');
119 codeAction1.edit = edit;
120 codeAction2.edit = edit;
121
122 assert(areCodeActionsEqual(codeAction1, codeAction2));
123 });
124
125 it('should treat actions with different types as inequal', () => {
126 const codeAction1 = new vscode.CodeAction(
127 'Fix me!',
128 vscode.CodeActionKind.Refactor
129 );
130
131 const codeAction2 = new vscode.CodeAction(
132 'Fix me!',
133 vscode.CodeActionKind.QuickFix
134 );
135
136 const edit = new vscode.WorkspaceEdit();
137 edit.replace(uri, range1, 'Replace with this');
138 codeAction1.edit = edit;
139 codeAction2.edit = edit;
140
141 assert(!areCodeActionsEqual(codeAction1, codeAction2));
142 });
143
144 it('should treat actions with different titles as inequal', () => {
145 const codeAction1 = new vscode.CodeAction(
146 'Fix me!',
147 vscode.CodeActionKind.Refactor
148 );
149
150 const codeAction2 = new vscode.CodeAction(
151 'Do something different!',
152 vscode.CodeActionKind.Refactor
153 );
154
155 const edit = new vscode.WorkspaceEdit();
156 edit.replace(uri, range1, 'Replace with this');
157 codeAction1.edit = edit;
158 codeAction2.edit = edit;
159
160 assert(!areCodeActionsEqual(codeAction1, codeAction2));
161 });
162
163 it('should treat actions with different edits as inequal', () => {
164 const codeAction1 = new vscode.CodeAction(
165 'Fix me!',
166 vscode.CodeActionKind.Refactor
167 );
168 const edit1 = new vscode.WorkspaceEdit();
169 edit1.replace(uri, range1, 'Replace with this');
170 codeAction1.edit = edit1;
171
172 const codeAction2 = new vscode.CodeAction(
173 'Fix me!',
174 vscode.CodeActionKind.Refactor
175 );
176 const edit2 = new vscode.WorkspaceEdit();
177 edit2.replace(uri, range1, 'Replace with this other thing');
178 codeAction2.edit = edit2;
179
180 assert(!areCodeActionsEqual(codeAction1, codeAction2));
181 });
182});
diff --git a/editors/code/src/utils/rust_diagnostics.ts b/editors/code/src/utils/rust_diagnostics.ts
index ed049c95e..3c524cb37 100644
--- a/editors/code/src/utils/rust_diagnostics.ts
+++ b/editors/code/src/utils/rust_diagnostics.ts
@@ -187,8 +187,18 @@ export function mapRustDiagnosticToVsCode(
187 187
188 const vd = new vscode.Diagnostic(location.range, rd.message, severity); 188 const vd = new vscode.Diagnostic(location.range, rd.message, severity);
189 189
190 vd.source = 'rustc'; 190 let source = 'rustc';
191 vd.code = rd.code ? rd.code.code : undefined; 191 let code = rd.code && rd.code.code;
192 if (code) {
193 // See if this is an RFC #2103 scoped lint (e.g. from Clippy)
194 const scopedCode = code.split('::');
195 if (scopedCode.length === 2) {
196 [source, code] = scopedCode;
197 }
198 }
199
200 vd.source = source;
201 vd.code = code;
192 vd.relatedInformation = []; 202 vd.relatedInformation = [];
193 203
194 for (const secondarySpan of secondarySpans) { 204 for (const secondarySpan of secondarySpans) {
diff --git a/editors/code/src/utils/vscode_diagnostics.ts b/editors/code/src/utils/vscode_diagnostics.ts
new file mode 100644
index 000000000..9d763c8d6
--- /dev/null
+++ b/editors/code/src/utils/vscode_diagnostics.ts
@@ -0,0 +1,73 @@
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}
15
16/** Compares two `vscode.TextEdit`s for equality */
17function areTextEditsEqual(
18 left: vscode.TextEdit,
19 right: vscode.TextEdit
20): boolean {
21 if (!left.range.isEqual(right.range)) {
22 return false;
23 }
24
25 if (left.newText !== right.newText) {
26 return false;
27 }
28
29 return true;
30}
31
32/** Compares two `vscode.CodeAction`s for equality */
33export function areCodeActionsEqual(
34 left: vscode.CodeAction,
35 right: vscode.CodeAction
36): boolean {
37 if (
38 left.kind !== right.kind ||
39 left.title !== right.title ||
40 !left.edit ||
41 !right.edit
42 ) {
43 return false;
44 }
45
46 const leftEditEntries = left.edit.entries();
47 const rightEditEntries = right.edit.entries();
48
49 if (leftEditEntries.length !== rightEditEntries.length) {
50 return false;
51 }
52
53 for (let i = 0; i < leftEditEntries.length; i++) {
54 const [leftUri, leftEdits] = leftEditEntries[i];
55 const [rightUri, rightEdits] = rightEditEntries[i];
56
57 if (leftUri.toString() !== rightUri.toString()) {
58 return false;
59 }
60
61 if (leftEdits.length !== rightEdits.length) {
62 return false;
63 }
64
65 for (let j = 0; j < leftEdits.length; j++) {
66 if (!areTextEditsEqual(leftEdits[j], rightEdits[j])) {
67 return false;
68 }
69 }
70 }
71
72 return true;
73}