Skip to content

Commit a93e127

Browse files
Merge two dictionaries into one
1 parent 31d6c5c commit a93e127

File tree

3 files changed

+66
-75
lines changed

3 files changed

+66
-75
lines changed

src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Diagnostics/RazorDiagnosticsPublisher.TestAccessor.cs

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,19 +44,11 @@ public Task WaitForDiagnosticsToPublishAsync()
4444
return instance._workQueue.WaitUntilCurrentBatchCompletesAsync();
4545
}
4646

47-
public void SetPublishedCSharpDiagnostics(string filePath, ImmutableArray<Diagnostic> diagnostics)
47+
public void SetPublishedDiagnostics(string filePath, RazorDiagnostic[] razorDiagnostics, Diagnostic[]? csharpDiagnostics)
4848
{
49-
lock (instance._publishedDiagnosticsGate)
49+
lock (instance._publishedDiagnostics)
5050
{
51-
instance._publishedCSharpDiagnostics[filePath] = diagnostics;
52-
}
53-
}
54-
55-
public void SetPublishedRazorDiagnostics(string filePath, ImmutableArray<RazorDiagnostic> diagnostics)
56-
{
57-
lock (instance._publishedDiagnosticsGate)
58-
{
59-
instance._publishedRazorDiagnostics[filePath] = diagnostics;
51+
instance._publishedDiagnostics[filePath] = new(razorDiagnostics, csharpDiagnostics);
6052
}
6153
}
6254

src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Diagnostics/RazorDiagnosticsPublisher.cs

Lines changed: 53 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Diagnostics;
2424

2525
internal partial class RazorDiagnosticsPublisher : IDocumentProcessedListener, IDisposable
2626
{
27+
private readonly record struct PublishedDiagnostics(IReadOnlyList<RazorDiagnostic> Razor, Diagnostic[]? CSharp)
28+
{
29+
public int Count => Razor.Count + (CSharp?.Length ?? 0);
30+
}
31+
2732
private static readonly TimeSpan s_publishDelay = TimeSpan.FromSeconds(2);
2833
private static readonly TimeSpan s_clearClosedDocumentsDelay = TimeSpan.FromSeconds(5);
2934

@@ -36,10 +41,7 @@ internal partial class RazorDiagnosticsPublisher : IDocumentProcessedListener, I
3641

3742
private readonly CancellationTokenSource _disposeTokenSource;
3843
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;
4345

4446
private readonly object _documentClosedGate = new();
4547
private Task _clearClosedDocumentsTask = Task.CompletedTask;
@@ -77,8 +79,7 @@ protected RazorDiagnosticsPublisher(
7779
_disposeTokenSource = new();
7880
_workQueue = new AsyncBatchingWorkQueue<IDocumentSnapshot>(publishDelay, ProcessBatchAsync, _disposeTokenSource.Token);
7981

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);
8283
_logger = loggerFactory.GetOrCreateLogger<RazorDiagnosticsPublisher>();
8384
}
8485

@@ -114,22 +115,28 @@ private async Task PublishDiagnosticsAsync(IDocumentSnapshot document, Cancellat
114115
var csharpDiagnostics = await GetCSharpDiagnosticsAsync(document, token).ConfigureAwait(false);
115116
var razorDiagnostics = result.GetCSharpDocument().Diagnostics;
116117

117-
lock (_publishedDiagnosticsGate)
118+
lock (_publishedDiagnostics)
118119
{
119120
var filePath = document.FilePath.AssumeNotNull();
120121

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))
123124
{
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+
};
127132

128-
_publishedRazorDiagnostics[filePath] = razorDiagnostics;
129-
if (csharpDiagnostics != null)
130-
{
131-
_publishedCSharpDiagnostics[filePath] = csharpDiagnostics;
133+
if (sameRazorDiagnostics && sameCSharpDiagnostics)
134+
{
135+
return;
136+
}
132137
}
138+
139+
_publishedDiagnostics[filePath] = new(razorDiagnostics, csharpDiagnostics);
133140
}
134141

135142
if (!document.TryGetText(out var sourceText))
@@ -138,8 +145,11 @@ private async Task PublishDiagnosticsAsync(IDocumentSnapshot document, Cancellat
138145
return;
139146
}
140147

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+
143153
PublishDiagnosticsForFilePath(document.FilePath, combinedDiagnostics);
144154

145155
if (_logger.IsEnabled(LogLevel.Trace))
@@ -220,51 +230,45 @@ async Task ClearClosedDocumentsAfterDelayAsync()
220230

