Skip to content

Commit 0d67749

Browse files
authored
Remove stale swiftc diagnostics (#860)
* Remove stale swiftc diagnostics Issue: #750 If the user modifies source and SourceKit diagnostics differ, they may want to cleanup all the old swiftc ones. This will be turned on by default. The swiftc must match with a former SourceKit one, and once SourceKit returns a new list of diagnostics, if that matched diagnostic is no longer there, assume it is fix and remove the matching swiftc error Tie this to a setting so the user can turn off. Keep track of all current diagnostics regardless of source to allow for this cleanup, and has the added benefit that we can update the list when the diagnosticsCollection setting changes. * Remove the setting to keep stale diagnostics * Fix review comments
1 parent 7aa6735 commit 0d67749

File tree

4 files changed

+193
-101
lines changed

4 files changed

+193
-101
lines changed

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,11 @@
169169
"command": "swift.attachDebugger",
170170
"title": "Attach to Process...",
171171
"category": "Swift"
172+
},
173+
{
174+
"command": "swift.clearDiagnosticsCollection",
175+
"title": "Clear Diagnostics Collection",
176+
"category": "Swift"
172177
}
173178
],
174179
"configuration": [

src/DiagnosticsManager.ts

Lines changed: 98 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ interface ParsedDiagnostic {
2525

2626
type DiagnosticsMap = Map<string, vscode.Diagnostic[]>;
2727

28+
const isEqual = (d1: vscode.Diagnostic, d2: vscode.Diagnostic) =>
29+
d1.range.start.isEqual(d2.range.start) && d1.message === d2.message;
30+
2831
/**
2932
* Handles the collection and deduplication of diagnostics from
3033
* various {@link vscode.Diagnostic.source | Diagnostic sources}.
@@ -40,18 +43,15 @@ export class DiagnosticsManager implements vscode.Disposable {
4043

4144
private diagnosticCollection: vscode.DiagnosticCollection =
4245
vscode.languages.createDiagnosticCollection("swift");
46+
private allDiagnostics: Map<string, vscode.Diagnostic[]> = new Map();
4347

4448
constructor(context: WorkspaceContext) {
4549
this.onDidChangeConfigurationDisposible = vscode.workspace.onDidChangeConfiguration(e => {
4650
if (e.affectsConfiguration("swift.diagnosticsCollection")) {
47-
if (!this.includeSwiftcDiagnostics()) {
48-
// Clean up "swiftc" diagnostics
49-
this.removeSwiftcDiagnostics();
50-
}
51-
if (!this.includeSourceKitDiagnostics()) {
52-
// Clean up SourceKit diagnostics
53-
this.removeSourceKitDiagnostics();
54-
}
51+
this.diagnosticCollection.clear();
52+
this.allDiagnostics.forEach((_, uri) =>
53+
this.updateDiagnosticsCollection(vscode.Uri.file(uri))
54+
);
5555
}
5656
});
5757
this.onDidStartTaskDisposible = vscode.tasks.onDidStartTask(event => {
@@ -92,108 +92,145 @@ export class DiagnosticsManager implements vscode.Disposable {
9292
* @param uri {@link vscode.Uri Uri} of the file these diagonstics apply to
9393
* @param sources The source of the diagnostics which will apply for cleaning
9494
* up diagnostics that have been removed. See {@link swiftc} and {@link sourcekit}
95-
* @param diagnostics Array of {@link vscode.Diagnostic}. This can be empty to remove old diagnostics for the specified `sources`.
95+
* @param newDiagnostics Array of {@link vscode.Diagnostic}. This can be empty to remove old diagnostics for the specified `sources`.
9696
*/
97-
handleDiagnostics(uri: vscode.Uri, sources: string[], diagnostics: vscode.Diagnostic[]): void {
97+
handleDiagnostics(
98+
uri: vscode.Uri,
99+
sources: string[],
100+
newDiagnostics: vscode.Diagnostic[]
101+
): void {
102+
const isFromSourceKit = !!DiagnosticsManager.sourcekit.find(s => sources.includes(s));
98103
// Is a descrepency between SourceKit-LSP and older versions
99104
// of Swift as to whether the first letter is capitalized or not,
100105
// so we'll always display messages capitalized to user and this
101106
// also will allow comparing messages when merging
102-
diagnostics.forEach(this.capitalizeMessage);
103-
const newDiagnostics = this.diagnosticCollection.get(uri)?.slice() || [];
107+
newDiagnostics = newDiagnostics.map(this.capitalizeMessage);
108+
const allDiagnostics = this.allDiagnostics.get(uri.fsPath)?.slice() || [];
104109
// Remove the old set of diagnostics from this source
105-
this.removeDiagnostics(newDiagnostics, sources);
110+
const removedDiagnostics = this.removeDiagnostics(allDiagnostics, d =>
111+
this.isSource(d, sources)
112+
);
113+
// Clean up any "fixed" swiftc diagnostics
114+
if (isFromSourceKit) {
115+
this.removeDiagnostics(
116+
removedDiagnostics,
117+
d1 => !!newDiagnostics.find(d2 => isEqual(d1, d2))
118+
);
119+
this.removeDiagnostics(
120+
allDiagnostics,
121+
d1 => this.isSwiftc(d1) && !!removedDiagnostics.find(d2 => isEqual(d1, d2))
122+
);
123+
}
124+
// Append the new diagnostics we just received
125+
allDiagnostics.push(...newDiagnostics);
126+
this.allDiagnostics.set(uri.fsPath, allDiagnostics);
127+
// Update the collection
128+
this.updateDiagnosticsCollection(uri);
129+
}
130+
131+
private updateDiagnosticsCollection(uri: vscode.Uri): void {
132+
const diagnostics = this.allDiagnostics.get(uri.fsPath) ?? [];
133+
const swiftcDiagnostics = diagnostics.filter(d => this.isSwiftc(d));
134+
const sourceKitDiagnostics = diagnostics.filter(d => this.isSourceKit(d));
135+
const mergedDiagnostics: vscode.Diagnostic[] = [];
106136
switch (configuration.diagnosticsCollection) {
107137
case "keepSourceKit":
108-
this.mergeDiagnostics(newDiagnostics, diagnostics, DiagnosticsManager.sourcekit);
138+
mergedDiagnostics.push(...swiftcDiagnostics);
139+
this.mergeDiagnostics(
140+
mergedDiagnostics,
141+
sourceKitDiagnostics,
142+
DiagnosticsManager.sourcekit
143+
);
109144
break;
110145
case "keepSwiftc":
111-
this.mergeDiagnostics(newDiagnostics, diagnostics, DiagnosticsManager.swiftc);
146+
mergedDiagnostics.push(...sourceKitDiagnostics);
147+
this.mergeDiagnostics(
148+
mergedDiagnostics,
149+
swiftcDiagnostics,
150+
DiagnosticsManager.swiftc
151+
);
112152
break;
113153
case "onlySourceKit":
114-
this.removeDiagnostics(newDiagnostics, DiagnosticsManager.swiftc); // Just in case
115-
if (DiagnosticsManager.swiftc.find(s => sources.includes(s))) {
116-
break;
117-
}
118-
newDiagnostics.push(...diagnostics);
154+
mergedDiagnostics.push(...sourceKitDiagnostics);
119155
break;
120156
case "onlySwiftc":
121-
this.removeDiagnostics(newDiagnostics, DiagnosticsManager.sourcekit); // Just in case
122-
if (DiagnosticsManager.sourcekit.find(s => sources.includes(s))) {
123-
break;
124-
}
125-
newDiagnostics.push(...diagnostics);
157+
mergedDiagnostics.push(...swiftcDiagnostics);
126158
break;
127159
case "keepAll":
128-
newDiagnostics.push(...diagnostics);
160+
mergedDiagnostics.push(...sourceKitDiagnostics);
161+
mergedDiagnostics.push(...swiftcDiagnostics);
129162
break;
130163
}
131-
this.diagnosticCollection.set(uri, newDiagnostics);
164+
this.diagnosticCollection.set(uri, mergedDiagnostics);
132165
}
133166

134167
private mergeDiagnostics(
135-
combinedDiagnostics: vscode.Diagnostic[],
136-
incomingDiagnostics: vscode.Diagnostic[],
168+
mergedDiagnostics: vscode.Diagnostic[],
169+
newDiagnostics: vscode.Diagnostic[],
137170
precedence: string[]
138171
): void {
139-
for (const diagnostic of incomingDiagnostics) {
172+
for (const diagnostic of newDiagnostics) {
140173
// See if a duplicate diagnostic exists
141-
const currentDiagnostic = combinedDiagnostics.find(
142-
d =>
143-
d.range.start.isEqual(diagnostic.range.start) &&
144-
d.message === diagnostic.message
145-
);
174+
const currentDiagnostic = mergedDiagnostics.find(d => isEqual(d, diagnostic));
146175
if (currentDiagnostic) {
147-
combinedDiagnostics.splice(combinedDiagnostics.indexOf(currentDiagnostic), 1);
176+
mergedDiagnostics.splice(mergedDiagnostics.indexOf(currentDiagnostic), 1);
148177
}
149178

150179
// Perform de-duplication
151180
if (precedence.includes(diagnostic.source || "")) {
152-
combinedDiagnostics.push(diagnostic);
181+
mergedDiagnostics.push(diagnostic);
153182
continue;
154183
}
155184
if (!currentDiagnostic || !precedence.includes(currentDiagnostic.source || "")) {
156-
combinedDiagnostics.push(diagnostic);
185+
mergedDiagnostics.push(diagnostic);
157186
continue;
158187
}
159-
combinedDiagnostics.push(currentDiagnostic);
188+
mergedDiagnostics.push(currentDiagnostic);
160189
}
161190
}
162191

163192
private removeSwiftcDiagnostics() {
164193
this.diagnosticCollection.forEach((uri, diagnostics) => {
165194
const newDiagnostics = diagnostics.slice();
166-
this.removeDiagnostics(newDiagnostics, DiagnosticsManager.swiftc);
195+
this.removeDiagnostics(newDiagnostics, d => this.isSwiftc(d));
167196
if (diagnostics.length !== newDiagnostics.length) {
168197
this.diagnosticCollection.set(uri, newDiagnostics);
169198
}
170199
});
171200
}
172201

173-
private removeSourceKitDiagnostics() {
174-
this.diagnosticCollection.forEach((uri, diagnostics) => {
175-
const newDiagnostics = diagnostics.slice();
176-
this.removeDiagnostics(newDiagnostics, DiagnosticsManager.sourcekit);
177-
if (diagnostics.length !== newDiagnostics.length) {
178-
this.diagnosticCollection.set(uri, newDiagnostics);
179-
}
180-
});
202+
private isSource(diagnostic: vscode.Diagnostic, sources: string[]): boolean {
203+
return sources.includes(diagnostic.source || "");
204+
}
205+
206+
private isSwiftc(diagnostic: vscode.Diagnostic): boolean {
207+
return this.isSource(diagnostic, DiagnosticsManager.swiftc);
181208
}
182209

183-
private removeDiagnostics(diagnostics: vscode.Diagnostic[], sources: string[]): void {
210+
private isSourceKit(diagnostic: vscode.Diagnostic): boolean {
211+
return this.isSource(diagnostic, DiagnosticsManager.sourcekit);
212+
}
213+
214+
private removeDiagnostics(
215+
diagnostics: vscode.Diagnostic[],
216+
matches: (d: vscode.Diagnostic) => boolean
217+
): vscode.Diagnostic[] {
218+
const removed: vscode.Diagnostic[] = [];
184219
let i = diagnostics.length;
185220
while (i--) {
186-
if (sources.includes(diagnostics[i].source || "")) {
187-
diagnostics.splice(i, 1);
221+
if (matches(diagnostics[i])) {
222+
removed.push(...diagnostics.splice(i, 1));
188223
}
189224
}
225+
return removed;
190226
}
191227

192228
/**
193229
* Clear the `swift` diagnostics collection. Mostly meant for testing purposes.
194230
*/
195231
clear(): void {
196232
this.diagnosticCollection.clear();
233+
this.allDiagnostics.clear();
197234
}
198235

199236
dispose() {
@@ -238,7 +275,6 @@ export class DiagnosticsManager implements vscode.Disposable {
238275
}
239276
const relatedInformation =
240277
result as vscode.DiagnosticRelatedInformation;
241-
this.capitalizeMessage(relatedInformation);
242278
if (
243279
lastDiagnostic.relatedInformation?.find(
244280
d =>
@@ -291,7 +327,7 @@ export class DiagnosticsManager implements vscode.Disposable {
291327
return;
292328
}
293329
const uri = match[1];
294-
const message = match[5];
330+
const message = this.capitalize(match[5]);
295331
const range = this.range(match[2], match[3]);
296332
const severity = this.severity(match[4]);
297333
if (severity === vscode.DiagnosticSeverity.Information) {
@@ -328,13 +364,17 @@ export class DiagnosticsManager implements vscode.Disposable {
328364
return severity;
329365
}
330366

331-
private capitalizeMessage(
332-
diagnostic: vscode.Diagnostic | vscode.DiagnosticRelatedInformation
333-
): void {
334-
const message = diagnostic.message;
335-
diagnostic.message = message.charAt(0).toUpperCase() + message.slice(1);
367+
private capitalize(message: string): string {
368+
return message.charAt(0).toUpperCase() + message.slice(1);
336369
}
337370

371+
private capitalizeMessage = (diagnostic: vscode.Diagnostic): vscode.Diagnostic => {
372+
const message = diagnostic.message;
373+
diagnostic = { ...diagnostic };
374+
diagnostic.message = this.capitalize(message);
375+
return diagnostic;
376+
};
377+
338378
private onDidStartTaskDisposible: vscode.Disposable;
339379
private onDidChangeConfigurationDisposible: vscode.Disposable;
340380
}

src/commands.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -845,5 +845,8 @@ export function register(ctx: WorkspaceContext): vscode.Disposable[] {
845845
}
846846
}),
847847
vscode.commands.registerCommand("swift.attachDebugger", () => attachDebugger(ctx)),
848+
vscode.commands.registerCommand("swift.clearDiagnosticsCollection", () =>
849+
ctx.diagnostics.clear()
850+
),
848851
];
849852
}

0 commit comments

Comments
 (0)