未验证 提交 bee9f73c 编写于 作者: H Heejae Chang 提交者: GitHub

added ability to clear all diagnostics reproted from a IDiagnosticUpd… (#33805)

* added ability to clear all diagnostics reproted from a IDiagnosticUpdateSource

previously IDiagnosticUpdateSource has to clear each diagnostics it reported group by group. that was fine for IDiagnosticUpdateSource that supports incremental update, but some source such as EditAndContinue doesn't support incremental update since their errors (emit errors) come and go as a bulk (whole project). when they update, they need to update everything. so tracking things in group for incremental update is unnecessary for such source.

the new API (Source.Cleared) make it easy for source to clear all diagnostics at once it produced.

* addressing PR feedbacks
上级 f424810c
......@@ -40,6 +40,7 @@ public EditAndContinueDiagnosticUpdateSource(IDiagnosticUpdateSourceRegistration
public bool SupportGetDiagnostics => false;
public event EventHandler<DiagnosticsUpdatedArgs> DiagnosticsUpdated;
public event EventHandler DiagnosticsCleared { add { } remove { } }
public ImmutableArray<DiagnosticData> GetDiagnostics(Workspace workspace, ProjectId projectId, DocumentId documentId, object id, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default)
{
......
......@@ -20,17 +20,17 @@ public void TestGetDiagnostics1()
{
using (var workspace = new TestWorkspace(TestExportProvider.ExportProviderWithCSharpAndVisualBasic))
{
var set = new ManualResetEvent(false);
var mutex = new ManualResetEvent(false);
var document = workspace.CurrentSolution.AddProject("TestProject", "TestProject", LanguageNames.CSharp).AddDocument("TestDocument", string.Empty);
var source = new TestDiagnosticUpdateSource(false, null);
var diagnosticService = new DiagnosticService(AsynchronousOperationListenerProvider.NullProvider);
diagnosticService.Register(source);
diagnosticService.DiagnosticsUpdated += (s, o) => { set.Set(); };
diagnosticService.DiagnosticsUpdated += (s, o) => { mutex.Set(); };
var id = Tuple.Create(workspace, document);
var diagnostic = RaiseDiagnosticEvent(set, source, workspace, document.Project.Id, document.Id, id);
var diagnostic = RaiseDiagnosticEvent(mutex, source, workspace, document.Project.Id, document.Id, id);
var data1 = diagnosticService.GetDiagnostics(workspace, null, null, null, false, CancellationToken.None);
Assert.Equal(diagnostic, data1.Single());
......@@ -51,7 +51,7 @@ public void TestGetDiagnostics2()
{
using (var workspace = new TestWorkspace(TestExportProvider.ExportProviderWithCSharpAndVisualBasic))
{
var set = new ManualResetEvent(false);
var mutex = new ManualResetEvent(false);
var document = workspace.CurrentSolution.AddProject("TestProject", "TestProject", LanguageNames.CSharp).AddDocument("TestDocument", string.Empty);
var document2 = document.Project.AddDocument("TestDocument2", string.Empty);
......@@ -59,19 +59,19 @@ public void TestGetDiagnostics2()
var diagnosticService = new DiagnosticService(AsynchronousOperationListenerProvider.NullProvider);
diagnosticService.Register(source);
diagnosticService.DiagnosticsUpdated += (s, o) => { set.Set(); };
diagnosticService.DiagnosticsUpdated += (s, o) => { mutex.Set(); };
var id = Tuple.Create(workspace, document);
RaiseDiagnosticEvent(set, source, workspace, document.Project.Id, document.Id, id);
RaiseDiagnosticEvent(mutex, source, workspace, document.Project.Id, document.Id, id);
var id2 = Tuple.Create(workspace, document.Project, document);
RaiseDiagnosticEvent(set, source, workspace, document.Project.Id, document.Id, id2);
RaiseDiagnosticEvent(mutex, source, workspace, document.Project.Id, document.Id, id2);
RaiseDiagnosticEvent(set, source, workspace, document2.Project.Id, document2.Id, Tuple.Create(workspace, document2));
RaiseDiagnosticEvent(mutex, source, workspace, document2.Project.Id, document2.Id, Tuple.Create(workspace, document2));
var id3 = Tuple.Create(workspace, document.Project);
RaiseDiagnosticEvent(set, source, workspace, document.Project.Id, null, id3);
RaiseDiagnosticEvent(set, source, workspace, null, null, Tuple.Create(workspace));
RaiseDiagnosticEvent(mutex, source, workspace, document.Project.Id, null, id3);
RaiseDiagnosticEvent(mutex, source, workspace, null, null, Tuple.Create(workspace));
var data1 = diagnosticService.GetDiagnostics(workspace, null, null, null, false, CancellationToken.None);
Assert.Equal(5, data1.Count());
......@@ -90,13 +90,75 @@ public void TestGetDiagnostics2()
}
}
[Fact, Trait(Traits.Feature, Traits.Features.Diagnostics)]
public void TestCleared()
{
using (var workspace = new TestWorkspace(TestExportProvider.ExportProviderWithCSharpAndVisualBasic))
{
var mutex = new ManualResetEvent(false);
var document = workspace.CurrentSolution.AddProject("TestProject", "TestProject", LanguageNames.CSharp).AddDocument("TestDocument", string.Empty);
var document2 = document.Project.AddDocument("TestDocument2", string.Empty);
var diagnosticService = new DiagnosticService(AsynchronousOperationListenerProvider.NullProvider);
var source1 = new TestDiagnosticUpdateSource(support: false, diagnosticData: null);
diagnosticService.Register(source1);
var source2 = new TestDiagnosticUpdateSource(support: false, diagnosticData: null);
diagnosticService.Register(source2);
diagnosticService.DiagnosticsUpdated += MarkSet;
// add bunch of data to the service for both sources
RaiseDiagnosticEvent(mutex, source1, workspace, document.Project.Id, document.Id, Tuple.Create(workspace, document));
RaiseDiagnosticEvent(mutex, source1, workspace, document.Project.Id, document.Id, Tuple.Create(workspace, document.Project, document));
RaiseDiagnosticEvent(mutex, source1, workspace, document2.Project.Id, document2.Id, Tuple.Create(workspace, document2));
RaiseDiagnosticEvent(mutex, source2, workspace, document.Project.Id, null, Tuple.Create(workspace, document.Project));
RaiseDiagnosticEvent(mutex, source2, workspace, null, null, Tuple.Create(workspace));
// confirm data is there.
var data1 = diagnosticService.GetDiagnostics(workspace, null, null, null, false, CancellationToken.None);
Assert.Equal(5, data1.Count());
diagnosticService.DiagnosticsUpdated -= MarkSet;
// confirm clear for a source
mutex.Reset();
var count = 0;
diagnosticService.DiagnosticsUpdated += MarkCalled;
source1.RaiseDiagnosticsClearedEvent();
mutex.WaitOne();
// confirm there are 2 data left
var data2 = diagnosticService.GetDiagnostics(workspace, null, null, null, false, CancellationToken.None);
Assert.Equal(2, data2.Count());
void MarkCalled(object sender, DiagnosticsUpdatedArgs args)
{
// event is serialized. no concurrent call
if (++count == 3)
{
mutex.Set();
}
}
void MarkSet(object sender, DiagnosticsUpdatedArgs args)
{
mutex.Set();
}
}
}
private static DiagnosticData RaiseDiagnosticEvent(ManualResetEvent set, TestDiagnosticUpdateSource source, TestWorkspace workspace, ProjectId project, DocumentId document, object id)
{
set.Reset();
var diagnostic = CreateDiagnosticData(workspace, project, document);
source.RaiseUpdateEvent(
source.RaiseDiagnosticsUpdatedEvent(
DiagnosticsUpdatedArgs.DiagnosticsCreated(id, workspace, workspace.CurrentSolution, project, document, ImmutableArray.Create(diagnostic)));
set.WaitOne();
......@@ -126,16 +188,22 @@ public TestDiagnosticUpdateSource(bool support, DiagnosticData[] diagnosticData)
public bool SupportGetDiagnostics { get { return _support; } }
public event EventHandler<DiagnosticsUpdatedArgs> DiagnosticsUpdated;
public event EventHandler DiagnosticsCleared;
public ImmutableArray<DiagnosticData> GetDiagnostics(Workspace workspace, ProjectId projectId, DocumentId documentId, object id, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default)
{
return _support ? _diagnosticData : ImmutableArray<DiagnosticData>.Empty;
}
public void RaiseUpdateEvent(DiagnosticsUpdatedArgs args)
public void RaiseDiagnosticsUpdatedEvent(DiagnosticsUpdatedArgs args)
{
DiagnosticsUpdated?.Invoke(this, args);
}
public void RaiseDiagnosticsClearedEvent()
{
DiagnosticsCleared?.Invoke(this, EventArgs.Empty);
}
}
}
}
......@@ -89,6 +89,7 @@ public void RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs args)
}
public event EventHandler<DiagnosticsUpdatedArgs> DiagnosticsUpdated;
public event EventHandler DiagnosticsCleared { add { } remove { } }
public bool SupportGetDiagnostics => false;
......
......@@ -28,6 +28,7 @@ public ImmutableArray<DiagnosticData> GetDiagnostics(Workspace workspace, Projec
}
public event EventHandler<DiagnosticsUpdatedArgs> DiagnosticsUpdated;
public event EventHandler DiagnosticsCleared { add { } remove { } }
public void RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs args)
{
......
......@@ -40,6 +40,7 @@ public IIncrementalAnalyzer CreateIncrementalAnalyzer(Workspace workspace)
}
public event EventHandler<DiagnosticsUpdatedArgs> DiagnosticsUpdated;
public event EventHandler DiagnosticsCleared { add { } remove { } }
// this only support push model, pull model will be provided by DiagnosticService by caching everything this one pushed
public bool SupportGetDiagnostics => false;
......
......@@ -43,6 +43,19 @@ private DiagnosticAnalyzerService(IDiagnosticUpdateSourceRegistrationService reg
}
}
public event EventHandler DiagnosticsCleared
{
add
{
// don't do anything. this update source doesn't use cleared event
}
remove
{
// don't do anything. this update source doesn't use cleared event
}
}
internal void RaiseDiagnosticsUpdated(DiagnosticsUpdatedArgs args)
{
// all diagnostics events are serialized.
......
......@@ -55,11 +55,8 @@ public DiagnosticService(IAsynchronousOperationListenerProvider listenerProvider
}
}
private void RaiseDiagnosticsUpdated(object sender, DiagnosticsUpdatedArgs args)
private void RaiseDiagnosticsUpdated(IDiagnosticUpdateSource source, DiagnosticsUpdatedArgs args)
{
Contract.ThrowIfNull(sender);
var source = (IDiagnosticUpdateSource)sender;
var ev = _eventMap.GetEventHandlers<EventHandler<DiagnosticsUpdatedArgs>>(DiagnosticsUpdatedEventName);
if (!RequireRunningEventTasks(source, ev))
{
......@@ -75,7 +72,35 @@ private void RaiseDiagnosticsUpdated(object sender, DiagnosticsUpdatedArgs args)
return;
}
ev.RaiseEvent(handler => handler(sender, args));
ev.RaiseEvent(handler => handler(source, args));
}).CompletesAsyncOperation(eventToken);
}
private void RaiseDiagnosticsCleared(IDiagnosticUpdateSource source)
{
var ev = _eventMap.GetEventHandlers<EventHandler<DiagnosticsUpdatedArgs>>(DiagnosticsUpdatedEventName);
if (!RequireRunningEventTasks(source, ev))
{
return;
}
var eventToken = _listener.BeginAsyncOperation(DiagnosticsUpdatedEventName);
_eventQueue.ScheduleTask(() =>
{
using (var pooledObject = SharedPools.Default<List<DiagnosticsUpdatedArgs>>().GetPooledObject())
{
var removed = pooledObject.Object;
if (!ClearDiagnosticsReportedBySource(source, removed))
{
// there is no change, nothing to raise events for.
return;
}
foreach (var args in removed)
{
ev.RaiseEvent(handler => handler(source, args));
}
}
}).CompletesAsyncOperation(eventToken);
}
......@@ -150,10 +175,46 @@ private bool UpdateDataMap(IDiagnosticUpdateSource source, DiagnosticsUpdatedArg
}
}
private bool ClearDiagnosticsReportedBySource(IDiagnosticUpdateSource source, List<DiagnosticsUpdatedArgs> removed)
{
// we expect source who uses this ability to have small number of diagnostics.
lock (_gate)
{
Debug.Assert(_updateSources.Contains(source));
// 2 different workspaces (ex, PreviewWorkspaces) can return same Args.Id, we need to
// distinguish them. so we separate diagnostics per workspace map.
if (!_map.TryGetValue(source, out var workspaceMap))
{
return false;
}
foreach (var (workspace, map) in workspaceMap)
{
foreach (var (id, data) in map)
{
removed.Add(DiagnosticsUpdatedArgs.DiagnosticsRemoved(id, data.Workspace, solution: null, data.ProjectId, data.DocumentId));
}
}
// all diagnostics from the source is cleared
_map.Remove(source);
return true;
}
}
private void OnDiagnosticsUpdated(object sender, DiagnosticsUpdatedArgs e)
{
AssertIfNull(e.Diagnostics);
RaiseDiagnosticsUpdated(sender, e);
// all events are serialized by async event handler
RaiseDiagnosticsUpdated((IDiagnosticUpdateSource)sender, e);
}
private void OnCleared(object sender, EventArgs e)
{
// all events are serialized by async event handler
RaiseDiagnosticsCleared((IDiagnosticUpdateSource)sender);
}
public IEnumerable<DiagnosticData> GetDiagnostics(
......
......@@ -28,7 +28,9 @@ public void Register(IDiagnosticUpdateSource source)
}
_updateSources = _updateSources.Add(source);
source.DiagnosticsUpdated += OnDiagnosticsUpdated;
source.DiagnosticsCleared += OnCleared;
}
}
}
......
......@@ -16,6 +16,11 @@ internal interface IDiagnosticUpdateSource
/// </summary>
event EventHandler<DiagnosticsUpdatedArgs> DiagnosticsUpdated;
/// <summary>
/// Raise this when all diagnostics reported from this update source has cleared
/// </summary>
event EventHandler DiagnosticsCleared;
/// <summary>
/// Return true if the source supports GetDiagnostics API otherwise, return false so that the engine can cache data from DiagnosticsUpdated in memory
/// </summary>
......
......@@ -76,6 +76,7 @@ internal class ExternalErrorDiagnosticUpdateSource : IDiagnosticUpdateSource
public event EventHandler<bool> BuildStarted;
public event EventHandler<DiagnosticsUpdatedArgs> DiagnosticsUpdated;
public event EventHandler DiagnosticsCleared { add { } remove { } }
public bool IsInProgress => BuildInprogressState != null;
......
......@@ -346,6 +346,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics
End Property
Public Event DiagnosticsUpdated As EventHandler(Of DiagnosticsUpdatedArgs) Implements IDiagnosticUpdateSource.DiagnosticsUpdated
Public Event DiagnosticsCleared As EventHandler Implements IDiagnosticUpdateSource.DiagnosticsCleared
Public Function GetDiagnostics(workspace As Microsoft.CodeAnalysis.Workspace, projectId As ProjectId, documentId As DocumentId, id As Object, includeSuppressedDiagnostics As Boolean, cancellationToken As CancellationToken) As ImmutableArray(Of DiagnosticData) Implements IDiagnosticUpdateSource.GetDiagnostics
Return If(includeSuppressedDiagnostics, _data, _data.WhereAsArray(Function(d) Not d.IsSuppressed))
......
......@@ -43,6 +43,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.EditAndContinue
End Property
Public Event DiagnosticsUpdated As EventHandler(Of DiagnosticsUpdatedArgs) Implements IDiagnosticUpdateSource.DiagnosticsUpdated
Public Event DiagnosticsCleared As EventHandler Implements IDiagnosticUpdateSource.DiagnosticsCleared
Public Function GetDiagnostics(workspace As Microsoft.CodeAnalysis.Workspace, projectId As ProjectId, documentId As DocumentId, id As Object, includeSuppressedDiagnostics As Boolean, cancellationToken As CancellationToken) As ImmutableArray(Of DiagnosticData) Implements IDiagnosticUpdateSource.GetDiagnostics
Return If(includeSuppressedDiagnostics, _data, _data.WhereAsArray(Function(d) Not d.IsSuppressed))
......
......@@ -444,7 +444,6 @@ public static DiagnosticData Create(Document document, Diagnostic diagnostic)
return additionalProperties == null
? properties
: properties.AddRange(additionalProperties);
throw new NotImplementedException();
}
/// <summary>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册