aboutsummaryrefslogtreecommitdiff
path: root/editors/code/src/utils/rust_diagnostics.ts
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2019-06-25 13:37:07 +0100
committerbors[bot] <26634292+bors[bot]@users.noreply.github.com>2019-06-25 13:37:07 +0100
commit4b0c37bd6e4cb3d47614ec6b42fb1deef9bc9324 (patch)
tree31ac3e97ade900dd51bc39c007316aa20a2e85fc /editors/code/src/utils/rust_diagnostics.ts
parentba97a5fbd2e14f38c633948f0d1551d0cf086ca3 (diff)
parent5c6ab1145319414e897a8eaca2bf1ad5558ccf24 (diff)
Merge #1439
1439: Rich mapping of cargo watch output r=matklad a=etaoins Currently we depend on the ASCII rendering string that `rustc` provides to populate Visual Studio Code's diagnostic. This has a number of shortcomings: 1. It's not a very good use of space in the error list 2. We can't jump to secondary spans (e.g. where a called function is defined) 3. We can't use Code Actions aka Quick Fix This moves all of the low-level parsing and mapping to a `rust_diagnostics.ts`. This uses some heuristics to map Rust diagnostics to VsCode: 1. As before, the Rust diagnostic message and primary span is used for the root diagnostic. However, we now just use the message instead of the rendered version. 2. Every secondary span is converted to "related information". This shows as child in the error list and can be jumped to. 3. Every child diagnostic is categorised in to three buckets: 1. If they have no span they're treated as another line of the root messages 2. If they have replacement text they're treated as a Code Action 3. If they have a span but no replacement text they're treated as related information (same as secondary spans). Co-authored-by: Ryan Cumming <[email protected]>
Diffstat (limited to 'editors/code/src/utils/rust_diagnostics.ts')
-rw-r--r--editors/code/src/utils/rust_diagnostics.ts226
1 files changed, 226 insertions, 0 deletions
diff --git a/editors/code/src/utils/rust_diagnostics.ts b/editors/code/src/utils/rust_diagnostics.ts
new file mode 100644
index 000000000..ed049c95e
--- /dev/null
+++ b/editors/code/src/utils/rust_diagnostics.ts
@@ -0,0 +1,226 @@
1import * as path from 'path';
2import * as vscode from 'vscode';
3
4// Reference:
5// https://github.com/rust-lang/rust/blob/master/src/libsyntax/json.rs
6export interface RustDiagnosticSpan {
7 line_start: number;
8 line_end: number;
9 column_start: number;
10 column_end: number;
11 is_primary: boolean;
12 file_name: string;
13 label?: string;
14 suggested_replacement?: string;
15 suggestion_applicability?:
16 | 'MachineApplicable'
17 | 'HasPlaceholders'
18 | 'MaybeIncorrect'
19 | 'Unspecified';
20}
21
22export interface RustDiagnostic {
23 spans: RustDiagnosticSpan[];
24 rendered: string;
25 message: string;
26 level: string;
27 code?: {
28 code: string;
29 };
30 children: RustDiagnostic[];
31}
32
33export interface MappedRustDiagnostic {
34 location: vscode.Location;
35 diagnostic: vscode.Diagnostic;
36 codeActions: vscode.CodeAction[];
37}
38
39interface MappedRustChildDiagnostic {
40 related?: vscode.DiagnosticRelatedInformation;
41 codeAction?: vscode.CodeAction;
42 messageLine?: string;
43}
44
45/**
46 * Converts a Rust level string to a VsCode severity
47 */
48function mapLevelToSeverity(s: string): vscode.DiagnosticSeverity {
49 if (s === 'error') {
50 return vscode.DiagnosticSeverity.Error;
51 }
52 if (s.startsWith('warn')) {
53 return vscode.DiagnosticSeverity.Warning;
54 }
55 return vscode.DiagnosticSeverity.Information;
56}
57
58/**
59 * Converts a Rust span to a VsCode location
60 */
61function mapSpanToLocation(span: RustDiagnosticSpan): vscode.Location {
62 const fileName = path.join(vscode.workspace.rootPath!, span.file_name);
63 const fileUri = vscode.Uri.file(fileName);
64
65 const range = new vscode.Range(
66 new vscode.Position(span.line_start - 1, span.column_start - 1),
67 new vscode.Position(span.line_end - 1, span.column_end - 1)
68 );
69
70 return new vscode.Location(fileUri, range);
71}
72
73/**
74 * Converts a secondary Rust span to a VsCode related information
75 *
76 * If the span is unlabelled this will return `undefined`.
77 */
78function mapSecondarySpanToRelated(
79 span: RustDiagnosticSpan
80): vscode.DiagnosticRelatedInformation | undefined {
81 if (!span.label) {
82 // Nothing to label this with
83 return;
84 }
85
86 const location = mapSpanToLocation(span);
87 return new vscode.DiagnosticRelatedInformation(location, span.label);
88}
89
90/**
91 * Determines if diagnostic is related to unused code
92 */
93function isUnusedOrUnnecessary(rd: RustDiagnostic): boolean {
94 if (!rd.code) {
95 return false;
96 }
97
98 return [
99 'dead_code',
100 'unknown_lints',
101 'unused_attributes',
102 'unused_imports',
103 'unused_macros',
104 'unused_variables'
105 ].includes(rd.code.code);
106}
107
108/**
109 * Converts a Rust child diagnostic to a VsCode related information
110 *
111 * This can have three outcomes:
112 *
113 * 1. If this is no primary span this will return a `noteLine`
114 * 2. If there is a primary span with a suggested replacement it will return a
115 * `codeAction`.
116 * 3. If there is a primary span without a suggested replacement it will return
117 * a `related`.
118 */
119function mapRustChildDiagnostic(rd: RustDiagnostic): MappedRustChildDiagnostic {
120 const span = rd.spans.find(s => s.is_primary);
121
122 if (!span) {
123 // `rustc` uses these spanless children as a way to print multi-line
124 // messages
125 return { messageLine: rd.message };
126 }
127
128 // If we have a primary span use its location, otherwise use the parent
129 const location = mapSpanToLocation(span);
130
131 // We need to distinguish `null` from an empty string
132 if (span && typeof span.suggested_replacement === 'string') {
133 const edit = new vscode.WorkspaceEdit();
134 edit.replace(location.uri, location.range, span.suggested_replacement);
135
136 // Include our replacement in the label unless it's empty
137 const title = span.suggested_replacement
138 ? `${rd.message}: \`${span.suggested_replacement}\``
139 : rd.message;
140
141 const codeAction = new vscode.CodeAction(
142 title,
143 vscode.CodeActionKind.QuickFix
144 );
145
146 codeAction.edit = edit;
147 codeAction.isPreferred =
148 span.suggestion_applicability === 'MachineApplicable';
149
150 return { codeAction };
151 } else {
152 const related = new vscode.DiagnosticRelatedInformation(
153 location,
154 rd.message
155 );
156
157 return { related };
158 }
159}
160
161/**
162 * Converts a Rust root diagnostic to VsCode form
163 *
164 * This flattens the Rust diagnostic by:
165 *
166 * 1. Creating a `vscode.Diagnostic` with the root message and primary span.
167 * 2. Adding any labelled secondary spans to `relatedInformation`
168 * 3. Categorising child diagnostics as either Quick Fix actions,
169 * `relatedInformation` or additional message lines.
170 *
171 * If the diagnostic has no primary span this will return `undefined`
172 */
173export function mapRustDiagnosticToVsCode(
174 rd: RustDiagnostic
175): MappedRustDiagnostic | undefined {
176 const codeActions = [];
177
178 const primarySpan = rd.spans.find(s => s.is_primary);
179 if (!primarySpan) {
180 return;
181 }
182
183 const location = mapSpanToLocation(primarySpan);
184 const secondarySpans = rd.spans.filter(s => !s.is_primary);
185
186 const severity = mapLevelToSeverity(rd.level);
187
188 const vd = new vscode.Diagnostic(location.range, rd.message, severity);
189
190 vd.source = 'rustc';
191 vd.code = rd.code ? rd.code.code : undefined;
192 vd.relatedInformation = [];
193
194 for (const secondarySpan of secondarySpans) {
195 const related = mapSecondarySpanToRelated(secondarySpan);
196 if (related) {
197 vd.relatedInformation.push(related);
198 }
199 }
200
201 for (const child of rd.children) {
202 const { related, codeAction, messageLine } = mapRustChildDiagnostic(
203 child
204 );
205
206 if (related) {
207 vd.relatedInformation.push(related);
208 }
209 if (codeAction) {
210 codeActions.push(codeAction);
211 }
212 if (messageLine) {
213 vd.message += `\n${messageLine}`;
214 }
215 }
216
217 if (isUnusedOrUnnecessary(rd)) {
218 vd.tags = [vscode.DiagnosticTag.Unnecessary];
219 }
220
221 return {
222 location,
223 diagnostic: vd,
224 codeActions
225 };
226}