221231
private void ClearClosedDocuments()
222232
{
223-
lock (_publishedDiagnosticsGate)
233+
lock (_publishedDiagnostics)
224234
{
225-
ClearClosedDocumentsPublishedDiagnostics(_publishedRazorDiagnostics);
226-
ClearClosedDocumentsPublishedDiagnostics(_publishedCSharpDiagnostics);
235+
using var documentsToRemove = new PooledArrayBuilder<(string filePath, bool publishToClear)>(capacity: _publishedDiagnostics.Count);
227236

228-
if (_publishedRazorDiagnostics.Count > 0 || _publishedCSharpDiagnostics.Count > 0)
237+
foreach (var (filePath, diagnostics) in _publishedDiagnostics)
229238
{
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+
}
233245
}
234246

235-
void ClearClosedDocumentsPublishedDiagnostics<T>(Dictionary<string, IReadOnlyList<T>> publishedDiagnostics) where T : class
247+
if (documentsToRemove.Count == 0)
236248
{
237-
using var documentsToRemove = new PooledArrayBuilder<(string key, bool publishEmptyDiagnostics)>(capacity: publishedDiagnostics.Count);
249+
return;
250+
}
238251

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);
248255

249-
if (documentsToRemove.Count == 0)
256+
if (publishToClear)
250257
{
251-
return;
258+
PublishDiagnosticsForFilePath(filePath, []);
252259
}
260+
}
253261

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();
263267
}
264268
}
265269
}
266270

