From 19faad9979c66544cbcc2c91b10cab5e40162ea7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 21 Nov 2022 09:53:46 -0800 Subject: [PATCH] Revert "Merge pull request #63858 from CyrusNajmabadi/pullDiags" This reverts commit b2bbe29dedcb9daeb80acd960aa091375f6a3cd4, reversing changes made to 78b0a645293130a33699daa4295b1f9572ae7fab. --- .../Squiggles/ErrorSquiggleProducerTests.cs | 31 ++-- .../InlineDiagnosticsTaggerProvider.cs | 3 +- ...tractDiagnosticsAdornmentTaggerProvider.cs | 20 +-- .../AbstractDiagnosticsTaggerProvider.cs | 145 ++++++++++++++++-- ...DiagnosticsClassificationTaggerProvider.cs | 5 +- .../DiagnosticsSquiggleTaggerProvider.cs | 3 +- .../DiagnosticsSuggestionTaggerProvider.cs | 5 +- .../DiagnosticsSquiggleTaggerProviderTests.cs | 11 +- .../Test/Diagnostics/MockDiagnosticService.cs | 23 +-- .../Helpers}/MockDiagnosticAnalyzerService.cs | 10 +- .../RemoteEditAndContinueServiceTests.cs | 7 +- .../Squiggles/TestDiagnosticTagProducer.cs | 3 - .../Features/Diagnostics/DiagnosticService.cs | 3 + .../Diagnostics/IDiagnosticService.cs | 11 ++ .../DocumentDiagnosticSource.cs | 5 +- .../DefaultDiagnosticUpdateSourceTests.vb | 3 +- .../DiagnosticTableDataSourceTests.vb | 4 + 17 files changed, 206 insertions(+), 86 deletions(-) rename src/EditorFeatures/{TestUtilities/Diagnostics => Test/EditAndContinue/Helpers}/MockDiagnosticAnalyzerService.cs (89%) diff --git a/src/EditorFeatures/CSharpTest/Squiggles/ErrorSquiggleProducerTests.cs b/src/EditorFeatures/CSharpTest/Squiggles/ErrorSquiggleProducerTests.cs index 6c8147d0e817a..4052da33329eb 100644 --- a/src/EditorFeatures/CSharpTest/Squiggles/ErrorSquiggleProducerTests.cs +++ b/src/EditorFeatures/CSharpTest/Squiggles/ErrorSquiggleProducerTests.cs @@ -15,8 +15,9 @@ using Microsoft.CodeAnalysis.CSharp.RemoveUnnecessaryImports; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo; -using Microsoft.CodeAnalysis.Editor.UnitTests; +using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; 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; @@ -279,10 +280,6 @@ 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() { @@ -297,23 +294,21 @@ class Test "; - using var workspace = TestWorkspace.Create(workspaceXml, composition: s_mockComposition); + using var workspace = TestWorkspace.Create(workspaceXml); 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(2, spans.Count()); + Assert.Equal(1, 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] @@ -330,14 +325,14 @@ class Test "; - using var workspace = TestWorkspace.Create(workspaceXml, composition: s_mockComposition); + using var workspace = TestWorkspace.Create(workspaceXml); 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/Core.Wpf/InlineDiagnostics/InlineDiagnosticsTaggerProvider.cs b/src/EditorFeatures/Core.Wpf/InlineDiagnostics/InlineDiagnosticsTaggerProvider.cs index f6a424427dff3..cf26c23c2eed4 100644 --- a/src/EditorFeatures/Core.Wpf/InlineDiagnostics/InlineDiagnosticsTaggerProvider.cs +++ b/src/EditorFeatures/Core.Wpf/InlineDiagnostics/InlineDiagnosticsTaggerProvider.cs @@ -41,14 +41,13 @@ 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, analyzerService, globalOptions, visibilityTracker, listenerProvider) + : base(threadingContext, diagnosticService, 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 729ee70ccb197..88c97ee7ff139 100644 --- a/src/EditorFeatures/Core/Diagnostics/AbstractDiagnosticsAdornmentTaggerProvider.cs +++ b/src/EditorFeatures/Core/Diagnostics/AbstractDiagnosticsAdornmentTaggerProvider.cs @@ -22,28 +22,31 @@ internal abstract class AbstractDiagnosticsAdornmentTaggerProvider : protected AbstractDiagnosticsAdornmentTaggerProvider( IThreadingContext threadingContext, IDiagnosticService diagnosticService, - IDiagnosticAnalyzerService analyzerService, IGlobalOptionService globalOptions, ITextBufferVisibilityTracker? visibilityTracker, IAsynchronousOperationListenerProvider listenerProvider) - : base(threadingContext, diagnosticService, analyzerService, globalOptions, visibilityTracker, listenerProvider.GetListener(FeatureAttribute.ErrorSquiggles)) + : base(threadingContext, diagnosticService, globalOptions, visibilityTracker, listenerProvider.GetListener(FeatureAttribute.ErrorSquiggles)) { } protected internal sealed override bool IsEnabled => true; protected internal sealed override ITagSpan? CreateTagSpan( - Workspace workspace, SnapshotSpan span, DiagnosticData data) + Workspace workspace, bool isLiveUpdate, SnapshotSpan span, DiagnosticData data) { var errorTag = CreateTag(workspace, data); if (errorTag == null) + { return null; + } - // 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); + // Live update squiggles have to be at least 1 character long. + var minimumLength = isLiveUpdate ? 1 : 0; + var adjustedSpan = AdjustSnapshotSpan(span, minimumLength); if (adjustedSpan.Length == 0) + { return null; + } return new TagSpan(adjustedSpan, errorTag); } @@ -75,9 +78,8 @@ protected static object CreateToolTipContent(Workspace workspace, DiagnosticData new ClassifiedTextRun(ClassificationTypeNames.Text, diagnostic.Message))); } - // 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 virtual SnapshotSpan AdjustSnapshotSpan(SnapshotSpan span, int minimumLength) + => AdjustSnapshotSpan(span, minimumLength, 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 537277ea02357..6f6f8b96ef2cc 100644 --- a/src/EditorFeatures/Core/Diagnostics/AbstractDiagnosticsTaggerProvider.cs +++ b/src/EditorFeatures/Core/Diagnostics/AbstractDiagnosticsTaggerProvider.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Immutable; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor; @@ -13,6 +14,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; @@ -22,36 +24,95 @@ using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Tagging; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics { /// - /// Base type for all taggers that interact with the and produce tags for - /// the diagnostics with different UI presentations. + /// 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. /// internal abstract partial class AbstractDiagnosticsTaggerProvider : AsynchronousTaggerProvider where TTag : ITag { private readonly IDiagnosticService _diagnosticService; - private readonly IDiagnosticAnalyzerService _analyzerService; + + /// + /// 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(); protected AbstractDiagnosticsTaggerProvider( IThreadingContext threadingContext, IDiagnosticService diagnosticService, - IDiagnosticAnalyzerService analyzerService, IGlobalOptionService globalOptions, ITextBufferVisibilityTracker? visibilityTracker, IAsynchronousOperationListener listener) : base(threadingContext, globalOptions, visibilityTracker, listener) { _diagnosticService = diagnosticService; - _analyzerService = analyzerService; + _diagnosticService.DiagnosticsUpdated += OnDiagnosticsUpdated; } 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, SnapshotSpan span, 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 override TaggerDelay EventChangeDelay => TaggerDelay.Short; protected override TaggerDelay AddedTagNotificationDelay => TaggerDelay.OnIdle; @@ -76,7 +137,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 not null ? ImmutableArray.Create(diagnosticData.DataLocation) : ImmutableArray.Empty; + => diagnosticData.DataLocation is object ? ImmutableArray.Create(diagnosticData.DataLocation) : ImmutableArray.Empty; protected override Task ProduceTagsAsync( TaggerContext context, DocumentSnapshotSpan spanToTag, int? caretPosition, CancellationToken cancellationToken) @@ -109,14 +170,60 @@ private async Task ProduceTagsAsync( var suppressedDiagnosticsSpans = (NormalizedSnapshotSpanCollection?)null; buffer?.Properties.TryGetProperty(PredefinedPreviewTaggerKeys.SuppressDiagnosticsSpansKey, out suppressedDiagnosticsSpans); - var sourceText = snapshot.AsText(); + 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); + } + } + private async Task ProduceTagsAsync( + TaggerContext context, DocumentSnapshotSpan spanToTag, + Workspace workspace, Document document, + NormalizedSnapshotSpanCollection? suppressedDiagnosticsSpans, + DiagnosticBucket bucket, CancellationToken cancellationToken) + { try { - var diagnostics = await _analyzerService.GetDiagnosticsForSpanAsync( - document, range: null, cancellationToken: cancellationToken).ConfigureAwait(false); + 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 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) { @@ -134,14 +241,16 @@ private async Task ProduceTagsAsync( // editorSnapshot. var diagnosticSpans = this.GetLocationsToTag(diagnosticData) - .Select(loc => loc.UnmappedFileSpan.GetClampedTextSpan(sourceText).ToSnapshotSpan(snapshot)); + .Select(location => GetDiagnosticSnapshotSpan(location, diagnosticSnapshot, editorSnapshot, sourceText)); foreach (var diagnosticSpan in diagnosticSpans) { if (diagnosticSpan.IntersectsWith(requestedSpan) && !IsSuppressed(suppressedDiagnosticsSpans, diagnosticSpan)) { - var tagSpan = this.CreateTagSpan(workspace, diagnosticSpan, diagnosticData); + var tagSpan = this.CreateTagSpan(workspace, isLiveUpdate, diagnosticSpan, diagnosticData); if (tagSpan != null) + { context.AddTag(tagSpan); + } } } } @@ -150,10 +259,18 @@ 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 diagnosticDataLocation.UnmappedFileSpan.GetClampedTextSpan(sourceText) + .ToSnapshotSpan(diagnosticSnapshot) + .TranslateTo(editorSnapshot, SpanTrackingMode.EdgeExclusive); + } } 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 3f2334bfbd361..6ad73f3dfec0d 100644 --- a/src/EditorFeatures/Core/Diagnostics/DiagnosticsClassificationTaggerProvider.cs +++ b/src/EditorFeatures/Core/Diagnostics/DiagnosticsClassificationTaggerProvider.cs @@ -46,12 +46,11 @@ 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, analyzerService, editorOptionsService.GlobalOptions, visibilityTracker, listenerProvider.GetListener(FeatureAttribute.Classification)) + : base(threadingContext, diagnosticService, editorOptionsService.GlobalOptions, visibilityTracker, listenerProvider.GetListener(FeatureAttribute.Classification)) { _typeMap = typeMap; _classificationTag = new ClassificationTag(_typeMap.GetClassificationType(ClassificationTypeDefinitions.UnnecessaryCode)); @@ -93,7 +92,7 @@ protected internal override bool IncludeDiagnostic(DiagnosticData data) return true; } - protected internal override ITagSpan CreateTagSpan(Workspace workspace, SnapshotSpan span, DiagnosticData data) + protected internal override ITagSpan CreateTagSpan(Workspace workspace, bool isLiveUpdate, 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 c556e2d7577dc..02b28ad345eb0 100644 --- a/src/EditorFeatures/Core/Diagnostics/DiagnosticsSquiggleTaggerProvider.cs +++ b/src/EditorFeatures/Core/Diagnostics/DiagnosticsSquiggleTaggerProvider.cs @@ -37,11 +37,10 @@ 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, analyzerService, globalOptions, visibilityTracker, listenerProvider) + : base(threadingContext, diagnosticService, globalOptions, visibilityTracker, listenerProvider) { } diff --git a/src/EditorFeatures/Core/Diagnostics/DiagnosticsSuggestionTaggerProvider.cs b/src/EditorFeatures/Core/Diagnostics/DiagnosticsSuggestionTaggerProvider.cs index 6d63f95e01c69..0779cb93ff45f 100644 --- a/src/EditorFeatures/Core/Diagnostics/DiagnosticsSuggestionTaggerProvider.cs +++ b/src/EditorFeatures/Core/Diagnostics/DiagnosticsSuggestionTaggerProvider.cs @@ -37,11 +37,10 @@ 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, analyzerService, globalOptions, visibilityTracker, listenerProvider) + : base(threadingContext, diagnosticService, globalOptions, visibilityTracker, listenerProvider) { } @@ -60,7 +59,7 @@ protected override IErrorTag CreateTag(Workspace workspace, DiagnosticData diagn PredefinedErrorTypeNames.HintedSuggestion, CreateToolTipContent(workspace, diagnostic)); - protected override SnapshotSpan AdjustSnapshotSpan(SnapshotSpan snapshotSpan) + protected override SnapshotSpan AdjustSnapshotSpan(SnapshotSpan snapshotSpan, int minimumLength) { // 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 99f8cbf6f86f4..628b2a77fcc06 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticsSquiggleTaggerProviderTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticsSquiggleTaggerProviderTests.cs @@ -10,7 +10,6 @@ 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; @@ -31,9 +30,7 @@ namespace Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics public class DiagnosticsSquiggleTaggerProviderTests { private static readonly TestComposition s_compositionWithMockDiagnosticService = - EditorTestCompositions.EditorFeatures - .AddExcludedPartTypes(typeof(IDiagnosticService), typeof(IDiagnosticAnalyzerService)) - .AddParts(typeof(MockDiagnosticService), typeof(MockDiagnosticAnalyzerService)); + EditorTestCompositions.EditorFeatures.AddExcludedPartTypes(typeof(IDiagnosticService)).AddParts(typeof(MockDiagnosticService)); [WpfFact] public async Task Test_TagSourceDiffer() @@ -128,7 +125,6 @@ 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. @@ -138,7 +134,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, analyzerService, Location.Create(tree, span)); + diagnosticService.CreateDiagnosticAndFireEvents(workspace, Location.Create(tree, span)); using var disposable = tagger as IDisposable; await listenerProvider.GetWaiter(FeatureAttribute.DiagnosticService).ExpeditedWaitAsync(); @@ -167,13 +163,12 @@ 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, analyzerService, Location.Create(tree, span)); + diagnosticService.CreateDiagnosticAndFireEvents(workspace, 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 29572be8f02a0..3daad491a914c 100644 --- a/src/EditorFeatures/Test/Diagnostics/MockDiagnosticService.cs +++ b/src/EditorFeatures/Test/Diagnostics/MockDiagnosticService.cs @@ -9,7 +9,6 @@ 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; @@ -17,12 +16,14 @@ 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? _diagnosticData; + private DiagnosticData? _diagnostic; public event EventHandler? DiagnosticsUpdated; @@ -42,7 +43,12 @@ private ImmutableArray GetDiagnostics(Workspace workspace, Proje Assert.Equal(projectId, GetProjectId(workspace)); Assert.Equal(documentId, GetDocumentId(workspace)); - return _diagnosticData == null ? ImmutableArray.Empty : ImmutableArray.Create(_diagnosticData); + 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); } public ImmutableArray GetPushDiagnosticBuckets(Workspace workspace, ProjectId? projectId, DocumentId? documentId, DiagnosticMode diagnosticMode, CancellationToken cancellationToken) @@ -55,23 +61,22 @@ private ImmutableArray GetDiagnosticBuckets(Workspace workspac Assert.Equal(projectId, GetProjectId(workspace)); Assert.Equal(documentId, GetDocumentId(workspace)); - return _diagnosticData == null + return _diagnostic == null ? ImmutableArray.Empty : ImmutableArray.Create(new DiagnosticBucket(this, workspace, GetProjectId(workspace), GetDocumentId(workspace))); } - internal void CreateDiagnosticAndFireEvents(Workspace workspace, MockDiagnosticAnalyzerService analyzerService, Location location) + internal void CreateDiagnosticAndFireEvents(Workspace workspace, Location location) { var document = workspace.CurrentSolution.Projects.Single().Documents.Single(); - _diagnosticData = DiagnosticData.Create(Diagnostic.Create(DiagnosticId, "MockCategory", "MockMessage", DiagnosticSeverity.Error, DiagnosticSeverity.Error, isEnabledByDefault: true, warningLevel: 0, + _diagnostic = 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(_diagnosticData))); + ImmutableArray.Create(_diagnostic))); } private static DocumentId GetDocumentId(Workspace workspace) diff --git a/src/EditorFeatures/TestUtilities/Diagnostics/MockDiagnosticAnalyzerService.cs b/src/EditorFeatures/Test/EditAndContinue/Helpers/MockDiagnosticAnalyzerService.cs similarity index 89% rename from src/EditorFeatures/TestUtilities/Diagnostics/MockDiagnosticAnalyzerService.cs rename to src/EditorFeatures/Test/EditAndContinue/Helpers/MockDiagnosticAnalyzerService.cs index 566e196de223c..bf4affae92cd9 100644 --- a/src/EditorFeatures/TestUtilities/Diagnostics/MockDiagnosticAnalyzerService.cs +++ b/src/EditorFeatures/Test/EditAndContinue/Helpers/MockDiagnosticAnalyzerService.cs @@ -5,26 +5,20 @@ 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.Editor.UnitTests.Diagnostics +namespace Microsoft.CodeAnalysis.EditAndContinue.UnitTests { - [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; @@ -54,7 +48,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) - => !Diagnostics.IsDefault ? Task.FromResult(Diagnostics) : throw new NotImplementedException(); + => 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/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs b/src/EditorFeatures/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs index 1471cb90cc07a..235a6343b8102 100644 --- a/src/EditorFeatures/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/RemoteEditAndContinueServiceTests.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Composition; using System.Linq; using System.Text; using System.Threading; @@ -17,8 +18,9 @@ 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; @@ -47,7 +49,6 @@ public async Task Proxy(TestHost testHost) var localComposition = EditorTestCompositions.EditorFeatures.WithTestHostParts(testHost) .AddExcludedPartTypes(typeof(DiagnosticAnalyzerService)) .AddParts(typeof(MockDiagnosticAnalyzerService), typeof(NoCompilationLanguageService)); - if (testHost == TestHost.InProcess) { localComposition = localComposition.AddParts(typeof(MockEditAndContinueWorkspaceService)); @@ -94,7 +95,7 @@ public async Task Proxy(TestHost testHost) var inProcOnlyDocument = solution.GetRequiredDocument(inProcOnlyDocumentId); var syntaxTree = await document.GetRequiredSyntaxTreeAsync(CancellationToken.None); - var mockDiagnosticService = (MockDiagnosticAnalyzerService)localWorkspace.GetService(); + var mockDiagnosticService = new MockDiagnosticAnalyzerService(globalOptions); void VerifyReanalyzeInvocation(ImmutableArray documentIds) { diff --git a/src/EditorFeatures/TestUtilities/Squiggles/TestDiagnosticTagProducer.cs b/src/EditorFeatures/TestUtilities/Squiggles/TestDiagnosticTagProducer.cs index 0122c087d6860..6e4493e45e86f 100644 --- a/src/EditorFeatures/TestUtilities/Squiggles/TestDiagnosticTagProducer.cs +++ b/src/EditorFeatures/TestUtilities/Squiggles/TestDiagnosticTagProducer.cs @@ -42,9 +42,6 @@ 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 53d4c87a5fa9c..19cb071f9d85e 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticService.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/DiagnosticService.cs @@ -303,6 +303,9 @@ 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 862f2bfa97217..8169798ba0c72 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/IDiagnosticService.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/IDiagnosticService.cs @@ -34,6 +34,17 @@ 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/DocumentDiagnosticSource.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs index 3daa48fbd293e..6887b5867f6a6 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs @@ -23,9 +23,8 @@ public DocumentDiagnosticSource(Document document) : base(document) protected override async Task> GetDiagnosticsWorkerAsync( IDiagnosticAnalyzerService diagnosticAnalyzerService, RequestContext context, 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 79fa25920263c..bf5f39e2ec6f5 100644 --- a/src/VisualStudio/Core/Test/Diagnostics/DefaultDiagnosticUpdateSourceTests.vb +++ b/src/VisualStudio/Core/Test/Diagnostics/DefaultDiagnosticUpdateSourceTests.vb @@ -44,7 +44,8 @@ class 123 { } Dim diagnosticService = DirectCast(workspace.ExportProvider.GetExportedValue(Of IDiagnosticService), DiagnosticService) - Dim miscService = DirectCast(workspace.GetService(Of IDiagnosticAnalyzerService), DiagnosticAnalyzerService) + Dim miscService = GetDefaultDiagnosticAnalyzerService(workspace) + Assert.False(miscService.SupportGetDiagnostics) 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 344171ac4cad3..4054b1d3e31a2 100644 --- a/src/VisualStudio/Core/Test/Diagnostics/DiagnosticTableDataSourceTests.vb +++ b/src/VisualStudio/Core/Test/Diagnostics/DiagnosticTableDataSourceTests.vb @@ -827,6 +827,10 @@ 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