diff --git a/src/EditorFeatures/CSharpTest/Squiggles/ErrorSquiggleProducerTests.cs b/src/EditorFeatures/CSharpTest/Squiggles/ErrorSquiggleProducerTests.cs index 4052da33329eb..6c8147d0e817a 100644 --- a/src/EditorFeatures/CSharpTest/Squiggles/ErrorSquiggleProducerTests.cs +++ b/src/EditorFeatures/CSharpTest/Squiggles/ErrorSquiggleProducerTests.cs @@ -15,9 +15,8 @@ using Microsoft.CodeAnalysis.CSharp.RemoveUnnecessaryImports; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo; -using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; +using Microsoft.CodeAnalysis.Editor.UnitTests; using Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics; -using Microsoft.CodeAnalysis.Editor.UnitTests.Extensions; using Microsoft.CodeAnalysis.Editor.UnitTests.Squiggles; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Options; @@ -280,6 +279,10 @@ public async Task TestNoErrorsAfterProjectRemoved() Assert.True(spans.Count == 0); } + private static readonly TestComposition s_mockComposition = EditorTestCompositions.EditorFeatures + .AddExcludedPartTypes(typeof(IDiagnosticAnalyzerService)) + .AddParts(typeof(MockDiagnosticAnalyzerService)); + [WpfFact] public async Task BuildErrorZeroLengthSpan() { @@ -294,21 +297,23 @@ class Test "; - using var workspace = TestWorkspace.Create(workspaceXml); + using var workspace = TestWorkspace.Create(workspaceXml, composition: s_mockComposition); var document = workspace.Documents.First(); var updateArgs = DiagnosticsUpdatedArgs.DiagnosticsCreated( - new object(), workspace, workspace.CurrentSolution, document.Project.Id, document.Id, - ImmutableArray.Create( - TestDiagnosticTagProducer.CreateDiagnosticData(document, new TextSpan(0, 0)), - TestDiagnosticTagProducer.CreateDiagnosticData(document, new TextSpan(0, 1)))); + new object(), workspace, workspace.CurrentSolution, document.Project.Id, document.Id, + ImmutableArray.Create( + TestDiagnosticTagProducer.CreateDiagnosticData(document, new TextSpan(0, 0)), + TestDiagnosticTagProducer.CreateDiagnosticData(document, new TextSpan(0, 1)))); var spans = await TestDiagnosticTagProducer.GetErrorsFromUpdateSource(workspace, updateArgs); - Assert.Equal(1, spans.Count()); + Assert.Equal(2, spans.Count()); var first = spans.First(); + var second = spans.Last(); Assert.Equal(1, first.Span.Span.Length); + Assert.Equal(1, second.Span.Span.Length); } [WpfFact] @@ -325,14 +330,14 @@ class Test "; - using var workspace = TestWorkspace.Create(workspaceXml); + using var workspace = TestWorkspace.Create(workspaceXml, composition: s_mockComposition); var document = workspace.Documents.First(); var updateArgs = DiagnosticsUpdatedArgs.DiagnosticsCreated( - new LiveId(), workspace, workspace.CurrentSolution, document.Project.Id, document.Id, - ImmutableArray.Create( - TestDiagnosticTagProducer.CreateDiagnosticData(document, new TextSpan(0, 0)), - TestDiagnosticTagProducer.CreateDiagnosticData(document, new TextSpan(0, 1)))); + new LiveId(), workspace, workspace.CurrentSolution, document.Project.Id, document.Id, + ImmutableArray.Create( + TestDiagnosticTagProducer.CreateDiagnosticData(document, new TextSpan(0, 0)), + TestDiagnosticTagProducer.CreateDiagnosticData(document, new TextSpan(0, 1)))); var spans = await TestDiagnosticTagProducer.GetErrorsFromUpdateSource(workspace, updateArgs); diff --git a/src/EditorFeatures/CSharpTest/TodoComment/NoCompilationTodoCommentTests.cs b/src/EditorFeatures/CSharpTest/TodoComment/NoCompilationTodoCommentTests.cs index c473e9c2e577f..a378b93eec6c0 100644 --- a/src/EditorFeatures/CSharpTest/TodoComment/NoCompilationTodoCommentTests.cs +++ b/src/EditorFeatures/CSharpTest/TodoComment/NoCompilationTodoCommentTests.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; +using Microsoft.CodeAnalysis.Editor.UnitTests; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Test.Utilities; diff --git a/src/EditorFeatures/Core.Wpf/InlineDiagnostics/InlineDiagnosticsTaggerProvider.cs b/src/EditorFeatures/Core.Wpf/InlineDiagnostics/InlineDiagnosticsTaggerProvider.cs index cf26c23c2eed4..f6a424427dff3 100644 --- a/src/EditorFeatures/Core.Wpf/InlineDiagnostics/InlineDiagnosticsTaggerProvider.cs +++ b/src/EditorFeatures/Core.Wpf/InlineDiagnostics/InlineDiagnosticsTaggerProvider.cs @@ -41,13 +41,14 @@ internal class InlineDiagnosticsTaggerProvider : AbstractDiagnosticsAdornmentTag public InlineDiagnosticsTaggerProvider( IThreadingContext threadingContext, IDiagnosticService diagnosticService, + IDiagnosticAnalyzerService analyzerService, IGlobalOptionService globalOptions, [Import(AllowDefault = true)] ITextBufferVisibilityTracker? visibilityTracker, IAsynchronousOperationListenerProvider listenerProvider, IEditorFormatMapService editorFormatMapService, IClassificationFormatMapService classificationFormatMapService, IClassificationTypeRegistryService classificationTypeRegistryService) - : base(threadingContext, diagnosticService, globalOptions, visibilityTracker, listenerProvider) + : base(threadingContext, diagnosticService, analyzerService, globalOptions, visibilityTracker, listenerProvider) { _editorFormatMap = editorFormatMapService.GetEditorFormatMap("text"); _classificationFormatMapService = classificationFormatMapService; diff --git a/src/EditorFeatures/Core/Diagnostics/AbstractDiagnosticsAdornmentTaggerProvider.cs b/src/EditorFeatures/Core/Diagnostics/AbstractDiagnosticsAdornmentTaggerProvider.cs index 88c97ee7ff139..729ee70ccb197 100644 --- a/src/EditorFeatures/Core/Diagnostics/AbstractDiagnosticsAdornmentTaggerProvider.cs +++ b/src/EditorFeatures/Core/Diagnostics/AbstractDiagnosticsAdornmentTaggerProvider.cs @@ -22,31 +22,28 @@ internal abstract class AbstractDiagnosticsAdornmentTaggerProvider : protected AbstractDiagnosticsAdornmentTaggerProvider( IThreadingContext threadingContext, IDiagnosticService diagnosticService, + IDiagnosticAnalyzerService analyzerService, IGlobalOptionService globalOptions, ITextBufferVisibilityTracker? visibilityTracker, IAsynchronousOperationListenerProvider listenerProvider) - : base(threadingContext, diagnosticService, globalOptions, visibilityTracker, listenerProvider.GetListener(FeatureAttribute.ErrorSquiggles)) + : base(threadingContext, diagnosticService, analyzerService, globalOptions, visibilityTracker, listenerProvider.GetListener(FeatureAttribute.ErrorSquiggles)) { } protected internal sealed override bool IsEnabled => true; protected internal sealed override ITagSpan? CreateTagSpan( - Workspace workspace, bool isLiveUpdate, SnapshotSpan span, DiagnosticData data) + Workspace workspace, SnapshotSpan span, DiagnosticData data) { var errorTag = CreateTag(workspace, data); if (errorTag == null) - { return null; - } - // Live update squiggles have to be at least 1 character long. - var minimumLength = isLiveUpdate ? 1 : 0; - var adjustedSpan = AdjustSnapshotSpan(span, minimumLength); + // Ensure the diagnostic has at least length 1. Tags must have a non-empty length in order to actually show + // up in the editor. + var adjustedSpan = AdjustSnapshotSpan(span); if (adjustedSpan.Length == 0) - { return null; - } return new TagSpan(adjustedSpan, errorTag); } @@ -78,8 +75,9 @@ protected static object CreateToolTipContent(Workspace workspace, DiagnosticData new ClassifiedTextRun(ClassificationTypeNames.Text, diagnostic.Message))); } - protected virtual SnapshotSpan AdjustSnapshotSpan(SnapshotSpan span, int minimumLength) - => AdjustSnapshotSpan(span, minimumLength, int.MaxValue); + // By default, tags must have at least length '1' so that they can be visible in the UI layer. + protected virtual SnapshotSpan AdjustSnapshotSpan(SnapshotSpan span) + => AdjustSnapshotSpan(span, minimumLength: 1, maximumLength: int.MaxValue); protected static SnapshotSpan AdjustSnapshotSpan(SnapshotSpan span, int minimumLength, int maximumLength) { diff --git a/src/EditorFeatures/Core/Diagnostics/AbstractDiagnosticsTaggerProvider.cs b/src/EditorFeatures/Core/Diagnostics/AbstractDiagnosticsTaggerProvider.cs index 8957bc99b76ca..4d7c2b0f433f7 100644 --- a/src/EditorFeatures/Core/Diagnostics/AbstractDiagnosticsTaggerProvider.cs +++ b/src/EditorFeatures/Core/Diagnostics/AbstractDiagnosticsTaggerProvider.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Immutable; using System.Linq; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor; @@ -14,9 +13,7 @@ using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Editor.Tagging; using Microsoft.CodeAnalysis.ErrorReporting; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text.Shared.Extensions; @@ -24,95 +21,36 @@ using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Tagging; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics { /// - /// Diagnostics works slightly differently than the rest of the taggers. For diagnostics, - /// we want to try to have an individual tagger per diagnostic producer per buffer. - /// However, the editor only allows a single tagger provider per buffer. So in order to - /// get the abstraction we want, we create one outer tagger provider that is associated - /// with the buffer. Then, under the covers, we create individual async taggers for each - /// diagnostic producer we hear about for that buffer. - /// - /// In essence, we have one tagger that wraps a multitude of taggers it delegates to. - /// Each of these taggers is nicely asynchronous and properly works within the async - /// tagging infrastructure. + /// Base type for all taggers that interact with the and produce tags for + /// the diagnostics with different UI presentations. /// internal abstract partial class AbstractDiagnosticsTaggerProvider : AsynchronousTaggerProvider where TTag : ITag { private readonly IDiagnosticService _diagnosticService; - - /// - /// Keep track of the ITextSnapshot for the open Document that was used when diagnostics were - /// produced for it. We need that because the DiagnoticService does not keep track of this - /// snapshot (so as to not hold onto a lot of memory), which means when we query it for - /// diagnostics, we don't know how to map the span of the diagnostic to the current snapshot - /// we're tagging. - /// - private static readonly ConditionalWeakTable _diagnosticIdToTextSnapshot = new(); + private readonly IDiagnosticAnalyzerService _analyzerService; protected AbstractDiagnosticsTaggerProvider( IThreadingContext threadingContext, IDiagnosticService diagnosticService, + IDiagnosticAnalyzerService analyzerService, IGlobalOptionService globalOptions, ITextBufferVisibilityTracker? visibilityTracker, IAsynchronousOperationListener listener) : base(threadingContext, globalOptions, visibilityTracker, listener) { _diagnosticService = diagnosticService; - _diagnosticService.DiagnosticsUpdated += OnDiagnosticsUpdated; + _analyzerService = analyzerService; } protected internal abstract bool IsEnabled { get; } protected internal abstract bool SupportsDignosticMode(DiagnosticMode mode); protected internal abstract bool IncludeDiagnostic(DiagnosticData data); - protected internal abstract ITagSpan? CreateTagSpan(Workspace workspace, bool isLiveUpdate, SnapshotSpan span, DiagnosticData data); - - private void OnDiagnosticsUpdated(object? sender, DiagnosticsUpdatedArgs e) - { - if (e.Solution == null || e.DocumentId == null) - { - return; - } - - if (_diagnosticIdToTextSnapshot.TryGetValue(e.Id, out var snapshot)) - { - return; - } - - var document = e.Solution.GetDocument(e.DocumentId); - - // If we couldn't find a normal document, and all features are enabled for source generated documents, - // attempt to locate a matching source generated document in the project. - if (document is null - && e.Workspace.Services.GetService()?.Options.EnableOpeningSourceGeneratedFiles == true - && e.Solution.GetProject(e.DocumentId.ProjectId) is { } project) - { - var documentId = e.DocumentId; - document = ThreadingContext.JoinableTaskFactory.Run(() => project.GetSourceGeneratedDocumentAsync(documentId, CancellationToken.None).AsTask()); - } - - // Open documents *should* always have their SourceText available, but we cannot guarantee - // (i.e. assert) that they do. That's because we're not on the UI thread here, so there's - // a small risk that between calling .IsOpen the file may then close, which then would - // cause TryGetText to fail. However, that's ok. In that case, if we do need to tag this - // document, we'll just use the current editor snapshot. If that's the same, then the tags - // will be hte same. If it is different, we'll eventually hear about the new diagnostics - // for it and we'll reach our fixed point. - if (document != null && document.IsOpen()) - { - // This should always be fast since the document is open. - var sourceText = document.State.GetTextSynchronously(cancellationToken: default); - snapshot = sourceText.FindCorrespondingEditorTextSnapshot(); - if (snapshot != null) - { - _diagnosticIdToTextSnapshot.GetValue(e.Id, _ => snapshot); - } - } - } + protected internal abstract ITagSpan? CreateTagSpan(Workspace workspace, SnapshotSpan span, DiagnosticData data); protected override TaggerDelay EventChangeDelay => TaggerDelay.Short; protected override TaggerDelay AddedTagNotificationDelay => TaggerDelay.OnIdle; @@ -137,7 +75,7 @@ protected override ITaggerEventSource CreateEventSource(ITextView? textView, ITe /// the diagnostic containing the location(s). /// an array of locations that should have the tag applied. protected internal virtual ImmutableArray GetLocationsToTag(DiagnosticData diagnosticData) - => diagnosticData.DataLocation is object ? ImmutableArray.Create(diagnosticData.DataLocation) : ImmutableArray.Empty; + => diagnosticData.DataLocation is not null ? ImmutableArray.Create(diagnosticData.DataLocation) : ImmutableArray.Empty; protected override Task ProduceTagsAsync( TaggerContext context, DocumentSnapshotSpan spanToTag, int? caretPosition, CancellationToken cancellationToken) @@ -170,60 +108,14 @@ private async Task ProduceTagsAsync( var suppressedDiagnosticsSpans = (NormalizedSnapshotSpanCollection?)null; buffer?.Properties.TryGetProperty(PredefinedPreviewTaggerKeys.SuppressDiagnosticsSpansKey, out suppressedDiagnosticsSpans); - var buckets = diagnosticMode switch - { - DiagnosticMode.Pull => _diagnosticService.GetPullDiagnosticBuckets(workspace, document.Project.Id, document.Id, diagnosticMode, cancellationToken), - DiagnosticMode.Push => _diagnosticService.GetPushDiagnosticBuckets(workspace, document.Project.Id, document.Id, diagnosticMode, cancellationToken), - _ => throw ExceptionUtilities.UnexpectedValue(diagnosticMode), - }; - - foreach (var bucket in buckets) - { - await ProduceTagsAsync( - context, spanToTag, workspace, document, - suppressedDiagnosticsSpans, bucket, cancellationToken).ConfigureAwait(false); - } - } + var sourceText = editorSnapshot.AsText(); - private async Task ProduceTagsAsync( - TaggerContext context, DocumentSnapshotSpan spanToTag, - Workspace workspace, Document document, - NormalizedSnapshotSpanCollection? suppressedDiagnosticsSpans, - DiagnosticBucket bucket, CancellationToken cancellationToken) - { try { - var diagnosticMode = GlobalOptions.GetDiagnosticMode(InternalDiagnosticsOptions.NormalDiagnosticMode); - - var id = bucket.Id; - var diagnostics = await _diagnosticService.GetPushDiagnosticsAsync( - workspace, document.Project.Id, document.Id, id, - includeSuppressedDiagnostics: false, - diagnosticMode, - cancellationToken).ConfigureAwait(false); - - var isLiveUpdate = id is ISupportLiveUpdate; + var diagnostics = await _analyzerService.GetDiagnosticsForSpanAsync( + document, range: null, cancellationToken: cancellationToken).ConfigureAwait(false); var requestedSpan = spanToTag.SnapshotSpan; - var editorSnapshot = requestedSpan.Snapshot; - - // Try to get the text snapshot that these diagnostics were created against. - // This may fail if this tagger was created *after* the notification for the - // diagnostics was already issued. That's ok. We'll take the spans as reported - // and apply them directly to the snapshot we have. Either no new changes will - // have happened, and these spans will be accurate, or a change will happen - // and we'll hear about and it update the spans shortly to the right position. - // - // Also, only use the diagnoticSnapshot if its text buffer matches our. The text - // buffer might be different if the file was closed/reopened. - // Note: when this happens, the diagnostic service will reanalyze the file. So - // up to date diagnostic spans will appear shortly after this. - _diagnosticIdToTextSnapshot.TryGetValue(id, out var diagnosticSnapshot); - diagnosticSnapshot = diagnosticSnapshot?.TextBuffer == editorSnapshot.TextBuffer - ? diagnosticSnapshot - : editorSnapshot; - - var sourceText = diagnosticSnapshot.AsText(); foreach (var diagnosticData in diagnostics) { @@ -241,16 +133,14 @@ private async Task ProduceTagsAsync( // editorSnapshot. var diagnosticSpans = this.GetLocationsToTag(diagnosticData) - .Select(location => GetDiagnosticSnapshotSpan(location, diagnosticSnapshot, editorSnapshot, sourceText)); + .Select(location => GetDiagnosticSnapshotSpan(location, editorSnapshot, sourceText)); foreach (var diagnosticSpan in diagnosticSpans) { if (diagnosticSpan.IntersectsWith(requestedSpan) && !IsSuppressed(suppressedDiagnosticsSpans, diagnosticSpan)) { - var tagSpan = this.CreateTagSpan(workspace, isLiveUpdate, diagnosticSpan, diagnosticData); + var tagSpan = this.CreateTagSpan(workspace, diagnosticSpan, diagnosticData); if (tagSpan != null) - { context.AddTag(tagSpan); - } } } } @@ -259,18 +149,13 @@ private async Task ProduceTagsAsync( catch (ArgumentOutOfRangeException ex) when (FatalError.ReportAndCatch(ex)) { // https://devdiv.visualstudio.com/DefaultCollection/DevDiv/_workitems?id=428328&_a=edit&triage=false - // explicitly report NFW to find out what is causing us for out of range. - // stop crashing on such occasions + // explicitly report NFW to find out what is causing us for out of range. stop crashing on such + // occasions return; } - static SnapshotSpan GetDiagnosticSnapshotSpan(DiagnosticDataLocation diagnosticDataLocation, ITextSnapshot diagnosticSnapshot, - ITextSnapshot editorSnapshot, SourceText sourceText) - { - return DiagnosticData.GetExistingOrCalculatedTextSpan(diagnosticDataLocation, sourceText) - .ToSnapshotSpan(diagnosticSnapshot) - .TranslateTo(editorSnapshot, SpanTrackingMode.EdgeExclusive); - } + static SnapshotSpan GetDiagnosticSnapshotSpan(DiagnosticDataLocation diagnosticDataLocation, ITextSnapshot editorSnapshot, SourceText sourceText) + => DiagnosticData.GetExistingOrCalculatedTextSpan(diagnosticDataLocation, sourceText).ToSnapshotSpan(editorSnapshot); } private static bool IsSuppressed(NormalizedSnapshotSpanCollection? suppressedSpans, SnapshotSpan span) diff --git a/src/EditorFeatures/Core/Diagnostics/DiagnosticsClassificationTaggerProvider.cs b/src/EditorFeatures/Core/Diagnostics/DiagnosticsClassificationTaggerProvider.cs index 6ad73f3dfec0d..3f2334bfbd361 100644 --- a/src/EditorFeatures/Core/Diagnostics/DiagnosticsClassificationTaggerProvider.cs +++ b/src/EditorFeatures/Core/Diagnostics/DiagnosticsClassificationTaggerProvider.cs @@ -46,11 +46,12 @@ internal partial class DiagnosticsClassificationTaggerProvider : AbstractDiagnos public DiagnosticsClassificationTaggerProvider( IThreadingContext threadingContext, IDiagnosticService diagnosticService, + IDiagnosticAnalyzerService analyzerService, ClassificationTypeMap typeMap, EditorOptionsService editorOptionsService, [Import(AllowDefault = true)] ITextBufferVisibilityTracker? visibilityTracker, IAsynchronousOperationListenerProvider listenerProvider) - : base(threadingContext, diagnosticService, editorOptionsService.GlobalOptions, visibilityTracker, listenerProvider.GetListener(FeatureAttribute.Classification)) + : base(threadingContext, diagnosticService, analyzerService, editorOptionsService.GlobalOptions, visibilityTracker, listenerProvider.GetListener(FeatureAttribute.Classification)) { _typeMap = typeMap; _classificationTag = new ClassificationTag(_typeMap.GetClassificationType(ClassificationTypeDefinitions.UnnecessaryCode)); @@ -92,7 +93,7 @@ protected internal override bool IncludeDiagnostic(DiagnosticData data) return true; } - protected internal override ITagSpan CreateTagSpan(Workspace workspace, bool isLiveUpdate, SnapshotSpan span, DiagnosticData data) + protected internal override ITagSpan CreateTagSpan(Workspace workspace, SnapshotSpan span, DiagnosticData data) => new TagSpan(span, _classificationTag); protected internal override ImmutableArray GetLocationsToTag(DiagnosticData diagnosticData) diff --git a/src/EditorFeatures/Core/Diagnostics/DiagnosticsSquiggleTaggerProvider.cs b/src/EditorFeatures/Core/Diagnostics/DiagnosticsSquiggleTaggerProvider.cs index 02b28ad345eb0..c556e2d7577dc 100644 --- a/src/EditorFeatures/Core/Diagnostics/DiagnosticsSquiggleTaggerProvider.cs +++ b/src/EditorFeatures/Core/Diagnostics/DiagnosticsSquiggleTaggerProvider.cs @@ -37,10 +37,11 @@ internal partial class DiagnosticsSquiggleTaggerProvider : AbstractDiagnosticsAd public DiagnosticsSquiggleTaggerProvider( IThreadingContext threadingContext, IDiagnosticService diagnosticService, + IDiagnosticAnalyzerService analyzerService, IGlobalOptionService globalOptions, [Import(AllowDefault = true)] ITextBufferVisibilityTracker? visibilityTracker, IAsynchronousOperationListenerProvider listenerProvider) - : base(threadingContext, diagnosticService, globalOptions, visibilityTracker, listenerProvider) + : base(threadingContext, diagnosticService, analyzerService, globalOptions, visibilityTracker, listenerProvider) { } diff --git a/src/EditorFeatures/Core/Diagnostics/DiagnosticsSuggestionTaggerProvider.cs b/src/EditorFeatures/Core/Diagnostics/DiagnosticsSuggestionTaggerProvider.cs index 0779cb93ff45f..6d63f95e01c69 100644 --- a/src/EditorFeatures/Core/Diagnostics/DiagnosticsSuggestionTaggerProvider.cs +++ b/src/EditorFeatures/Core/Diagnostics/DiagnosticsSuggestionTaggerProvider.cs @@ -37,10 +37,11 @@ internal sealed partial class DiagnosticsSuggestionTaggerProvider : public DiagnosticsSuggestionTaggerProvider( IThreadingContext threadingContext, IDiagnosticService diagnosticService, + IDiagnosticAnalyzerService analyzerService, IGlobalOptionService globalOptions, [Import(AllowDefault = true)] ITextBufferVisibilityTracker? visibilityTracker, IAsynchronousOperationListenerProvider listenerProvider) - : base(threadingContext, diagnosticService, globalOptions, visibilityTracker, listenerProvider) + : base(threadingContext, diagnosticService, analyzerService, globalOptions, visibilityTracker, listenerProvider) { } @@ -59,7 +60,7 @@ protected override IErrorTag CreateTag(Workspace workspace, DiagnosticData diagn PredefinedErrorTypeNames.HintedSuggestion, CreateToolTipContent(workspace, diagnostic)); - protected override SnapshotSpan AdjustSnapshotSpan(SnapshotSpan snapshotSpan, int minimumLength) + protected override SnapshotSpan AdjustSnapshotSpan(SnapshotSpan snapshotSpan) { // We always want suggestion tags to be two characters long. return AdjustSnapshotSpan(snapshotSpan, minimumLength: 2, maximumLength: 2); diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticsSquiggleTaggerProviderTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticsSquiggleTaggerProviderTests.cs index 628b2a77fcc06..99f8cbf6f86f4 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticsSquiggleTaggerProviderTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticsSquiggleTaggerProviderTests.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; @@ -30,7 +31,9 @@ namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics public class DiagnosticsSquiggleTaggerProviderTests { private static readonly TestComposition s_compositionWithMockDiagnosticService = - EditorTestCompositions.EditorFeatures.AddExcludedPartTypes(typeof(IDiagnosticService)).AddParts(typeof(MockDiagnosticService)); + EditorTestCompositions.EditorFeatures + .AddExcludedPartTypes(typeof(IDiagnosticService), typeof(IDiagnosticAnalyzerService)) + .AddParts(typeof(MockDiagnosticService), typeof(MockDiagnosticAnalyzerService)); [WpfFact] public async Task Test_TagSourceDiffer() @@ -125,6 +128,7 @@ public async Task TestWithMockDiagnosticService_TaggerProviderCreatedBeforeIniti var listenerProvider = workspace.ExportProvider.GetExportedValue(); var diagnosticService = Assert.IsType(workspace.ExportProvider.GetExportedValue()); + var analyzerService = Assert.IsType(workspace.ExportProvider.GetExportedValue()); var provider = workspace.ExportProvider.GetExportedValues().OfType().Single(); // Create the tagger before the first diagnostic event has been fired. @@ -134,7 +138,7 @@ public async Task TestWithMockDiagnosticService_TaggerProviderCreatedBeforeIniti // Now product the first diagnostic and fire the events. var tree = await workspace.CurrentSolution.Projects.Single().Documents.Single().GetRequiredSyntaxTreeAsync(CancellationToken.None); var span = TextSpan.FromBounds(0, 5); - diagnosticService.CreateDiagnosticAndFireEvents(workspace, Location.Create(tree, span)); + diagnosticService.CreateDiagnosticAndFireEvents(workspace, analyzerService, Location.Create(tree, span)); using var disposable = tagger as IDisposable; await listenerProvider.GetWaiter(FeatureAttribute.DiagnosticService).ExpeditedWaitAsync(); @@ -163,12 +167,13 @@ public async Task TestWithMockDiagnosticService_TaggerProviderCreatedAfterInitia var listenerProvider = workspace.ExportProvider.GetExportedValue(); var diagnosticService = Assert.IsType(workspace.ExportProvider.GetExportedValue()); + var analyzerService = Assert.IsType(workspace.ExportProvider.GetExportedValue()); var provider = workspace.ExportProvider.GetExportedValues().OfType().Single(); // Create and fire the diagnostic events before the tagger is even made. var tree = await workspace.CurrentSolution.Projects.Single().Documents.Single().GetRequiredSyntaxTreeAsync(CancellationToken.None); var span = TextSpan.FromBounds(0, 5); - diagnosticService.CreateDiagnosticAndFireEvents(workspace, Location.Create(tree, span)); + diagnosticService.CreateDiagnosticAndFireEvents(workspace, analyzerService, Location.Create(tree, span)); var tagger = provider.CreateTagger(workspace.Documents.First().GetTextBuffer()); Contract.ThrowIfNull(tagger); diff --git a/src/EditorFeatures/Test/Diagnostics/MockDiagnosticService.cs b/src/EditorFeatures/Test/Diagnostics/MockDiagnosticService.cs index 3daad491a914c..29572be8f02a0 100644 --- a/src/EditorFeatures/Test/Diagnostics/MockDiagnosticService.cs +++ b/src/EditorFeatures/Test/Diagnostics/MockDiagnosticService.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; using Roslyn.Utilities; @@ -16,14 +17,12 @@ namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics { - [Export(typeof(IDiagnosticService))] - [Shared] - [PartNotDiscoverable] + [Export(typeof(IDiagnosticService)), Shared, PartNotDiscoverable] internal class MockDiagnosticService : IDiagnosticService { public const string DiagnosticId = "MockId"; - private DiagnosticData? _diagnostic; + private DiagnosticData? _diagnosticData; public event EventHandler? DiagnosticsUpdated; @@ -43,12 +42,7 @@ private ImmutableArray GetDiagnostics(Workspace workspace, Proje Assert.Equal(projectId, GetProjectId(workspace)); Assert.Equal(documentId, GetDocumentId(workspace)); - return _diagnostic == null ? ImmutableArray.Empty : ImmutableArray.Create(_diagnostic); - } - - public ImmutableArray GetPullDiagnosticBuckets(Workspace workspace, ProjectId? projectId, DocumentId? documentId, DiagnosticMode diagnosticMode, CancellationToken cancellationToken) - { - return GetDiagnosticBuckets(workspace, projectId, documentId); + return _diagnosticData == null ? ImmutableArray.Empty : ImmutableArray.Create(_diagnosticData); } public ImmutableArray GetPushDiagnosticBuckets(Workspace workspace, ProjectId? projectId, DocumentId? documentId, DiagnosticMode diagnosticMode, CancellationToken cancellationToken) @@ -61,22 +55,23 @@ private ImmutableArray GetDiagnosticBuckets(Workspace workspac Assert.Equal(projectId, GetProjectId(workspace)); Assert.Equal(documentId, GetDocumentId(workspace)); - return _diagnostic == null + return _diagnosticData == null ? ImmutableArray.Empty : ImmutableArray.Create(new DiagnosticBucket(this, workspace, GetProjectId(workspace), GetDocumentId(workspace))); } - internal void CreateDiagnosticAndFireEvents(Workspace workspace, Location location) + internal void CreateDiagnosticAndFireEvents(Workspace workspace, MockDiagnosticAnalyzerService analyzerService, Location location) { var document = workspace.CurrentSolution.Projects.Single().Documents.Single(); - _diagnostic = DiagnosticData.Create(Diagnostic.Create(DiagnosticId, "MockCategory", "MockMessage", DiagnosticSeverity.Error, DiagnosticSeverity.Error, isEnabledByDefault: true, warningLevel: 0, + _diagnosticData = DiagnosticData.Create(Diagnostic.Create(DiagnosticId, "MockCategory", "MockMessage", DiagnosticSeverity.Error, DiagnosticSeverity.Error, isEnabledByDefault: true, warningLevel: 0, location: location), document); + analyzerService.Diagnostics = ImmutableArray.Create(_diagnosticData); DiagnosticsUpdated?.Invoke(this, DiagnosticsUpdatedArgs.DiagnosticsCreated( this, workspace, workspace.CurrentSolution, GetProjectId(workspace), GetDocumentId(workspace), - ImmutableArray.Create(_diagnostic))); + ImmutableArray.Create(_diagnosticData))); } private static DocumentId GetDocumentId(Workspace workspace) diff --git a/src/EditorFeatures/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs b/src/EditorFeatures/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs index 16afc6ed3ca84..7d64e15eeafa4 100644 --- a/src/EditorFeatures/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Composition; using System.Linq; using System.Text; using System.Threading; @@ -18,9 +17,8 @@ using Microsoft.CodeAnalysis.EditAndContinue.Contracts; using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; using Microsoft.CodeAnalysis.Editor.UnitTests; +using Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Remote.Testing; @@ -43,7 +41,9 @@ private static string Inspect(DiagnosticData d) [Theory, CombinatorialData] public async Task Proxy(TestHost testHost) { - var localComposition = EditorTestCompositions.EditorFeatures.WithTestHostParts(testHost); + var localComposition = EditorTestCompositions.EditorFeatures.WithTestHostParts(testHost) + .AddExcludedPartTypes(typeof(DiagnosticAnalyzerService)) + .AddParts(typeof(MockDiagnosticAnalyzerService)); if (testHost == TestHost.InProcess) { localComposition = localComposition.AddParts(typeof(MockEditAndContinueWorkspaceService)); @@ -80,7 +80,7 @@ public async Task Proxy(TestHost testHost) var project = solution.Projects.Single(); var document = project.Documents.Single(); - var mockDiagnosticService = new MockDiagnosticAnalyzerService(globalOptions); + var mockDiagnosticService = (MockDiagnosticAnalyzerService)localWorkspace.GetService(); void VerifyReanalyzeInvocation(ImmutableArray documentIds) { diff --git a/src/EditorFeatures/Test/EditAndContinue/Helpers/MockDiagnosticAnalyzerService.cs b/src/EditorFeatures/TestUtilities/Diagnostics/MockDiagnosticAnalyzerService.cs similarity index 89% rename from src/EditorFeatures/Test/EditAndContinue/Helpers/MockDiagnosticAnalyzerService.cs rename to src/EditorFeatures/TestUtilities/Diagnostics/MockDiagnosticAnalyzerService.cs index bf4affae92cd9..566e196de223c 100644 --- a/src/EditorFeatures/Test/EditAndContinue/Helpers/MockDiagnosticAnalyzerService.cs +++ b/src/EditorFeatures/TestUtilities/Diagnostics/MockDiagnosticAnalyzerService.cs @@ -5,20 +5,26 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Composition; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; -namespace Microsoft.CodeAnalysis.EditAndContinue.UnitTests +namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics { + [Export(typeof(IDiagnosticAnalyzerService)), Shared, PartNotDiscoverable] internal class MockDiagnosticAnalyzerService : IDiagnosticAnalyzerService { public readonly List DocumentsToReanalyze = new(); + public ImmutableArray Diagnostics; + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public MockDiagnosticAnalyzerService(IGlobalOptionService globalOptions) { GlobalOptions = globalOptions; @@ -48,7 +54,7 @@ public Task> GetDiagnosticsForIdsAsync(Solution s => throw new NotImplementedException(); public Task> GetDiagnosticsForSpanAsync(Document document, TextSpan? range, Func? shouldIncludeDiagnostic, bool includeCompilerDiagnostics, bool includeSuppressedDiagnostics = true, CodeActionRequestPriority priority = CodeActionRequestPriority.None, Func? addOperationScope = null, CancellationToken cancellationToken = default) - => throw new NotImplementedException(); + => !Diagnostics.IsDefault ? Task.FromResult(Diagnostics) : throw new NotImplementedException(); public Task> GetProjectDiagnosticsForIdsAsync(Solution solution, ProjectId? projectId = null, ImmutableHashSet? diagnosticIds = null, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default) => throw new NotImplementedException(); diff --git a/src/EditorFeatures/TestUtilities/Squiggles/TestDiagnosticTagProducer.cs b/src/EditorFeatures/TestUtilities/Squiggles/TestDiagnosticTagProducer.cs index 0ea6f9dd2aa19..205794045d2c8 100644 --- a/src/EditorFeatures/TestUtilities/Squiggles/TestDiagnosticTagProducer.cs +++ b/src/EditorFeatures/TestUtilities/Squiggles/TestDiagnosticTagProducer.cs @@ -42,6 +42,9 @@ internal static async Task>> GetErrorsFromUpdateSource(Test var tagger = wrapper.TaggerProvider.CreateTagger(workspace.Documents.First().GetTextBuffer()); using var disposable = (IDisposable)tagger; + var analyzerServer = (MockDiagnosticAnalyzerService)workspace.GetService(); + analyzerServer.Diagnostics = updateArgs.GetAllDiagnosticsRegardlessOfPushPullSetting(); + source.RaiseDiagnosticsUpdated(updateArgs); await wrapper.WaitForTags(); diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticService.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticService.cs index 19cb071f9d85e..53d4c87a5fa9c 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticService.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticService.cs @@ -303,9 +303,6 @@ private async ValueTask> GetDiagnosticsAsync( return result.ToImmutable(); } - public ImmutableArray GetPullDiagnosticBuckets(Workspace workspace, ProjectId projectId, DocumentId documentId, DiagnosticMode diagnosticMode, CancellationToken cancellationToken) - => GetDiagnosticBuckets(workspace, projectId, documentId, forPullDiagnostics: true, diagnosticMode, cancellationToken); - public ImmutableArray GetPushDiagnosticBuckets(Workspace workspace, ProjectId projectId, DocumentId documentId, DiagnosticMode diagnosticMode, CancellationToken cancellationToken) => GetDiagnosticBuckets(workspace, projectId, documentId, forPullDiagnostics: false, diagnosticMode, cancellationToken); diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/IDiagnosticService.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/IDiagnosticService.cs index 8169798ba0c72..862f2bfa97217 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/IDiagnosticService.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/IDiagnosticService.cs @@ -34,17 +34,6 @@ ValueTask> GetPushDiagnosticsAsync( Workspace workspace, ProjectId? projectId, DocumentId? documentId, object? id, bool includeSuppressedDiagnostics, DiagnosticMode diagnosticMode, CancellationToken cancellationToken); - /// - /// Get current buckets storing our grouped diagnostics. - /// - /// Option controlling if pull diagnostics are allowed for the client. The - /// only provides diagnostics for either push or pull purposes (but not both). - /// If the caller's desired purpose doesn't match the option value, then this will return nothing, otherwise it - /// will return the requested buckets. - ImmutableArray GetPullDiagnosticBuckets( - Workspace workspace, ProjectId? projectId, DocumentId? documentId, - DiagnosticMode diagnosticMode, CancellationToken cancellationToken); - /// /// Get current buckets storing our grouped diagnostics. /// diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs index 462969aa2ed8b..49189fd47d5cb 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.TodoComments; @@ -51,15 +52,21 @@ private async Task> GetTodoCommentDiagnosticsAsyn if (this.Document is not Document document) return ImmutableArray.Empty; - var service = document.GetLanguageService(); + var service = document.GetLanguageService(); if (service == null) return ImmutableArray.Empty; var tokenList = document.Project.Solution.Options.GetOption(TodoCommentOptionsStorage.TokenList); var descriptors = GetAndCacheDescriptors(tokenList); - var comments = await service.GetTodoCommentDataAsync(document, descriptors, cancellationToken).ConfigureAwait(false); - return comments.SelectAsArray(comment => new DiagnosticData( + var comments = await service.GetTodoCommentsAsync(document, descriptors, cancellationToken).ConfigureAwait(false); + if (comments.Length == 0) + return ImmutableArray.Empty; + + using var _ = ArrayBuilder.GetInstance(out var converted); + await TodoComment.ConvertAsync(document, comments, converted, cancellationToken).ConfigureAwait(false); + + return converted.SelectAsArray(comment => new DiagnosticData( id: "TODO", category: "TODO", message: comment.Message, @@ -73,16 +80,16 @@ private async Task> GetTodoCommentDiagnosticsAsyn language: document.Project.Language, location: new DiagnosticDataLocation( document.Id, - originalFilePath: comment.Span.Path, - mappedFilePath: comment.MappedSpan.Path, - originalStartLine: comment.Span.StartLinePosition.Line, - originalStartColumn: comment.Span.StartLinePosition.Character, - originalEndLine: comment.Span.EndLinePosition.Line, - originalEndColumn: comment.Span.EndLinePosition.Character, - mappedStartLine: comment.MappedSpan.StartLinePosition.Line, - mappedStartColumn: comment.MappedSpan.StartLinePosition.Character, - mappedEndLine: comment.MappedSpan.EndLinePosition.Line, - mappedEndColumn: comment.MappedSpan.EndLinePosition.Character))); + originalFilePath: comment.OriginalFilePath, + mappedFilePath: comment.MappedFilePath, + originalStartLine: comment.OriginalLine, + originalStartColumn: comment.OriginalColumn, + originalEndLine: comment.OriginalLine, + originalEndColumn: comment.OriginalColumn, + mappedStartLine: comment.MappedLine, + mappedStartColumn: comment.MappedColumn, + mappedEndLine: comment.MappedLine, + mappedEndColumn: comment.MappedColumn))); } private static ImmutableArray GetAndCacheDescriptors(ImmutableArray tokenList) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs index 90863825d9f9d..c622c46b1b2fd 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs @@ -23,8 +23,9 @@ public DocumentDiagnosticSource(Document document) : base(document) protected override async Task> GetDiagnosticsWorkerAsync( IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, DiagnosticMode diagnosticMode, CancellationToken cancellationToken) { - // We call GetDiagnosticsForSpanAsync here instead of GetDiagnosticsForIdsAsync as it has faster perf characteristics. - // GetDiagnosticsForIdsAsync runs analyzers against the entire compilation whereas GetDiagnosticsForSpanAsync will only run analyzers against the request document. + // We call GetDiagnosticsForSpanAsync here instead of GetDiagnosticsForIdsAsync as it has faster perf + // characteristics. GetDiagnosticsForIdsAsync runs analyzers against the entire compilation whereas + // GetDiagnosticsForSpanAsync will only run analyzers against the request document. var allSpanDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForSpanAsync(Document, range: null, cancellationToken: cancellationToken).ConfigureAwait(false); return allSpanDiagnostics; } diff --git a/src/VisualStudio/Core/Test/Diagnostics/DefaultDiagnosticUpdateSourceTests.vb b/src/VisualStudio/Core/Test/Diagnostics/DefaultDiagnosticUpdateSourceTests.vb index bf5f39e2ec6f5..79fa25920263c 100644 --- a/src/VisualStudio/Core/Test/Diagnostics/DefaultDiagnosticUpdateSourceTests.vb +++ b/src/VisualStudio/Core/Test/Diagnostics/DefaultDiagnosticUpdateSourceTests.vb @@ -44,8 +44,7 @@ class 123 { } Dim diagnosticService = DirectCast(workspace.ExportProvider.GetExportedValue(Of IDiagnosticService), DiagnosticService) - Dim miscService = GetDefaultDiagnosticAnalyzerService(workspace) - Assert.False(miscService.SupportGetDiagnostics) + Dim miscService = DirectCast(workspace.GetService(Of IDiagnosticAnalyzerService), DiagnosticAnalyzerService) DiagnosticProvider.Enable(workspace, DiagnosticProvider.Options.Syntax) diff --git a/src/VisualStudio/Core/Test/Diagnostics/DiagnosticTableDataSourceTests.vb b/src/VisualStudio/Core/Test/Diagnostics/DiagnosticTableDataSourceTests.vb index 9a7ac01524e4e..8acfbe2ff325e 100644 --- a/src/VisualStudio/Core/Test/Diagnostics/DiagnosticTableDataSourceTests.vb +++ b/src/VisualStudio/Core/Test/Diagnostics/DiagnosticTableDataSourceTests.vb @@ -837,10 +837,6 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Return diagnostics End Function - Public Function GetPullDiagnosticBuckets(workspace As Workspace, projectId As ProjectId, documentId As DocumentId, diagnosticMode As DiagnosticMode, cancellationToken As CancellationToken) As ImmutableArray(Of DiagnosticBucket) Implements IDiagnosticService.GetPullDiagnosticBuckets - Return GetDiagnosticsBuckets(workspace, projectId, documentId) - End Function - Public Function GetPushDiagnosticBuckets(workspace As Workspace, projectId As ProjectId, documentId As DocumentId, diagnosticMode As DiagnosticMode, cancellationToken As CancellationToken) As ImmutableArray(Of DiagnosticBucket) Implements IDiagnosticService.GetPushDiagnosticBuckets Return GetDiagnosticsBuckets(workspace, projectId, documentId) End Function