267-
private void PublishDiagnosticsForFilePath(string filePath, IEnumerable<Diagnostic> diagnostics)
271+
private void PublishDiagnosticsForFilePath(string filePath, Diagnostic[] diagnostics)
268272
{
269273
var uriBuilder = new UriBuilder()
270274
{
@@ -279,7 +283,7 @@ private void PublishDiagnosticsForFilePath(string filePath, IEnumerable<Diagnost
279283
new PublishDiagnosticParams()
280284
{
281285
Uri = uriBuilder.Uri,
282-
Diagnostics = diagnostics.ToArray(),
286+
Diagnostics = diagnostics,
283287
},
284288
_disposeTokenSource.Token)
285289
.Forget();

src/Razor/test/Microsoft.AspNetCore.Razor.LanguageServer.Test/Diagnostics/RazorDiagnosticsPublisherTest.cs

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Diagnostics;
3131

3232
public class RazorDiagnosticsPublisherTest(ITestOutputHelper testOutput) : LanguageServerTestBase(testOutput)
3333
{
34-
private static readonly ImmutableArray<RazorDiagnostic> s_singleRazorDiagnostic =
34+
private static readonly RazorDiagnostic[] s_singleRazorDiagnostic =
3535
[
3636
RazorDiagnosticFactory.CreateDirective_BlockDirectiveCannotBeImported("test")
3737
];
3838

39-
private static readonly ImmutableArray<Diagnostic> s_singleCSharpDiagnostic =
39+
private static readonly Diagnostic[] s_singleCSharpDiagnostic =
4040
[
4141
new Diagnostic()
4242
{
@@ -288,7 +288,7 @@ public async Task PublishDiagnosticsAsync_NewRazorDiagnosticsGetPublished()
288288

289289
using var publisher = new TestRazorDiagnosticsPublisher(_projectManager, clientConnectionMock.Object, TestLanguageServerFeatureOptions.Instance, translateDiagnosticsService, documentContextFactory, LoggerFactory);
290290
var publisherAccessor = publisher.GetTestAccessor();
291-
publisherAccessor.SetPublishedRazorDiagnostics(processedOpenDocument.FilePath, []);
291+
publisherAccessor.SetPublishedDiagnostics(processedOpenDocument.FilePath, razorDiagnostics: [], csharpDiagnostics: null);
292292

293293
// Act
294294
await publisherAccessor.PublishDiagnosticsAsync(processedOpenDocument, DisposalToken);
@@ -384,7 +384,7 @@ public async Task PublishDiagnosticsAsync_NoopsIfRazorDiagnosticsAreSameAsPrevio
384384

385385
using var publisher = new TestRazorDiagnosticsPublisher(_projectManager, clientConnectionMock.Object, TestLanguageServerFeatureOptions.Instance, translateDiagnosticsService, documentContextFactory, LoggerFactory);
386386
var publisherAccessor = publisher.GetTestAccessor();
387-
publisherAccessor.SetPublishedRazorDiagnostics(processedOpenDocument.FilePath, s_singleRazorDiagnostic);
387+
publisherAccessor.SetPublishedDiagnostics(processedOpenDocument.FilePath, s_singleRazorDiagnostic, csharpDiagnostics: null);
388388

389389
// Act & Assert
390390
await publisherAccessor.PublishDiagnosticsAsync(processedOpenDocument, DisposalToken);
@@ -467,8 +467,7 @@ public void ClearClosedDocuments_ClearsDiagnosticsForClosedDocument()
467467
using var publisher = new TestRazorDiagnosticsPublisher(_projectManager, clientConnectionMock.Object, TestLanguageServerFeatureOptions.Instance, translateDiagnosticsService, documentContextFactory, LoggerFactory);
468468
var publisherAccessor = publisher.GetTestAccessor();
469469
Assert.NotNull(_closedDocument.FilePath);
470-
publisherAccessor.SetPublishedRazorDiagnostics(_closedDocument.FilePath, s_singleRazorDiagnostic);
471-
publisherAccessor.SetPublishedCSharpDiagnostics(_closedDocument.FilePath, s_singleCSharpDiagnostic);
470+
publisherAccessor.SetPublishedDiagnostics(_closedDocument.FilePath, s_singleRazorDiagnostic, s_singleCSharpDiagnostic);
472471

473472
// Act
474473
publisherAccessor.ClearClosedDocuments();
@@ -488,8 +487,7 @@ public void ClearClosedDocuments_NoopsIfDocumentIsStillOpen()
488487
using var publisher = new TestRazorDiagnosticsPublisher(_projectManager, clientConnectionMock.Object, TestLanguageServerFeatureOptions.Instance, translateDiagnosticsService, documentContextFactory, LoggerFactory);
489488
var publisherAccessor = publisher.GetTestAccessor();
490489
Assert.NotNull(_openedDocument.FilePath);
491-
publisherAccessor.SetPublishedRazorDiagnostics(_openedDocument.FilePath, s_singleRazorDiagnostic);
492-
publisherAccessor.SetPublishedCSharpDiagnostics(_openedDocument.FilePath, s_singleCSharpDiagnostic);
490+
publisherAccessor.SetPublishedDiagnostics(_openedDocument.FilePath, s_singleRazorDiagnostic, s_singleCSharpDiagnostic);
493491

494492
// Act & Assert
495493
publisherAccessor.ClearClosedDocuments();
@@ -506,8 +504,7 @@ public void ClearClosedDocuments_NoopsIfDocumentIsClosedButNoDiagnostics()
506504
using var publisher = new TestRazorDiagnosticsPublisher(_projectManager, clientConnectionMock.Object, TestLanguageServerFeatureOptions.Instance, translateDiagnosticsService, documentContextFactory, LoggerFactory);
507505
var publisherAccessor = publisher.GetTestAccessor();
508506
Assert.NotNull(_closedDocument.FilePath);
509-
publisherAccessor.SetPublishedRazorDiagnostics(_closedDocument.FilePath, []);
510-
publisherAccessor.SetPublishedCSharpDiagnostics(_closedDocument.FilePath, []);
507+
publisherAccessor.SetPublishedDiagnostics(_closedDocument.FilePath, razorDiagnostics: [], csharpDiagnostics: []);
511508

512509
// Act & Assert
513510
publisherAccessor.ClearClosedDocuments();
@@ -525,10 +522,8 @@ public void ClearClosedDocuments_RestartsTimerIfDocumentsStillOpen()
525522
var publisherAccessor = publisher.GetTestAccessor();
526523
Assert.NotNull(_closedDocument.FilePath);
527524
Assert.NotNull(_openedDocument.FilePath);
528-
publisherAccessor.SetPublishedRazorDiagnostics(_closedDocument.FilePath, []);
529-
publisherAccessor.SetPublishedCSharpDiagnostics(_closedDocument.FilePath, []);
530-
publisherAccessor.SetPublishedRazorDiagnostics(_openedDocument.FilePath, []);
531-
publisherAccessor.SetPublishedCSharpDiagnostics(_openedDocument.FilePath, []);
525+
publisherAccessor.SetPublishedDiagnostics(_closedDocument.FilePath, razorDiagnostics: [], csharpDiagnostics: []);
526+
publisherAccessor.SetPublishedDiagnostics(_openedDocument.FilePath, razorDiagnostics: [], csharpDiagnostics: []);
532527

533528
// Act
534529
publisherAccessor.ClearClosedDocuments();
@@ -537,7 +532,7 @@ public void ClearClosedDocuments_RestartsTimerIfDocumentsStillOpen()
537532
Assert.True(publisherAccessor.IsWaitingToClearClosedDocuments);
538533
}
539534

540-
private static RazorCodeDocument CreateCodeDocument(ImmutableArray<RazorDiagnostic> diagnostics)
535+
private static RazorCodeDocument CreateCodeDocument(RazorDiagnostic[] diagnostics)
541536
{
542537
var codeDocument = TestRazorCodeDocument.Create("hello");
543538
var razorCSharpDocument = RazorCSharpDocument.Create(codeDocument, "hello", RazorCodeGenerationOptions.CreateDefault(), diagnostics);

0 commit comments

Comments
 (0)