aboutsummaryrefslogtreecommitdiff
path: root/editors
diff options
context:
space:
mode:
Diffstat (limited to 'editors')
-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.ts161
-rw-r--r--editors/code/src/test/vscode_diagnostics.test.ts164
-rw-r--r--editors/code/src/utils/vscode_diagnostics.ts73
11 files changed, 765 insertions, 61 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..5eb064b97
--- /dev/null
+++ b/editors/code/src/test/rust_diagnostics.test.ts
@@ -0,0 +1,161 @@
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(
41 diagnostic.message,
42 [
43 `method \`next\` has an incompatible type for trait`,
44 `expected type \`fn(&mut ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&ty::Ref<M>>\``,
45 ` found type \`fn(&ty::list_iter::ListIterator<'list, M>) -> std::option::Option<&'list ty::Ref<M>>\``
46 ].join('\n')
47 );
48 assert.strictEqual(diagnostic.code, 'E0053');
49 assert.strictEqual(diagnostic.tags, undefined);
50
51 // No related information
52 assert.deepStrictEqual(diagnostic.relatedInformation, []);
53
54 // There are no code actions available
55 assert.strictEqual(codeActions.length, 0);
56 });
57
58 it('should map an unused variable warning', () => {
59 const { diagnostic, codeActions } = mapFixtureToVsCode(
60 'warning/unused_variables'
61 );
62
63 assert.strictEqual(
64 diagnostic.severity,
65 vscode.DiagnosticSeverity.Warning
66 );
67 assert.strictEqual(
68 diagnostic.message,
69 [
70 'unused variable: `foo`',
71 '#[warn(unused_variables)] on by default'
72 ].join('\n')
73 );
74 assert.strictEqual(diagnostic.code, 'unused_variables');
75 assert.deepStrictEqual(diagnostic.tags, [
76 vscode.DiagnosticTag.Unnecessary
77 ]);
78
79 // No related information
80 assert.deepStrictEqual(diagnostic.relatedInformation, []);
81
82 // One code action available to prefix the variable
83 assert.strictEqual(codeActions.length, 1);
84 const [codeAction] = codeActions;
85 assert.strictEqual(
86 codeAction.title,
87 'consider prefixing with an underscore: `_foo`'
88 );
89 assert(codeAction.isPreferred);
90 });
91
92 it('should map a wrong number of parameters error', () => {
93 const { diagnostic, codeActions } = mapFixtureToVsCode('error/E0061');
94
95 assert.strictEqual(
96 diagnostic.severity,
97 vscode.DiagnosticSeverity.Error
98 );
99 assert.strictEqual(
100 diagnostic.message,
101 'this function takes 2 parameters but 3 parameters were supplied'
102 );
103 assert.strictEqual(diagnostic.code, 'E0061');
104 assert.strictEqual(diagnostic.tags, undefined);
105
106 // One related information for the original definition
107 const relatedInformation = diagnostic.relatedInformation;
108 if (!relatedInformation) {
109 return assert.fail('Related information unexpectedly undefined');
110 }
111 assert.strictEqual(relatedInformation.length, 1);
112 const [related] = relatedInformation;
113 assert.strictEqual(related.message, 'defined here');
114
115 // There are no actions available
116 assert.strictEqual(codeActions.length, 0);
117 });
118
119 it('should map a Clippy copy pass by ref warning', () => {
120 const { diagnostic, codeActions } = mapFixtureToVsCode(
121 'clippy/trivially_copy_pass_by_ref'
122 );
123
124 assert.strictEqual(
125 diagnostic.severity,
126 vscode.DiagnosticSeverity.Warning
127 );
128 assert.strictEqual(
129 diagnostic.message,
130 [
131 'this argument is passed by reference, but would be more efficient if passed by value',
132 '#[warn(clippy::trivially_copy_pass_by_ref)] implied by #[warn(clippy::all)]',
133 'for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref'
134 ].join('\n')
135 );
136 assert.strictEqual(
137 diagnostic.code,
138 'clippy::trivially_copy_pass_by_ref'
139 );
140 assert.strictEqual(diagnostic.tags, undefined);
141
142 // One related information for the lint definition
143 const relatedInformation = diagnostic.relatedInformation;
144 if (!relatedInformation) {
145 return assert.fail('Related information unexpectedly undefined');
146 }
147 assert.strictEqual(relatedInformation.length, 1);
148 const [related] = relatedInformation;
149 assert.strictEqual(related.message, 'lint level defined here');
150
151 // One code action available to pass by value
152 assert.strictEqual(codeActions.length, 1);
153 const [codeAction] = codeActions;
154 assert.strictEqual(
155 codeAction.title,
156 'consider passing by value instead: `self`'
157 );
158 // Clippy does not mark this as machine applicable
159 assert.strictEqual(codeAction.isPreferred, false);
160 });
161});
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..ca4345626
--- /dev/null
+++ b/editors/code/src/test/vscode_diagnostics.test.ts
@@ -0,0 +1,164 @@
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 ranges as inequal', () => {
39 const diagnostic1 = new vscode.Diagnostic(
40 range1,
41 'Hello, world!',
42 vscode.DiagnosticSeverity.Error
43 );
44
45 const diagnostic2 = new vscode.Diagnostic(
46 range2,
47 'Hello, world!',
48 vscode.DiagnosticSeverity.Error
49 );
50
51 assert(!areDiagnosticsEqual(diagnostic1, diagnostic2));
52 });
53
54 it('should treat diagnostics with different messages as inequal', () => {
55 const diagnostic1 = new vscode.Diagnostic(
56 range1,
57 'Hello, world!',
58 vscode.DiagnosticSeverity.Error
59 );
60
61 const diagnostic2 = new vscode.Diagnostic(
62 range1,
63 'Goodbye!, world!',
64 vscode.DiagnosticSeverity.Error
65 );
66
67 assert(!areDiagnosticsEqual(diagnostic1, diagnostic2));
68 });
69
70 it('should treat diagnostics with different severities as inequal', () => {
71 const diagnostic1 = new vscode.Diagnostic(
72 range1,
73 'Hello, world!',
74 vscode.DiagnosticSeverity.Warning
75 );
76
77 const diagnostic2 = new vscode.Diagnostic(
78 range1,
79 'Hello, world!',
80 vscode.DiagnosticSeverity.Error
81 );
82
83 assert(!areDiagnosticsEqual(diagnostic1, diagnostic2));
84 });
85});
86
87describe('areCodeActionsEqual', () => {
88 it('should treat identical actions as equal', () => {
89 const codeAction1 = new vscode.CodeAction(
90 'Fix me!',
91 vscode.CodeActionKind.QuickFix
92 );
93
94 const codeAction2 = new vscode.CodeAction(
95 'Fix me!',
96 vscode.CodeActionKind.QuickFix
97 );
98
99 const edit = new vscode.WorkspaceEdit();
100 edit.replace(uri, range1, 'Replace with this');
101 codeAction1.edit = edit;
102 codeAction2.edit = edit;
103
104 assert(areCodeActionsEqual(codeAction1, codeAction2));
105 });
106
107 it('should treat actions with different types as inequal', () => {
108 const codeAction1 = new vscode.CodeAction(
109 'Fix me!',
110 vscode.CodeActionKind.Refactor
111 );
112
113 const codeAction2 = new vscode.CodeAction(
114 'Fix me!',
115 vscode.CodeActionKind.QuickFix
116 );
117
118 const edit = new vscode.WorkspaceEdit();
119 edit.replace(uri, range1, 'Replace with this');
120 codeAction1.edit = edit;
121 codeAction2.edit = edit;
122
123 assert(!areCodeActionsEqual(codeAction1, codeAction2));
124 });
125
126 it('should treat actions with different titles as inequal', () => {
127 const codeAction1 = new vscode.CodeAction(
128 'Fix me!',
129 vscode.CodeActionKind.Refactor
130 );
131
132 const codeAction2 = new vscode.CodeAction(
133 'Do something different!',
134 vscode.CodeActionKind.Refactor
135 );
136
137 const edit = new vscode.WorkspaceEdit();
138 edit.replace(uri, range1, 'Replace with this');
139 codeAction1.edit = edit;
140 codeAction2.edit = edit;
141
142 assert(!areCodeActionsEqual(codeAction1, codeAction2));
143 });
144
145 it('should treat actions with different edits as inequal', () => {
146 const codeAction1 = new vscode.CodeAction(
147 'Fix me!',
148 vscode.CodeActionKind.Refactor
149 );
150 const edit1 = new vscode.WorkspaceEdit();
151 edit1.replace(uri, range1, 'Replace with this');
152 codeAction1.edit = edit1;
153
154 const codeAction2 = new vscode.CodeAction(
155 'Fix me!',
156 vscode.CodeActionKind.Refactor
157 );
158 const edit2 = new vscode.WorkspaceEdit();
159 edit2.replace(uri, range1, 'Replace with this other thing');
160 codeAction2.edit = edit2;
161
162 assert(!areCodeActionsEqual(codeAction1, codeAction2));
163 });
164});
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}