@@ -24,6 +24,11 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Diagnostics;
24
24
25
25
internal partial class RazorDiagnosticsPublisher : IDocumentProcessedListener , IDisposable
26
26
{
27
+ private readonly record struct PublishedDiagnostics ( IReadOnlyList < RazorDiagnostic > Razor , Diagnostic [ ] ? CSharp )
28
+ {
29
+ public int Count => Razor . Count + ( CSharp ? . Length ?? 0 ) ;
30
+ }
31
+
27
32
private static readonly TimeSpan s_publishDelay = TimeSpan . FromSeconds ( 2 ) ;
28
33
private static readonly TimeSpan s_clearClosedDocumentsDelay = TimeSpan . FromSeconds ( 5 ) ;
29
34
@@ -36,10 +41,7 @@ internal partial class RazorDiagnosticsPublisher : IDocumentProcessedListener, I
36
41
37
42
private readonly CancellationTokenSource _disposeTokenSource ;
38
43
private readonly AsyncBatchingWorkQueue < IDocumentSnapshot > _workQueue ;
39
-
40
- private readonly object _publishedDiagnosticsGate = new ( ) ;
41
- private readonly Dictionary < string , IReadOnlyList < RazorDiagnostic > > _publishedRazorDiagnostics ;
42
- private readonly Dictionary < string , IReadOnlyList < Diagnostic > > _publishedCSharpDiagnostics ;
44
+ private readonly Dictionary < string , PublishedDiagnostics > _publishedDiagnostics ;
43
45
44
46
private readonly object _documentClosedGate = new ( ) ;
45
47
private Task _clearClosedDocumentsTask = Task . CompletedTask ;
@@ -77,8 +79,7 @@ protected RazorDiagnosticsPublisher(
77
79
_disposeTokenSource = new ( ) ;
78
80
_workQueue = new AsyncBatchingWorkQueue < IDocumentSnapshot > ( publishDelay , ProcessBatchAsync , _disposeTokenSource . Token ) ;
79
81
80
- _publishedRazorDiagnostics = new Dictionary < string , IReadOnlyList < RazorDiagnostic > > ( FilePathComparer . Instance ) ;
81
- _publishedCSharpDiagnostics = new Dictionary < string , IReadOnlyList < Diagnostic > > ( FilePathComparer . Instance ) ;
82
+ _publishedDiagnostics = new Dictionary < string , PublishedDiagnostics > ( FilePathComparer . Instance ) ;
82
83
_logger = loggerFactory . GetOrCreateLogger < RazorDiagnosticsPublisher > ( ) ;
83
84
}
84
85
@@ -114,22 +115,28 @@ private async Task PublishDiagnosticsAsync(IDocumentSnapshot document, Cancellat
114
115
var csharpDiagnostics = await GetCSharpDiagnosticsAsync ( document , token ) . ConfigureAwait ( false ) ;
115
116
var razorDiagnostics = result . GetCSharpDocument ( ) . Diagnostics ;
116
117
117
- lock ( _publishedDiagnosticsGate )
118
+ lock ( _publishedDiagnostics )
118
119
{
119
120
var filePath = document . FilePath . AssumeNotNull ( ) ;
120
121
121
- if ( _publishedRazorDiagnostics . TryGetValue ( filePath , out var previousRazorDiagnostics ) && razorDiagnostics . SequenceEqual ( previousRazorDiagnostics )
122
- && ( csharpDiagnostics == null || ( _publishedCSharpDiagnostics . TryGetValue ( filePath , out var previousCsharpDiagnostics ) && csharpDiagnostics . SequenceEqual ( previousCsharpDiagnostics ) ) ) )
122
+ // See if these are the same diagnostics as last time. If so, we don't need to publish.
123
+ if ( _publishedDiagnostics . TryGetValue ( filePath , out var previousDiagnostics ) )
123
124
{
124
- // Diagnostics are the same as last publish
125
- return ;
126
- }
125
+ var sameRazorDiagnostics = razorDiagnostics . SequenceEqual ( previousDiagnostics . Razor ) ;
126
+ var sameCSharpDiagnostics = ( csharpDiagnostics , previousDiagnostics . CSharp ) switch
127
+ {
128
+ ( not null , not null ) => csharpDiagnostics . SequenceEqual ( previousDiagnostics . CSharp ) ,
129
+ ( null , null ) => true ,
130
+ _ => false ,
131
+ } ;
127
132
128
- _publishedRazorDiagnostics [ filePath ] = razorDiagnostics ;
129
- if ( csharpDiagnostics != null )
130
- {
131
- _publishedCSharpDiagnostics [ filePath ] = csharpDiagnostics ;
133
+ if ( sameRazorDiagnostics && sameCSharpDiagnostics )
134
+ {
135
+ return ;
136
+ }
132
137
}
138
+
139
+ _publishedDiagnostics [ filePath ] = new ( razorDiagnostics , csharpDiagnostics ) ;
133
140
}
134
141
135
142
if ( ! document . TryGetText ( out var sourceText ) )
@@ -138,8 +145,11 @@ private async Task PublishDiagnosticsAsync(IDocumentSnapshot document, Cancellat
138
145
return ;
139
146
}
140
147
141
- var convertedDiagnostics = razorDiagnostics . Select ( razorDiagnostic => RazorDiagnosticConverter . Convert ( razorDiagnostic , sourceText , document ) ) ;
142
- var combinedDiagnostics = csharpDiagnostics == null ? convertedDiagnostics : convertedDiagnostics . Concat ( csharpDiagnostics ) ;
148
+ Diagnostic [ ] combinedDiagnostics = [
149
+ .. razorDiagnostics . Select ( d => RazorDiagnosticConverter . Convert ( d , sourceText , document ) ) ,
150
+ .. csharpDiagnostics ?? [ ]
151
+ ] ;
152
+
143
153
PublishDiagnosticsForFilePath ( document . FilePath , combinedDiagnostics ) ;
144
154
145
155
if ( _logger . IsEnabled ( LogLevel . Trace ) )
@@ -220,51 +230,45 @@ async Task ClearClosedDocumentsAfterDelayAsync()
220
230
221
231
private void ClearClosedDocuments ( )
222
232
{
223
- lock ( _publishedDiagnosticsGate )
233
+ lock ( _publishedDiagnostics )
224
234
{
225
- ClearClosedDocumentsPublishedDiagnostics ( _publishedRazorDiagnostics ) ;
226
- ClearClosedDocumentsPublishedDiagnostics ( _publishedCSharpDiagnostics ) ;
235
+ using var documentsToRemove = new PooledArrayBuilder < ( string filePath , bool publishToClear ) > ( capacity : _publishedDiagnostics . Count ) ;
227
236
228
- if ( _publishedRazorDiagnostics . Count > 0 || _publishedCSharpDiagnostics . Count > 0 )
237
+ foreach ( var ( filePath , diagnostics ) in _publishedDiagnostics )
229
238
{
230
- // There's no way for us to know when a document is closed at this layer. Therefore, we need to poll every X seconds
231
- // and check if the currently tracked documents are closed. In practice this work is super minimal.
232
- StartDelayToClearDocuments ( ) ;
239
+ if ( ! _projectManager . IsDocumentOpen ( filePath ) )
240
+ {
241
+ // If there were previously published diagnostics for this document, take note so
242
+ // we can publish an empty set of diagnostics.
243
+ documentsToRemove . Add ( ( filePath , publishToClear : diagnostics . Count > 0 ) ) ;
244
+ }
233
245
}
234
246
235
- void ClearClosedDocumentsPublishedDiagnostics < T > ( Dictionary < string , IReadOnlyList < T > > publishedDiagnostics ) where T : class
247
+ if ( documentsToRemove . Count == 0 )
236
248
{
237
- using var documentsToRemove = new PooledArrayBuilder < ( string key , bool publishEmptyDiagnostics ) > ( capacity : publishedDiagnostics . Count ) ;
249
+ return ;
250
+ }
238
251
239
- foreach ( var ( key , value ) in publishedDiagnostics )
240
- {
241
- if ( ! _projectManager . IsDocumentOpen ( key ) )
242
- {
243
- // If there were previously published diagnostics for this document, take note so
244
- // we can publish an empty set of diagnostics.
245
- documentsToRemove . Add ( ( key , publishEmptyDiagnostics : value . Count > 0 ) ) ;
246
- }
247
- }
252
+ foreach ( var ( filePath , publishToClear ) in documentsToRemove )
253
+ {
254
+ _publishedDiagnostics . Remove ( filePath ) ;
248
255
249
- if ( documentsToRemove . Count == 0 )
256
+ if ( publishToClear )
250
257
{
251
- return ;
258
+ PublishDiagnosticsForFilePath ( filePath , [ ] ) ;
252
259
}
260
+ }
253
261
254
- foreach ( var ( key , publishEmptyDiagnostics ) in documentsToRemove )
255
- {
256
- publishedDiagnostics . Remove ( key ) ;
257
-
258
- if ( publishEmptyDiagnostics )
259
- {
260
- PublishDiagnosticsForFilePath ( key , [ ] ) ;
261
- }
262
- }
262
+ if ( _publishedDiagnostics . Count > 0 )
263
+ {
264
+ // There's no way for us to know when a document is closed at this layer. Therefore, we need to poll every X seconds
265
+ // and check if the currently tracked documents are closed. In practice this work is super minimal.
266
+ StartDelayToClearDocuments ( ) ;
263
267
}
264
268
}
265
269
}
266
270
267
- private void PublishDiagnosticsForFilePath ( string filePath , IEnumerable < Diagnostic > diagnostics )
271
+ private void PublishDiagnosticsForFilePath ( string filePath , Diagnostic [ ] diagnostics )
268
272
{
269
273
var uriBuilder = new UriBuilder ( )
270
274
{
@@ -279,7 +283,7 @@ private void PublishDiagnosticsForFilePath(string filePath, IEnumerable<Diagnost
279
283
new PublishDiagnosticParams ( )
280
284
{
281
285
Uri = uriBuilder . Uri ,
282
- Diagnostics = diagnostics . ToArray ( ) ,
286
+ Diagnostics = diagnostics ,
283
287
} ,
284
288
_disposeTokenSource . Token )
285
289
. Forget ( ) ;
0 commit comments