@@ -25,6 +25,9 @@ interface ParsedDiagnostic {
25
25
26
26
type DiagnosticsMap = Map < string , vscode . Diagnostic [ ] > ;
27
27
28
+ const isEqual = ( d1 : vscode . Diagnostic , d2 : vscode . Diagnostic ) =>
29
+ d1 . range . start . isEqual ( d2 . range . start ) && d1 . message === d2 . message ;
30
+
28
31
/**
29
32
* Handles the collection and deduplication of diagnostics from
30
33
* various {@link vscode.Diagnostic.source | Diagnostic sources}.
@@ -40,18 +43,15 @@ export class DiagnosticsManager implements vscode.Disposable {
40
43
41
44
private diagnosticCollection : vscode . DiagnosticCollection =
42
45
vscode . languages . createDiagnosticCollection ( "swift" ) ;
46
+ private allDiagnostics : Map < string , vscode . Diagnostic [ ] > = new Map ( ) ;
43
47
44
48
constructor ( context : WorkspaceContext ) {
45
49
this . onDidChangeConfigurationDisposible = vscode . workspace . onDidChangeConfiguration ( e => {
46
50
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
+ ) ;
55
55
}
56
56
} ) ;
57
57
this . onDidStartTaskDisposible = vscode . tasks . onDidStartTask ( event => {
@@ -92,108 +92,145 @@ export class DiagnosticsManager implements vscode.Disposable {
92
92
* @param uri {@link vscode.Uri Uri } of the file these diagonstics apply to
93
93
* @param sources The source of the diagnostics which will apply for cleaning
94
94
* 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`.
96
96
*/
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 ) ) ;
98
103
// Is a descrepency between SourceKit-LSP and older versions
99
104
// of Swift as to whether the first letter is capitalized or not,
100
105
// so we'll always display messages capitalized to user and this
101
106
// 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 ( ) || [ ] ;
104
109
// 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 [ ] = [ ] ;
106
136
switch ( configuration . diagnosticsCollection ) {
107
137
case "keepSourceKit" :
108
- this . mergeDiagnostics ( newDiagnostics , diagnostics , DiagnosticsManager . sourcekit ) ;
138
+ mergedDiagnostics . push ( ...swiftcDiagnostics ) ;
139
+ this . mergeDiagnostics (
140
+ mergedDiagnostics ,
141
+ sourceKitDiagnostics ,
142
+ DiagnosticsManager . sourcekit
143
+ ) ;
109
144
break ;
110
145
case "keepSwiftc" :
111
- this . mergeDiagnostics ( newDiagnostics , diagnostics , DiagnosticsManager . swiftc ) ;
146
+ mergedDiagnostics . push ( ...sourceKitDiagnostics ) ;
147
+ this . mergeDiagnostics (
148
+ mergedDiagnostics ,
149
+ swiftcDiagnostics ,
150
+ DiagnosticsManager . swiftc
151
+ ) ;
112
152
break ;
113
153
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 ) ;
119
155
break ;
120
156
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 ) ;
126
158
break ;
127
159
case "keepAll" :
128
- newDiagnostics . push ( ...diagnostics ) ;
160
+ mergedDiagnostics . push ( ...sourceKitDiagnostics ) ;
161
+ mergedDiagnostics . push ( ...swiftcDiagnostics ) ;
129
162
break ;
130
163
}
131
- this . diagnosticCollection . set ( uri , newDiagnostics ) ;
164
+ this . diagnosticCollection . set ( uri , mergedDiagnostics ) ;
132
165
}
133
166
134
167
private mergeDiagnostics (
135
- combinedDiagnostics : vscode . Diagnostic [ ] ,
136
- incomingDiagnostics : vscode . Diagnostic [ ] ,
168
+ mergedDiagnostics : vscode . Diagnostic [ ] ,
169
+ newDiagnostics : vscode . Diagnostic [ ] ,
137
170
precedence : string [ ]
138
171
) : void {
139
- for ( const diagnostic of incomingDiagnostics ) {
172
+ for ( const diagnostic of newDiagnostics ) {
140
173
// 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 ) ) ;
146
175
if ( currentDiagnostic ) {
147
- combinedDiagnostics . splice ( combinedDiagnostics . indexOf ( currentDiagnostic ) , 1 ) ;
176
+ mergedDiagnostics . splice ( mergedDiagnostics . indexOf ( currentDiagnostic ) , 1 ) ;
148
177
}
149
178
150
179
// Perform de-duplication
151
180
if ( precedence . includes ( diagnostic . source || "" ) ) {
152
- combinedDiagnostics . push ( diagnostic ) ;
181
+ mergedDiagnostics . push ( diagnostic ) ;
153
182
continue ;
154
183
}
155
184
if ( ! currentDiagnostic || ! precedence . includes ( currentDiagnostic . source || "" ) ) {
156
- combinedDiagnostics . push ( diagnostic ) ;
185
+ mergedDiagnostics . push ( diagnostic ) ;
157
186
continue ;
158
187
}
159
- combinedDiagnostics . push ( currentDiagnostic ) ;
188
+ mergedDiagnostics . push ( currentDiagnostic ) ;
160
189
}
161
190
}
162
191
163
192
private removeSwiftcDiagnostics ( ) {
164
193
this . diagnosticCollection . forEach ( ( uri , diagnostics ) => {
165
194
const newDiagnostics = diagnostics . slice ( ) ;
166
- this . removeDiagnostics ( newDiagnostics , DiagnosticsManager . swiftc ) ;
195
+ this . removeDiagnostics ( newDiagnostics , d => this . isSwiftc ( d ) ) ;
167
196
if ( diagnostics . length !== newDiagnostics . length ) {
168
197
this . diagnosticCollection . set ( uri , newDiagnostics ) ;
169
198
}
170
199
} ) ;
171
200
}
172
201
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 ) ;
181
208
}
182
209
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 [ ] = [ ] ;
184
219
let i = diagnostics . length ;
185
220
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 ) ) ;
188
223
}
189
224
}
225
+ return removed ;
190
226
}
191
227
192
228
/**
193
229
* Clear the `swift` diagnostics collection. Mostly meant for testing purposes.
194
230
*/
195
231
clear ( ) : void {
196
232
this . diagnosticCollection . clear ( ) ;
233
+ this . allDiagnostics . clear ( ) ;
197
234
}
198
235
199
236
dispose ( ) {
@@ -238,7 +275,6 @@ export class DiagnosticsManager implements vscode.Disposable {
238
275
}
239
276
const relatedInformation =
240
277
result as vscode . DiagnosticRelatedInformation ;
241
- this . capitalizeMessage ( relatedInformation ) ;
242
278
if (
243
279
lastDiagnostic . relatedInformation ?. find (
244
280
d =>
@@ -291,7 +327,7 @@ export class DiagnosticsManager implements vscode.Disposable {
291
327
return ;
292
328
}
293
329
const uri = match [ 1 ] ;
294
- const message = match [ 5 ] ;
330
+ const message = this . capitalize ( match [ 5 ] ) ;
295
331
const range = this . range ( match [ 2 ] , match [ 3 ] ) ;
296
332
const severity = this . severity ( match [ 4 ] ) ;
297
333
if ( severity === vscode . DiagnosticSeverity . Information ) {
@@ -328,13 +364,17 @@ export class DiagnosticsManager implements vscode.Disposable {
328
364
return severity ;
329
365
}
330
366
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 ) ;
336
369
}
337
370
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
+
338
378
private onDidStartTaskDisposible : vscode . Disposable ;
339
379
private onDidChangeConfigurationDisposible : vscode . Disposable ;
340
380
}
0 commit comments