diff --git a/eng/targets/Services.props b/eng/targets/Services.props index 590cd0c2a1518..54114e7e60223 100644 --- a/eng/targets/Services.props +++ b/eng/targets/Services.props @@ -16,6 +16,7 @@ + diff --git a/src/EditorFeatures/CSharp/CodeCleanup/CSharpCodeCleanupService.cs b/src/EditorFeatures/CSharp/CodeCleanup/CSharpCodeCleanupService.cs index f00c77eb4d622..09e04eb4ca899 100644 --- a/src/EditorFeatures/CSharp/CodeCleanup/CSharpCodeCleanupService.cs +++ b/src/EditorFeatures/CSharp/CodeCleanup/CSharpCodeCleanupService.cs @@ -16,7 +16,8 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeCleanup; [ExportLanguageService(typeof(ICodeCleanupService), LanguageNames.CSharp), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal class CSharpCodeCleanupService(ICodeFixService codeFixService, IDiagnosticAnalyzerService diagnosticAnalyzerService) : AbstractCodeCleanupService(codeFixService, diagnosticAnalyzerService) +internal sealed class CSharpCodeCleanupService(ICodeFixService codeFixService) + : AbstractCodeCleanupService(codeFixService) { /// /// Maps format document code cleanup options to DiagnosticId[] diff --git a/src/EditorFeatures/Core.Wpf/Copilot/CopilotWpfTextCreationListener.cs b/src/EditorFeatures/Core.Wpf/Copilot/CopilotWpfTextCreationListener.cs new file mode 100644 index 0000000000000..8bdb1bfaf16db --- /dev/null +++ b/src/EditorFeatures/Core.Wpf/Copilot/CopilotWpfTextCreationListener.cs @@ -0,0 +1,127 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.ComponentModel.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Collections; +using Microsoft.CodeAnalysis.Editor; +using Microsoft.CodeAnalysis.Editor.Shared.Extensions; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Shared.TestHooks; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Threading; +using Microsoft.VisualStudio.Language.Proposals; +using Microsoft.VisualStudio.Language.Suggestions; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.CodeAnalysis.Copilot; + +[Export(typeof(IWpfTextViewCreationListener))] +[ContentType(ContentTypeNames.RoslynContentType)] +[TextViewRole(PredefinedTextViewRoles.Document)] +internal sealed class CopilotWpfTextViewCreationListener : IWpfTextViewCreationListener +{ + private readonly IGlobalOptionService _globalOptions; + private readonly IThreadingContext _threadingContext; + private readonly Lazy _suggestionServiceBase; + private readonly IAsynchronousOperationListener _listener; + + private readonly AsyncBatchingWorkQueue<(bool accepted, ProposalBase proposal)> _completionWorkQueue; + + private int _started; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public CopilotWpfTextViewCreationListener( + IGlobalOptionService globalOptions, + IThreadingContext threadingContext, + Lazy suggestionServiceBase, + IAsynchronousOperationListenerProvider listenerProvider) + { + _globalOptions = globalOptions; + _threadingContext = threadingContext; + _suggestionServiceBase = suggestionServiceBase; + _listener = listenerProvider.GetListener(FeatureAttribute.CopilotChangeAnalysis); + + _completionWorkQueue = new AsyncBatchingWorkQueue<(bool accepted, ProposalBase proposal)>( + DelayTimeSpan.Idle, + ProcessCompletionEventsAsync, + _listener, + _threadingContext.DisposalToken); + } + + public void TextViewCreated(IWpfTextView textView) + { + // On the first roslyn text view created, kick off work to hydrate the suggestion service and register to events + // from it. + if (Interlocked.CompareExchange(ref _started, 1, 0) == 0) + { + var token = _listener.BeginAsyncOperation(nameof(TextViewCreated)); + Task.Run(() => + { + var suggestionService = _suggestionServiceBase.Value; + suggestionService.SuggestionAccepted += OnCompletionSuggestionAccepted; + suggestionService.SuggestionDismissed += OnCompletionSuggestionDismissed; + }).CompletesAsyncOperation(token); + } + } + + private void OnCompletionSuggestionAccepted(object sender, SuggestionAcceptedEventArgs e) + => OnCompletionSuggestionEvent(accepted: true, e.FinalProposal); + + private void OnCompletionSuggestionDismissed(object sender, SuggestionDismissedEventArgs e) + => OnCompletionSuggestionEvent(accepted: false, e.FinalProposal); + + private void OnCompletionSuggestionEvent(bool accepted, ProposalBase? proposal) + { + if (proposal is not { Edits.Count: > 0 }) + return; + + _completionWorkQueue.AddWork((accepted, proposal)); + } + + private async ValueTask ProcessCompletionEventsAsync( + ImmutableSegmentedList<(bool accepted, ProposalBase proposal)> list, CancellationToken cancellationToken) + { + // Ignore if analyzing changes is disabled for this user. + if (!_globalOptions.GetOption(CopilotOptions.AnalyzeCopilotChanges)) + return; + + foreach (var (accepted, proposal) in list) + await ProcessCompletionEventAsync(accepted, proposal, cancellationToken).ConfigureAwait(false); + } + + private static async ValueTask ProcessCompletionEventAsync( + bool accepted, ProposalBase proposal, CancellationToken cancellationToken) + { + const string featureId = "Completion"; + var proposalId = proposal.ProposalId; + + foreach (var editGroup in proposal.Edits.GroupBy(e => e.Span.Snapshot)) + { + cancellationToken.ThrowIfCancellationRequested(); + + var snapshot = editGroup.Key; + var document = snapshot.GetOpenDocumentInCurrentContextWithChanges(); + + if (document is null) + continue; + + using var _ = PooledObjects.ArrayBuilder.GetInstance(out var textChanges); + foreach (var edit in editGroup) + textChanges.Add(new TextChange(edit.Span.Span.ToTextSpan(), edit.ReplacementText)); + + await CopilotChangeAnalysisUtilities.AnalyzeCopilotChangeAsync( + document, accepted, featureId, proposalId, textChanges, cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/src/EditorFeatures/Core.Wpf/InlineDiagnostics/AbstractDiagnosticsTaggerProvider.SingleDiagnosticKindPullTaggerProvider.cs b/src/EditorFeatures/Core.Wpf/InlineDiagnostics/AbstractDiagnosticsTaggerProvider.SingleDiagnosticKindPullTaggerProvider.cs index d38f7a89b74eb..e58806e7b93f2 100644 --- a/src/EditorFeatures/Core.Wpf/InlineDiagnostics/AbstractDiagnosticsTaggerProvider.SingleDiagnosticKindPullTaggerProvider.cs +++ b/src/EditorFeatures/Core.Wpf/InlineDiagnostics/AbstractDiagnosticsTaggerProvider.SingleDiagnosticKindPullTaggerProvider.cs @@ -35,14 +35,12 @@ internal abstract partial class AbstractDiagnosticsTaggerProvider /// private sealed class SingleDiagnosticKindPullTaggerProvider( AbstractDiagnosticsTaggerProvider callback, - IDiagnosticAnalyzerService analyzerService, DiagnosticKind diagnosticKind, TaggerHost taggerHost, string featureName) : AsynchronousTaggerProvider(taggerHost, featureName) { private readonly DiagnosticKind _diagnosticKind = diagnosticKind; - private readonly IDiagnosticAnalyzerService _analyzerService = analyzerService; // The following three fields are used to help calculate diagnostic performance for syntax errors upon file open. // During TagsChanged notification for syntax errors, VSPlatform will check the buffer's property bag for a @@ -104,7 +102,8 @@ private async Task ProduceTagsAsync( var snapshot = documentSpanToTag.SnapshotSpan.Snapshot; var project = document.Project; - var workspace = project.Solution.Workspace; + var solution = project.Solution; + var workspace = solution.Workspace; // See if we've marked any spans as those we want to suppress diagnostics for. // This can happen for buffers used in the preview workspace where some feature @@ -124,7 +123,8 @@ private async Task ProduceTagsAsync( // NOTE: We pass 'includeSuppressedDiagnostics: true' to ensure that IDE0079 (unnecessary suppressions) // are flagged and faded in the editor. IDE0079 analyzer requires all source suppressed diagnostics to // be provided to it to function correctly. - var diagnostics = await _analyzerService.GetDiagnosticsForSpanAsync( + var analyzerService = solution.Services.GetRequiredService(); + var diagnostics = await analyzerService.GetDiagnosticsForSpanAsync( document, requestedSpan.Span.ToTextSpan(), diagnosticKind: _diagnosticKind, diff --git a/src/EditorFeatures/Core.Wpf/InlineDiagnostics/AbstractDiagnosticsTaggerProvider.cs b/src/EditorFeatures/Core.Wpf/InlineDiagnostics/AbstractDiagnosticsTaggerProvider.cs index f9efa1398c4ca..3aaacb2c06c51 100644 --- a/src/EditorFeatures/Core.Wpf/InlineDiagnostics/AbstractDiagnosticsTaggerProvider.cs +++ b/src/EditorFeatures/Core.Wpf/InlineDiagnostics/AbstractDiagnosticsTaggerProvider.cs @@ -31,7 +31,6 @@ internal abstract partial class AbstractDiagnosticsTaggerProvider : ITagge private readonly ImmutableArray _diagnosticsTaggerProviders; public AbstractDiagnosticsTaggerProvider( - IDiagnosticAnalyzerService analyzerService, TaggerHost taggerHost, string featureName) { @@ -48,7 +47,7 @@ public AbstractDiagnosticsTaggerProvider( return; SingleDiagnosticKindPullTaggerProvider CreateDiagnosticsTaggerProvider(DiagnosticKind diagnosticKind) - => new(this, analyzerService, diagnosticKind, taggerHost, featureName); + => new(this, diagnosticKind, taggerHost, featureName); } // Functionality for subclasses to control how this diagnostic tagging operates. All the individual diff --git a/src/EditorFeatures/Core.Wpf/InlineDiagnostics/InlineDiagnosticsTaggerProvider.cs b/src/EditorFeatures/Core.Wpf/InlineDiagnostics/InlineDiagnosticsTaggerProvider.cs index 257c80587890c..e5e814ab7e1ab 100644 --- a/src/EditorFeatures/Core.Wpf/InlineDiagnostics/InlineDiagnosticsTaggerProvider.cs +++ b/src/EditorFeatures/Core.Wpf/InlineDiagnostics/InlineDiagnosticsTaggerProvider.cs @@ -25,15 +25,12 @@ namespace Microsoft.CodeAnalysis.Editor.InlineDiagnostics; [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class InlineDiagnosticsTaggerProvider( - IDiagnosticAnalyzerService analyzerService, TaggerHost taggerHost, IEditorFormatMapService editorFormatMapService, IClassificationFormatMapService classificationFormatMapService, IClassificationTypeRegistryService classificationTypeRegistryService) : AbstractDiagnosticsTaggerProvider( - analyzerService, - taggerHost, - FeatureAttribute.ErrorSquiggles) + taggerHost, FeatureAttribute.ErrorSquiggles) { private readonly IEditorFormatMap _editorFormatMap = editorFormatMapService.GetEditorFormatMap("text"); private readonly IClassificationFormatMapService _classificationFormatMapService = classificationFormatMapService; diff --git a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsProvider.cs b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsProvider.cs index 5939394e80816..ba0c0f51e2a55 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsProvider.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsProvider.cs @@ -24,11 +24,10 @@ public AnalyzerSettingsProvider( string fileName, AnalyzerSettingsUpdater settingsUpdater, Workspace workspace, - IDiagnosticAnalyzerService analyzerService, IGlobalOptionService optionService) : base(fileName, settingsUpdater, workspace, optionService) { - _analyzerService = analyzerService; + _analyzerService = workspace.Services.GetRequiredService(); Update(); } diff --git a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsProviderFactory.cs b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsProviderFactory.cs index 4a0dedbe75bb5..49de006cc6b58 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsProviderFactory.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsProviderFactory.cs @@ -11,12 +11,11 @@ namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.DataProvider.Analyz internal sealed class AnalyzerSettingsProviderFactory( Workspace workspace, - IDiagnosticAnalyzerService analyzerService, IGlobalOptionService globalOptionService) : IWorkspaceSettingsProviderFactory { public ISettingsProvider GetForFile(string filePath) { var updater = new AnalyzerSettingsUpdater(workspace, filePath); - return new AnalyzerSettingsProvider(filePath, updater, workspace, analyzerService, globalOptionService); + return new AnalyzerSettingsProvider(filePath, updater, workspace, globalOptionService); } } diff --git a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsWorkspaceServiceFactory.cs b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsWorkspaceServiceFactory.cs index 7945e987215b1..095d1a9d5624a 100644 --- a/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsWorkspaceServiceFactory.cs +++ b/src/EditorFeatures/Core/EditorConfigSettings/DataProvider/Analyzer/AnalyzerSettingsWorkspaceServiceFactory.cs @@ -16,9 +16,8 @@ namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.DataProvider.Analyz [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class AnalyzerSettingsWorkspaceServiceFactory( - IDiagnosticAnalyzerService analyzerService, IGlobalOptionService globalOptionService) : IWorkspaceServiceFactory { public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => new AnalyzerSettingsProviderFactory(workspaceServices.Workspace, analyzerService, globalOptionService); + => new AnalyzerSettingsProviderFactory(workspaceServices.Workspace, globalOptionService); } diff --git a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptPullDiagnosticHandlerProvider.cs b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptPullDiagnosticHandlerProvider.cs index b5527e89596a0..7fbba09f1e8da 100644 --- a/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptPullDiagnosticHandlerProvider.cs +++ b/src/EditorFeatures/Core/ExternalAccess/VSTypeScript/VSTypeScriptPullDiagnosticHandlerProvider.cs @@ -17,10 +17,9 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript; [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal class VSTypeScriptDocumentPullDiagnosticHandlerFactory( - IDiagnosticAnalyzerService analyzerService, IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, - IGlobalOptionService globalOptions) : DocumentPullDiagnosticHandlerFactory(analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions) + IGlobalOptionService globalOptions) : DocumentPullDiagnosticHandlerFactory(diagnosticSourceManager, diagnosticsRefresher, globalOptions) { } @@ -29,9 +28,8 @@ internal class VSTypeScriptDocumentPullDiagnosticHandlerFactory( [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal class VSTypeScriptWorkspacePullDiagnosticHandler( LspWorkspaceRegistrationService registrationService, - IDiagnosticAnalyzerService analyzerService, IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, - IGlobalOptionService globalOptions) : WorkspacePullDiagnosticHandlerFactory(registrationService, analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions) + IGlobalOptionService globalOptions) : WorkspacePullDiagnosticHandlerFactory(registrationService, diagnosticSourceManager, diagnosticsRefresher, globalOptions) { } diff --git a/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.StateMachine.cs b/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.StateMachine.cs index 33e6adf031c9b..960c6d7e229c1 100644 --- a/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.StateMachine.cs +++ b/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.StateMachine.cs @@ -38,7 +38,6 @@ private sealed class StateMachine private readonly IInlineRenameService _inlineRenameService; private readonly IAsynchronousOperationListener _asyncListener; - private readonly IDiagnosticAnalyzerService _diagnosticAnalyzerService; // Store committed sessions so they can be restored on undo/redo. The undo transactions // may live beyond the lifetime of the buffer tracked by this StateMachine, so storing @@ -58,7 +57,6 @@ public StateMachine( IThreadingContext threadingContext, ITextBuffer buffer, IInlineRenameService inlineRenameService, - IDiagnosticAnalyzerService diagnosticAnalyzerService, IGlobalOptionService globalOptions, IAsynchronousOperationListener asyncListener) { @@ -67,7 +65,6 @@ public StateMachine( Buffer.Changed += Buffer_Changed; _inlineRenameService = inlineRenameService; _asyncListener = asyncListener; - _diagnosticAnalyzerService = diagnosticAnalyzerService; GlobalOptions = globalOptions; } @@ -238,8 +235,8 @@ public bool ClearVisibleTrackingSession() // provide a diagnostic/codefix, but nothing has changed in the workspace // to trigger the diagnostic system to reanalyze, so we trigger it // manually. - - _diagnosticAnalyzerService?.RequestDiagnosticRefresh(); + var service = document.Project.Solution.Services.GetRequiredService(); + service.RequestDiagnosticRefresh(); } // Disallow the existing TrackingSession from triggering IdentifierFound. diff --git a/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.cs b/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.cs index 8e4deef024bea..4fb1de3d67acc 100644 --- a/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.cs +++ b/src/EditorFeatures/Core/RenameTracking/RenameTrackingTaggerProvider.cs @@ -40,19 +40,17 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.RenameTracking; internal sealed partial class RenameTrackingTaggerProvider( IThreadingContext threadingContext, IInlineRenameService inlineRenameService, - IDiagnosticAnalyzerService diagnosticAnalyzerService, IGlobalOptionService globalOptions, IAsynchronousOperationListenerProvider listenerProvider) : ITaggerProvider { private readonly IThreadingContext _threadingContext = threadingContext; private readonly IAsynchronousOperationListener _asyncListener = listenerProvider.GetListener(FeatureAttribute.RenameTracking); private readonly IInlineRenameService _inlineRenameService = inlineRenameService; - private readonly IDiagnosticAnalyzerService _diagnosticAnalyzerService = diagnosticAnalyzerService; private readonly IGlobalOptionService _globalOptions = globalOptions; public ITagger CreateTagger(ITextBuffer buffer) where T : ITag { - var stateMachine = buffer.Properties.GetOrCreateSingletonProperty(() => new StateMachine(_threadingContext, buffer, _inlineRenameService, _diagnosticAnalyzerService, _globalOptions, _asyncListener)); + var stateMachine = buffer.Properties.GetOrCreateSingletonProperty(() => new StateMachine(_threadingContext, buffer, _inlineRenameService, _globalOptions, _asyncListener)); return new Tagger(stateMachine) as ITagger; } diff --git a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs index 149dd06940b05..936e2318e1ba4 100644 --- a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs +++ b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs @@ -43,14 +43,14 @@ public async Task TestGetFirstDiagnosticWithFixAsync() "; using var workspace = TestWorkspace.CreateCSharp(code, composition: s_compositionWithMockDiagnosticUpdateSourceRegistrationService, openDocuments: true); - var diagnosticService = workspace.GetService(); + var diagnosticService = workspace.Services.GetRequiredService(); var analyzerReference = new TestAnalyzerReferenceByLanguage(DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap()); workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences([analyzerReference])); var logger = SpecializedCollections.SingletonEnumerable(new Lazy(() => workspace.Services.GetRequiredService())); var fixService = new CodeFixService( - diagnosticService, logger, fixers, configurationProviders: []); + logger, fixers, configurationProviders: []); var reference = new MockAnalyzerReference(); var project = workspace.CurrentSolution.Projects.Single().AddAnalyzerReference(reference); @@ -323,7 +323,7 @@ private static async Task GetFirstDiagnosticWithFixWithExceptionValidationAsync( Assert.True(errorReported); } - private static (EditorTestWorkspace workspace, IDiagnosticAnalyzerService analyzerService, CodeFixService codeFixService, IErrorLoggerService errorLogger) ServiceSetup( + private static (EditorTestWorkspace workspace, CodeFixService codeFixService, IErrorLoggerService errorLogger) ServiceSetup( CodeFixProvider codefix, bool includeConfigurationFixProviders = false, bool throwExceptionInFixerCreation = false, @@ -331,7 +331,7 @@ private static (EditorTestWorkspace workspace, IDiagnosticAnalyzerService analyz string code = "class Program { }") => ServiceSetup([codefix], includeConfigurationFixProviders, throwExceptionInFixerCreation, additionalDocument, code); - private static (EditorTestWorkspace workspace, IDiagnosticAnalyzerService analyzerService, CodeFixService codeFixService, IErrorLoggerService errorLogger) ServiceSetup( + private static (EditorTestWorkspace workspace, CodeFixService codeFixService, IErrorLoggerService errorLogger) ServiceSetup( ImmutableArray codefixers, bool includeConfigurationFixProviders = false, bool throwExceptionInFixerCreation = false, @@ -355,7 +355,7 @@ private static (EditorTestWorkspace workspace, IDiagnosticAnalyzerService analyz var analyzerReference = new TestAnalyzerReferenceByLanguage(DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap()); workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences([analyzerReference])); - var diagnosticService = workspace.GetService(); + var diagnosticService = workspace.Services.GetRequiredService(); var logger = SpecializedCollections.SingletonEnumerable(new Lazy(() => new TestErrorLogger())); var errorLogger = logger.First().Value; @@ -363,13 +363,8 @@ private static (EditorTestWorkspace workspace, IDiagnosticAnalyzerService analyz ? workspace.ExportProvider.GetExports() : []; - var fixService = new CodeFixService( - diagnosticService, - logger, - fixers, - configurationFixProviders); - - return (workspace, diagnosticService, fixService, errorLogger); + var fixService = new CodeFixService(logger, fixers, configurationFixProviders); + return (workspace, fixService, errorLogger); } private static void GetDocumentAndExtensionManager( @@ -757,11 +752,10 @@ private static async Task> GetNuGetAndVsixCode using var workspace = TestWorkspace.CreateCSharp(code, composition: s_compositionWithMockDiagnosticUpdateSourceRegistrationService, openDocuments: true); - var diagnosticService = workspace.GetService(); + var diagnosticService = workspace.Services.GetRequiredService(); var logger = SpecializedCollections.SingletonEnumerable(new Lazy(() => workspace.Services.GetRequiredService())); - var fixService = new CodeFixService( - diagnosticService, logger, vsixFixers, configurationProviders: []); + var fixService = new CodeFixService(logger, vsixFixers, configurationProviders: []); diagnosticAnalyzer ??= new MockAnalyzerReference.MockDiagnosticAnalyzer(); var analyzers = ImmutableArray.Create(diagnosticAnalyzer); @@ -1033,7 +1027,6 @@ void M() var tuple = ServiceSetup(codeFix, code: code); using var workspace = tuple.workspace; - var analyzerService = tuple.analyzerService; GetDocumentAndExtensionManager(workspace, out var document, out var extensionManager, analyzerReference); @@ -1047,6 +1040,7 @@ void M() // We enable full solution analysis so the 'AnalyzeDocumentAsync' doesn't skip analysis based on whether the document is active/open. workspace.GlobalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp, BackgroundAnalysisScope.FullSolution); + var analyzerService = workspace.Services.GetRequiredService(); var diagnostics = await analyzerService.ForceAnalyzeProjectAsync(sourceDocument.Project, CancellationToken.None); await VerifyCachedDiagnosticsAsync( sourceDocument, expectedCachedDiagnostic: diagnosticOnFixLineInPriorSnapshot, testSpan, diagnostics); diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs index 1cdbde9790790..6355aa95a0c64 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs @@ -61,9 +61,7 @@ public async Task TestHasSuccessfullyLoadedBeingFalse() var document = GetDocumentFromIncompleteProject(workspace); - var exportProvider = workspace.Services.SolutionServices.ExportProvider; - var service = exportProvider.GetExportedValue(); - var globalOptions = exportProvider.GetExportedValue(); + var service = workspace.Services.GetRequiredService(); var diagnostics = await service.GetDiagnosticsForIdsAsync( workspace.CurrentSolution.Projects.Single(), documentId: null, diagnosticIds: null, shouldIncludeAnalyzer: null, @@ -175,8 +173,7 @@ public async Task TestDisabledByDefaultAnalyzerEnabledWithEditorConfig(bool enab var applied = workspace.TryApplyChanges(document.Project.Solution); Assert.True(applied); - var exportProvider = workspace.Services.SolutionServices.ExportProvider; - var service = exportProvider.GetExportedValue(); + var service = workspace.Services.GetRequiredService(); // listen to events var syntaxDiagnostic = false; @@ -215,9 +212,7 @@ private static async Task TestAnalyzerAsync( Func, (bool, bool)> resultSetter, bool expectedSyntax, bool expectedSemantic) { - var exportProvider = workspace.Services.SolutionServices.ExportProvider; - - var service = exportProvider.GetExportedValue(); + var service = workspace.Services.GetRequiredService(); var syntax = false; var semantic = false; @@ -259,7 +254,7 @@ public async Task TestHostAnalyzerOrderingAsync() "Dummy", LanguageNames.CSharp)); - var service = Assert.IsType(exportProvider.GetExportedValue()); + var service = Assert.IsType(workspace.Services.GetRequiredService()); var analyzers = await service.GetTestAccessor().GetAnalyzersAsync(project, CancellationToken.None).ConfigureAwait(false); var analyzersArray = analyzers.ToArray(); @@ -307,8 +302,7 @@ public async Task TestHostAnalyzerErrorNotLeaking() loader: TextLoader.From(TextAndVersion.Create(SourceText.From("class A {}"), VersionStamp.Create(), filePath: "test.cs")), filePath: "test.cs")])); - var exportProvider = workspace.Services.SolutionServices.ExportProvider; - var service = exportProvider.GetExportedValue(); + var service = workspace.Services.GetRequiredService(); var diagnostics = await service.ForceAnalyzeProjectAsync(project, CancellationToken.None); Assert.NotEmpty(diagnostics); @@ -392,8 +386,7 @@ private static AdhocWorkspace CreateWorkspaceWithProjectAndAnalyzer(DiagnosticAn private static async Task TestFullSolutionAnalysisForProjectAsync(AdhocWorkspace workspace, Project project, bool expectAnalyzerExecuted) { - var exportProvider = workspace.Services.SolutionServices.ExportProvider; - var service = exportProvider.GetExportedValue(); + var service = workspace.Services.GetRequiredService(); var diagnostics = await service.ForceAnalyzeProjectAsync(project, CancellationToken.None); @@ -438,8 +431,7 @@ internal async Task TestAdditionalFileAnalyzer(bool registerFromInitialize, bool var applied = workspace.TryApplyChanges(project.Solution); Assert.True(applied); - var exportProvider = workspace.Services.SolutionServices.ExportProvider; - var service = exportProvider.GetExportedValue(); + var service = workspace.Services.GetRequiredService(); var firstAdditionalDocument = project.AdditionalDocuments.FirstOrDefault(); @@ -503,7 +495,7 @@ internal async Task TestDiagnosticSuppressor(bool includeAnalyzer, bool includeS var project = workspace.CurrentSolution.Projects.Single(); var document = project.Documents.Single(); - var service = workspace.GetService(); + var service = workspace.Services.GetRequiredService(); var globalOptions = workspace.GetService(); switch (analysisScope) @@ -620,7 +612,7 @@ void M() else Assert.IsType(document); - var service = workspace.GetService(); + var service = workspace.Services.GetRequiredService(); var text = await document.GetTextAsync(); @@ -858,7 +850,7 @@ internal async Task TestGeneratorProducedDiagnostics(bool fullSolutionAnalysis, workspace.OpenDocument(document.Id); } - var service = workspace.GetService(); + var service = workspace.Services.GetRequiredService(); var diagnostics = await service.ForceAnalyzeProjectAsync(project, CancellationToken.None); diff --git a/src/EditorFeatures/Test/RenameTracking/RenameTrackingTestState.cs b/src/EditorFeatures/Test/RenameTracking/RenameTrackingTestState.cs index e74ee8de48d20..4c28368add087 100644 --- a/src/EditorFeatures/Test/RenameTracking/RenameTrackingTestState.cs +++ b/src/EditorFeatures/Test/RenameTracking/RenameTrackingTestState.cs @@ -98,7 +98,6 @@ public RenameTrackingTestState( var tracker = new RenameTrackingTaggerProvider( Workspace.GetService(), Workspace.GetService(), - Workspace.GetService(), Workspace.GetService(), Workspace.GetService()); diff --git a/src/EditorFeatures/Test2/CodeFixes/CodeFixServiceTests.vb b/src/EditorFeatures/Test2/CodeFixes/CodeFixServiceTests.vb index 5176fb2119f8f..da7fef606b2a5 100644 --- a/src/EditorFeatures/Test2/CodeFixes/CodeFixServiceTests.vb +++ b/src/EditorFeatures/Test2/CodeFixes/CodeFixServiceTests.vb @@ -56,10 +56,9 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.CodeFixes.UnitTests Dim project = workspace.CurrentSolution.Projects(0) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim logger = SpecializedCollections.SingletonEnumerable(New Lazy(Of IErrorLoggerService)(Function() workspace.Services.GetService(Of IErrorLoggerService))) Dim codefixService = New CodeFixService( - diagnosticService, logger, {New Lazy(Of CodeFixProvider, Mef.CodeChangeProviderMetadata)( Function() workspaceCodeFixProvider, @@ -127,10 +126,9 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.CodeFixes.UnitTests Dim project = workspace.CurrentSolution.Projects(0) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim logger = SpecializedCollections.SingletonEnumerable(New Lazy(Of IErrorLoggerService)(Function() workspace.Services.GetService(Of IErrorLoggerService))) Dim codefixService = New CodeFixService( - diagnosticService, logger, {New Lazy(Of CodeFixProvider, Mef.CodeChangeProviderMetadata)( Function() workspaceCodeFixProvider, diff --git a/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb b/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb index bbb8f0f4a8012..10b648744e280 100644 --- a/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/DiagnosticProviderTests.vb @@ -290,7 +290,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim analyzerReference = New TestAnalyzerReferenceByLanguage(compilerAnalyzersMap) workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences({analyzerReference})) - Dim analyzerService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim analyzerService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Return analyzerService End Function diff --git a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb index 9913c17cfea6b..416331a733318 100644 --- a/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb +++ b/src/EditorFeatures/Test2/Diagnostics/DiagnosticServiceTests.vb @@ -86,7 +86,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim hostAnalyzers = solution.SolutionState.Analyzers Dim project = solution.Projects(0) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() ' Verify available diagnostic descriptors/analyzers Dim descriptorsMap = hostAnalyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -192,7 +192,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim project = solution.Projects(0) Dim hostAnalyzers = solution.SolutionState.Analyzers - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() ' Add project analyzer reference with no analyzers. Dim projectAnalyzersEmpty = ImmutableArray(Of DiagnosticAnalyzer).Empty @@ -236,7 +236,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim projectAnalyzerReference = New AnalyzerImageReference( ImmutableArray.Create(Of DiagnosticAnalyzer)(New TestDiagnosticAnalyzer1(1)), display:=referenceName) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() project = project.WithAnalyzerReferences(ImmutableArray.Create(Of AnalyzerReference)(projectAnalyzerReference)) @@ -272,7 +272,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim project = solution.Projects(0) Dim hostAnalyzers = solution.SolutionState.Analyzers - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() ' Verify available diagnostic descriptors/analyzers Dim descriptorsMap = hostAnalyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -343,7 +343,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests SerializerService.TestAccessor.AddAnalyzerImageReferences(p2.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim hostAnalyzers = solution.SolutionState.Analyzers Dim workspaceDescriptors = hostAnalyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache) @@ -384,7 +384,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim analyzerReference = New TestAnalyzerReferenceByLanguage(analyzersMap) workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences({analyzerReference})) - Dim diagnosticService2 = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService2 = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptors = workspace.CurrentSolution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService2.AnalyzerInfoCache) Assert.Equal(1, descriptors.Count) @@ -432,7 +432,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Dim analyzerReference2 = CreateAnalyzerFileReference(Assembly.GetExecutingAssembly().Location) project = project.AddAnalyzerReference(analyzerReference2) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = workspace.CurrentSolution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) ' Verify no duplicate diagnostics. @@ -486,7 +486,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -521,7 +521,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -566,7 +566,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests project = project.AddAnalyzerReference(analyzerReference) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim span = (Await document.GetSyntaxRootAsync().ConfigureAwait(False)).FullSpan Dim diagnostics = Await GetDiagnosticsForSpanAsync(diagnosticService, document, span).ConfigureAwait(False) @@ -588,7 +588,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests Using workspace = TestWorkspace.CreateWorkspace(test, composition:=s_compositionWithMockDiagnosticUpdateSourceRegistrationService) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() For Each actionKind As OperationAnalyzer.ActionKind In [Enum].GetValues(GetType(OperationAnalyzer.ActionKind)) Dim solution = workspace.CurrentSolution @@ -633,7 +633,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -667,7 +667,7 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.Diagnostics.UnitTests SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() ' Ensure no duplicate diagnostics. Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -743,7 +743,7 @@ class AnonymousFunctions SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() ' Ensure no duplicate diagnostics. Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -779,7 +779,7 @@ class AnonymousFunctions SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -942,7 +942,7 @@ class AnonymousFunctions SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -982,7 +982,7 @@ class AnonymousFunctions SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -1038,7 +1038,7 @@ class AnonymousFunctions SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -1090,7 +1090,7 @@ public class B SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -1133,7 +1133,7 @@ public class B SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -1176,7 +1176,7 @@ public class B SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -1227,7 +1227,7 @@ End Class SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -1290,7 +1290,7 @@ public class B SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -1337,7 +1337,7 @@ public class B SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -1408,7 +1408,7 @@ public class B project = additionalDoc.Project Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Await TestCompilationAnalyzerWithAnalyzerOptionsCoreAsync(project, additionalDocText, diagnosticService) @@ -1961,7 +1961,7 @@ End Class SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -2021,7 +2021,7 @@ namespace ConsoleApplication1 SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -2088,7 +2088,7 @@ class MyClass SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() ' Verify available diagnostic descriptors/analyzers Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -2127,7 +2127,7 @@ class MyClass SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() ' Verify available diagnostic descriptors Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) @@ -2187,7 +2187,7 @@ class C Dim span = localDecl.Span Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() ' Verify diagnostics for span Dim diagnostics = Await GetDiagnosticsForSpanAsync(diagnosticService, document, span) @@ -2228,7 +2228,7 @@ class C SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) @@ -2273,7 +2273,7 @@ class MyClass SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() ' Get diagnostics for span for the given DiagnosticKind Dim document = project.Documents.Single() @@ -2343,7 +2343,7 @@ class MyClass SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() ' Get diagnostics for span for fine grained DiagnosticKind in random order Dim document = project.Documents.Single() @@ -2433,7 +2433,7 @@ public class C SerializerService.TestAccessor.AddAnalyzerImageReferences(project.AnalyzerReferences) Dim mefExportProvider = DirectCast(workspace.Services.HostServices, IMefHostExportProvider) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim descriptorsMap = solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(diagnosticService.AnalyzerInfoCache, project) Assert.Equal(1, descriptorsMap.Count) diff --git a/src/EditorFeatures/Test2/Rename/RenameTestHelpers.vb b/src/EditorFeatures/Test2/Rename/RenameTestHelpers.vb index 19f53caf23725..bf29cf35ecad4 100644 --- a/src/EditorFeatures/Test2/Rename/RenameTestHelpers.vb +++ b/src/EditorFeatures/Test2/Rename/RenameTestHelpers.vb @@ -109,7 +109,6 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Rename Dim tracker = New RenameTrackingTaggerProvider( workspace.GetService(Of IThreadingContext), workspace.GetService(Of IInlineRenameService)(), - workspace.GetService(Of IDiagnosticAnalyzerService)(), workspace.GetService(Of IGlobalOptionService)(), workspace.GetService(Of IAsynchronousOperationListenerProvider)) diff --git a/src/EditorFeatures/VisualBasic/CodeCleanup/VisualBasicCodeCleanupService.vb b/src/EditorFeatures/VisualBasic/CodeCleanup/VisualBasicCodeCleanupService.vb index c23580a6824f3..f0c150f02a1e7 100644 --- a/src/EditorFeatures/VisualBasic/CodeCleanup/VisualBasicCodeCleanupService.vb +++ b/src/EditorFeatures/VisualBasic/CodeCleanup/VisualBasicCodeCleanupService.vb @@ -82,8 +82,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.CodeCleanup - Public Sub New(codeFixService As ICodeFixService, diagnosticAnalyzerService As IDiagnosticAnalyzerService) - MyBase.New(codeFixService, diagnosticAnalyzerService) + Public Sub New(codeFixService As ICodeFixService) + MyBase.New(codeFixService) End Sub Protected Overrides ReadOnly Property OrganizeImportsDescription As String = VBFeaturesResources.Organize_Imports diff --git a/src/Features/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs b/src/Features/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs index 10a161b802860..2e3bd31b2d426 100644 --- a/src/Features/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs +++ b/src/Features/CSharpTest/Diagnostics/Suppression/SuppressionTests.cs @@ -446,12 +446,11 @@ void Method() var analyzerReference = new AnalyzerImageReference([new CSharpCompilerDiagnosticAnalyzer()]); workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences([analyzerReference])); - var diagnosticService = workspace.ExportProvider.GetExportedValue(); + var diagnosticService = workspace.Services.GetRequiredService(); var suppressionProvider = CreateDiagnosticProviderAndFixer(workspace).Item2; var suppressionProviderFactory = new Lazy(() => suppressionProvider, new CodeChangeProviderMetadata("SuppressionProvider", languages: [LanguageNames.CSharp])); var fixService = new CodeFixService( - diagnosticService, loggers: [], fixers: [], [suppressionProviderFactory]); diff --git a/src/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.FixAllDiagnosticProvider.cs b/src/Features/Core/Portable/CodeFixes/Service/CodeFixService.FixAllDiagnosticProvider.cs similarity index 83% rename from src/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.FixAllDiagnosticProvider.cs rename to src/Features/Core/Portable/CodeFixes/Service/CodeFixService.FixAllDiagnosticProvider.cs index 46e18b82466e5..0a8648a8a5977 100644 --- a/src/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.FixAllDiagnosticProvider.cs +++ b/src/Features/Core/Portable/CodeFixes/Service/CodeFixService.FixAllDiagnosticProvider.cs @@ -18,14 +18,11 @@ internal partial class CodeFixService { private sealed class FixAllDiagnosticProvider : FixAllContext.SpanBasedDiagnosticProvider { - private readonly IDiagnosticAnalyzerService _diagnosticService; private readonly ImmutableHashSet? _diagnosticIds; private readonly bool _includeSuppressedDiagnostics; - public FixAllDiagnosticProvider(IDiagnosticAnalyzerService diagnosticService, ImmutableHashSet diagnosticIds) + public FixAllDiagnosticProvider(ImmutableHashSet diagnosticIds) { - _diagnosticService = diagnosticService; - // When computing FixAll for unnecessary pragma suppression diagnostic, // we need to include suppressed diagnostics, as well as reported compiler and analyzer diagnostics. // A null value for '_diagnosticIds' ensures the latter. @@ -46,7 +43,8 @@ private ImmutableArray Filter(ImmutableArray dia public override async Task> GetDocumentDiagnosticsAsync(Document document, CancellationToken cancellationToken) { - var diagnostics = Filter(await _diagnosticService.GetDiagnosticsForIdsAsync( + var service = document.Project.Solution.Services.GetRequiredService(); + var diagnostics = Filter(await service.GetDiagnosticsForIdsAsync( document.Project, document.Id, _diagnosticIds, shouldIncludeAnalyzer: null, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: false, cancellationToken).ConfigureAwait(false)); Contract.ThrowIfFalse(diagnostics.All(d => d.DocumentId != null)); return await diagnostics.ToDiagnosticsAsync(document.Project, cancellationToken).ConfigureAwait(false); @@ -55,7 +53,9 @@ public override async Task> GetDocumentDiagnosticsAsync( public override async Task> GetDocumentSpanDiagnosticsAsync(Document document, TextSpan fixAllSpan, CancellationToken cancellationToken) { bool shouldIncludeDiagnostic(string id) => _diagnosticIds == null || _diagnosticIds.Contains(id); - var diagnostics = Filter(await _diagnosticService.GetDiagnosticsForSpanAsync( + + var service = document.Project.Solution.Services.GetRequiredService(); + var diagnostics = Filter(await service.GetDiagnosticsForSpanAsync( document, fixAllSpan, shouldIncludeDiagnostic, priorityProvider: new DefaultCodeActionRequestPriorityProvider(), DiagnosticKind.All, isExplicit: false, cancellationToken).ConfigureAwait(false)); @@ -66,7 +66,8 @@ public override async Task> GetDocumentSpanDiagnosticsAs public override async Task> GetAllDiagnosticsAsync(Project project, CancellationToken cancellationToken) { // Get all diagnostics for the entire project, including document diagnostics. - var diagnostics = Filter(await _diagnosticService.GetDiagnosticsForIdsAsync( + var service = project.Solution.Services.GetRequiredService(); + var diagnostics = Filter(await service.GetDiagnosticsForIdsAsync( project, documentId: null, _diagnosticIds, shouldIncludeAnalyzer: null, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: false, cancellationToken).ConfigureAwait(false)); return await diagnostics.ToDiagnosticsAsync(project, cancellationToken).ConfigureAwait(false); } @@ -74,7 +75,8 @@ public override async Task> GetAllDiagnosticsAsync(Proje public override async Task> GetProjectDiagnosticsAsync(Project project, CancellationToken cancellationToken) { // Get all no-location diagnostics for the project, doesn't include document diagnostics. - var diagnostics = Filter(await _diagnosticService.GetProjectDiagnosticsForIdsAsync( + var service = project.Solution.Services.GetRequiredService(); + var diagnostics = Filter(await service.GetProjectDiagnosticsForIdsAsync( project, _diagnosticIds, shouldIncludeAnalyzer: null, includeNonLocalDocumentDiagnostics: false, cancellationToken).ConfigureAwait(false)); Contract.ThrowIfFalse(diagnostics.All(d => d.DocumentId == null)); return await diagnostics.ToDiagnosticsAsync(project, cancellationToken).ConfigureAwait(false); diff --git a/src/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.FixAllPredefinedDiagnosticProvider.cs b/src/Features/Core/Portable/CodeFixes/Service/CodeFixService.FixAllPredefinedDiagnosticProvider.cs similarity index 100% rename from src/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.FixAllPredefinedDiagnosticProvider.cs rename to src/Features/Core/Portable/CodeFixes/Service/CodeFixService.FixAllPredefinedDiagnosticProvider.cs diff --git a/src/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.ProjectCodeFixProvider.cs b/src/Features/Core/Portable/CodeFixes/Service/CodeFixService.ProjectCodeFixProvider.cs similarity index 100% rename from src/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.ProjectCodeFixProvider.cs rename to src/Features/Core/Portable/CodeFixes/Service/CodeFixService.ProjectCodeFixProvider.cs diff --git a/src/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs b/src/Features/Core/Portable/CodeFixes/Service/CodeFixService.cs similarity index 97% rename from src/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs rename to src/Features/Core/Portable/CodeFixes/Service/CodeFixService.cs index 1ffd392fbd3b1..43f05dd523d43 100644 --- a/src/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs +++ b/src/Features/Core/Portable/CodeFixes/Service/CodeFixService.cs @@ -28,7 +28,7 @@ using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Telemetry; using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.Threading; +using Microsoft.CodeAnalysis.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CodeFixes @@ -39,7 +39,6 @@ namespace Microsoft.CodeAnalysis.CodeFixes [Export(typeof(ICodeFixService)), Shared] internal partial class CodeFixService : ICodeFixService { - private readonly IDiagnosticAnalyzerService _diagnosticService; private readonly ImmutableArray> _fixers; private readonly ImmutableDictionary>> _fixersPerLanguageMap; @@ -59,12 +58,10 @@ internal partial class CodeFixService : ICodeFixService [ImportingConstructor] [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] public CodeFixService( - IDiagnosticAnalyzerService diagnosticAnalyzerService, [ImportMany] IEnumerable> loggers, [ImportMany] IEnumerable> fixers, [ImportMany] IEnumerable> configurationProviders) { - _diagnosticService = diagnosticAnalyzerService; _errorLoggers = [.. loggers]; _fixers = [.. fixers]; @@ -103,11 +100,16 @@ public CodeFixService( { using var _ = TelemetryLogging.LogBlockTimeAggregatedHistogram(FunctionId.CodeFix_Summary, $"Pri{priorityProvider.Priority.GetPriorityInt()}.{nameof(GetMostSevereFixAsync)}"); + // Ensure we yield here so the caller can continue on. + await Task.Yield().ConfigureAwait(false); + ImmutableArray allDiagnostics; - using (TelemetryLogging.LogBlockTimeAggregatedHistogram(FunctionId.CodeFix_Summary, $"Pri{priorityProvider.Priority.GetPriorityInt()}.{nameof(GetMostSevereFixAsync)}.{nameof(_diagnosticService.GetDiagnosticsForSpanAsync)}")) + using (TelemetryLogging.LogBlockTimeAggregatedHistogram( + FunctionId.CodeFix_Summary, $"Pri{priorityProvider.Priority.GetPriorityInt()}.{nameof(GetMostSevereFixAsync)}.{nameof(IDiagnosticAnalyzerService.GetDiagnosticsForSpanAsync)}")) { - allDiagnostics = await _diagnosticService.GetDiagnosticsForSpanAsync( + var service = document.Project.Solution.Services.GetRequiredService(); + allDiagnostics = await service.GetDiagnosticsForSpanAsync( document, range, GetShouldIncludeDiagnosticPredicate(document, priorityProvider), priorityProvider, DiagnosticKind.All, isExplicit: false, cancellationToken).ConfigureAwait(false); @@ -157,7 +159,7 @@ public CodeFixService( CancellationToken cancellationToken) { // Ensure we yield here so the caller can continue on. - await TaskScheduler.Default.SwitchTo(alwaysYield: true); + await Task.Yield().ConfigureAwait(false); await foreach (var collection in StreamFixesAsync( document, spanToDiagnostics, fixAllForInSpan: false, @@ -195,9 +197,11 @@ public async IAsyncEnumerable StreamFixesAsync( // user-invoked diagnostic requests, for example, user invoked Ctrl + Dot operation for lightbulb. ImmutableArray diagnostics; - using (TelemetryLogging.LogBlockTimeAggregatedHistogram(FunctionId.CodeFix_Summary, $"Pri{priorityProvider.Priority.GetPriorityInt()}.{nameof(_diagnosticService.GetDiagnosticsForSpanAsync)}")) + using (TelemetryLogging.LogBlockTimeAggregatedHistogram( + FunctionId.CodeFix_Summary, $"Pri{priorityProvider.Priority.GetPriorityInt()}.{nameof(IDiagnosticAnalyzerService.GetDiagnosticsForSpanAsync)}")) { - diagnostics = await _diagnosticService.GetDiagnosticsForSpanAsync( + var service = document.Project.Solution.Services.GetRequiredService(); + diagnostics = await service.GetDiagnosticsForSpanAsync( document, range, GetShouldIncludeDiagnosticPredicate(document, priorityProvider), priorityProvider, DiagnosticKind.All, isExplicit: true, cancellationToken).ConfigureAwait(false); if (!includeSuppressionFixes) @@ -295,9 +299,11 @@ private static SortedDictionary> ConvertToMap( using var _ = TelemetryLogging.LogBlockTimeAggregatedHistogram(FunctionId.CodeFix_Summary, $"{nameof(GetDocumentFixAllForIdInSpanAsync)}"); ImmutableArray diagnostics; - using (TelemetryLogging.LogBlockTimeAggregatedHistogram(FunctionId.CodeFix_Summary, $"{nameof(GetDocumentFixAllForIdInSpanAsync)}.{nameof(_diagnosticService.GetDiagnosticsForSpanAsync)}")) + using (TelemetryLogging.LogBlockTimeAggregatedHistogram( + FunctionId.CodeFix_Summary, $"{nameof(GetDocumentFixAllForIdInSpanAsync)}.{nameof(IDiagnosticAnalyzerService.GetDiagnosticsForSpanAsync)}")) { - diagnostics = await _diagnosticService.GetDiagnosticsForSpanAsync( + var service = document.Project.Solution.Services.GetRequiredService(); + diagnostics = await service.GetDiagnosticsForSpanAsync( document, range, diagnosticId, priorityProvider: new DefaultCodeActionRequestPriorityProvider(), DiagnosticKind.All, isExplicit: false, cancellationToken).ConfigureAwait(false); @@ -313,9 +319,9 @@ private static SortedDictionary> ConvertToMap( using var resultDisposer = ArrayBuilder.GetInstance(out var result); var spanToDiagnostics = new SortedDictionary> - { - { range, diagnostics.ToList() }, - }; + { + { range, diagnostics.ToList() }, + }; await foreach (var collection in StreamFixesAsync( document, spanToDiagnostics, fixAllForInSpan: true, new DefaultCodeActionRequestPriorityProvider(), @@ -785,7 +791,7 @@ await diagnosticsWithSameSpan.OrderByDescending(d => d.Severity) var diagnosticProvider = fixAllForInSpan ? new FixAllPredefinedDiagnosticProvider(allDiagnostics) - : (FixAllContext.DiagnosticProvider)new FixAllDiagnosticProvider(_diagnosticService, diagnosticIds); + : (FixAllContext.DiagnosticProvider)new FixAllDiagnosticProvider(diagnosticIds); var codeFixProvider = (fixer as CodeFixProvider) ?? new WrapperCodeFixProvider((IConfigurationFixProvider)fixer, diagnostics.Select(d => d.Id)); diff --git a/src/LanguageServer/Protocol/Features/CodeFixes/ICodeFixService.cs b/src/Features/Core/Portable/CodeFixes/Service/ICodeFixService.cs similarity index 100% rename from src/LanguageServer/Protocol/Features/CodeFixes/ICodeFixService.cs rename to src/Features/Core/Portable/CodeFixes/Service/ICodeFixService.cs diff --git a/src/Features/Core/Portable/Copilot/CopilotChangeAnalysis.cs b/src/Features/Core/Portable/Copilot/CopilotChangeAnalysis.cs new file mode 100644 index 0000000000000..8df5f9a04b589 --- /dev/null +++ b/src/Features/Core/Portable/Copilot/CopilotChangeAnalysis.cs @@ -0,0 +1,58 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Runtime.Serialization; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.CodeAnalysis.Copilot; + +/// Total time to do all analysis (including diagnostics, code fixes, and application). +/// Total time to do all diagnostic computation over all diagnostic kinds. +[DataContract] +internal readonly record struct CopilotChangeAnalysis( + [property: DataMember(Order = 0)] bool Succeeded, + [property: DataMember(Order = 1)] int OldDocumentLength, + [property: DataMember(Order = 2)] int NewDocumentLength, + [property: DataMember(Order = 3)] int TextChangeDelta, + [property: DataMember(Order = 4)] int ProjectDocumentCount, + [property: DataMember(Order = 5)] int ProjectSourceGeneratedDocumentCount, + [property: DataMember(Order = 6)] int ProjectConeCount, + [property: DataMember(Order = 7)] TimeSpan TotalAnalysisTime, + [property: DataMember(Order = 8)] TimeSpan ForkingTime, + [property: DataMember(Order = 9)] TimeSpan TotalDiagnosticComputationTime, + [property: DataMember(Order = 10)] ImmutableArray DiagnosticAnalyses, + [property: DataMember(Order = 11)] CopilotCodeFixAnalysis CodeFixAnalysis); + +/// What diagnostic kind this is analysis data for. +/// How long it took to produce the diagnostics for this diagnostic kind. +/// Mapping from to the number of diagnostics produced for that id. +/// Mapping from to the number of diagnostics produced for that category. +/// Mapping from to the number of diagnostics produced for that severity. +[DataContract] +internal readonly record struct CopilotDiagnosticAnalysis( + [property: DataMember(Order = 0)] DiagnosticKind Kind, + [property: DataMember(Order = 1)] TimeSpan ComputationTime, + [property: DataMember(Order = 2)] Dictionary IdToCount, + [property: DataMember(Order = 3)] Dictionary CategoryToCount, + [property: DataMember(Order = 4)] Dictionary SeverityToCount); + +/// Total time to compute code fixes for the changed regions. +/// Total time to apply code fixes for the changed regions. +/// Mapping from diagnostic id to to how many diagnostics with that id had fixes. +/// Mapping from diagnostic id to the total time taken to fix diagnostics with that id. +/// Mapping from diagnostic id to the name of the provider that provided the fix. +/// Mapping from provider name to the total time taken to fix diagnostics with that provider. +/// Mapping from provider name to whether or not that provider conflicted with another provider in producing a fix. +[DataContract] +internal readonly record struct CopilotCodeFixAnalysis( + [property: DataMember(Order = 0)] TimeSpan TotalComputationTime, + [property: DataMember(Order = 1)] TimeSpan TotalApplicationTime, + [property: DataMember(Order = 2)] Dictionary DiagnosticIdToCount, + [property: DataMember(Order = 3)] Dictionary DiagnosticIdToApplicationTime, + [property: DataMember(Order = 4)] Dictionary> DiagnosticIdToProviderName, + [property: DataMember(Order = 5)] Dictionary ProviderNameToApplicationTime, + [property: DataMember(Order = 6)] Dictionary ProviderNameToHasConflict); diff --git a/src/Features/Core/Portable/Copilot/CopilotChangeAnalysisUtilities.cs b/src/Features/Core/Portable/Copilot/CopilotChangeAnalysisUtilities.cs new file mode 100644 index 0000000000000..664136fd80f4b --- /dev/null +++ b/src/Features/Core/Portable/Copilot/CopilotChangeAnalysisUtilities.cs @@ -0,0 +1,139 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Copilot; + +internal static class CopilotChangeAnalysisUtilities +{ + /// + /// Analyzes and collects interesting data about an edit made by some copilot feature, and reports that back as + /// telemetry to help inform what automatic fixing features we should invest in. + /// + /// The document being edited. The document should represent the contents of hte file + /// prior to the being applied. + /// Whether or not the user accepted the copilot suggestion, or rejected it. Used to + /// determine if there are interesting issues occurring that might be leading to the user rejecting the change + /// (for example, excessive syntax errors). + /// The name of the feature making the text change. For example 'Completion'. Used + /// to bucket information by feature area in case certain feature produce different sets of diagnostics or + /// fixes commonly. + /// Copilot proposal id (generally a stringified ). Used to be able + /// to map from one of these proposed edits to any additional telemetry stored in other tables about this copilot + /// interaction. + /// The actual text changes to make. The text changes do not have to be normalized. + /// Though they should not overlap. If they overlap, this request will be ignored. These would be the changes + /// passed to for the text snapshot corresponding to + /// . + public static async Task AnalyzeCopilotChangeAsync( + Document document, + bool accepted, + string featureId, + string proposalId, + IEnumerable textChanges, + CancellationToken cancellationToken) + { + // Currently we do not support analyzing languges other than C# and VB. This is because we only want to do + // this analsis in our OOP process to avoid perf impact on the VS process. And we don't have OOP for other + // languages yet. + if (!document.SupportsSemanticModel) + return; + + var normalizedEdits = Normalize(textChanges); + if (normalizedEdits.IsDefaultOrEmpty) + return; + + var changeAnalysisService = document.Project.Solution.Services.GetRequiredService(); + var analysisResult = await changeAnalysisService.AnalyzeChangeAsync( + document, normalizedEdits, cancellationToken).ConfigureAwait(false); + + CopilotChangeAnalysisUtilities.LogCopilotChangeAnalysis( + featureId, accepted, proposalId, analysisResult, cancellationToken).Dispose(); + } + + private static ImmutableArray Normalize(IEnumerable textChanges) + { + using var _ = PooledObjects.ArrayBuilder.GetInstance(out var builder); + foreach (var textChange in textChanges) + builder.Add(textChange); + + // Ensure everything is sorted. + builder.Sort(static (c1, c2) => c1.Span.Start - c2.Span.Start); + + // Now, go through and make sure no edit overlaps another. + for (int i = 1, n = builder.Count; i < n; i++) + { + var lastEdit = builder[i - 1]; + var currentEdit = builder[i]; + + if (lastEdit.Span.OverlapsWith(currentEdit.Span)) + return default; + } + + // Things look good. Can process these sorted edits. + return builder.ToImmutableAndClear(); + } + + public static IDisposable LogCopilotChangeAnalysis( + string featureId, bool accepted, string proposalId, CopilotChangeAnalysis analysisResult, CancellationToken cancellationToken) + { + return Logger.LogBlock(FunctionId.Copilot_AnalyzeChange, KeyValueLogMessage.Create(d => + { + d["Accepted"] = accepted; + d["FeatureId"] = featureId; + d["ProposalId"] = proposalId; + + d["Succeeded"] = analysisResult.Succeeded; + + d["OldDocumentLength"] = analysisResult.OldDocumentLength; + d["NewDocumentLength"] = analysisResult.NewDocumentLength; + d["TextChangeDelta"] = analysisResult.TextChangeDelta; + + d["ProjectDocumentCount"] = analysisResult.ProjectDocumentCount; + d["ProjectSourceGeneratedDocumentCount"] = analysisResult.ProjectSourceGeneratedDocumentCount; + d["ProjectConeCount"] = analysisResult.ProjectConeCount; + + foreach (var diagnosticAnalysis in analysisResult.DiagnosticAnalyses) + { + var keyPrefix = $"DiagnosticAnalysis_{diagnosticAnalysis.Kind}"; + + d[$"{keyPrefix}_ComputationTime"] = Stringify(diagnosticAnalysis.ComputationTime); + d[$"{keyPrefix}_IdToCount"] = StringifyDictionary(diagnosticAnalysis.IdToCount); + d[$"{keyPrefix}_CategoryToCount"] = StringifyDictionary(diagnosticAnalysis.CategoryToCount); + d[$"{keyPrefix}_SeverityToCount"] = StringifyDictionary(diagnosticAnalysis.SeverityToCount); + } + + d["CodeFixAnalysis_TotalComputationTime"] = Stringify(analysisResult.CodeFixAnalysis.TotalComputationTime); + d["CodeFixAnalysis_TotalApplicationTime"] = Stringify(analysisResult.CodeFixAnalysis.TotalApplicationTime); + d["CodeFixAnalysis_DiagnosticIdToCount"] = StringifyDictionary(analysisResult.CodeFixAnalysis.DiagnosticIdToCount); + d["CodeFixAnalysis_DiagnosticIdToApplicationTime"] = StringifyDictionary(analysisResult.CodeFixAnalysis.DiagnosticIdToApplicationTime); + d["CodeFixAnalysis_DiagnosticIdToProviderName"] = StringifyDictionary(analysisResult.CodeFixAnalysis.DiagnosticIdToProviderName); + d["CodeFixAnalysis_ProviderNameToApplicationTime"] = StringifyDictionary(analysisResult.CodeFixAnalysis.ProviderNameToApplicationTime); + d["CodeFixAnalysis_ProviderNameToHasConflict"] = StringifyDictionary(analysisResult.CodeFixAnalysis.ProviderNameToHasConflict); + }), + cancellationToken); + } + + private static string StringifyDictionary(Dictionary dictionary) where TKey : notnull where TValue : notnull + => string.Join(",", dictionary.Select(kvp => FormattableString.Invariant($"{kvp.Key}_{Stringify(kvp.Value)}")).OrderBy(v => v)); + + private static string Stringify(TValue value) where TValue : notnull + { + if (value is IEnumerable strings) + return string.Join(":", strings.OrderBy(v => v)); + + if (value is TimeSpan timeSpan) + return timeSpan.TotalMilliseconds.ToString("G17"); + + return value.ToString() ?? ""; + } +} diff --git a/src/Features/Core/Portable/Copilot/CopilotOptions.cs b/src/Features/Core/Portable/Copilot/CopilotOptions.cs new file mode 100644 index 0000000000000..e5d68f2f3b96d --- /dev/null +++ b/src/Features/Core/Portable/Copilot/CopilotOptions.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.Options; + +namespace Microsoft.CodeAnalysis.Copilot; + +internal static class CopilotOptions +{ + public static Option2 AnalyzeCopilotChanges { get; } = new Option2("dotnet_analyze_copilot_changes", defaultValue: false); +} diff --git a/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs b/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs new file mode 100644 index 0000000000000..f4e4f8e626e36 --- /dev/null +++ b/src/Features/Core/Portable/Copilot/ICopilotChangeAnalysisService.cs @@ -0,0 +1,367 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Remote; +using Microsoft.CodeAnalysis.Shared; +using Microsoft.CodeAnalysis.Shared.Collections; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Utilities; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Threading; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Copilot; + +internal interface ICopilotChangeAnalysisService : IWorkspaceService +{ + /// + /// Kicks of work to analyze a change that copilot suggested making to a document. is + /// the state of the document prior to the edits, and are the changes Copilot wants to + /// make to it. must be sorted and normalized before calling this. + /// + Task AnalyzeChangeAsync( + Document document, ImmutableArray changes, CancellationToken cancellationToken); +} + +[ExportWorkspaceService(typeof(ICopilotChangeAnalysisService)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class DefaultCopilotChangeAnalysisService( + ICodeFixService codeFixService) : ICopilotChangeAnalysisService +{ + private const string RoslynPrefix = "Microsoft.CodeAnalysis."; + + private readonly ICodeFixService _codeFixService = codeFixService; + + private static bool IsSorted(ImmutableArray array, Comparison comparison) + => array.IsSorted(Comparer.Create(comparison)); + + public async Task AnalyzeChangeAsync( + Document document, + ImmutableArray changes, + CancellationToken cancellationToken) + { + Contract.ThrowIfFalse(document.SupportsSemanticModel); + + Contract.ThrowIfTrue(!IsSorted(changes, static (c1, c2) => c1.Span.Start - c2.Span.Start), "'changes' was not sorted."); + Contract.ThrowIfTrue(new NormalizedTextSpanCollection(changes.Select(c => c.Span)).Count != changes.Length, "'changes' was not normalized."); + + var client = await RemoteHostClient.TryGetClientAsync(document.Project, cancellationToken).ConfigureAwait(false); + + if (client != null) + { + var value = await client.TryInvokeAsync( + // Don't need to sync the entire solution over. Just the cone of projects this document it contained within. + document.Project, + (service, checksum, cancellationToken) => service.AnalyzeChangeAsync( + checksum, document.Id, changes, cancellationToken), + cancellationToken).ConfigureAwait(false); + return value.HasValue ? value.Value : default; + } + else + { + return await AnalyzeChangeInCurrentProcessAsync( + document, changes, cancellationToken).ConfigureAwait(false); + } + } + + private async Task AnalyzeChangeInCurrentProcessAsync( + Document document, + ImmutableArray changes, + CancellationToken cancellationToken) + { + // Keep track of how long our analysis takes entirely. + var totalAnalysisTimeStopWatch = SharedStopwatch.StartNew(); + + var forkingTimeStopWatch = SharedStopwatch.StartNew(); + + // Fork the starting document with the changes copilot wants to make. Keep track of where the edited spans + // move to in the forked doucment, as that is what we will want to analyze. + var oldText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + var newText = oldText.WithChanges(changes); + + var newDocument = document.WithText(newText); + + // Get the semantic model and keep it alive so none of the work we do causes it to be dropped. + var semanticModel = await newDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var forkingTime = forkingTimeStopWatch.Elapsed; + + var totalDelta = 0; + using var _ = ArrayBuilder.GetInstance(out var newSpans); + + foreach (var change in changes) + { + var newTextLength = change.NewText!.Length; + + newSpans.Add(new TextSpan(change.Span.Start + totalDelta, newTextLength)); + totalDelta += newTextLength - change.Span.Length; + } + + // First, determine the diagnostics produced in the edits that copilot makes. Done non-concurrently with + // ComputeCodeFixAnalysisAsync as we want good data on just how long it takes to even compute the varying + // types of diagnostics, without contending with the code fix analysis. + var totalDiagnosticComputationTimeStopWatch = SharedStopwatch.StartNew(); + var diagnosticAnalyses = await ComputeAllDiagnosticAnalysesAsync( + newDocument, newSpans, cancellationToken).ConfigureAwait(false); + var totalDiagnosticComputationTime = totalDiagnosticComputationTimeStopWatch.Elapsed; + + // After computing diagnostics, do another analysis pass to see if we would have been able to fixup any of + // the code copilot produced. + var codeFixAnalysis = await ComputeCodeFixAnalysisAsync( + newDocument, newSpans, cancellationToken).ConfigureAwait(false); + + var totalAnalysisTime = totalAnalysisTimeStopWatch.Elapsed; + + var sourceGeneratedDocuments = await newDocument.Project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false); + + var projectDocumentCount = newDocument.Project.DocumentIds.Count; + var projectSourceGeneratedDocumentCount = sourceGeneratedDocuments.Count(); + var projectConeCount = 1 + document.Project.Solution + .GetProjectDependencyGraph() + .GetProjectsThatThisProjectTransitivelyDependsOn(document.Project.Id).Count; + + GC.KeepAlive(semanticModel); + + return new CopilotChangeAnalysis( + Succeeded: true, + OldDocumentLength: oldText.Length, + NewDocumentLength: newText.Length, + TextChangeDelta: totalDelta, + ProjectDocumentCount: projectDocumentCount, + ProjectSourceGeneratedDocumentCount: projectSourceGeneratedDocumentCount, + ProjectConeCount: projectConeCount, + TotalAnalysisTime: totalAnalysisTime, + ForkingTime: forkingTime, + TotalDiagnosticComputationTime: totalDiagnosticComputationTime, + diagnosticAnalyses, + codeFixAnalysis); + } + + private static CodeAction GetFirstAction(CodeFix codeFix) + { + var action = codeFix.Action; + while (action is { NestedCodeActions: [var nestedAction, ..] }) + action = nestedAction; + + return action; + } + + private static void IncrementCount(Dictionary map, TKey key) where TKey : notnull + { + map.TryGetValue(key, out var idCount); + map[key] = idCount + 1; + } + + private static void IncrementElapsedTime(Dictionary map, TKey key, TimeSpan elapsed) where TKey : notnull + { + map.TryGetValue(key, out var currentElapsed); + map[key] = currentElapsed + elapsed; + } + + private static Task> ComputeAllDiagnosticAnalysesAsync( + Document newDocument, + ArrayBuilder newSpans, + CancellationToken cancellationToken) + { + // Compute the data in parallel for each diagnostic kind. + return ProducerConsumer.RunParallelAsync( + [DiagnosticKind.CompilerSyntax, DiagnosticKind.CompilerSemantic, DiagnosticKind.AnalyzerSyntax, DiagnosticKind.AnalyzerSemantic], + static async (diagnosticKind, callback, args, cancellationToken) => + { + var (newDocument, newSpans) = args; + + var computationTime = SharedStopwatch.StartNew(); + + // Compute the diagnostics. + var diagnostics = await ComputeDiagnosticsAsync( + newDocument, newSpans, diagnosticKind, cancellationToken).ConfigureAwait(false); + + // Collect the data to report as telemetry. + var idToCount = new Dictionary(); + var categoryToCount = new Dictionary(); + var severityToCount = new Dictionary(); + + foreach (var diagnostic in diagnostics) + { + IncrementCount(idToCount, diagnostic.Id); + IncrementCount(categoryToCount, diagnostic.Category); + IncrementCount(severityToCount, diagnostic.Severity); + } + + callback(new CopilotDiagnosticAnalysis( + diagnosticKind, + computationTime.Elapsed, + idToCount, + categoryToCount, + severityToCount)); + }, + args: (newDocument, newSpans), + cancellationToken); + + static Task> ComputeDiagnosticsAsync( + Document newDocument, + ArrayBuilder newSpans, + DiagnosticKind diagnosticKind, + CancellationToken cancellationToken) + { + // Get diagnostics in parallel for all edited spans, for the desired diagnostic kind. + return ProducerConsumer.RunParallelAsync( + newSpans, + static async (span, callback, args, cancellationToken) => + { + var (newDocument, diagnosticKind) = args; + var diagnosticAnalyzerService = newDocument.Project.Solution.Services.GetRequiredService(); + var diagnostics = await diagnosticAnalyzerService.GetDiagnosticsForSpanAsync( + newDocument, span, diagnosticKind, cancellationToken).ConfigureAwait(false); + foreach (var diagnostic in diagnostics) + { + // Ignore supressed and hidden diagnostics. These are things the user has said they do not + // care about and would then have no interest in being auto fixed. + if (IsVisibleDiagnostic(diagnostic.IsSuppressed, diagnostic.Severity)) + callback(diagnostic); + } + }, + args: (newDocument, diagnosticKind), + cancellationToken); + } + } + + private static bool IsVisibleDiagnostic(bool isSuppressed, DiagnosticSeverity severity) + => !isSuppressed && severity != DiagnosticSeverity.Hidden; + + private async Task ComputeCodeFixAnalysisAsync( + Document newDocument, + ArrayBuilder newSpans, + CancellationToken cancellationToken) + { + // Determine how long it would be to even compute code fixes for these changed regions. + var totalComputationStopWatch = SharedStopwatch.StartNew(); + var codeFixCollections = await ComputeCodeFixCollectionsAsync().ConfigureAwait(false); + var totalComputationTime = totalComputationStopWatch.Elapsed; + + var diagnosticIdToCount = new Dictionary(); + var diagnosticIdToApplicationTime = new Dictionary(); + var diagnosticIdToProviderName = new Dictionary>(); + var providerNameToApplicationTime = new Dictionary(); + var providerNameToHasConflict = new Dictionary(); + + var totalApplicationTimeStopWatch = SharedStopwatch.StartNew(); + await ProducerConsumer<(CodeFixCollection collection, TimeSpan elapsedTime)>.RunParallelAsync( + codeFixCollections, + produceItems: static async (codeFixCollection, callback, args, cancellationToken) => + { + var (@this, solution, _, _, _, _, _) = args; + var firstAction = GetFirstAction(codeFixCollection.Fixes[0]); + + var applicationTimeStopWatch = SharedStopwatch.StartNew(); + var result = await firstAction.GetPreviewOperationsAsync(solution, cancellationToken).ConfigureAwait(false); + callback((codeFixCollection, applicationTimeStopWatch.Elapsed)); + }, + consumeItems: static async (values, args, cancellationToken) => + { + var (@this, solution, diagnosticIdToCount, diagnosticIdToApplicationTime, diagnosticIdToProviderName, providerNameToApplicationTime, providerNameToHasConflict) = args; + + // Track which text span each code fix says it will be fixing. We can use this to efficiently determine + // which codefixes 'conflict' with some other codefix (in that that multiple features think they can fix + // the same span of code). We would need some mechanism to determine which we would prefer to take in + // order to have a good experience in such a case. + var intervalTree = new SimpleMutableIntervalTree(new CodeFixCollectionIntervalIntrospector()); + + await foreach (var (codeFixCollection, applicationTime) in values) + { + var diagnosticId = codeFixCollection.FirstDiagnostic.Id; + var providerName = GetProviderName(codeFixCollection); + + IncrementCount(diagnosticIdToCount, diagnosticId); + IncrementElapsedTime(diagnosticIdToApplicationTime, diagnosticId, applicationTime); + diagnosticIdToProviderName.MultiAdd(diagnosticId, providerName); + IncrementElapsedTime(providerNameToApplicationTime, providerName, applicationTime); + + intervalTree.AddIntervalInPlace(codeFixCollection); + } + + // Now go over the fixed spans and see which intersect with other spans + using var intersectingCollections = TemporaryArray.Empty; + foreach (var codeFixCollection in intervalTree) + { + intersectingCollections.Clear(); + intervalTree.FillWithIntervalsThatIntersectWith( + codeFixCollection.TextSpan.Start, + codeFixCollection.TextSpan.Length, + ref intersectingCollections.AsRef()); + + var providerName = GetProviderName(codeFixCollection); + + // >= 2 because we want to see how many total fixers fix a particular span, and we only care if + // we're seeing multiple. + var newHasConflictValue = intersectingCollections.Count >= 2; + var storedHasConflictValue = providerNameToHasConflict.TryGetValue(providerName, out var currentHasConflictValue) && currentHasConflictValue; + + providerNameToHasConflict[providerName] = storedHasConflictValue || newHasConflictValue; + } + }, + args: (@this: this, newDocument.Project.Solution, diagnosticIdToCount, diagnosticIdToApplicationTime, diagnosticIdToProviderName, providerNameToApplicationTime, providerNameToHasConflict), + cancellationToken).ConfigureAwait(false); + var totalApplicationTime = totalApplicationTimeStopWatch.Elapsed; + + return new CopilotCodeFixAnalysis( + totalComputationTime, + totalApplicationTime, + diagnosticIdToCount, + diagnosticIdToApplicationTime, + diagnosticIdToProviderName, + providerNameToApplicationTime, + providerNameToHasConflict); + + Task> ComputeCodeFixCollectionsAsync() + { + return ProducerConsumer.RunParallelAsync( + newSpans, + static async (span, callback, args, cancellationToken) => + { + var (@this, newDocument) = args; + await foreach (var codeFixCollection in @this._codeFixService.StreamFixesAsync( + newDocument, span, cancellationToken).ConfigureAwait(false)) + { + // Ignore the suppress/configure codefixes that are almost always present. + // We would not ever want to apply those to a copilot change. + if (codeFixCollection is + { + Provider: not IConfigurationFixProvider, + Fixes: [var codeFix, ..], + } && + IsVisibleDiagnostic(codeFix.PrimaryDiagnostic.IsSuppressed, codeFix.PrimaryDiagnostic.Severity) && + (codeFixCollection.Provider.GetType().Namespace ?? "").StartsWith(RoslynPrefix)) + { + callback(codeFixCollection); + } + } + }, + args: (@this: this, newDocument), + cancellationToken); + } + + static string GetProviderName(CodeFixCollection codeFixCollection) + => codeFixCollection.Provider.GetType().FullName![RoslynPrefix.Length..]; + } + + private readonly struct CodeFixCollectionIntervalIntrospector : IIntervalIntrospector + { + public TextSpan GetSpan(CodeFixCollection value) + => value.TextSpan; + } +} diff --git a/src/Features/Core/Portable/Copilot/IRemoteCopilotChangeAnalysisService.cs b/src/Features/Core/Portable/Copilot/IRemoteCopilotChangeAnalysisService.cs new file mode 100644 index 0000000000000..d7cb6a1305e19 --- /dev/null +++ b/src/Features/Core/Portable/Copilot/IRemoteCopilotChangeAnalysisService.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Copilot; + +/// Remote version of +internal interface IRemoteCopilotChangeAnalysisService : IWorkspaceService +{ + /// + ValueTask AnalyzeChangeAsync( + Checksum solutionChecksum, DocumentId documentId, ImmutableArray edits, CancellationToken cancellationToken); +} diff --git a/src/Features/Core/Portable/Diagnostics/CodeAnalysisDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/CodeAnalysisDiagnosticAnalyzerService.cs index 6776c555c1283..fcff353750e2e 100644 --- a/src/Features/Core/Portable/Diagnostics/CodeAnalysisDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/CodeAnalysisDiagnosticAnalyzerService.cs @@ -22,10 +22,7 @@ namespace Microsoft.CodeAnalysis.Diagnostics; internal sealed class CodeAnalysisDiagnosticAnalyzerServiceFactory() : IWorkspaceServiceFactory { public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - { - var diagnosticAnalyzerService = workspaceServices.SolutionServices.ExportProvider.GetExports().Single().Value; - return new CodeAnalysisDiagnosticAnalyzerService(diagnosticAnalyzerService, workspaceServices.Workspace); - } + => new CodeAnalysisDiagnosticAnalyzerService(workspaceServices.Workspace); private sealed class CodeAnalysisDiagnosticAnalyzerService : ICodeAnalysisDiagnosticAnalyzerService { @@ -49,11 +46,10 @@ private sealed class CodeAnalysisDiagnosticAnalyzerService : ICodeAnalysisDiagno private readonly ConcurrentSet _clearedProjectIds = []; public CodeAnalysisDiagnosticAnalyzerService( - IDiagnosticAnalyzerService diagnosticAnalyzerService, Workspace workspace) { - _diagnosticAnalyzerService = diagnosticAnalyzerService; _workspace = workspace; + _diagnosticAnalyzerService = _workspace.Services.GetRequiredService(); _workspace.WorkspaceChanged += OnWorkspaceChanged; } diff --git a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs index 3ce287befb209..1a9e32b133d80 100644 --- a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs @@ -7,11 +7,12 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Diagnostics; -internal interface IDiagnosticAnalyzerService +internal interface IDiagnosticAnalyzerService : IWorkspaceService { /// /// Provides and caches analyzer information. diff --git a/src/Features/Core/Portable/Diagnostics/IDiagnosticsRefresher.cs b/src/Features/Core/Portable/Diagnostics/IDiagnosticsRefresher.cs index e185068a39c34..dbfce0abd2433 100644 --- a/src/Features/Core/Portable/Diagnostics/IDiagnosticsRefresher.cs +++ b/src/Features/Core/Portable/Diagnostics/IDiagnosticsRefresher.cs @@ -3,6 +3,9 @@ // See the LICENSE file in the project root for more information. using System; +using System.Composition; +using System.Threading; +using Microsoft.CodeAnalysis.Host.Mef; namespace Microsoft.CodeAnalysis.Diagnostics; @@ -26,3 +29,27 @@ internal interface IDiagnosticsRefresher /// int GlobalStateVersion { get; } } + +[Export(typeof(IDiagnosticsRefresher)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class DefaultDiagnosticsRefresher() : IDiagnosticsRefresher +{ + /// + /// Incremented every time a refresh is requested. + /// + private int _globalStateVersion; + + public event Action? WorkspaceRefreshRequested; + + public void RequestWorkspaceRefresh() + { + // bump version before sending the request to the client: + Interlocked.Increment(ref _globalStateVersion); + + WorkspaceRefreshRequested?.Invoke(); + } + + public int GlobalStateVersion + => _globalStateVersion; +} diff --git a/src/LanguageServer/Protocol/Features/Options/DiagnosticOptionsStorage.cs b/src/Features/Core/Portable/Diagnostics/Options/DiagnosticOptionsStorage.cs similarity index 100% rename from src/LanguageServer/Protocol/Features/Options/DiagnosticOptionsStorage.cs rename to src/Features/Core/Portable/Diagnostics/Options/DiagnosticOptionsStorage.cs diff --git a/src/LanguageServer/Protocol/Features/Options/SolutionCrawlerOptionsStorage.cs b/src/Features/Core/Portable/Diagnostics/Options/SolutionCrawlerOptionsStorage.cs similarity index 100% rename from src/LanguageServer/Protocol/Features/Options/SolutionCrawlerOptionsStorage.cs rename to src/Features/Core/Portable/Diagnostics/Options/SolutionCrawlerOptionsStorage.cs diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService.cs similarity index 64% rename from src/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs rename to src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService.cs index d70c50f09b744..0586c1bbe6bc4 100644 --- a/src/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService.cs @@ -5,22 +5,41 @@ using System; using System.Collections.Immutable; using System.Composition; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.Text; -using Microsoft.VisualStudio.Threading; +using Microsoft.CodeAnalysis.Threading; namespace Microsoft.CodeAnalysis.Diagnostics { - [Export(typeof(IDiagnosticAnalyzerService)), Shared] - internal partial class DiagnosticAnalyzerService : IDiagnosticAnalyzerService + [ExportWorkspaceServiceFactory(typeof(IDiagnosticAnalyzerService)), Shared] + [method: ImportingConstructor] + [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + internal sealed class DiagnosticAnalyzerServiceFactory( + IGlobalOptionService globalOptions, + IDiagnosticsRefresher diagnosticsRefresher, + DiagnosticAnalyzerInfoCache.SharedGlobalCache globalCache, + [Import(AllowDefault = true)] IAsynchronousOperationListenerProvider? listenerProvider) : IWorkspaceServiceFactory + { + public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) + { + return new DiagnosticAnalyzerService( + globalOptions, + diagnosticsRefresher, + globalCache, + listenerProvider, + workspaceServices.Workspace); + } + } + + internal sealed partial class DiagnosticAnalyzerService : IDiagnosticAnalyzerService { private static readonly Option2 s_crashOnAnalyzerException = new("dotnet_crash_on_analyzer_exception", defaultValue: false); @@ -29,23 +48,21 @@ internal partial class DiagnosticAnalyzerService : IDiagnosticAnalyzerService public IAsynchronousOperationListener Listener { get; } private IGlobalOptionService GlobalOptions { get; } - private readonly ConditionalWeakTable _map = new(); - private readonly ConditionalWeakTable.CreateValueCallback _createIncrementalAnalyzer; private readonly IDiagnosticsRefresher _diagnosticsRefresher; + private readonly DiagnosticIncrementalAnalyzer _incrementalAnalyzer; - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public DiagnosticAnalyzerService( - IAsynchronousOperationListenerProvider listenerProvider, - DiagnosticAnalyzerInfoCache.SharedGlobalCache globalCache, IGlobalOptionService globalOptions, - IDiagnosticsRefresher diagnosticsRefresher) + IDiagnosticsRefresher diagnosticsRefresher, + DiagnosticAnalyzerInfoCache.SharedGlobalCache globalCache, + IAsynchronousOperationListenerProvider? listenerProvider, + Workspace workspace) { AnalyzerInfoCache = globalCache.AnalyzerInfoCache; - Listener = listenerProvider.GetListener(FeatureAttribute.DiagnosticService); + Listener = listenerProvider?.GetListener(FeatureAttribute.DiagnosticService) ?? AsynchronousOperationListenerProvider.NullListener; GlobalOptions = globalOptions; _diagnosticsRefresher = diagnosticsRefresher; - _createIncrementalAnalyzer = CreateIncrementalAnalyzerCallback; + _incrementalAnalyzer = new DiagnosticIncrementalAnalyzer(this, AnalyzerInfoCache, this.GlobalOptions); globalOptions.AddOptionChangedHandler(this, (_, _, e) => { @@ -54,6 +71,10 @@ public DiagnosticAnalyzerService( RequestDiagnosticRefresh(); } }); + + // When the workspace changes what context a document is in (when a user picks a different tfm to view the + // document in), kick off a refresh so that diagnostics properly update in the task list and editor. + workspace.DocumentActiveContextChanged += (_, _) => RequestDiagnosticRefresh(); } public static Task GetDiagnosticVersionAsync(Project project, CancellationToken cancellationToken) @@ -87,27 +108,21 @@ public async Task> GetDiagnosticsForSpanAsync( bool isExplicit, CancellationToken cancellationToken) { - var analyzer = CreateIncrementalAnalyzer(document.Project.Solution.Workspace); - // always make sure that analyzer is called on background thread. - await TaskScheduler.Default; + await Task.Yield().ConfigureAwait(false); priorityProvider ??= new DefaultCodeActionRequestPriorityProvider(); - return await analyzer.GetDiagnosticsForSpanAsync( + return await _incrementalAnalyzer.GetDiagnosticsForSpanAsync( document, range, shouldIncludeDiagnostic, priorityProvider, diagnosticKinds, isExplicit, cancellationToken).ConfigureAwait(false); } - public async Task> ForceAnalyzeProjectAsync(Project project, CancellationToken cancellationToken) - { - var analyzer = CreateIncrementalAnalyzer(project.Solution.Workspace); - return await analyzer.ForceAnalyzeProjectAsync(project, cancellationToken).ConfigureAwait(false); - } + public Task> ForceAnalyzeProjectAsync(Project project, CancellationToken cancellationToken) + => _incrementalAnalyzer.ForceAnalyzeProjectAsync(project, cancellationToken); public Task> GetDiagnosticsForIdsAsync( Project project, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) { - var analyzer = CreateIncrementalAnalyzer(project.Solution.Workspace); - return analyzer.GetDiagnosticsForIdsAsync(project, documentId, diagnosticIds, shouldIncludeAnalyzer, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); + return _incrementalAnalyzer.GetDiagnosticsForIdsAsync(project, documentId, diagnosticIds, shouldIncludeAnalyzer, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken); } public Task> GetProjectDiagnosticsForIdsAsync( @@ -115,8 +130,7 @@ public Task> GetProjectDiagnosticsForIdsAsync( Func? shouldIncludeAnalyzer, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) { - var analyzer = CreateIncrementalAnalyzer(project.Solution.Workspace); - return analyzer.GetProjectDiagnosticsForIdsAsync(project, diagnosticIds, shouldIncludeAnalyzer, includeNonLocalDocumentDiagnostics, cancellationToken); + return _incrementalAnalyzer.GetProjectDiagnosticsForIdsAsync(project, diagnosticIds, shouldIncludeAnalyzer, includeNonLocalDocumentDiagnostics, cancellationToken); } public TestAccessor GetTestAccessor() @@ -125,9 +139,7 @@ public TestAccessor GetTestAccessor() public readonly struct TestAccessor(DiagnosticAnalyzerService service) { public Task> GetAnalyzersAsync(Project project, CancellationToken cancellationToken) - { - return service.CreateIncrementalAnalyzer(project.Solution.Workspace).GetAnalyzersForTestingPurposesOnlyAsync(project, cancellationToken); - } + => service._incrementalAnalyzer.GetAnalyzersForTestingPurposesOnlyAsync(project, cancellationToken); } } } diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/DocumentAnalysisExecutor.cs b/src/Features/Core/Portable/Diagnostics/Service/DocumentAnalysisExecutor.cs similarity index 100% rename from src/LanguageServer/Protocol/Features/Diagnostics/DocumentAnalysisExecutor.cs rename to src/Features/Core/Portable/Diagnostics/Service/DocumentAnalysisExecutor.cs diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/DocumentAnalysisExecutor_Helpers.cs b/src/Features/Core/Portable/Diagnostics/Service/DocumentAnalysisExecutor_Helpers.cs similarity index 100% rename from src/LanguageServer/Protocol/Features/Diagnostics/DocumentAnalysisExecutor_Helpers.cs rename to src/Features/Core/Portable/Diagnostics/Service/DocumentAnalysisExecutor_Helpers.cs diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs b/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs similarity index 100% rename from src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs rename to src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs b/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs similarity index 100% rename from src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs rename to src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.HostAnalyzerInfo.cs b/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.HostAnalyzerInfo.cs similarity index 100% rename from src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.HostAnalyzerInfo.cs rename to src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.HostAnalyzerInfo.cs diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.IncrementalMemberEditAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.IncrementalMemberEditAnalyzer.cs similarity index 100% rename from src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.IncrementalMemberEditAnalyzer.cs rename to src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.IncrementalMemberEditAnalyzer.cs diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.IncrementalMemberEditAnalyzer_MemberSpans.cs b/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.IncrementalMemberEditAnalyzer_MemberSpans.cs similarity index 100% rename from src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.IncrementalMemberEditAnalyzer_MemberSpans.cs rename to src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.IncrementalMemberEditAnalyzer_MemberSpans.cs diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs b/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs similarity index 100% rename from src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs rename to src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs b/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs similarity index 100% rename from src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs rename to src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.cs similarity index 100% rename from src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs rename to src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.cs diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs similarity index 100% rename from src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs rename to src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs b/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs similarity index 100% rename from src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs rename to src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs similarity index 100% rename from src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs rename to src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/InProcOrRemoteHostAnalyzerRunner.cs b/src/Features/Core/Portable/Diagnostics/Service/EngineV2/InProcOrRemoteHostAnalyzerRunner.cs similarity index 100% rename from src/LanguageServer/Protocol/Features/Diagnostics/EngineV2/InProcOrRemoteHostAnalyzerRunner.cs rename to src/Features/Core/Portable/Diagnostics/Service/EngineV2/InProcOrRemoteHostAnalyzerRunner.cs diff --git a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptDiagnosticAnalyzerService.cs index 305b32beb9fdd..7f5f386c57b96 100644 --- a/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/ExternalAccess/VSTypeScript/VSTypeScriptDiagnosticAnalyzerService.cs @@ -3,22 +3,19 @@ // See the LICENSE file in the project root for more information. using System; -using System.Composition; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api; using System.Collections.Generic; +using System.Composition; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript.Api; +using Microsoft.CodeAnalysis.Host.Mef; namespace Microsoft.CodeAnalysis.ExternalAccess.VSTypeScript; -[Shared] -[Export(typeof(IVSTypeScriptDiagnosticAnalyzerService))] +[Export(typeof(IVSTypeScriptDiagnosticAnalyzerService)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] -internal sealed class VSTypeScriptAnalyzerService(IDiagnosticAnalyzerService service) : IVSTypeScriptDiagnosticAnalyzerService +internal sealed class VSTypeScriptAnalyzerService(IDiagnosticsRefresher refresher) : IVSTypeScriptDiagnosticAnalyzerService { - private readonly IDiagnosticAnalyzerService _service = service; - public void Reanalyze(Workspace? workspace, IEnumerable? projectIds, IEnumerable? documentIds, bool highPriority) - => _service.RequestDiagnosticRefresh(); + => refresher.RequestWorkspaceRefresh(); } diff --git a/src/LanguageServer/Protocol.TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs b/src/LanguageServer/Protocol.TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs index 7dd8669e53a19..2eebf43a6fd47 100644 --- a/src/LanguageServer/Protocol.TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs +++ b/src/LanguageServer/Protocol.TestUtilities/Diagnostics/TestDiagnosticAnalyzerDriver.cs @@ -25,9 +25,7 @@ public class TestDiagnosticAnalyzerDriver public TestDiagnosticAnalyzerDriver(Workspace workspace, bool includeSuppressedDiagnostics = false, bool includeNonLocalDocumentDiagnostics = false) { - var mefServices = workspace.Services.SolutionServices.ExportProvider; - - _diagnosticAnalyzerService = mefServices.GetExportedValue(); + _diagnosticAnalyzerService = workspace.Services.GetRequiredService(); _includeSuppressedDiagnostics = includeSuppressedDiagnostics; _includeNonLocalDocumentDiagnostics = includeNonLocalDocumentDiagnostics; } diff --git a/src/LanguageServer/Protocol/Features/CodeCleanup/AbstractCodeCleanupService.cs b/src/LanguageServer/Protocol/Features/CodeCleanup/AbstractCodeCleanupService.cs index 67409f355e2ae..ef20bcf28d4f8 100644 --- a/src/LanguageServer/Protocol/Features/CodeCleanup/AbstractCodeCleanupService.cs +++ b/src/LanguageServer/Protocol/Features/CodeCleanup/AbstractCodeCleanupService.cs @@ -19,258 +19,252 @@ using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.CodeCleanup +namespace Microsoft.CodeAnalysis.CodeCleanup; + +internal abstract class AbstractCodeCleanupService(ICodeFixService codeFixService) : ICodeCleanupService { - internal abstract class AbstractCodeCleanupService : ICodeCleanupService - { - private readonly ICodeFixService _codeFixService; - private readonly IDiagnosticAnalyzerService _diagnosticService; + private readonly ICodeFixService _codeFixService = codeFixService; + + protected abstract string OrganizeImportsDescription { get; } + protected abstract ImmutableArray GetDiagnosticSets(); - protected AbstractCodeCleanupService(ICodeFixService codeFixService, IDiagnosticAnalyzerService diagnosticAnalyzerService) + public async Task CleanupAsync( + Document document, + EnabledDiagnosticOptions enabledDiagnostics, + IProgress progressTracker, + CancellationToken cancellationToken) + { + // add one item for the code fixers we get from nuget, we'll do last + var thirdPartyDiagnosticIdsAndTitles = ImmutableArray<(string diagnosticId, string? title)>.Empty; + if (enabledDiagnostics.RunThirdPartyFixers) { - _codeFixService = codeFixService; - _diagnosticService = diagnosticAnalyzerService; + thirdPartyDiagnosticIdsAndTitles = await GetThirdPartyDiagnosticIdsAndTitlesAsync(document, cancellationToken).ConfigureAwait(false); + progressTracker.AddItems(thirdPartyDiagnosticIdsAndTitles.Length); } - protected abstract string OrganizeImportsDescription { get; } - protected abstract ImmutableArray GetDiagnosticSets(); + // add one item for the 'format' action + if (enabledDiagnostics.FormatDocument) + { + progressTracker.AddItems(1); + } - public async Task CleanupAsync( - Document document, - EnabledDiagnosticOptions enabledDiagnostics, - IProgress progressTracker, - CancellationToken cancellationToken) + // and one for 'remove/sort usings' if we're going to run that. + var organizeUsings = enabledDiagnostics.OrganizeUsings.IsRemoveUnusedImportEnabled || + enabledDiagnostics.OrganizeUsings.IsSortImportsEnabled; + if (organizeUsings) { - // add one item for the code fixers we get from nuget, we'll do last - var thirdPartyDiagnosticIdsAndTitles = ImmutableArray<(string diagnosticId, string? title)>.Empty; - if (enabledDiagnostics.RunThirdPartyFixers) - { - thirdPartyDiagnosticIdsAndTitles = await GetThirdPartyDiagnosticIdsAndTitlesAsync(document, cancellationToken).ConfigureAwait(false); - progressTracker.AddItems(thirdPartyDiagnosticIdsAndTitles.Length); - } + progressTracker.AddItems(1); + } - // add one item for the 'format' action - if (enabledDiagnostics.FormatDocument) - { - progressTracker.AddItems(1); - } + if (enabledDiagnostics.Diagnostics.Any()) + { + progressTracker.AddItems(enabledDiagnostics.Diagnostics.Length); + } - // and one for 'remove/sort usings' if we're going to run that. - var organizeUsings = enabledDiagnostics.OrganizeUsings.IsRemoveUnusedImportEnabled || - enabledDiagnostics.OrganizeUsings.IsSortImportsEnabled; - if (organizeUsings) - { - progressTracker.AddItems(1); - } + document = await ApplyCodeFixesAsync( + document, enabledDiagnostics.Diagnostics, progressTracker, cancellationToken).ConfigureAwait(false); - if (enabledDiagnostics.Diagnostics.Any()) - { - progressTracker.AddItems(enabledDiagnostics.Diagnostics.Length); - } + if (enabledDiagnostics.RunThirdPartyFixers) + { + document = await ApplyThirdPartyCodeFixesAsync( + document, thirdPartyDiagnosticIdsAndTitles, progressTracker, cancellationToken).ConfigureAwait(false); + } - document = await ApplyCodeFixesAsync( - document, enabledDiagnostics.Diagnostics, progressTracker, cancellationToken).ConfigureAwait(false); + // do the remove usings after code fix, as code fix might remove some code which can results in unused usings. + if (organizeUsings) + { + progressTracker.Report(CodeAnalysisProgress.Description(this.OrganizeImportsDescription)); + document = await RemoveSortUsingsAsync( + document, enabledDiagnostics.OrganizeUsings, cancellationToken).ConfigureAwait(false); + progressTracker.ItemCompleted(); + } - if (enabledDiagnostics.RunThirdPartyFixers) - { - document = await ApplyThirdPartyCodeFixesAsync( - document, thirdPartyDiagnosticIdsAndTitles, progressTracker, cancellationToken).ConfigureAwait(false); - } + if (enabledDiagnostics.FormatDocument) + { + var formattingOptions = await document.GetSyntaxFormattingOptionsAsync(cancellationToken).ConfigureAwait(false); - // do the remove usings after code fix, as code fix might remove some code which can results in unused usings. - if (organizeUsings) + progressTracker.Report(CodeAnalysisProgress.Description(FeaturesResources.Formatting_document)); + using (Logger.LogBlock(FunctionId.CodeCleanup_Format, cancellationToken)) { - progressTracker.Report(CodeAnalysisProgress.Description(this.OrganizeImportsDescription)); - document = await RemoveSortUsingsAsync( - document, enabledDiagnostics.OrganizeUsings, cancellationToken).ConfigureAwait(false); + document = await Formatter.FormatAsync(document, formattingOptions, cancellationToken).ConfigureAwait(false); progressTracker.ItemCompleted(); } + } - if (enabledDiagnostics.FormatDocument) - { - var formattingOptions = await document.GetSyntaxFormattingOptionsAsync(cancellationToken).ConfigureAwait(false); + if (enabledDiagnostics.RunThirdPartyFixers) + { + document = await ApplyThirdPartyCodeFixesAsync( + document, thirdPartyDiagnosticIdsAndTitles, progressTracker, cancellationToken).ConfigureAwait(false); + } - progressTracker.Report(CodeAnalysisProgress.Description(FeaturesResources.Formatting_document)); - using (Logger.LogBlock(FunctionId.CodeCleanup_Format, cancellationToken)) - { - document = await Formatter.FormatAsync(document, formattingOptions, cancellationToken).ConfigureAwait(false); - progressTracker.ItemCompleted(); - } - } + return document; + } - if (enabledDiagnostics.RunThirdPartyFixers) + private static async Task RemoveSortUsingsAsync( + Document document, OrganizeUsingsSet organizeUsingsSet, CancellationToken cancellationToken) + { + if (organizeUsingsSet.IsRemoveUnusedImportEnabled && + document.GetLanguageService() is { } removeUsingsService) + { + using (Logger.LogBlock(FunctionId.CodeCleanup_RemoveUnusedImports, cancellationToken)) { - document = await ApplyThirdPartyCodeFixesAsync( - document, thirdPartyDiagnosticIdsAndTitles, progressTracker, cancellationToken).ConfigureAwait(false); + var formattingOptions = await document.GetSyntaxFormattingOptionsAsync(cancellationToken).ConfigureAwait(false); + document = await removeUsingsService.RemoveUnnecessaryImportsAsync(document, cancellationToken).ConfigureAwait(false); } - - return document; } - private static async Task RemoveSortUsingsAsync( - Document document, OrganizeUsingsSet organizeUsingsSet, CancellationToken cancellationToken) + if (organizeUsingsSet.IsSortImportsEnabled && + document.GetLanguageService() is { } organizeImportsService) { - if (organizeUsingsSet.IsRemoveUnusedImportEnabled && - document.GetLanguageService() is { } removeUsingsService) + using (Logger.LogBlock(FunctionId.CodeCleanup_SortImports, cancellationToken)) { - using (Logger.LogBlock(FunctionId.CodeCleanup_RemoveUnusedImports, cancellationToken)) - { - var formattingOptions = await document.GetSyntaxFormattingOptionsAsync(cancellationToken).ConfigureAwait(false); - document = await removeUsingsService.RemoveUnnecessaryImportsAsync(document, cancellationToken).ConfigureAwait(false); - } + var organizeOptions = await document.GetOrganizeImportsOptionsAsync(cancellationToken).ConfigureAwait(false); + document = await organizeImportsService.OrganizeImportsAsync(document, organizeOptions, cancellationToken).ConfigureAwait(false); } - - if (organizeUsingsSet.IsSortImportsEnabled && - document.GetLanguageService() is { } organizeImportsService) - { - using (Logger.LogBlock(FunctionId.CodeCleanup_SortImports, cancellationToken)) - { - var organizeOptions = await document.GetOrganizeImportsOptionsAsync(cancellationToken).ConfigureAwait(false); - document = await organizeImportsService.OrganizeImportsAsync(document, organizeOptions, cancellationToken).ConfigureAwait(false); - } - } - - return document; } - private async Task ApplyCodeFixesAsync( - Document document, ImmutableArray enabledDiagnosticSets, - IProgress progressTracker, CancellationToken cancellationToken) - { - // Add a progressTracker item for each enabled option we're going to fixup. - foreach (var diagnosticSet in enabledDiagnosticSets) - { - cancellationToken.ThrowIfCancellationRequested(); + return document; + } - progressTracker.Report(CodeAnalysisProgress.Description(diagnosticSet.Description)); - document = await ApplyCodeFixesForSpecificDiagnosticIdsAsync( - document, diagnosticSet.DiagnosticIds, diagnosticSet.IsAnyDiagnosticIdExplicitlyEnabled, progressTracker, cancellationToken).ConfigureAwait(false); + private async Task ApplyCodeFixesAsync( + Document document, ImmutableArray enabledDiagnosticSets, + IProgress progressTracker, CancellationToken cancellationToken) + { + // Add a progressTracker item for each enabled option we're going to fixup. + foreach (var diagnosticSet in enabledDiagnosticSets) + { + cancellationToken.ThrowIfCancellationRequested(); - // Mark this option as being completed. - progressTracker.ItemCompleted(); - } + progressTracker.Report(CodeAnalysisProgress.Description(diagnosticSet.Description)); + document = await ApplyCodeFixesForSpecificDiagnosticIdsAsync( + document, diagnosticSet.DiagnosticIds, diagnosticSet.IsAnyDiagnosticIdExplicitlyEnabled, progressTracker, cancellationToken).ConfigureAwait(false); - return document; + // Mark this option as being completed. + progressTracker.ItemCompleted(); } - private async Task ApplyCodeFixesForSpecificDiagnosticIdsAsync( - Document document, ImmutableArray diagnosticIds, bool isAnyDiagnosticIdExplicitlyEnabled, IProgress progressTracker, CancellationToken cancellationToken) - { - // Enable fixes for all diagnostic severities if any of the diagnostic IDs has been explicitly enabled in Code Cleanup. - // Otherwise, only enable fixes for Warning and Error severity diagnostics. - var minimumSeverity = isAnyDiagnosticIdExplicitlyEnabled ? DiagnosticSeverity.Hidden : DiagnosticSeverity.Warning; - - foreach (var diagnosticId in diagnosticIds) - { - using (Logger.LogBlock(FunctionId.CodeCleanup_ApplyCodeFixesAsync, diagnosticId, cancellationToken)) - { - document = await ApplyCodeFixesForSpecificDiagnosticIdAsync( - document, diagnosticId, minimumSeverity, progressTracker, cancellationToken).ConfigureAwait(false); - } - } + return document; + } - return document; - } + private async Task ApplyCodeFixesForSpecificDiagnosticIdsAsync( + Document document, ImmutableArray diagnosticIds, bool isAnyDiagnosticIdExplicitlyEnabled, IProgress progressTracker, CancellationToken cancellationToken) + { + // Enable fixes for all diagnostic severities if any of the diagnostic IDs has been explicitly enabled in Code Cleanup. + // Otherwise, only enable fixes for Warning and Error severity diagnostics. + var minimumSeverity = isAnyDiagnosticIdExplicitlyEnabled ? DiagnosticSeverity.Hidden : DiagnosticSeverity.Warning; - private async Task ApplyCodeFixesForSpecificDiagnosticIdAsync( - Document document, string diagnosticId, DiagnosticSeverity minimumSeverity, IProgress progressTracker, CancellationToken cancellationToken) + foreach (var diagnosticId in diagnosticIds) { - var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - var textSpan = new TextSpan(0, tree.Length); - - var fixCollection = await _codeFixService.GetDocumentFixAllForIdInSpanAsync( - document, textSpan, diagnosticId, minimumSeverity, cancellationToken).ConfigureAwait(false); - if (fixCollection == null) + using (Logger.LogBlock(FunctionId.CodeCleanup_ApplyCodeFixesAsync, diagnosticId, cancellationToken)) { - return document; + document = await ApplyCodeFixesForSpecificDiagnosticIdAsync( + document, diagnosticId, minimumSeverity, progressTracker, cancellationToken).ConfigureAwait(false); } + } - var fixAllService = document.Project.Solution.Services.GetRequiredService(); - - var solution = await fixAllService.GetFixAllChangedSolutionAsync( - new FixAllContext(fixCollection.FixAllState!, progressTracker, cancellationToken)).ConfigureAwait(false); - Contract.ThrowIfNull(solution); + return document; + } - return solution.GetDocument(document.Id) ?? throw new NotSupportedException(FeaturesResources.Removal_of_document_not_supported); - } + private async Task ApplyCodeFixesForSpecificDiagnosticIdAsync( + Document document, string diagnosticId, DiagnosticSeverity minimumSeverity, IProgress progressTracker, CancellationToken cancellationToken) + { + var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + var textSpan = new TextSpan(0, tree.Length); - private async Task> GetThirdPartyDiagnosticIdsAndTitlesAsync(Document document, CancellationToken cancellationToken) + var fixCollection = await _codeFixService.GetDocumentFixAllForIdInSpanAsync( + document, textSpan, diagnosticId, minimumSeverity, cancellationToken).ConfigureAwait(false); + if (fixCollection == null) { - var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - var range = new TextSpan(0, tree.Length); + return document; + } - // Compute diagnostics for everything that is not an IDE analyzer - var diagnostics = await _diagnosticService.GetDiagnosticsForSpanAsync(document, range, - shouldIncludeDiagnostic: static diagnosticId => !(IDEDiagnosticIdToOptionMappingHelper.IsKnownIDEDiagnosticId(diagnosticId)), - priorityProvider: new DefaultCodeActionRequestPriorityProvider(), - DiagnosticKind.All, isExplicit: false, - cancellationToken).ConfigureAwait(false); + var fixAllService = document.Project.Solution.Services.GetRequiredService(); - // We don't want code cleanup automatically cleaning suppressed diagnostics. - diagnostics = diagnostics.WhereAsArray(d => !d.IsSuppressed); + var solution = await fixAllService.GetFixAllChangedSolutionAsync( + new FixAllContext(fixCollection.FixAllState!, progressTracker, cancellationToken)).ConfigureAwait(false); + Contract.ThrowIfNull(solution); - // ensure more than just known diagnostics were returned - if (!diagnostics.Any()) - { - return []; - } + return solution.GetDocument(document.Id) ?? throw new NotSupportedException(FeaturesResources.Removal_of_document_not_supported); + } - return diagnostics.SelectAsArray(static d => (d.Id, d.Title)).Distinct(); + private async Task> GetThirdPartyDiagnosticIdsAndTitlesAsync( + Document document, CancellationToken cancellationToken) + { + var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + var range = new TextSpan(0, tree.Length); + + // Compute diagnostics for everything that is not an IDE analyzer + var diagnosticService = document.Project.Solution.Services.GetRequiredService(); + var diagnostics = await diagnosticService.GetDiagnosticsForSpanAsync(document, range, + shouldIncludeDiagnostic: static diagnosticId => !(IDEDiagnosticIdToOptionMappingHelper.IsKnownIDEDiagnosticId(diagnosticId)), + priorityProvider: new DefaultCodeActionRequestPriorityProvider(), + DiagnosticKind.All, isExplicit: false, + cancellationToken).ConfigureAwait(false); + + // We don't want code cleanup automatically cleaning suppressed diagnostics. + diagnostics = diagnostics.WhereAsArray(d => !d.IsSuppressed); + + // ensure more than just known diagnostics were returned + if (!diagnostics.Any()) + { + return []; } - private async Task ApplyThirdPartyCodeFixesAsync( - Document document, - ImmutableArray<(string diagnosticId, string? title)> diagnosticIds, - IProgress progressTracker, - CancellationToken cancellationToken) + return diagnostics.SelectAsArray(static d => (d.Id, d.Title)).Distinct(); + } + + private async Task ApplyThirdPartyCodeFixesAsync( + Document document, + ImmutableArray<(string diagnosticId, string? title)> diagnosticIds, + IProgress progressTracker, + CancellationToken cancellationToken) + { + foreach (var (diagnosticId, title) in diagnosticIds) { - foreach (var (diagnosticId, title) in diagnosticIds) + cancellationToken.ThrowIfCancellationRequested(); + + progressTracker.Report(CodeAnalysisProgress.Description(string.Format(FeaturesResources.Fixing_0, title ?? diagnosticId))); + // Apply codefixes for diagnostics with a severity of warning or higher + var updatedDocument = await _codeFixService.ApplyCodeFixesForSpecificDiagnosticIdAsync( + document, diagnosticId, DiagnosticSeverity.Warning, progressTracker, cancellationToken).ConfigureAwait(false); + + // If changes were made to the solution snap shot outside the current document discard the changes. + // The assumption here is that if we are applying a third party code fix to a document it only affects the document. + // Symbol renames and other complex refactorings we do not want to include in code cleanup. + // We can revisit this if we get feedback to the contrary + if (!ChangesMadeOutsideDocument(document, updatedDocument)) { - cancellationToken.ThrowIfCancellationRequested(); - - progressTracker.Report(CodeAnalysisProgress.Description(string.Format(FeaturesResources.Fixing_0, title ?? diagnosticId))); - // Apply codefixes for diagnostics with a severity of warning or higher - var updatedDocument = await _codeFixService.ApplyCodeFixesForSpecificDiagnosticIdAsync( - document, diagnosticId, DiagnosticSeverity.Warning, progressTracker, cancellationToken).ConfigureAwait(false); - - // If changes were made to the solution snap shot outside the current document discard the changes. - // The assumption here is that if we are applying a third party code fix to a document it only affects the document. - // Symbol renames and other complex refactorings we do not want to include in code cleanup. - // We can revisit this if we get feedback to the contrary - if (!ChangesMadeOutsideDocument(document, updatedDocument)) - { - document = updatedDocument; - } - - progressTracker.ItemCompleted(); + document = updatedDocument; } - return document; + progressTracker.ItemCompleted(); + } - static bool ChangesMadeOutsideDocument(Document currentDocument, Document updatedDocument) - { - var solutionChanges = updatedDocument.Project.Solution.GetChanges(currentDocument.Project.Solution); - return - solutionChanges.GetAddedProjects().Any() || - solutionChanges.GetRemovedProjects().Any() || - solutionChanges.GetAddedAnalyzerReferences().Any() || - solutionChanges.GetRemovedAnalyzerReferences().Any() || - solutionChanges.GetProjectChanges().Any( - projectChanges => projectChanges.GetAddedProjectReferences().Any() || - projectChanges.GetRemovedProjectReferences().Any() || - projectChanges.GetAddedMetadataReferences().Any() || - projectChanges.GetRemovedMetadataReferences().Any() || - projectChanges.GetAddedAnalyzerReferences().Any() || - projectChanges.GetRemovedAnalyzerReferences().Any() || - projectChanges.GetAddedDocuments().Any() || - projectChanges.GetAddedAdditionalDocuments().Any() || - projectChanges.GetAddedAnalyzerConfigDocuments().Any() || - projectChanges.GetChangedDocuments().Any(documentId => documentId != updatedDocument.Id) || - projectChanges.GetChangedAdditionalDocuments().Any(documentId => documentId != updatedDocument.Id) || - projectChanges.GetChangedAnalyzerConfigDocuments().Any(documentId => documentId != updatedDocument.Id)); - } + return document; + + static bool ChangesMadeOutsideDocument(Document currentDocument, Document updatedDocument) + { + var solutionChanges = updatedDocument.Project.Solution.GetChanges(currentDocument.Project.Solution); + return + solutionChanges.GetAddedProjects().Any() || + solutionChanges.GetRemovedProjects().Any() || + solutionChanges.GetAddedAnalyzerReferences().Any() || + solutionChanges.GetRemovedAnalyzerReferences().Any() || + solutionChanges.GetProjectChanges().Any( + projectChanges => projectChanges.GetAddedProjectReferences().Any() || + projectChanges.GetRemovedProjectReferences().Any() || + projectChanges.GetAddedMetadataReferences().Any() || + projectChanges.GetRemovedMetadataReferences().Any() || + projectChanges.GetAddedAnalyzerReferences().Any() || + projectChanges.GetRemovedAnalyzerReferences().Any() || + projectChanges.GetAddedDocuments().Any() || + projectChanges.GetAddedAdditionalDocuments().Any() || + projectChanges.GetAddedAnalyzerConfigDocuments().Any() || + projectChanges.GetChangedDocuments().Any(documentId => documentId != updatedDocument.Id) || + projectChanges.GetChangedAdditionalDocuments().Any(documentId => documentId != updatedDocument.Id) || + projectChanges.GetChangedAnalyzerConfigDocuments().Any(documentId => documentId != updatedDocument.Id)); } - public EnabledDiagnosticOptions GetAllDiagnostics() - => new(FormatDocument: true, RunThirdPartyFixers: true, Diagnostics: GetDiagnosticSets(), OrganizeUsings: new OrganizeUsingsSet(isRemoveUnusedImportEnabled: true, isSortImportsEnabled: true)); } + public EnabledDiagnosticOptions GetAllDiagnostics() + => new(FormatDocument: true, RunThirdPartyFixers: true, Diagnostics: GetDiagnosticSets(), OrganizeUsings: new OrganizeUsingsSet(isRemoveUnusedImportEnabled: true, isSortImportsEnabled: true)); } diff --git a/src/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService_IncrementalAnalyzer.cs b/src/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService_IncrementalAnalyzer.cs deleted file mode 100644 index 9a0b3fa16fdb9..0000000000000 --- a/src/LanguageServer/Protocol/Features/Diagnostics/DiagnosticAnalyzerService_IncrementalAnalyzer.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace Microsoft.CodeAnalysis.Diagnostics; - -internal partial class DiagnosticAnalyzerService -{ - private DiagnosticIncrementalAnalyzer CreateIncrementalAnalyzer(Workspace workspace) - { - return _map.GetValue(workspace, _createIncrementalAnalyzer); - } - - private DiagnosticIncrementalAnalyzer CreateIncrementalAnalyzerCallback(Workspace workspace) - { - // subscribe to active context changed event for new workspace - workspace.DocumentActiveContextChanged += OnDocumentActiveContextChanged; - - return new DiagnosticIncrementalAnalyzer(this, AnalyzerInfoCache, this.GlobalOptions); - } - - private void OnDocumentActiveContextChanged(object? sender, DocumentActiveContextChangedEventArgs e) - => RequestDiagnosticRefresh(); -} diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs index 106d2dc6bfc78..21c18891297be 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractDocumentPullDiagnosticHandler.cs @@ -14,12 +14,10 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal abstract class AbstractDocumentPullDiagnosticHandler( - IDiagnosticAnalyzerService diagnosticAnalyzerService, IDiagnosticsRefresher diagnosticRefresher, IDiagnosticSourceManager diagnosticSourceManager, IGlobalOptionService globalOptions) : AbstractPullDiagnosticHandler( - diagnosticAnalyzerService, diagnosticRefresher, globalOptions), ITextDocumentIdentifierHandler where TDiagnosticsParams : IPartialResultParams diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs index d1f6ead2e0d06..bd4cba8018918 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs @@ -26,7 +26,6 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; /// The LSP type that is reported via IProgress /// The LSP type that is returned on completion of the request. internal abstract partial class AbstractPullDiagnosticHandler( - IDiagnosticAnalyzerService diagnosticAnalyzerService, IDiagnosticsRefresher diagnosticRefresher, IGlobalOptionService globalOptions) : ILspServiceRequestHandler @@ -43,7 +42,6 @@ internal abstract partial class AbstractPullDiagnosticHandler /// Map of diagnostic category to the diagnostics cache for that category. diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs index df726f111c6c4..7b1b18d6a84fe 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/AbstractWorkspacePullDiagnosticsHandler.cs @@ -37,10 +37,9 @@ internal abstract class AbstractWorkspacePullDiagnosticsHandler true; @@ -26,7 +26,7 @@ public ValueTask> CreateDiagnosticSourcesAsync { if (context.GetTrackedDocument() is { } document) { - return new([new DocumentDiagnosticSource(diagnosticAnalyzerService, kind, document)]); + return new([new DocumentDiagnosticSource(kind, document)]); } return new([]); @@ -35,8 +35,8 @@ public ValueTask> CreateDiagnosticSourcesAsync [Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - private sealed class DocumentCompilerSyntaxDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) - : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(diagnosticAnalyzerService, + private sealed class DocumentCompilerSyntaxDiagnosticSourceProvider() + : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider( DiagnosticKind.CompilerSyntax, PullDiagnosticCategories.DocumentCompilerSyntax) { } @@ -44,17 +44,17 @@ private sealed class DocumentCompilerSyntaxDiagnosticSourceProvider([Import] IDi [Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - private sealed class DocumentCompilerSemanticDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + private sealed class DocumentCompilerSemanticDiagnosticSourceProvider() : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider( - diagnosticAnalyzerService, DiagnosticKind.CompilerSemantic, PullDiagnosticCategories.DocumentCompilerSemantic) + DiagnosticKind.CompilerSemantic, PullDiagnosticCategories.DocumentCompilerSemantic) { } [Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - private sealed class DocumentAnalyzerSyntaxDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) - : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(diagnosticAnalyzerService, + private sealed class DocumentAnalyzerSyntaxDiagnosticSourceProvider() + : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider( DiagnosticKind.AnalyzerSyntax, PullDiagnosticCategories.DocumentAnalyzerSyntax) { } @@ -62,8 +62,8 @@ private sealed class DocumentAnalyzerSyntaxDiagnosticSourceProvider([Import] IDi [Export(typeof(IDiagnosticSourceProvider)), Shared] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - private sealed class DocumentAnalyzerSemanticDiagnosticSourceProvider([Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) - : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider(diagnosticAnalyzerService, + private sealed class DocumentAnalyzerSemanticDiagnosticSourceProvider() + : AbstractDocumentSyntaxAndSemanticDiagnosticSourceProvider( DiagnosticKind.AnalyzerSemantic, PullDiagnosticCategories.DocumentAnalyzerSemantic) { } diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs index e18ca39e8f871..573f414478133 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSourceProviders/WorkspaceDocumentsAndProjectDiagnosticSourceProvider.cs @@ -21,7 +21,6 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class WorkspaceDocumentsAndProjectDiagnosticSourceProvider( - [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService, [Import] IGlobalOptionService globalOptions) : IDiagnosticSourceProvider { @@ -59,11 +58,11 @@ public async ValueTask> CreateDiagnosticSource var codeAnalysisService = solution.Services.GetRequiredService(); foreach (var project in WorkspaceDiagnosticSourceHelpers.GetProjectsInPriorityOrder(solution, context.SupportedLanguages)) - await AddDocumentsAndProjectAsync(project, diagnosticAnalyzerService, cancellationToken).ConfigureAwait(false); + await AddDocumentsAndProjectAsync(project, cancellationToken).ConfigureAwait(false); return result.ToImmutableAndClear(); - async Task AddDocumentsAndProjectAsync(Project project, IDiagnosticAnalyzerService diagnosticAnalyzerService, CancellationToken cancellationToken) + async Task AddDocumentsAndProjectAsync(Project project, CancellationToken cancellationToken) { var fullSolutionAnalysisEnabled = globalOptions.IsFullSolutionAnalysisEnabled(project.Language, out var compilerFullSolutionAnalysisEnabled, out var analyzersFullSolutionAnalysisEnabled); if (!fullSolutionAnalysisEnabled && !codeAnalysisService.HasProjectBeenAnalyzed(project.Id)) @@ -95,7 +94,7 @@ void AddDocumentSources(IEnumerable documents) { // Add the appropriate FSA or CodeAnalysis document source to get document diagnostics. var documentDiagnosticSource = fullSolutionAnalysisEnabled - ? AbstractWorkspaceDocumentDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(document, diagnosticAnalyzerService, shouldIncludeAnalyzer) + ? AbstractWorkspaceDocumentDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(document, shouldIncludeAnalyzer) : AbstractWorkspaceDocumentDiagnosticSource.CreateForCodeAnalysisDiagnostics(document, codeAnalysisService); result.Add(documentDiagnosticSource); } @@ -105,7 +104,7 @@ void AddDocumentSources(IEnumerable documents) void AddProjectSource() { var projectDiagnosticSource = fullSolutionAnalysisEnabled - ? AbstractProjectDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(project, diagnosticAnalyzerService, shouldIncludeAnalyzer) + ? AbstractProjectDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(project, shouldIncludeAnalyzer) : AbstractProjectDiagnosticSource.CreateForCodeAnalysisDiagnostics(project, codeAnalysisService); result.Add(projectDiagnosticSource); } diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs index f88810864a7bb..03a37330ae190 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractDocumentDiagnosticSource.cs @@ -14,6 +14,7 @@ internal abstract class AbstractDocumentDiagnosticSource(TDocument do where TDocument : TextDocument { public TDocument Document { get; } = document; + public Solution Solution => this.Document.Project.Solution; public abstract bool IsLiveSource(); diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs index 18fc22f98ca51..d814da265f1d5 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractProjectDiagnosticSource.cs @@ -15,9 +15,10 @@ internal abstract class AbstractProjectDiagnosticSource(Project project) : IDiagnosticSource { protected Project Project => project; + protected Solution Solution => this.Project.Solution; - public static AbstractProjectDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(Project project, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) - => new FullSolutionAnalysisDiagnosticSource(project, diagnosticAnalyzerService, shouldIncludeAnalyzer); + public static AbstractProjectDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(Project project, Func? shouldIncludeAnalyzer) + => new FullSolutionAnalysisDiagnosticSource(project, shouldIncludeAnalyzer); public static AbstractProjectDiagnosticSource CreateForCodeAnalysisDiagnostics(Project project, ICodeAnalysisDiagnosticAnalyzerService codeAnalysisService) => new CodeAnalysisDiagnosticSource(project, codeAnalysisService); @@ -33,7 +34,7 @@ public static AbstractProjectDiagnosticSource CreateForCodeAnalysisDiagnostics(P : null; public string ToDisplayString() => Project.Name; - private sealed class FullSolutionAnalysisDiagnosticSource(Project project, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) + private sealed class FullSolutionAnalysisDiagnosticSource(Project project, Func? shouldIncludeAnalyzer) : AbstractProjectDiagnosticSource(project) { /// @@ -50,7 +51,8 @@ public override async Task> GetDiagnosticsAsync( // we're passing in. If information is already cached for that snapshot, it will be returned. Otherwise, // it will be computed on demand. Because it is always accurate as per this snapshot, all spans are correct // and do not need to be adjusted. - var diagnostics = await diagnosticAnalyzerService.GetProjectDiagnosticsForIdsAsync( + var service = this.Solution.Services.GetRequiredService(); + var diagnostics = await service.GetProjectDiagnosticsForIdsAsync( Project, diagnosticIds: null, shouldIncludeAnalyzer, includeNonLocalDocumentDiagnostics: false, cancellationToken).ConfigureAwait(false); // TODO(cyrusn): In the future we could consider reporting these, but with a flag on the diagnostic mentioning diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs index 7777dba417737..9e0a50253403e 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/AbstractWorkspaceDocumentDiagnosticSource.cs @@ -15,13 +15,14 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal abstract class AbstractWorkspaceDocumentDiagnosticSource(TextDocument document) : AbstractDocumentDiagnosticSource(document) { - public static AbstractWorkspaceDocumentDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(TextDocument document, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) - => new FullSolutionAnalysisDiagnosticSource(document, diagnosticAnalyzerService, shouldIncludeAnalyzer); + public static AbstractWorkspaceDocumentDiagnosticSource CreateForFullSolutionAnalysisDiagnostics(TextDocument document, Func? shouldIncludeAnalyzer) + => new FullSolutionAnalysisDiagnosticSource(document, shouldIncludeAnalyzer); public static AbstractWorkspaceDocumentDiagnosticSource CreateForCodeAnalysisDiagnostics(TextDocument document, ICodeAnalysisDiagnosticAnalyzerService codeAnalysisService) => new CodeAnalysisDiagnosticSource(document, codeAnalysisService); - private sealed class FullSolutionAnalysisDiagnosticSource(TextDocument document, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) + private sealed class FullSolutionAnalysisDiagnosticSource( + TextDocument document, Func? shouldIncludeAnalyzer) : AbstractWorkspaceDocumentDiagnosticSource(document) { /// @@ -44,20 +45,20 @@ public override async Task> GetDiagnosticsAsync( if (Document is SourceGeneratedDocument sourceGeneratedDocument) { // Unfortunately GetDiagnosticsForIdsAsync returns nothing for source generated documents. - var documentDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForSpanAsync( + var service = this.Solution.Services.GetRequiredService(); + var documentDiagnostics = await service.GetDiagnosticsForSpanAsync( sourceGeneratedDocument, range: null, DiagnosticKind.All, cancellationToken).ConfigureAwait(false); documentDiagnostics = documentDiagnostics.WhereAsArray(d => !d.IsSuppressed); return documentDiagnostics; } else { - var projectDiagnostics = await GetProjectDiagnosticsAsync(diagnosticAnalyzerService, cancellationToken).ConfigureAwait(false); + var projectDiagnostics = await GetProjectDiagnosticsAsync(cancellationToken).ConfigureAwait(false); return projectDiagnostics.WhereAsArray(d => d.DocumentId == Document.Id); } } - private async ValueTask> GetProjectDiagnosticsAsync( - IDiagnosticAnalyzerService diagnosticAnalyzerService, CancellationToken cancellationToken) + private async ValueTask> GetProjectDiagnosticsAsync(CancellationToken cancellationToken) { if (!s_projectToDiagnostics.TryGetValue(Document.Project, out var lazyDiagnostics)) { @@ -75,7 +76,8 @@ AsyncLazy> GetLazyDiagnostics() _ => AsyncLazy.Create( async cancellationToken => { - var allDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( + var service = this.Solution.Services.GetRequiredService(); + var allDiagnostics = await service.GetDiagnosticsForIdsAsync( Document.Project, documentId: null, diagnosticIds: null, shouldIncludeAnalyzer, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false); diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs index 34203462a17d3..9960d4dc5a7e5 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/DocumentDiagnosticSource.cs @@ -10,7 +10,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -internal sealed class DocumentDiagnosticSource(IDiagnosticAnalyzerService diagnosticAnalyzerService, DiagnosticKind diagnosticKind, TextDocument document) +internal sealed class DocumentDiagnosticSource(DiagnosticKind diagnosticKind, TextDocument document) : AbstractDocumentDiagnosticSource(document) { public DiagnosticKind DiagnosticKind { get; } = diagnosticKind; @@ -27,7 +27,8 @@ public override async Task> GetDiagnosticsAsync( // 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( + var service = this.Solution.Services.GetRequiredService(); + var allSpanDiagnostics = await service.GetDiagnosticsForSpanAsync( Document, range: null, diagnosticKind: this.DiagnosticKind, cancellationToken).ConfigureAwait(false); // Note: we do not filter our suppressed diagnostics we we want unnecessary suppressions to be reported. diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs index b089870386125..2551c6592a82b 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticSources/NonLocalDocumentDiagnosticSource.cs @@ -10,7 +10,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; -internal sealed class NonLocalDocumentDiagnosticSource(TextDocument document, IDiagnosticAnalyzerService diagnosticAnalyzerService, Func? shouldIncludeAnalyzer) +internal sealed class NonLocalDocumentDiagnosticSource( + TextDocument document, Func? shouldIncludeAnalyzer) : AbstractDocumentDiagnosticSource(document) { private readonly Func? _shouldIncludeAnalyzer = shouldIncludeAnalyzer; @@ -25,7 +26,8 @@ public override async Task> GetDiagnosticsAsync( // We call GetDiagnosticsForIdsAsync as we want to ensure we get the full set of non-local diagnostics for this // document including those reported as a compilation end diagnostic. These are not included in document pull // (uses GetDiagnosticsForSpan) due to cost. - var diagnostics = await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync( + var service = this.Solution.Services.GetRequiredService(); + var diagnostics = await service.GetDiagnosticsForIdsAsync( Document.Project, Document.Id, diagnosticIds: null, _shouldIncludeAnalyzer, includeLocalDocumentDiagnostics: false, includeNonLocalDocumentDiagnostics: true, cancellationToken).ConfigureAwait(false); diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticsRefreshQueue.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticsRefreshQueue.cs index 44e4f3e3ac3b7..0157e87911c49 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticsRefreshQueue.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/DiagnosticsRefreshQueue.cs @@ -19,14 +19,14 @@ internal sealed class Factory : ILspServiceFactory { private readonly IAsynchronousOperationListenerProvider _asyncListenerProvider; private readonly LspWorkspaceRegistrationService _lspWorkspaceRegistrationService; - private readonly Refresher _refresher; + private readonly IDiagnosticsRefresher _refresher; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public Factory( IAsynchronousOperationListenerProvider asynchronousOperationListenerProvider, LspWorkspaceRegistrationService lspWorkspaceRegistrationService, - Refresher refresher) + IDiagnosticsRefresher refresher) { _asyncListenerProvider = asynchronousOperationListenerProvider; _lspWorkspaceRegistrationService = lspWorkspaceRegistrationService; @@ -42,44 +42,14 @@ public ILspService CreateILspService(LspServices lspServices, WellKnownLspServer } } - [Shared] - [Export(typeof(Refresher))] - [Export(typeof(IDiagnosticsRefresher))] - internal sealed class Refresher : IDiagnosticsRefresher - { - /// - /// Incremented every time a refresh is requested. - /// - private int _globalStateVersion; - - public event Action? WorkspaceRefreshRequested; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public Refresher() - { - } - - public void RequestWorkspaceRefresh() - { - // bump version before sending the request to the client: - Interlocked.Increment(ref _globalStateVersion); - - WorkspaceRefreshRequested?.Invoke(); - } - - public int GlobalStateVersion - => _globalStateVersion; - } - - private readonly Refresher _refresher; + private readonly IDiagnosticsRefresher _refresher; private DiagnosticsRefreshQueue( IAsynchronousOperationListenerProvider asynchronousOperationListenerProvider, LspWorkspaceRegistrationService lspWorkspaceRegistrationService, LspWorkspaceManager lspWorkspaceManager, IClientLanguageServerManager notificationManager, - Refresher refresher) + IDiagnosticsRefresher refresher) : base(asynchronousOperationListenerProvider, lspWorkspaceRegistrationService, lspWorkspaceManager, notificationManager) { _refresher = refresher; diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs index 0578f40c43090..e292788273dd7 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandler.cs @@ -15,11 +15,10 @@ internal partial class DocumentPullDiagnosticHandler : AbstractDocumentPullDiagnosticHandler { public DocumentPullDiagnosticHandler( - IDiagnosticAnalyzerService analyzerService, IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticRefresher, IGlobalOptionService globalOptions) - : base(analyzerService, diagnosticRefresher, diagnosticSourceManager, globalOptions) + : base(diagnosticRefresher, diagnosticSourceManager, globalOptions) { } diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs index 07a6cfcaabc17..c7685e2c82409 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/DocumentPullDiagnosticHandlerFactory.cs @@ -14,7 +14,6 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; [ExportCSharpVisualBasicLspServiceFactory(typeof(DocumentPullDiagnosticHandler)), Shared] internal class DocumentPullDiagnosticHandlerFactory : ILspServiceFactory { - private readonly IDiagnosticAnalyzerService _analyzerService; private readonly IDiagnosticSourceManager _diagnosticSourceManager; private readonly IDiagnosticsRefresher _diagnosticsRefresher; private readonly IGlobalOptionService _globalOptions; @@ -22,17 +21,15 @@ internal class DocumentPullDiagnosticHandlerFactory : ILspServiceFactory [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public DocumentPullDiagnosticHandlerFactory( - IDiagnosticAnalyzerService analyzerService, IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, IGlobalOptionService globalOptions) { - _analyzerService = analyzerService; _diagnosticSourceManager = diagnosticSourceManager; _diagnosticsRefresher = diagnosticsRefresher; _globalOptions = globalOptions; } public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) - => new DocumentPullDiagnosticHandler(_analyzerService, _diagnosticSourceManager, _diagnosticsRefresher, _globalOptions); + => new DocumentPullDiagnosticHandler(_diagnosticSourceManager, _diagnosticsRefresher, _globalOptions); } diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs index 5ac98fefc7389..064d4761e82ab 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentNonLocalDiagnosticSourceProvider.cs @@ -19,8 +19,7 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class PublicDocumentNonLocalDiagnosticSourceProvider( - [Import] IGlobalOptionService globalOptions, - [Import] IDiagnosticAnalyzerService diagnosticAnalyzerService) + [Import] IGlobalOptionService globalOptions) : IDiagnosticSourceProvider { public const string NonLocal = nameof(NonLocal); @@ -36,7 +35,7 @@ public ValueTask> CreateDiagnosticSourcesAsync globalOptions.GetBackgroundAnalysisScope(textDocument.Project.Language) == BackgroundAnalysisScope.FullSolution) { // NOTE: Compiler does not report any non-local diagnostics, so we bail out for compiler analyzer. - return new([new NonLocalDocumentDiagnosticSource(textDocument, diagnosticAnalyzerService, a => !a.IsCompilerAnalyzer())]); + return new([new NonLocalDocumentDiagnosticSource(textDocument, a => !a.IsCompilerAnalyzer())]); } return new([]); diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticHandlerFactory.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticHandlerFactory.cs index 3ae460c780e84..705f469ad8ed7 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticHandlerFactory.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticHandlerFactory.cs @@ -17,7 +17,6 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; [ExportCSharpVisualBasicLspServiceFactory(typeof(PublicDocumentPullDiagnosticsHandler)), Shared] internal sealed class PublicDocumentPullDiagnosticHandlerFactory : ILspServiceFactory { - private readonly IDiagnosticAnalyzerService _analyzerService; private readonly IDiagnosticSourceManager _diagnosticSourceManager; private readonly IDiagnosticsRefresher _diagnosticRefresher; private readonly IGlobalOptionService _globalOptions; @@ -25,12 +24,10 @@ internal sealed class PublicDocumentPullDiagnosticHandlerFactory : ILspServiceFa [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public PublicDocumentPullDiagnosticHandlerFactory( - IDiagnosticAnalyzerService analyzerService, IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticRefresher, IGlobalOptionService globalOptions) { - _analyzerService = analyzerService; _diagnosticSourceManager = diagnosticSourceManager; _diagnosticRefresher = diagnosticRefresher; _globalOptions = globalOptions; @@ -39,6 +36,6 @@ public PublicDocumentPullDiagnosticHandlerFactory( public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { var clientLanguageServerManager = lspServices.GetRequiredService(); - return new PublicDocumentPullDiagnosticsHandler(clientLanguageServerManager, _analyzerService, _diagnosticSourceManager, _diagnosticRefresher, _globalOptions); + return new PublicDocumentPullDiagnosticsHandler(clientLanguageServerManager, _diagnosticSourceManager, _diagnosticRefresher, _globalOptions); } } diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs index 877ecb816e1a0..286c397aa7e00 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicDocumentPullDiagnosticsHandler.cs @@ -24,11 +24,10 @@ internal sealed partial class PublicDocumentPullDiagnosticsHandler : AbstractDoc public PublicDocumentPullDiagnosticsHandler( IClientLanguageServerManager clientLanguageServerManager, - IDiagnosticAnalyzerService analyzerService, IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, IGlobalOptionService globalOptions) - : base(analyzerService, diagnosticsRefresher, diagnosticSourceManager, globalOptions) + : base(diagnosticsRefresher, diagnosticSourceManager, globalOptions) { _clientLanguageServerManager = clientLanguageServerManager; } diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs index ea0814316fbfc..fd8fd73e460fc 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticHandlerFactory.cs @@ -16,7 +16,6 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Public; [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class PublicWorkspacePullDiagnosticHandlerFactory( LspWorkspaceRegistrationService registrationService, - IDiagnosticAnalyzerService analyzerService, IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, IGlobalOptionService globalOptions) : ILspServiceFactory @@ -24,6 +23,6 @@ internal sealed class PublicWorkspacePullDiagnosticHandlerFactory( public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { var workspaceManager = lspServices.GetRequiredService(); - return new PublicWorkspacePullDiagnosticsHandler(workspaceManager, registrationService, analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions); + return new PublicWorkspacePullDiagnosticsHandler(workspaceManager, registrationService, diagnosticSourceManager, diagnosticsRefresher, globalOptions); } } diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs index 8b375e0059b3e..053d9e0790564 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/Public/PublicWorkspacePullDiagnosticsHandler.cs @@ -25,11 +25,10 @@ internal sealed partial class PublicWorkspacePullDiagnosticsHandler : AbstractWo public PublicWorkspacePullDiagnosticsHandler( LspWorkspaceManager workspaceManager, LspWorkspaceRegistrationService registrationService, - IDiagnosticAnalyzerService analyzerService, IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticRefresher, IGlobalOptionService globalOptions) - : base(workspaceManager, registrationService, analyzerService, diagnosticSourceManager, diagnosticRefresher, globalOptions) + : base(workspaceManager, registrationService, diagnosticSourceManager, diagnosticRefresher, globalOptions) { } diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs index 05b60afae44a9..9710bd9a508ed 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs @@ -17,12 +17,11 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; internal sealed partial class WorkspacePullDiagnosticHandler( LspWorkspaceManager workspaceManager, LspWorkspaceRegistrationService registrationService, - IDiagnosticAnalyzerService analyzerService, IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, IGlobalOptionService globalOptions) : AbstractWorkspacePullDiagnosticsHandler( - workspaceManager, registrationService, analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions) + workspaceManager, registrationService, diagnosticSourceManager, diagnosticsRefresher, globalOptions) { // All workspace diagnostics are potential duplicates given that they can be overridden by the diagnostics // produced by document diagnostics. diff --git a/src/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs b/src/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs index 7c13f4d46b74a..9204ca4f9b451 100644 --- a/src/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs +++ b/src/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandlerFactory.cs @@ -16,7 +16,6 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics; [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal class WorkspacePullDiagnosticHandlerFactory( LspWorkspaceRegistrationService registrationService, - IDiagnosticAnalyzerService analyzerService, IDiagnosticSourceManager diagnosticSourceManager, IDiagnosticsRefresher diagnosticsRefresher, IGlobalOptionService globalOptions) : ILspServiceFactory @@ -24,6 +23,6 @@ internal class WorkspacePullDiagnosticHandlerFactory( public ILspService CreateILspService(LspServices lspServices, WellKnownLspServerKinds serverKind) { var workspaceManager = lspServices.GetRequiredService(); - return new WorkspacePullDiagnosticHandler(workspaceManager, registrationService, analyzerService, diagnosticSourceManager, diagnosticsRefresher, globalOptions); + return new WorkspacePullDiagnosticHandler(workspaceManager, registrationService, diagnosticSourceManager, diagnosticsRefresher, globalOptions); } } diff --git a/src/Tools/ExternalAccess/Razor/EditorFeatures/Cohost/Handlers/Diagnostics.cs b/src/Tools/ExternalAccess/Razor/EditorFeatures/Cohost/Handlers/Diagnostics.cs index f22cafb4e8384..6e39c496e756b 100644 --- a/src/Tools/ExternalAccess/Razor/EditorFeatures/Cohost/Handlers/Diagnostics.cs +++ b/src/Tools/ExternalAccess/Razor/EditorFeatures/Cohost/Handlers/Diagnostics.cs @@ -17,8 +17,9 @@ internal static class Diagnostics { public static async Task> GetDocumentDiagnosticsAsync(Document document, bool supportsVisualStudioExtensions, CancellationToken cancellationToken) { - var globalOptionsService = document.Project.Solution.Services.ExportProvider.GetService(); - var diagnosticAnalyzerService = document.Project.Solution.Services.ExportProvider.GetService(); + var solutionServices = document.Project.Solution.Services; + var globalOptionsService = solutionServices.ExportProvider.GetService(); + var diagnosticAnalyzerService = solutionServices.GetRequiredService(); var diagnostics = await diagnosticAnalyzerService.GetDiagnosticsForSpanAsync( document, range: null, DiagnosticKind.All, cancellationToken).ConfigureAwait(false); diff --git a/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs b/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs index 3bf4c29fe1470..b34e7b7ba4193 100644 --- a/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs +++ b/src/VisualStudio/Core/Def/Options/VisualStudioOptionStorage.cs @@ -102,6 +102,7 @@ public bool TryFetch(LocalUserRegistryOptionPersister persister, OptionKey2 opti public static readonly IReadOnlyDictionary Storages = new Dictionary() { + {"dotnet_analyze_copilot_changes", new FeatureFlagStorage(@"Roslyn.AnalyzeCopilotChanges")}, {"dotnet_collapse_empty_metadata_implementations_when_first_opened", new RoamingProfileStorage("TextEditor.%LANGUAGE%.Specific.CollapseEmptyMetadataImplementationsWhenFirstOpened")}, {"dotnet_collapse_imports_when_first_opened", new RoamingProfileStorage("TextEditor.%LANGUAGE%.Specific.CollapseImportsWhenFirstOpened")}, {"dotnet_collapse_metadata_implementations_when_first_opened", new RoamingProfileStorage("TextEditor.%LANGUAGE%.Specific.CollapseMetadataImplementationsWhenFirstOpened")}, diff --git a/src/VisualStudio/Core/Def/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs b/src/VisualStudio/Core/Def/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs index b2992e2b257d0..e9f30d70a1d98 100644 --- a/src/VisualStudio/Core/Def/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs +++ b/src/VisualStudio/Core/Def/TableDataSource/Suppression/VisualStudioSuppressionFixService.cs @@ -42,7 +42,6 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.Suppression; internal sealed class VisualStudioSuppressionFixService( IThreadingContext threadingContext, VisualStudioWorkspaceImpl workspace, - IDiagnosticAnalyzerService diagnosticService, ICodeFixService codeFixService, ICodeActionEditHandlerService editHandlerService, VisualStudioDiagnosticListSuppressionStateService suppressionStateService, @@ -53,7 +52,6 @@ internal sealed class VisualStudioSuppressionFixService( private readonly IThreadingContext _threadingContext = threadingContext; private readonly VisualStudioWorkspaceImpl _workspace = workspace; private readonly IAsynchronousOperationListener _listener = listenerProvider.GetListener(FeatureAttribute.ErrorList); - private readonly IDiagnosticAnalyzerService _diagnosticService = diagnosticService; private readonly ICodeFixService _codeFixService = codeFixService; private readonly IFixMultipleOccurrencesService _fixMultipleOccurencesService = workspace.Services.GetRequiredService(); private readonly ICodeActionEditHandlerService _editHandlerService = editHandlerService; @@ -348,7 +346,7 @@ await _editHandlerService.ApplyAsync( cancellationToken).ConfigureAwait(false); // Kick off diagnostic re-analysis for affected projects so that diagnostics gets refreshed. - _diagnosticService.RequestDiagnosticRefresh(); + _workspace.Services.GetRequiredService().RequestDiagnosticRefresh(); } } catch (OperationCanceledException) @@ -484,7 +482,8 @@ private async Task>> Ge RoslynDebug.AssertNotNull(latestDocumentDiagnosticsMap); var uniqueDiagnosticIds = group.SelectMany(kvp => kvp.Value.Select(d => d.Id)).ToImmutableHashSet(); - var latestProjectDiagnostics = (await _diagnosticService.GetDiagnosticsForIdsAsync(project, documentId: null, + var diagnosticService = _workspace.Services.GetRequiredService(); + var latestProjectDiagnostics = (await diagnosticService.GetDiagnosticsForIdsAsync(project, documentId: null, diagnosticIds: uniqueDiagnosticIds, shouldIncludeAnalyzer: null, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken) .ConfigureAwait(false)).Where(IsDocumentDiagnostic); @@ -574,7 +573,8 @@ private async Task>> Get RoslynDebug.AssertNotNull(latestDiagnosticsToFix); var uniqueDiagnosticIds = diagnostics.Select(d => d.Id).ToImmutableHashSet(); - var latestDiagnosticsFromDiagnosticService = (await _diagnosticService.GetDiagnosticsForIdsAsync(project, documentId: null, + var diagnosticService = _workspace.Services.GetRequiredService(); + var latestDiagnosticsFromDiagnosticService = (await diagnosticService.GetDiagnosticsForIdsAsync(project, documentId: null, diagnosticIds: uniqueDiagnosticIds, shouldIncludeAnalyzer: null, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, cancellationToken) .ConfigureAwait(false)); diff --git a/src/VisualStudio/Core/Def/TaskList/ExternalErrorDiagnosticUpdateSource.cs b/src/VisualStudio/Core/Def/TaskList/ExternalErrorDiagnosticUpdateSource.cs index 7fd735634876b..5a90fc40c77fe 100644 --- a/src/VisualStudio/Core/Def/TaskList/ExternalErrorDiagnosticUpdateSource.cs +++ b/src/VisualStudio/Core/Def/TaskList/ExternalErrorDiagnosticUpdateSource.cs @@ -38,7 +38,6 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; internal sealed class ExternalErrorDiagnosticUpdateSource : IDisposable { private readonly Workspace _workspace; - private readonly IDiagnosticAnalyzerService _diagnosticService; private readonly IAsynchronousOperationListener _listener; private readonly CancellationToken _disposalToken; private readonly IServiceBroker _serviceBroker; @@ -65,14 +64,12 @@ internal sealed class ExternalErrorDiagnosticUpdateSource : IDisposable [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public ExternalErrorDiagnosticUpdateSource( VisualStudioWorkspace workspace, - IDiagnosticAnalyzerService diagnosticService, IAsynchronousOperationListenerProvider listenerProvider, [Import(typeof(SVsFullAccessServiceBroker))] IServiceBroker serviceBroker, IThreadingContext threadingContext) { _disposalToken = threadingContext.DisposalToken; _workspace = workspace; - _diagnosticService = diagnosticService; _listener = listenerProvider.GetListener(FeatureAttribute.ErrorList); _serviceBroker = serviceBroker; @@ -90,7 +87,7 @@ private async ValueTask ProcessTaskQueueItemsAsync(ImmutableSegmentedList _diagnosticService.AnalyzerInfoCache; + public DiagnosticAnalyzerInfoCache AnalyzerInfoCache => this._workspace.Services.GetRequiredService().AnalyzerInfoCache; public void Dispose() { diff --git a/src/VisualStudio/Core/Def/Venus/ContainedLanguage.cs b/src/VisualStudio/Core/Def/Venus/ContainedLanguage.cs index cf11ec5068991..89ee84515afff 100644 --- a/src/VisualStudio/Core/Def/Venus/ContainedLanguage.cs +++ b/src/VisualStudio/Core/Def/Venus/ContainedLanguage.cs @@ -80,7 +80,7 @@ internal ContainedLanguage( Workspace = workspace; _editorAdaptersFactoryService = componentModel.GetService(); - _diagnosticAnalyzerService = componentModel.GetService(); + _diagnosticAnalyzerService = workspace.Services.GetRequiredService(); // Get the ITextBuffer for the secondary buffer Marshal.ThrowExceptionForHR(bufferCoordinator.GetSecondaryBuffer(out var secondaryTextLines)); diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/BaseDiagnosticAndGeneratorItemSource.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/BaseDiagnosticAndGeneratorItemSource.cs index 2197f03fa4d48..cdaeba7816eb8 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/BaseDiagnosticAndGeneratorItemSource.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/BaseDiagnosticAndGeneratorItemSource.cs @@ -26,7 +26,6 @@ internal abstract partial class BaseDiagnosticAndGeneratorItemSource : IAttached { private static readonly DiagnosticDescriptorComparer s_comparer = new(); - private readonly IDiagnosticAnalyzerService _diagnosticAnalyzerService; private readonly BulkObservableCollection _items = []; private readonly CancellationTokenSource _cancellationTokenSource = new(); @@ -48,14 +47,12 @@ public BaseDiagnosticAndGeneratorItemSource( Workspace workspace, ProjectId projectId, IAnalyzersCommandHandler commandHandler, - IDiagnosticAnalyzerService diagnosticAnalyzerService, IAsynchronousOperationListenerProvider listenerProvider) { _threadingContext = threadingContext; Workspace = workspace; ProjectId = projectId; CommandHandler = commandHandler; - _diagnosticAnalyzerService = diagnosticAnalyzerService; _workQueue = new AsyncBatchingWorkQueue( DelayTimeSpan.Idle, @@ -152,8 +149,9 @@ ImmutableArray GenerateDiagnosticItems( var specificDiagnosticOptions = project.CompilationOptions!.SpecificDiagnosticOptions; var analyzerConfigOptions = project.GetAnalyzerConfigOptions(); + var diagnosticAnalyzerService = this.Workspace.Services.GetRequiredService(); return analyzerReference.GetAnalyzers(project.Language) - .SelectMany(a => _diagnosticAnalyzerService.AnalyzerInfoCache.GetDiagnosticDescriptors(a)) + .SelectMany(a => diagnosticAnalyzerService.AnalyzerInfoCache.GetDiagnosticDescriptors(a)) .GroupBy(d => d.Id) .OrderBy(g => g.Key, StringComparer.CurrentCulture) .SelectAsArray(g => diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/CpsDiagnosticItemSource.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/CpsDiagnosticItemSource.cs index 2d7c2aa84cc51..c36d8a75123d4 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/CpsDiagnosticItemSource.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/CpsDiagnosticItemSource.cs @@ -29,9 +29,8 @@ public CpsDiagnosticItemSource( ProjectId projectId, IVsHierarchyItem item, IAnalyzersCommandHandler commandHandler, - IDiagnosticAnalyzerService analyzerService, IAsynchronousOperationListenerProvider listenerProvider) - : base(threadingContext, workspace, projectId, commandHandler, analyzerService, listenerProvider) + : base(threadingContext, workspace, projectId, commandHandler, listenerProvider) { _item = item; _projectDirectoryPath = Path.GetDirectoryName(projectPath); diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/CpsDiagnosticItemSourceProvider.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/CpsDiagnosticItemSourceProvider.cs index 6983a954d3f69..75b02e1e51ea6 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/CpsDiagnosticItemSourceProvider.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/CpsDiagnosticItemSourceProvider.cs @@ -30,14 +30,12 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplore internal sealed class CpsDiagnosticItemSourceProvider( IThreadingContext threadingContext, [Import(typeof(AnalyzersCommandHandler))] IAnalyzersCommandHandler commandHandler, - IDiagnosticAnalyzerService diagnosticAnalyzerService, VisualStudioWorkspace workspace, IAsynchronousOperationListenerProvider listenerProvider) : AttachedCollectionSourceProvider { private readonly IThreadingContext _threadingContext = threadingContext; private readonly IAnalyzersCommandHandler _commandHandler = commandHandler; - private readonly IDiagnosticAnalyzerService _diagnosticAnalyzerService = diagnosticAnalyzerService; private readonly Workspace _workspace = workspace; private readonly IAsynchronousOperationListenerProvider _listenerProvider = listenerProvider; @@ -63,7 +61,7 @@ internal sealed class CpsDiagnosticItemSourceProvider( if (hierarchy.GetCanonicalName(itemId, out var projectCanonicalName) == VSConstants.S_OK) { return new CpsDiagnosticItemSource( - _threadingContext, _workspace, projectCanonicalName, projectId, item, _commandHandler, _diagnosticAnalyzerService, _listenerProvider); + _threadingContext, _workspace, projectCanonicalName, projectId, item, _commandHandler, _listenerProvider); } } } diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/LegacyDiagnosticItemSource.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/LegacyDiagnosticItemSource.cs index e88a2616faac6..0fe084b978786 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/LegacyDiagnosticItemSource.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/LegacyDiagnosticItemSource.cs @@ -16,14 +16,12 @@ public LegacyDiagnosticItemSource( IThreadingContext threadingContext, AnalyzerItem item, IAnalyzersCommandHandler commandHandler, - IDiagnosticAnalyzerService diagnosticAnalyzerService, IAsynchronousOperationListenerProvider listenerProvider) : base( threadingContext, item.AnalyzersFolder.Workspace, item.AnalyzersFolder.ProjectId, commandHandler, - diagnosticAnalyzerService, listenerProvider) { _item = item; diff --git a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/LegacyDiagnosticItemSourceProvider.cs b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/LegacyDiagnosticItemSourceProvider.cs index f0513880d775f..b11b690e091d7 100644 --- a/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/LegacyDiagnosticItemSourceProvider.cs +++ b/src/VisualStudio/Core/Impl/SolutionExplorer/DiagnosticItem/LegacyDiagnosticItemSourceProvider.cs @@ -23,7 +23,6 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplore internal sealed class LegacyDiagnosticItemSourceProvider( IThreadingContext threadingContext, [Import(typeof(AnalyzersCommandHandler))] IAnalyzersCommandHandler commandHandler, - IDiagnosticAnalyzerService diagnosticAnalyzerService, IAsynchronousOperationListenerProvider listenerProvider) : AttachedCollectionSourceProvider { protected override IAttachedCollectionSource? CreateCollectionSource(AnalyzerItem item, string relationshipName) @@ -31,7 +30,7 @@ internal sealed class LegacyDiagnosticItemSourceProvider( if (relationshipName == KnownRelationships.Contains) { return new LegacyDiagnosticItemSource( - threadingContext, item, commandHandler, diagnosticAnalyzerService, listenerProvider); + threadingContext, item, commandHandler, listenerProvider); } return null; diff --git a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs index f061e876b2ab5..b4fb4f4950a05 100644 --- a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs @@ -5,6 +5,7 @@ #nullable disable using System; +using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; @@ -14,10 +15,12 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Copilot; using Microsoft.CodeAnalysis.DesignerAttribute; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; @@ -28,6 +31,8 @@ using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.UnitTests; +using Microsoft.CodeAnalysis.UnitTests.Logging; +using Microsoft.VisualStudio.Telemetry; using Microsoft.VisualStudio.Threading; using Roslyn.Test.Utilities; using Roslyn.Test.Utilities.TestGenerators; @@ -38,10 +43,22 @@ namespace Roslyn.VisualStudio.Next.UnitTests.Remote { [UseExportProvider] [Trait(Traits.Feature, Traits.Features.RemoteHost)] - public sealed class ServiceHubServicesTests + public sealed partial class ServiceHubServicesTests { - private static TestWorkspace CreateWorkspace(Type[] additionalParts = null) - => new(composition: FeaturesTestCompositions.Features.WithTestHostParts(TestHost.OutOfProcess).AddParts(additionalParts)); + private static TestWorkspace CreateWorkspace( + Type[] additionalParts = null, + Type[] additionalRemoteParts = null) + { + var workspace = new TestWorkspace(composition: FeaturesTestCompositions.Features.WithTestHostParts(TestHost.OutOfProcess).AddParts(additionalParts)); + + if (additionalRemoteParts != null) + { + var clientProvider = (InProcRemoteHostClientProvider)workspace.Services.GetService(); + clientProvider.AdditionalRemoteParts = additionalRemoteParts; + } + + return workspace; + } [Fact] public async Task TestRemoteHostSynchronize() @@ -200,7 +217,7 @@ await DesignerAttributeDiscoveryService.DiscoverDesignerAttributesAsync( CancellationToken.None); } - private class DesignerAttributeComputerCallback : IDesignerAttributeDiscoveryService.ICallback + private sealed class DesignerAttributeComputerCallback : IDesignerAttributeDiscoveryService.ICallback { private readonly TaskCompletionSource> _infosSource = new(); @@ -354,11 +371,11 @@ public async Task TestRemoteWorkspaceCircularReferences() SolutionId.CreateNewId(), VersionStamp.Create(), "", [ ProjectInfo.Create( - p1, VersionStamp.Create(), "p1", "p1", LanguageNames.CSharp, outputFilePath: file.Path, - projectReferences: [new ProjectReference(p2)]), - ProjectInfo.Create( - p2, VersionStamp.Create(), "p2", "p2", LanguageNames.CSharp, - metadataReferences: [MetadataReference.CreateFromFile(file.Path)]) + p1, VersionStamp.Create(), "p1", "p1", LanguageNames.CSharp, outputFilePath: file.Path, + projectReferences: [new ProjectReference(p2)]), + ProjectInfo.Create( + p2, VersionStamp.Create(), "p2", "p2", LanguageNames.CSharp, + metadataReferences: [MetadataReference.CreateFromFile(file.Path)]) ]); using var remoteWorkspace = new RemoteWorkspace(FeaturesTestCompositions.RemoteHost.GetHostServices()); @@ -1569,6 +1586,98 @@ internal async Task TestNonCompilationLanguage(SourceGeneratorExecutionPreferenc Assert.NotEqual(initialExecutionMap[noCompilationProject.Id], finalExecutionMap[noCompilationProject.Id]); } + [Fact] + internal async Task TestCopilotChangeAnalysis() + { + using var workspace = new TestWorkspace(composition: FeaturesTestCompositions.Features); + + var code = """ + class C + { + void M() + { + X + } + } + """; + workspace.InitializeDocuments(LanguageNames.CSharp, files: [code]); + + var analyzerReference = new TestAnalyzerReferenceByLanguage(DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap()); + workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences([analyzerReference])); + + var service = workspace.Services.GetRequiredService(); + var document = workspace.CurrentSolution.Projects.Single().Documents.Single(); + + var text = await document.GetTextAsync(); + var listIndex = text.ToString().IndexOf("X"); + + var result = await service.AnalyzeChangeAsync( + document, [new TextChange(new TextSpan(listIndex, 1), """ + <<<<<<< + Goo + ======= + Bar + >>>>>>> + """)], CancellationToken.None); + + Assert.True(result.Succeeded); + + var diagnosticAnalysis = result.DiagnosticAnalyses.Single(d => d.Kind == DiagnosticKind.CompilerSyntax); + Assert.Equal(1, diagnosticAnalysis.IdToCount["CS8300"]); + + Assert.Equal(1, result.CodeFixAnalysis.DiagnosticIdToCount["CS8300"]); + Assert.Equal("CSharp.ConflictMarkerResolution.CSharpResolveConflictMarkerCodeFixProvider", result.CodeFixAnalysis.DiagnosticIdToProviderName["CS8300"].Single()); + + var logger = new TestTelemetryLogger(); + Logger.SetLogger(logger); + TestTelemetryLogger.TestScope scope; + using (CopilotChangeAnalysisUtilities.LogCopilotChangeAnalysis("TestCode", accepted: true, "TestProposalId", result, CancellationToken.None)) + { + scope = logger.OpenedScopes.Single(); + } + Logger.SetLogger(null); + + var endEvent = scope.EndEvent; + Assert.Equal("vs/ide/vbcs/copilot/analyzechange", endEvent.Name); + + var properties = endEvent.Properties; + + Assert.Equal(true, properties["vs.ide.vbcs.copilot.analyzechange.succeeded"]); + Assert.Equal(true, properties["vs.ide.vbcs.copilot.analyzechange.accepted"]); + Assert.Equal("TestProposalId", properties["vs.ide.vbcs.copilot.analyzechange.proposalid"]); + + Assert.Equal(44, properties["vs.ide.vbcs.copilot.analyzechange.olddocumentlength"]); + Assert.Equal(78, properties["vs.ide.vbcs.copilot.analyzechange.newdocumentlength"]); + Assert.Equal(34, properties["vs.ide.vbcs.copilot.analyzechange.textchangedelta"]); + + Assert.Equal(1, properties["vs.ide.vbcs.copilot.analyzechange.projectdocumentcount"]); + Assert.Equal(0, properties["vs.ide.vbcs.copilot.analyzechange.projectsourcegenerateddocumentcount"]); + Assert.Equal(1, properties["vs.ide.vbcs.copilot.analyzechange.projectconecount"]); + + // No analyzer diagnostics in this scenario. + + Assert.Equal("", properties["vs.ide.vbcs.copilot.analyzechange.diagnosticanalysis_analyzersyntax_idtocount"]); + Assert.Equal("", properties["vs.ide.vbcs.copilot.analyzechange.diagnosticanalysis_analyzersyntax_categorytocount"]); + Assert.Equal("", properties["vs.ide.vbcs.copilot.analyzechange.diagnosticanalysis_analyzersyntax_severitytocount"]); + + Assert.Equal("", properties["vs.ide.vbcs.copilot.analyzechange.diagnosticanalysis_analyzersemantic_idtocount"]); + Assert.Equal("", properties["vs.ide.vbcs.copilot.analyzechange.diagnosticanalysis_analyzersemantic_categorytocount"]); + Assert.Equal("", properties["vs.ide.vbcs.copilot.analyzechange.diagnosticanalysis_analyzersemantic_severitytocount"]); + + // CS1002_1 means we got one CS1002 diagnostic. Whereas CS1525_3 means we got 3 CS1525 diagnostics. + Assert.Equal("CS1002_1,CS1513_1,CS1525_3,CS8300_1", properties["vs.ide.vbcs.copilot.analyzechange.diagnosticanalysis_compilersyntax_idtocount"]); + Assert.Equal("Compiler_6", properties["vs.ide.vbcs.copilot.analyzechange.diagnosticanalysis_compilersyntax_categorytocount"]); + Assert.Equal("Error_6", properties["vs.ide.vbcs.copilot.analyzechange.diagnosticanalysis_compilersyntax_severitytocount"]); + + Assert.Equal("CS0103_1", properties["vs.ide.vbcs.copilot.analyzechange.diagnosticanalysis_compilersemantic_idtocount"]); + Assert.Equal("Compiler_1", properties["vs.ide.vbcs.copilot.analyzechange.diagnosticanalysis_compilersemantic_categorytocount"]); + Assert.Equal("Error_1", properties["vs.ide.vbcs.copilot.analyzechange.diagnosticanalysis_compilersemantic_severitytocount"]); + + Assert.Equal("CS0103_1,CS8300_1", properties["vs.ide.vbcs.copilot.analyzechange.codefixanalysis_diagnosticidtocount"]); + Assert.Equal("CS0103_CSharp.GenerateVariable.CSharpGenerateVariableCodeFixProvider,CS8300_CSharp.ConflictMarkerResolution.CSharpResolveConflictMarkerCodeFixProvider", + properties["vs.ide.vbcs.copilot.analyzechange.codefixanalysis_diagnosticidtoprovidername"]); + } + private static void VerifyStates(Solution solution1, Solution solution2, string projectName, ImmutableArray documentNames) { Assert.Equal(WorkspaceKind.RemoteWorkspace, solution1.WorkspaceKind); @@ -1661,7 +1770,7 @@ private static Solution Populate(Solution solution) solution = AddProject(solution, LanguageNames.CSharp, [ "class CS { }", - "class CS2 { }" + "class CS2 { }" ], [ "cs additional file content" @@ -1670,11 +1779,11 @@ private static Solution Populate(Solution solution) solution = AddProject(solution, LanguageNames.VisualBasic, [ "Class VB\r\nEnd Class", - "Class VB2\r\nEnd Class" + "Class VB2\r\nEnd Class" ], [ "vb additional file content" - ], [solution.ProjectIds.First()]); + ], [solution.ProjectIds[0]]); solution = AddProject(solution, LanguageNames.CSharp, [ @@ -1687,21 +1796,21 @@ private static Solution Populate(Solution solution) solution = AddProject(solution, LanguageNames.CSharp, [ "class OrphanCS { }", - "class OrphanCS2 { }" + "class OrphanCS2 { }" ], [ "cs additional file content", - "cs additional file content2" + "cs additional file content2" ], []); solution = AddProject(solution, LanguageNames.CSharp, [ "class CS { }", - "class CS2 { }", - "class CS3 { }", - "class CS4 { }", - "class CS5 { }", - ], + "class CS2 { }", + "class CS3 { }", + "class CS4 { }", + "class CS5 { }", + ], [ "cs additional file content" ], []); @@ -1709,11 +1818,11 @@ private static Solution Populate(Solution solution) solution = AddProject(solution, LanguageNames.VisualBasic, [ "Class VB\r\nEnd Class", - "Class VB2\r\nEnd Class", - "Class VB3\r\nEnd Class", - "Class VB4\r\nEnd Class", - "Class VB5\r\nEnd Class", - ], + "Class VB2\r\nEnd Class", + "Class VB3\r\nEnd Class", + "Class VB4\r\nEnd Class", + "Class VB5\r\nEnd Class", + ], [ "vb additional file content" ], []); @@ -1721,8 +1830,16 @@ private static Solution Populate(Solution solution) return solution; } - private static Solution AddProject(Solution solution, string language, string[] documents, string[] additionalDocuments, ProjectId[] p2pReferences) + private static Solution AddProject( + Solution solution, + string language, + string[] documents, + string[] additionalDocuments = null, + ProjectId[] p2pReferences = null) { + additionalDocuments ??= []; + p2pReferences ??= []; + var projectName = $"Project{solution.ProjectIds.Count}"; var project = solution.AddProject(projectName, $"{projectName}.dll", language) .AddMetadataReference(MetadataReference.CreateFromFile(typeof(object).Assembly.Location)) diff --git a/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb b/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb index 9476f840de109..1872750ced9a5 100644 --- a/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb +++ b/src/VisualStudio/Core/Test/Diagnostics/ExternalDiagnosticUpdateSourceTests.vb @@ -233,7 +233,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Dim project = workspace.CurrentSolution.Projects.First() - Dim service = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim service = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() Dim threadingContext = workspace.ExportProvider.GetExportedValue(Of IThreadingContext) Dim testServiceBroker = workspace.ExportProvider.GetExportedValue(Of TestServiceBroker) Dim vsWorkspace = workspace.ExportProvider.GetExportedValue(Of MockVisualStudioWorkspace)() diff --git a/src/VisualStudio/Core/Test/SolutionExplorer/CpsDiagnosticItemSourceTests.vb b/src/VisualStudio/Core/Test/SolutionExplorer/CpsDiagnosticItemSourceTests.vb index 1896615e99942..fd1a95b6ad9cb 100644 --- a/src/VisualStudio/Core/Test/SolutionExplorer/CpsDiagnosticItemSourceTests.vb +++ b/src/VisualStudio/Core/Test/SolutionExplorer/CpsDiagnosticItemSourceTests.vb @@ -45,7 +45,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer project.FilePath, project.Id, New MockHierarchyItem() With {.CanonicalName = "\net472\analyzerdependency\" + analyzerPath}, - New FakeAnalyzersCommandHandler, workspace.GetService(Of IDiagnosticAnalyzerService), + New FakeAnalyzersCommandHandler(), listenerProvider) Assert.True(source.HasItems) diff --git a/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb b/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb index 7074c37fd513b..c5a06f41e1129 100644 --- a/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb +++ b/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb @@ -272,7 +272,6 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer workspace.GetService(Of IThreadingContext), New AnalyzerItem(New AnalyzersFolderItem(workspace.GetService(Of IThreadingContext), workspace, projectId, Nothing, Nothing), analyzerReference, Nothing), New FakeAnalyzersCommandHandler, - workspace.GetService(Of IDiagnosticAnalyzerService), workspace.GetService(Of IAsynchronousOperationListenerProvider)) End Function diff --git a/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb b/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb index 38be0fb67008c..2aec249ad56ae 100644 --- a/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb +++ b/src/VisualStudio/Core/Test/Venus/DocumentService_IntegrationTests.vb @@ -229,7 +229,7 @@ class { } ' confirm there are errors Assert.True(model.GetDiagnostics().Any()) - Dim diagnosticService = workspace.GetService(Of IDiagnosticAnalyzerService)() + Dim diagnosticService = workspace.Services.GetRequiredService(Of IDiagnosticAnalyzerService)() ' confirm diagnostic support is off for the document Assert.False(document.SupportsDiagnostics()) diff --git a/src/VisualStudio/ExternalAccess/FSharp/Internal/Diagnostics/FSharpDiagnosticAnalyzerService.cs b/src/VisualStudio/ExternalAccess/FSharp/Internal/Diagnostics/FSharpDiagnosticAnalyzerService.cs index b4fef38d66eef..6354e43149495 100644 --- a/src/VisualStudio/ExternalAccess/FSharp/Internal/Diagnostics/FSharpDiagnosticAnalyzerService.cs +++ b/src/VisualStudio/ExternalAccess/FSharp/Internal/Diagnostics/FSharpDiagnosticAnalyzerService.cs @@ -7,25 +7,17 @@ using System; using System.Collections.Generic; using System.Composition; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.ExternalAccess.FSharp.Diagnostics; using Microsoft.CodeAnalysis.Host.Mef; -namespace Microsoft.CodeAnalysis.ExternalAccess.FSharp.Internal.Diagnostics -{ - [Shared] - [Export(typeof(IFSharpDiagnosticAnalyzerService))] - internal class FSharpDiagnosticAnalyzerService : IFSharpDiagnosticAnalyzerService - { - private readonly Microsoft.CodeAnalysis.Diagnostics.IDiagnosticAnalyzerService _delegatee; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public FSharpDiagnosticAnalyzerService(Microsoft.CodeAnalysis.Diagnostics.IDiagnosticAnalyzerService delegatee) - { - _delegatee = delegatee; - } +namespace Microsoft.CodeAnalysis.ExternalAccess.FSharp.Internal.Diagnostics; - public void Reanalyze(Workspace workspace, IEnumerable projectIds = null, IEnumerable documentIds = null, bool highPriority = false) - => _delegatee.RequestDiagnosticRefresh(); - } +[Export(typeof(IFSharpDiagnosticAnalyzerService)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal class FSharpDiagnosticAnalyzerService(IDiagnosticsRefresher refresher) : IFSharpDiagnosticAnalyzerService +{ + public void Reanalyze(Workspace workspace, IEnumerable projectIds = null, IEnumerable documentIds = null, bool highPriority = false) + => refresher.RequestWorkspaceRefresh(); } diff --git a/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs b/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs index 343deb91c658c..1d19cac5f4bd5 100644 --- a/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs +++ b/src/Workspaces/Core/Portable/Shared/TestHooks/FeatureAttribute.cs @@ -20,6 +20,7 @@ internal static class FeatureAttribute public const string CompletionSet = nameof(CompletionSet); public const string CopilotImplementNotImplementedException = nameof(CopilotImplementNotImplementedException); public const string CopilotSuggestions = nameof(CopilotSuggestions); + public const string CopilotChangeAnalysis = nameof(CopilotChangeAnalysis); public const string DesignerAttributes = nameof(DesignerAttributes); public const string DiagnosticService = nameof(DiagnosticService); public const string DocumentOutline = nameof(DocumentOutline); diff --git a/src/Workspaces/CoreTestUtilities/Logging/TestTelemetryLogger.cs b/src/Workspaces/CoreTestUtilities/Logging/TestTelemetryLogger.cs new file mode 100644 index 0000000000000..9e7b293241adb --- /dev/null +++ b/src/Workspaces/CoreTestUtilities/Logging/TestTelemetryLogger.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Microsoft.CodeAnalysis.Internal.Log; +using Microsoft.CodeAnalysis.Telemetry; +using Microsoft.VisualStudio.Telemetry; +using Xunit; + +namespace Microsoft.CodeAnalysis.UnitTests.Logging; + +internal sealed class TestTelemetryLogger : TelemetryLogger +{ + public TestTelemetryLogger(bool logDelta = false) + { + LogDelta = logDelta; + } + + protected override bool LogDelta { get; } + + public sealed class TestScope + { + public readonly TelemetryEvent EndEvent; + public readonly LogType Type; + + public TestScope(TelemetryEvent endEvent, LogType type) + { + EndEvent = endEvent; + Type = type; + } + } + + public List PostedEvents = []; + public HashSet OpenedScopes = []; + + public override bool IsEnabled(FunctionId functionId) + => true; + + protected override void PostEvent(TelemetryEvent telemetryEvent) + { + PostedEvents.Add(telemetryEvent); + } + + protected override object Start(string eventName, LogType type) + { + var scope = new TestScope(new TelemetryEvent(eventName), type); + OpenedScopes.Add(scope); + return scope; + } + + protected override void End(object scope, TelemetryResult result) + { + Assert.True(OpenedScopes.Remove((TestScope)scope)); + } + + protected override TelemetryEvent GetEndEvent(object scope) + => ((TestScope)scope).EndEvent; +} diff --git a/src/Workspaces/CoreTestUtilities/Microsoft.CodeAnalysis.Workspaces.Test.Utilities.csproj b/src/Workspaces/CoreTestUtilities/Microsoft.CodeAnalysis.Workspaces.Test.Utilities.csproj index cfc6c47a176a1..d24648010f3f3 100644 --- a/src/Workspaces/CoreTestUtilities/Microsoft.CodeAnalysis.Workspaces.Test.Utilities.csproj +++ b/src/Workspaces/CoreTestUtilities/Microsoft.CodeAnalysis.Workspaces.Test.Utilities.csproj @@ -64,5 +64,6 @@ + diff --git a/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs b/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs index f270b5f81590b..621a4bfb963fd 100644 --- a/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs +++ b/src/Workspaces/CoreTestUtilities/Remote/InProcRemostHostClient.cs @@ -16,319 +16,319 @@ using Nerdbank.Streams; using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Remote.Testing +namespace Microsoft.CodeAnalysis.Remote.Testing; + +internal sealed partial class InProcRemoteHostClient : RemoteHostClient { - internal sealed partial class InProcRemoteHostClient : RemoteHostClient + private readonly SolutionServices _workspaceServices; + private readonly InProcRemoteServices _inprocServices; + private readonly RemoteServiceCallbackDispatcherRegistry _callbackDispatchers; + + public static RemoteHostClient Create(SolutionServices services, RemoteServiceCallbackDispatcherRegistry callbackDispatchers, TraceListener? traceListener, RemoteHostTestData testData) { - private readonly SolutionServices _workspaceServices; - private readonly InProcRemoteServices _inprocServices; - private readonly RemoteServiceCallbackDispatcherRegistry _callbackDispatchers; + var inprocServices = new InProcRemoteServices(services, traceListener, testData); + var instance = new InProcRemoteHostClient(services, inprocServices, callbackDispatchers); - public static RemoteHostClient Create(SolutionServices services, RemoteServiceCallbackDispatcherRegistry callbackDispatchers, TraceListener? traceListener, RemoteHostTestData testData) - { - var inprocServices = new InProcRemoteServices(services, traceListener, testData); - var instance = new InProcRemoteHostClient(services, inprocServices, callbackDispatchers); + // return instance + return instance; + } - // return instance - return instance; - } + private InProcRemoteHostClient( + SolutionServices services, + InProcRemoteServices inprocServices, + RemoteServiceCallbackDispatcherRegistry callbackDispatchers) + { + _workspaceServices = services; + _callbackDispatchers = callbackDispatchers; + _inprocServices = inprocServices; + } - private InProcRemoteHostClient( - SolutionServices services, - InProcRemoteServices inprocServices, - RemoteServiceCallbackDispatcherRegistry callbackDispatchers) - { - _workspaceServices = services; - _callbackDispatchers = callbackDispatchers; - _inprocServices = inprocServices; - } + public static async Task GetTestClientAsync(Workspace workspace) + { + var client = (InProcRemoteHostClient?)await TryGetClientAsync(workspace, CancellationToken.None).ConfigureAwait(false); + Contract.ThrowIfNull(client); + return client; + } - public static async Task GetTestClientAsync(Workspace workspace) - { - var client = (InProcRemoteHostClient?)await TryGetClientAsync(workspace, CancellationToken.None).ConfigureAwait(false); - Contract.ThrowIfNull(client); - return client; - } + public RemoteWorkspace GetRemoteWorkspace() + => TestData.WorkspaceManager.GetWorkspace(); - public RemoteWorkspace GetRemoteWorkspace() - => TestData.WorkspaceManager.GetWorkspace(); + public RemoteHostTestData TestData => _inprocServices.TestData; - public RemoteHostTestData TestData => _inprocServices.TestData; + public override RemoteServiceConnection CreateConnection(object? callbackTarget) where T : class + { + var descriptor = ServiceDescriptors.Instance.GetServiceDescriptor(typeof(T), RemoteProcessConfiguration.ServerGC); + var callbackDispatcher = (descriptor.ClientInterface != null) ? _callbackDispatchers.GetDispatcher(typeof(T)) : null; + + return new BrokeredServiceConnection( + descriptor, + callbackTarget, + callbackDispatcher, + _inprocServices.ServiceBrokerClient, + _workspaceServices.GetRequiredService().AssetStorage, + _workspaceServices.GetRequiredService(), + shutdownCancellationService: null, + remoteProcess: null); + } - public override RemoteServiceConnection CreateConnection(object? callbackTarget) where T : class - { - var descriptor = ServiceDescriptors.Instance.GetServiceDescriptor(typeof(T), RemoteProcessConfiguration.ServerGC); - var callbackDispatcher = (descriptor.ClientInterface != null) ? _callbackDispatchers.GetDispatcher(typeof(T)) : null; - - return new BrokeredServiceConnection( - descriptor, - callbackTarget, - callbackDispatcher, - _inprocServices.ServiceBrokerClient, - _workspaceServices.GetRequiredService().AssetStorage, - _workspaceServices.GetRequiredService(), - shutdownCancellationService: null, - remoteProcess: null); - } + public override void Dispose() + { + _inprocServices.Dispose(); + } + + public sealed class ServiceProvider : IServiceProvider + { + public readonly TraceSource TraceSource; + public readonly RemoteHostTestData TestData; - public override void Dispose() + public ServiceProvider(TraceSource traceSource, RemoteHostTestData testData) { - _inprocServices.Dispose(); + TraceSource = traceSource; + TestData = testData; } - public sealed class ServiceProvider : IServiceProvider + public object GetService(Type serviceType) { - public readonly TraceSource TraceSource; - public readonly RemoteHostTestData TestData; - - public ServiceProvider(TraceSource traceSource, RemoteHostTestData testData) + if (typeof(TraceSource) == serviceType) { - TraceSource = traceSource; - TestData = testData; + return TraceSource; } - public object GetService(Type serviceType) + if (typeof(RemoteHostTestData) == serviceType) { - if (typeof(TraceSource) == serviceType) - { - return TraceSource; - } + return TestData; + } + + throw ExceptionUtilities.UnexpectedValue(serviceType); + } + } - if (typeof(RemoteHostTestData) == serviceType) - { - return TestData; - } + private sealed class InProcServiceBroker : IServiceBroker + { + private readonly InProcRemoteServices _services; - throw ExceptionUtilities.UnexpectedValue(serviceType); - } + public InProcServiceBroker(InProcRemoteServices services) + { + _services = services; } - private sealed class InProcServiceBroker : IServiceBroker + public event EventHandler? AvailabilityChanged { add { } remove { } } + + // This method is currently not needed for our IServiceBroker usage patterns. + public ValueTask GetPipeAsync(ServiceMoniker serviceMoniker, ServiceActivationOptions options, CancellationToken cancellationToken) + => throw ExceptionUtilities.Unreachable(); + + public ValueTask GetProxyAsync(ServiceRpcDescriptor descriptor, ServiceActivationOptions options, CancellationToken cancellationToken) where T : class { - private readonly InProcRemoteServices _services; + var pipePair = FullDuplexStream.CreatePipePair(); + + var clientConnection = descriptor + .WithTraceSource(_services.ServiceProvider.TraceSource) + .ConstructRpcConnection(pipePair.Item2); + + Contract.ThrowIfFalse(options.ClientRpcTarget is null == descriptor.ClientInterface is null); - public InProcServiceBroker(InProcRemoteServices services) + if (descriptor.ClientInterface != null) { - _services = services; + Contract.ThrowIfNull(options.ClientRpcTarget); + clientConnection.AddLocalRpcTarget(options.ClientRpcTarget); } - public event EventHandler? AvailabilityChanged { add { } remove { } } + // Clear RPC target so that the server connection is forced to create a new proxy for the callback + // instead of just invoking the callback object directly (this emulates the product that does + // not serialize the callback object over). + options.ClientRpcTarget = null; - // This method is currently not needed for our IServiceBroker usage patterns. - public ValueTask GetPipeAsync(ServiceMoniker serviceMoniker, ServiceActivationOptions options, CancellationToken cancellationToken) - => throw ExceptionUtilities.Unreachable(); + // Creates service instance and connects it to the pipe. + // We don't need to store the instance anywhere. + _ = _services.CreateBrokeredService(descriptor, pipePair.Item1, options); - public ValueTask GetProxyAsync(ServiceRpcDescriptor descriptor, ServiceActivationOptions options, CancellationToken cancellationToken) where T : class - { - var pipePair = FullDuplexStream.CreatePipePair(); + clientConnection.StartListening(); - var clientConnection = descriptor - .WithTraceSource(_services.ServiceProvider.TraceSource) - .ConstructRpcConnection(pipePair.Item2); + return ValueTaskFactory.FromResult((T?)clientConnection.ConstructRpcClient()); + } + } - Contract.ThrowIfFalse(options.ClientRpcTarget is null == descriptor.ClientInterface is null); + private sealed class InProcRemoteServices : IDisposable + { + public readonly ServiceProvider ServiceProvider; + private readonly Dictionary> _inProcBrokeredServicesMap = []; + private readonly Dictionary _remoteBrokeredServicesMap = []; - if (descriptor.ClientInterface != null) - { - Contract.ThrowIfNull(options.ClientRpcTarget); - clientConnection.AddLocalRpcTarget(options.ClientRpcTarget); - } + public readonly IServiceBroker ServiceBroker; + public readonly ServiceBrokerClient ServiceBrokerClient; - // Clear RPC target so that the server connection is forced to create a new proxy for the callback - // instead of just invoking the callback object directly (this emulates the product that does - // not serialize the callback object over). - options.ClientRpcTarget = null; + public InProcRemoteServices(SolutionServices workspaceServices, TraceListener? traceListener, RemoteHostTestData testData) + { + var remoteLogger = new TraceSource("InProcRemoteClient") + { + Switch = { Level = SourceLevels.Warning }, + }; - // Creates service instance and connects it to the pipe. - // We don't need to store the instance anywhere. - _ = _services.CreateBrokeredService(descriptor, pipePair.Item1, options); + if (traceListener != null) + { + remoteLogger.Listeners.Add(traceListener); + } - clientConnection.StartListening(); + ServiceProvider = new ServiceProvider(remoteLogger, testData); - return ValueTaskFactory.FromResult((T?)clientConnection.ConstructRpcClient()); - } + ServiceBroker = new InProcServiceBroker(this); +#pragma warning disable VSTHRD012 // Provide JoinableTaskFactory where allowed + ServiceBrokerClient = new ServiceBrokerClient(ServiceBroker); +#pragma warning restore + + RegisterInProcBrokeredService(SolutionAssetProvider.ServiceDescriptor, () => new SolutionAssetProvider(workspaceServices)); + RegisterRemoteBrokeredService(new RemoteAssetSynchronizationService.Factory()); + RegisterRemoteBrokeredService(new RemoteAsynchronousOperationListenerService.Factory()); + RegisterRemoteBrokeredService(new RemoteCodeLensReferencesService.Factory()); + RegisterRemoteBrokeredService(new RemoteConvertTupleToStructCodeRefactoringService.Factory()); + RegisterRemoteBrokeredService(new RemoteCopilotChangeAnalysisService.Factory()); + RegisterRemoteBrokeredService(new RemoteDependentTypeFinderService.Factory()); + RegisterRemoteBrokeredService(new RemoteDesignerAttributeDiscoveryService.Factory()); + RegisterRemoteBrokeredService(new RemoteDiagnosticAnalyzerService.Factory()); + RegisterRemoteBrokeredService(new RemoteDocumentHighlightsService.Factory()); + RegisterRemoteBrokeredService(new RemoteEditAndContinueService.Factory()); + RegisterRemoteBrokeredService(new RemoteEncapsulateFieldService.Factory()); + RegisterRemoteBrokeredService(new RemoteExtensionMethodImportCompletionService.Factory()); + RegisterRemoteBrokeredService(new RemoteFindUsagesService.Factory()); + RegisterRemoteBrokeredService(new RemoteFullyQualifyService.Factory()); + RegisterRemoteBrokeredService(new RemoteInheritanceMarginService.Factory()); + RegisterRemoteBrokeredService(new RemoteKeepAliveService.Factory()); + RegisterRemoteBrokeredService(new RemoteLegacySolutionEventsAggregationService.Factory()); + RegisterRemoteBrokeredService(new RemoteMissingImportDiscoveryService.Factory()); + RegisterRemoteBrokeredService(new RemoteNavigateToSearchService.Factory()); + RegisterRemoteBrokeredService(new RemoteNavigationBarItemService.Factory()); + RegisterRemoteBrokeredService(new RemoteProcessTelemetryService.Factory()); + RegisterRemoteBrokeredService(new RemoteRelatedDocumentsService.Factory()); + RegisterRemoteBrokeredService(new RemoteRenamerService.Factory()); + RegisterRemoteBrokeredService(new RemoteSemanticClassificationService.Factory()); + RegisterRemoteBrokeredService(new RemoteSemanticSearchService.Factory()); + RegisterRemoteBrokeredService(new RemoteSourceGenerationService.Factory()); + RegisterRemoteBrokeredService(new RemoteStackTraceExplorerService.Factory()); + RegisterRemoteBrokeredService(new RemoteSymbolFinderService.Factory()); + RegisterRemoteBrokeredService(new RemoteSymbolSearchUpdateService.Factory()); + RegisterRemoteBrokeredService(new RemoteTaskListService.Factory()); + RegisterRemoteBrokeredService(new RemoteUnitTestingSearchService.Factory()); + RegisterRemoteBrokeredService(new RemoteUnusedReferenceAnalysisService.Factory()); + RegisterRemoteBrokeredService(new RemoteValueTrackingService.Factory()); } - private sealed class InProcRemoteServices : IDisposable + public void Dispose() + => ServiceBrokerClient.Dispose(); + + public RemoteHostTestData TestData => ServiceProvider.TestData; + + public void RegisterInProcBrokeredService(ServiceDescriptor serviceDescriptor, Func serviceFactory) { - public readonly ServiceProvider ServiceProvider; - private readonly Dictionary> _inProcBrokeredServicesMap = []; - private readonly Dictionary _remoteBrokeredServicesMap = []; + _inProcBrokeredServicesMap.Add(serviceDescriptor.Moniker, serviceFactory); + } - public readonly IServiceBroker ServiceBroker; - public readonly ServiceBrokerClient ServiceBrokerClient; + public void RegisterRemoteBrokeredService(BrokeredServiceBase.IFactory serviceFactory) + { + var moniker = ServiceDescriptors.Instance.GetServiceDescriptorForServiceFactory(serviceFactory.ServiceType).Moniker; + _remoteBrokeredServicesMap.Add(moniker, serviceFactory); + } - public InProcRemoteServices(SolutionServices workspaceServices, TraceListener? traceListener, RemoteHostTestData testData) + public object CreateBrokeredService(ServiceRpcDescriptor descriptor, IDuplexPipe pipe, ServiceActivationOptions options) + { + if (_inProcBrokeredServicesMap.TryGetValue(descriptor.Moniker, out var inProcFactory)) { - var remoteLogger = new TraceSource("InProcRemoteClient") - { - Switch = { Level = SourceLevels.Warning }, - }; + // This code is similar to service creation implemented in BrokeredServiceBase.FactoryBase. + // Currently don't support callback creation as we don't have in-proc service with callbacks yet. + Contract.ThrowIfFalse(descriptor.ClientInterface == null); - if (traceListener != null) - { - remoteLogger.Listeners.Add(traceListener); - } + var serviceConnection = descriptor.WithTraceSource(ServiceProvider.TraceSource).ConstructRpcConnection(pipe); + var service = inProcFactory(); - ServiceProvider = new ServiceProvider(remoteLogger, testData); + serviceConnection.AddLocalRpcTarget(service); + serviceConnection.StartListening(); - ServiceBroker = new InProcServiceBroker(this); -#pragma warning disable VSTHRD012 // Provide JoinableTaskFactory where allowed - ServiceBrokerClient = new ServiceBrokerClient(ServiceBroker); -#pragma warning restore + return service; + } - RegisterInProcBrokeredService(SolutionAssetProvider.ServiceDescriptor, () => new SolutionAssetProvider(workspaceServices)); - RegisterRemoteBrokeredService(new RemoteAssetSynchronizationService.Factory()); - RegisterRemoteBrokeredService(new RemoteAsynchronousOperationListenerService.Factory()); - RegisterRemoteBrokeredService(new RemoteCodeLensReferencesService.Factory()); - RegisterRemoteBrokeredService(new RemoteConvertTupleToStructCodeRefactoringService.Factory()); - RegisterRemoteBrokeredService(new RemoteDependentTypeFinderService.Factory()); - RegisterRemoteBrokeredService(new RemoteDesignerAttributeDiscoveryService.Factory()); - RegisterRemoteBrokeredService(new RemoteDiagnosticAnalyzerService.Factory()); - RegisterRemoteBrokeredService(new RemoteDocumentHighlightsService.Factory()); - RegisterRemoteBrokeredService(new RemoteEditAndContinueService.Factory()); - RegisterRemoteBrokeredService(new RemoteEncapsulateFieldService.Factory()); - RegisterRemoteBrokeredService(new RemoteExtensionMethodImportCompletionService.Factory()); - RegisterRemoteBrokeredService(new RemoteFindUsagesService.Factory()); - RegisterRemoteBrokeredService(new RemoteFullyQualifyService.Factory()); - RegisterRemoteBrokeredService(new RemoteInheritanceMarginService.Factory()); - RegisterRemoteBrokeredService(new RemoteKeepAliveService.Factory()); - RegisterRemoteBrokeredService(new RemoteLegacySolutionEventsAggregationService.Factory()); - RegisterRemoteBrokeredService(new RemoteMissingImportDiscoveryService.Factory()); - RegisterRemoteBrokeredService(new RemoteNavigateToSearchService.Factory()); - RegisterRemoteBrokeredService(new RemoteNavigationBarItemService.Factory()); - RegisterRemoteBrokeredService(new RemoteProcessTelemetryService.Factory()); - RegisterRemoteBrokeredService(new RemoteRelatedDocumentsService.Factory()); - RegisterRemoteBrokeredService(new RemoteRenamerService.Factory()); - RegisterRemoteBrokeredService(new RemoteSemanticClassificationService.Factory()); - RegisterRemoteBrokeredService(new RemoteSemanticSearchService.Factory()); - RegisterRemoteBrokeredService(new RemoteSourceGenerationService.Factory()); - RegisterRemoteBrokeredService(new RemoteStackTraceExplorerService.Factory()); - RegisterRemoteBrokeredService(new RemoteSymbolFinderService.Factory()); - RegisterRemoteBrokeredService(new RemoteSymbolSearchUpdateService.Factory()); - RegisterRemoteBrokeredService(new RemoteTaskListService.Factory()); - RegisterRemoteBrokeredService(new RemoteUnitTestingSearchService.Factory()); - RegisterRemoteBrokeredService(new RemoteUnusedReferenceAnalysisService.Factory()); - RegisterRemoteBrokeredService(new RemoteValueTrackingService.Factory()); + if (_remoteBrokeredServicesMap.TryGetValue(descriptor.Moniker, out var remoteFactory)) + { + return remoteFactory.Create(pipe, ServiceProvider, options, ServiceBroker); } - public void Dispose() - => ServiceBrokerClient.Dispose(); + throw ExceptionUtilities.UnexpectedValue(descriptor.Moniker); + } + + private sealed class WrappedStream : Stream + { + private readonly IDisposable _service; + private readonly Stream _stream; - public RemoteHostTestData TestData => ServiceProvider.TestData; + public WrappedStream(IDisposable service, Stream stream) + { + // tie service's lifetime with that of stream + _service = service; + _stream = stream; + } - public void RegisterInProcBrokeredService(ServiceDescriptor serviceDescriptor, Func serviceFactory) + public override long Position { - _inProcBrokeredServicesMap.Add(serviceDescriptor.Moniker, serviceFactory); + get { return _stream.Position; } + set { _stream.Position = value; } } - public void RegisterRemoteBrokeredService(BrokeredServiceBase.IFactory serviceFactory) + public override int ReadTimeout { - var moniker = ServiceDescriptors.Instance.GetServiceDescriptorForServiceFactory(serviceFactory.ServiceType).Moniker; - _remoteBrokeredServicesMap.Add(moniker, serviceFactory); + get { return _stream.ReadTimeout; } + set { _stream.ReadTimeout = value; } } - public object CreateBrokeredService(ServiceRpcDescriptor descriptor, IDuplexPipe pipe, ServiceActivationOptions options) + public override int WriteTimeout { - if (_inProcBrokeredServicesMap.TryGetValue(descriptor.Moniker, out var inProcFactory)) - { - // This code is similar to service creation implemented in BrokeredServiceBase.FactoryBase. - // Currently don't support callback creation as we don't have in-proc service with callbacks yet. - Contract.ThrowIfFalse(descriptor.ClientInterface == null); + get { return _stream.WriteTimeout; } + set { _stream.WriteTimeout = value; } + } - var serviceConnection = descriptor.WithTraceSource(ServiceProvider.TraceSource).ConstructRpcConnection(pipe); - var service = inProcFactory(); + public override bool CanRead => _stream.CanRead; + public override bool CanSeek => _stream.CanSeek; + public override bool CanWrite => _stream.CanWrite; + public override long Length => _stream.Length; + public override bool CanTimeout => _stream.CanTimeout; - serviceConnection.AddLocalRpcTarget(service); - serviceConnection.StartListening(); + public override void Flush() => _stream.Flush(); + public override Task FlushAsync(CancellationToken cancellationToken) => _stream.FlushAsync(cancellationToken); - return service; - } + public override long Seek(long offset, SeekOrigin origin) => _stream.Seek(offset, origin); + public override void SetLength(long value) => _stream.SetLength(value); - if (_remoteBrokeredServicesMap.TryGetValue(descriptor.Moniker, out var remoteFactory)) - { - return remoteFactory.Create(pipe, ServiceProvider, options, ServiceBroker); - } + public override int ReadByte() => _stream.ReadByte(); + public override void WriteByte(byte value) => _stream.WriteByte(value); - throw ExceptionUtilities.UnexpectedValue(descriptor.Moniker); - } + public override int Read(byte[] buffer, int offset, int count) => _stream.Read(buffer, offset, count); + public override void Write(byte[] buffer, int offset, int count) => _stream.Write(buffer, offset, count); - private sealed class WrappedStream : Stream - { - private readonly IDisposable _service; - private readonly Stream _stream; - - public WrappedStream(IDisposable service, Stream stream) - { - // tie service's lifetime with that of stream - _service = service; - _stream = stream; - } - - public override long Position - { - get { return _stream.Position; } - set { _stream.Position = value; } - } - - public override int ReadTimeout - { - get { return _stream.ReadTimeout; } - set { _stream.ReadTimeout = value; } - } - - public override int WriteTimeout - { - get { return _stream.WriteTimeout; } - set { _stream.WriteTimeout = value; } - } - - public override bool CanRead => _stream.CanRead; - public override bool CanSeek => _stream.CanSeek; - public override bool CanWrite => _stream.CanWrite; - public override long Length => _stream.Length; - public override bool CanTimeout => _stream.CanTimeout; - - public override void Flush() => _stream.Flush(); - public override Task FlushAsync(CancellationToken cancellationToken) => _stream.FlushAsync(cancellationToken); - - public override long Seek(long offset, SeekOrigin origin) => _stream.Seek(offset, origin); - public override void SetLength(long value) => _stream.SetLength(value); - - public override int ReadByte() => _stream.ReadByte(); - public override void WriteByte(byte value) => _stream.WriteByte(value); - - public override int Read(byte[] buffer, int offset, int count) => _stream.Read(buffer, offset, count); - public override void Write(byte[] buffer, int offset, int count) => _stream.Write(buffer, offset, count); - - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => _stream.ReadAsync(buffer, offset, count, cancellationToken); - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => _stream.WriteAsync(buffer, offset, count, cancellationToken); + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => _stream.ReadAsync(buffer, offset, count, cancellationToken); + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => _stream.WriteAsync(buffer, offset, count, cancellationToken); #if NET // nullability annotations differ - public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) => _stream.BeginRead(buffer, offset, count, callback, state); - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) => _stream.BeginWrite(buffer, offset, count, callback, state); + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) => _stream.BeginRead(buffer, offset, count, callback, state); + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) => _stream.BeginWrite(buffer, offset, count, callback, state); #else - public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object? state) => _stream.BeginRead(buffer, offset, count, callback, state); - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object? state) => _stream.BeginWrite(buffer, offset, count, callback, state); + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object? state) => _stream.BeginRead(buffer, offset, count, callback, state); + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object? state) => _stream.BeginWrite(buffer, offset, count, callback, state); #endif - public override int EndRead(IAsyncResult asyncResult) => _stream.EndRead(asyncResult); - public override void EndWrite(IAsyncResult asyncResult) => _stream.EndWrite(asyncResult); + public override int EndRead(IAsyncResult asyncResult) => _stream.EndRead(asyncResult); + public override void EndWrite(IAsyncResult asyncResult) => _stream.EndWrite(asyncResult); - public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) => _stream.CopyToAsync(destination, bufferSize, cancellationToken); + public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) => _stream.CopyToAsync(destination, bufferSize, cancellationToken); - public override void Close() - { - _service.Dispose(); - _stream.Close(); - } + public override void Close() + { + _service.Dispose(); + _stream.Close(); + } - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); - _service.Dispose(); - _stream.Dispose(); - } + _service.Dispose(); + _stream.Dispose(); } } } diff --git a/src/Workspaces/Remote/Core/RemoteWorkspacesResources.resx b/src/Workspaces/Remote/Core/RemoteWorkspacesResources.resx index b95710fff4a67..597b4673146c9 100644 --- a/src/Workspaces/Remote/Core/RemoteWorkspacesResources.resx +++ b/src/Workspaces/Remote/Core/RemoteWorkspacesResources.resx @@ -234,4 +234,7 @@ Related documents + + Copilot change analysis + \ No newline at end of file diff --git a/src/Workspaces/Remote/Core/ServiceDescriptors.cs b/src/Workspaces/Remote/Core/ServiceDescriptors.cs index ba153c6bd072b..0ca3b9255d4d5 100644 --- a/src/Workspaces/Remote/Core/ServiceDescriptors.cs +++ b/src/Workspaces/Remote/Core/ServiceDescriptors.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.CodeLens; using Microsoft.CodeAnalysis.Completion.Providers; using Microsoft.CodeAnalysis.ConvertTupleToStruct; +using Microsoft.CodeAnalysis.Copilot; using Microsoft.CodeAnalysis.DesignerAttribute; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.DocumentHighlighting; @@ -35,137 +36,137 @@ using Microsoft.CodeAnalysis.UnusedReferences; using Microsoft.CodeAnalysis.ValueTracking; -namespace Microsoft.CodeAnalysis.Remote +namespace Microsoft.CodeAnalysis.Remote; + +/// +/// Service descriptors of brokered Roslyn ServiceHub services. +/// +internal sealed class ServiceDescriptors { - /// - /// Service descriptors of brokered Roslyn ServiceHub services. - /// - internal sealed class ServiceDescriptors - { - internal const string ComponentName = "LanguageServices"; + internal const string ComponentName = "LanguageServices"; - private const string InterfaceNamePrefix = "IRemote"; - private const string InterfaceNameSuffix = "Service"; + private const string InterfaceNamePrefix = "IRemote"; + private const string InterfaceNameSuffix = "Service"; - private const string Suffix64 = "64"; - private const string SuffixServerGC = "S"; - private const string SuffixCoreClr = "Core"; + private const string Suffix64 = "64"; + private const string SuffixServerGC = "S"; + private const string SuffixCoreClr = "Core"; - public static readonly ServiceDescriptors Instance = new(ComponentName, GetFeatureDisplayName, RemoteSerializationOptions.Default, new (Type, Type?)[] - { - (typeof(IRemoteAssetSynchronizationService), null), - (typeof(IRemoteAsynchronousOperationListenerService), null), - (typeof(IRemoteCodeLensReferencesService), null), - (typeof(IRemoteConvertTupleToStructCodeRefactoringService), null), - (typeof(IRemoteDependentTypeFinderService), null), - (typeof(IRemoteDesignerAttributeDiscoveryService), typeof(IRemoteDesignerAttributeDiscoveryService.ICallback)), - (typeof(IRemoteDiagnosticAnalyzerService), null), - (typeof(IRemoteDocumentHighlightsService), null), - (typeof(IRemoteEditAndContinueService), typeof(IRemoteEditAndContinueService.ICallback)), - (typeof(IRemoteEncapsulateFieldService), null), - (typeof(IRemoteExtensionMethodImportCompletionService), null), - (typeof(IRemoteFindUsagesService), typeof(IRemoteFindUsagesService.ICallback)), - (typeof(IRemoteFullyQualifyService), null), - (typeof(IRemoteInheritanceMarginService), null), - (typeof(IRemoteKeepAliveService), null), - (typeof(IRemoteLegacySolutionEventsAggregationService), null), - (typeof(IRemoteMissingImportDiscoveryService), typeof(IRemoteMissingImportDiscoveryService.ICallback)), - (typeof(IRemoteNavigateToSearchService), typeof(IRemoteNavigateToSearchService.ICallback)), - (typeof(IRemoteNavigationBarItemService), null), - (typeof(IRemoteProcessTelemetryService), null), - (typeof(IRemoteRelatedDocumentsService), typeof(IRemoteRelatedDocumentsService.ICallback)), - (typeof(IRemoteRenamerService), null), - (typeof(IRemoteSemanticClassificationService), null), - (typeof(IRemoteSemanticSearchService), typeof(IRemoteSemanticSearchService.ICallback)), - (typeof(IRemoteSourceGenerationService), null), - (typeof(IRemoteStackTraceExplorerService), null), - (typeof(IRemoteSymbolFinderService), typeof(IRemoteSymbolFinderService.ICallback)), - (typeof(IRemoteSymbolSearchUpdateService), null), - (typeof(IRemoteTaskListService), null), - (typeof(IRemoteUnitTestingSearchService), null), - (typeof(IRemoteUnusedReferenceAnalysisService), null), - (typeof(IRemoteValueTrackingService), null), - }); - - internal readonly RemoteSerializationOptions Options; - private readonly ImmutableDictionary _descriptors; - private readonly string _componentName; - private readonly Func _featureDisplayNameProvider; - - public ServiceDescriptors( - string componentName, - Func featureDisplayNameProvider, - RemoteSerializationOptions serializationOptions, - IEnumerable<(Type serviceInterface, Type? callbackInterface)> interfaces) - { - Options = serializationOptions; - _componentName = componentName; - _featureDisplayNameProvider = featureDisplayNameProvider; - _descriptors = interfaces.ToImmutableDictionary(i => i.serviceInterface, i => CreateDescriptors(i.serviceInterface, i.callbackInterface)); - } + public static readonly ServiceDescriptors Instance = new(ComponentName, GetFeatureDisplayName, RemoteSerializationOptions.Default, new (Type, Type?)[] + { + (typeof(IRemoteAssetSynchronizationService), null), + (typeof(IRemoteAsynchronousOperationListenerService), null), + (typeof(IRemoteCodeLensReferencesService), null), + (typeof(IRemoteConvertTupleToStructCodeRefactoringService), null), + (typeof(IRemoteCopilotChangeAnalysisService), null), + (typeof(IRemoteDependentTypeFinderService), null), + (typeof(IRemoteDesignerAttributeDiscoveryService), typeof(IRemoteDesignerAttributeDiscoveryService.ICallback)), + (typeof(IRemoteDiagnosticAnalyzerService), null), + (typeof(IRemoteDocumentHighlightsService), null), + (typeof(IRemoteEditAndContinueService), typeof(IRemoteEditAndContinueService.ICallback)), + (typeof(IRemoteEncapsulateFieldService), null), + (typeof(IRemoteExtensionMethodImportCompletionService), null), + (typeof(IRemoteFindUsagesService), typeof(IRemoteFindUsagesService.ICallback)), + (typeof(IRemoteFullyQualifyService), null), + (typeof(IRemoteInheritanceMarginService), null), + (typeof(IRemoteKeepAliveService), null), + (typeof(IRemoteLegacySolutionEventsAggregationService), null), + (typeof(IRemoteMissingImportDiscoveryService), typeof(IRemoteMissingImportDiscoveryService.ICallback)), + (typeof(IRemoteNavigateToSearchService), typeof(IRemoteNavigateToSearchService.ICallback)), + (typeof(IRemoteNavigationBarItemService), null), + (typeof(IRemoteProcessTelemetryService), null), + (typeof(IRemoteRelatedDocumentsService), typeof(IRemoteRelatedDocumentsService.ICallback)), + (typeof(IRemoteRenamerService), null), + (typeof(IRemoteSemanticClassificationService), null), + (typeof(IRemoteSemanticSearchService), typeof(IRemoteSemanticSearchService.ICallback)), + (typeof(IRemoteSourceGenerationService), null), + (typeof(IRemoteStackTraceExplorerService), null), + (typeof(IRemoteSymbolFinderService), typeof(IRemoteSymbolFinderService.ICallback)), + (typeof(IRemoteSymbolSearchUpdateService), null), + (typeof(IRemoteTaskListService), null), + (typeof(IRemoteUnitTestingSearchService), null), + (typeof(IRemoteUnusedReferenceAnalysisService), null), + (typeof(IRemoteValueTrackingService), null), + }); + + internal readonly RemoteSerializationOptions Options; + private readonly ImmutableDictionary _descriptors; + private readonly string _componentName; + private readonly Func _featureDisplayNameProvider; + + public ServiceDescriptors( + string componentName, + Func featureDisplayNameProvider, + RemoteSerializationOptions serializationOptions, + IEnumerable<(Type serviceInterface, Type? callbackInterface)> interfaces) + { + Options = serializationOptions; + _componentName = componentName; + _featureDisplayNameProvider = featureDisplayNameProvider; + _descriptors = interfaces.ToImmutableDictionary(i => i.serviceInterface, i => CreateDescriptors(i.serviceInterface, i.callbackInterface)); + } - internal static string GetSimpleName(Type serviceInterface) - { - Contract.ThrowIfFalse(serviceInterface.IsInterface); - var interfaceName = serviceInterface.Name; - Contract.ThrowIfFalse(interfaceName.StartsWith(InterfaceNamePrefix, StringComparison.Ordinal)); - Contract.ThrowIfFalse(interfaceName.EndsWith(InterfaceNameSuffix, StringComparison.Ordinal)); + internal static string GetSimpleName(Type serviceInterface) + { + Contract.ThrowIfFalse(serviceInterface.IsInterface); + var interfaceName = serviceInterface.Name; + Contract.ThrowIfFalse(interfaceName.StartsWith(InterfaceNamePrefix, StringComparison.Ordinal)); + Contract.ThrowIfFalse(interfaceName.EndsWith(InterfaceNameSuffix, StringComparison.Ordinal)); - return interfaceName.Substring(InterfaceNamePrefix.Length, interfaceName.Length - InterfaceNamePrefix.Length - InterfaceNameSuffix.Length); - } + return interfaceName.Substring(InterfaceNamePrefix.Length, interfaceName.Length - InterfaceNamePrefix.Length - InterfaceNameSuffix.Length); + } - private (ServiceDescriptor descriptorCoreClr64, ServiceDescriptor descriptorCoreClr64ServerGC) CreateDescriptors(Type serviceInterface, Type? callbackInterface) - { - Contract.ThrowIfFalse(callbackInterface == null || callbackInterface.IsInterface); + private (ServiceDescriptor descriptorCoreClr64, ServiceDescriptor descriptorCoreClr64ServerGC) CreateDescriptors(Type serviceInterface, Type? callbackInterface) + { + Contract.ThrowIfFalse(callbackInterface == null || callbackInterface.IsInterface); - var simpleName = GetSimpleName(serviceInterface); - var descriptorCoreClr64 = ServiceDescriptor.CreateRemoteServiceDescriptor(_componentName, simpleName, SuffixCoreClr + Suffix64, Options, _featureDisplayNameProvider, callbackInterface); - var descriptorCoreClr64ServerGC = ServiceDescriptor.CreateRemoteServiceDescriptor(_componentName, simpleName, SuffixCoreClr + Suffix64 + SuffixServerGC, Options, _featureDisplayNameProvider, callbackInterface); + var simpleName = GetSimpleName(serviceInterface); + var descriptorCoreClr64 = ServiceDescriptor.CreateRemoteServiceDescriptor(_componentName, simpleName, SuffixCoreClr + Suffix64, Options, _featureDisplayNameProvider, callbackInterface); + var descriptorCoreClr64ServerGC = ServiceDescriptor.CreateRemoteServiceDescriptor(_componentName, simpleName, SuffixCoreClr + Suffix64 + SuffixServerGC, Options, _featureDisplayNameProvider, callbackInterface); - return (descriptorCoreClr64, descriptorCoreClr64ServerGC); - } + return (descriptorCoreClr64, descriptorCoreClr64ServerGC); + } - public static bool IsCurrentProcessRunningOnCoreClr() - => !RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework") && - !RuntimeInformation.FrameworkDescription.StartsWith(".NET Native"); + public static bool IsCurrentProcessRunningOnCoreClr() + => !RuntimeInformation.FrameworkDescription.StartsWith(".NET Framework") && + !RuntimeInformation.FrameworkDescription.StartsWith(".NET Native"); - public ServiceDescriptor GetServiceDescriptorForServiceFactory(Type serviceType) - => GetServiceDescriptor(serviceType, RemoteProcessConfiguration.ServerGC); + public ServiceDescriptor GetServiceDescriptorForServiceFactory(Type serviceType) + => GetServiceDescriptor(serviceType, RemoteProcessConfiguration.ServerGC); - public ServiceDescriptor GetServiceDescriptor(Type serviceType, RemoteProcessConfiguration configuration) + public ServiceDescriptor GetServiceDescriptor(Type serviceType, RemoteProcessConfiguration configuration) + { + if (!_descriptors.TryGetValue(serviceType, out var descriptor)) { - if (!_descriptors.TryGetValue(serviceType, out var descriptor)) - { - throw ExceptionUtilities.UnexpectedValue(serviceType); - } - - var (descriptorCoreClr64, descriptorCoreClr64ServerGC) = descriptor; - return (configuration & RemoteProcessConfiguration.ServerGC) switch - { - 0 => descriptorCoreClr64, - RemoteProcessConfiguration.ServerGC => descriptorCoreClr64ServerGC, - _ => throw ExceptionUtilities.Unreachable() - }; + throw ExceptionUtilities.UnexpectedValue(serviceType); } - /// - /// is a short service name, e.g. "EditAndContinue". - /// - internal static string GetFeatureDisplayName(string serviceName) - => RemoteWorkspacesResources.GetResourceString("FeatureName_" + serviceName); + var (descriptorCoreClr64, descriptorCoreClr64ServerGC) = descriptor; + return (configuration & RemoteProcessConfiguration.ServerGC) switch + { + 0 => descriptorCoreClr64, + RemoteProcessConfiguration.ServerGC => descriptorCoreClr64ServerGC, + _ => throw ExceptionUtilities.Unreachable() + }; + } - internal TestAccessor GetTestAccessor() - => new(this); + /// + /// is a short service name, e.g. "EditAndContinue". + /// + internal static string GetFeatureDisplayName(string serviceName) + => RemoteWorkspacesResources.GetResourceString("FeatureName_" + serviceName); - internal readonly struct TestAccessor - { - private readonly ServiceDescriptors _serviceDescriptors; + internal TestAccessor GetTestAccessor() + => new(this); - internal TestAccessor(ServiceDescriptors serviceDescriptors) - => _serviceDescriptors = serviceDescriptors; + internal readonly struct TestAccessor + { + private readonly ServiceDescriptors _serviceDescriptors; - public ImmutableDictionary Descriptors - => _serviceDescriptors._descriptors; - } + internal TestAccessor(ServiceDescriptors serviceDescriptors) + => _serviceDescriptors = serviceDescriptors; + + public ImmutableDictionary Descriptors + => _serviceDescriptors._descriptors; } } diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.cs.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.cs.xlf index d62020c15d81f..a7aac7c8f5c3c 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.cs.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.cs.xlf @@ -27,6 +27,11 @@ Převést řazenou kolekci členů na refaktoring struktury + + Copilot change analysis + Copilot change analysis + + Dependent type finder Vyhledávání závislých typů diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.de.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.de.xlf index 8bb309e1d00d5..91fe72238f07a 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.de.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.de.xlf @@ -27,6 +27,11 @@ Tupel in Strukturrefactoring konvertieren + + Copilot change analysis + Copilot change analysis + + Dependent type finder Suche nach abhängigen Typen diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.es.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.es.xlf index 9d9bce54ca77b..62894076b0674 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.es.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.es.xlf @@ -27,6 +27,11 @@ Convertir tupla en refactorización de estructura + + Copilot change analysis + Copilot change analysis + + Dependent type finder Localizador de tipos dependientes diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.fr.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.fr.xlf index 4a39bd4627f35..86c9029e8523c 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.fr.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.fr.xlf @@ -27,6 +27,11 @@ Refactorisation de la conversion de tuple en struct + + Copilot change analysis + Copilot change analysis + + Dependent type finder Outil de recherche de type dépendant diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.it.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.it.xlf index b90d57e88f718..a4df8092dfd10 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.it.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.it.xlf @@ -27,6 +27,11 @@ Conversione della tupla nel refactoring dello struct + + Copilot change analysis + Copilot change analysis + + Dependent type finder Strumento di ricerca tipi dipendenti diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ja.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ja.xlf index 6578f074c30e8..040492ebc259b 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ja.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ja.xlf @@ -27,6 +27,11 @@ タプルを構造体のリファクタリングに変換する + + Copilot change analysis + Copilot change analysis + + Dependent type finder 依存型の検索 diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ko.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ko.xlf index 02d43a4f1dd04..7caa3e8988efb 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ko.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ko.xlf @@ -27,6 +27,11 @@ 튜플을 구조체 리팩터링으로 변환 + + Copilot change analysis + Copilot change analysis + + Dependent type finder 종속 형식 찾기 diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pl.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pl.xlf index ce3bfa18af5f1..40907fced6133 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pl.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pl.xlf @@ -27,6 +27,11 @@ Refaktoryzacja konwersji krotki na strukturę + + Copilot change analysis + Copilot change analysis + + Dependent type finder Wyszukiwanie typów zależnych diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pt-BR.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pt-BR.xlf index dc123a1d667aa..18b9ac3348c6a 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pt-BR.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.pt-BR.xlf @@ -27,6 +27,11 @@ Converter a tupla em refatoração de struct + + Copilot change analysis + Copilot change analysis + + Dependent type finder Localizador de tipo dependente diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ru.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ru.xlf index 9aefb92600d36..b6ad1290a067a 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ru.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.ru.xlf @@ -27,6 +27,11 @@ Преобразовать кортеж в рефакторинг структуры + + Copilot change analysis + Copilot change analysis + + Dependent type finder Поиск зависимых типов diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.tr.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.tr.xlf index ce2c1d33eeb3d..faaded48149ba 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.tr.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.tr.xlf @@ -27,6 +27,11 @@ Demeti yapıya dönüştürme yeniden düzenlemesi + + Copilot change analysis + Copilot change analysis + + Dependent type finder Bağımlı tür bulucu diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hans.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hans.xlf index 444eed20fcf2e..8b43d692c4613 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hans.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hans.xlf @@ -27,6 +27,11 @@ 将元组转换为结构重构 + + Copilot change analysis + Copilot change analysis + + Dependent type finder 依赖类型查找器 diff --git a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hant.xlf b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hant.xlf index 873caeb08c9f8..5b852fd8625d8 100644 --- a/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hant.xlf +++ b/src/Workspaces/Remote/Core/xlf/RemoteWorkspacesResources.zh-Hant.xlf @@ -27,6 +27,11 @@ 將元組轉換為結構重構 + + Copilot change analysis + Copilot change analysis + + Dependent type finder 相依類型尋找工具 diff --git a/src/Workspaces/Remote/ServiceHub/Services/Copilot/RemoteCopilotChangeAnalysisService.cs b/src/Workspaces/Remote/ServiceHub/Services/Copilot/RemoteCopilotChangeAnalysisService.cs new file mode 100644 index 0000000000000..0d18aa960ce4d --- /dev/null +++ b/src/Workspaces/Remote/ServiceHub/Services/Copilot/RemoteCopilotChangeAnalysisService.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Copilot; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Remote; + +internal sealed partial class RemoteCopilotChangeAnalysisService( + in BrokeredServiceBase.ServiceConstructionArguments arguments) + : BrokeredServiceBase(arguments), IRemoteCopilotChangeAnalysisService +{ + internal sealed class Factory : FactoryBase + { + protected override IRemoteCopilotChangeAnalysisService CreateService(in ServiceConstructionArguments arguments) + => new RemoteCopilotChangeAnalysisService(arguments); + } + + public ValueTask AnalyzeChangeAsync( + Checksum solutionChecksum, + DocumentId documentId, + ImmutableArray edits, + CancellationToken cancellationToken) + { + return RunServiceAsync(solutionChecksum, async solution => + { + var document = await solution.GetRequiredDocumentAsync( + documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); + + var service = solution.Services.GetRequiredService(); + return await service.AnalyzeChangeAsync(document, edits, cancellationToken).ConfigureAwait(false); + }, cancellationToken); + } +} diff --git a/src/Workspaces/Remote/ServiceHubTest/Microsoft.CodeAnalysis.Remote.ServiceHub.UnitTests.csproj b/src/Workspaces/Remote/ServiceHubTest/Microsoft.CodeAnalysis.Remote.ServiceHub.UnitTests.csproj index cf1a0699f5eff..75ebdbb39a818 100644 --- a/src/Workspaces/Remote/ServiceHubTest/Microsoft.CodeAnalysis.Remote.ServiceHub.UnitTests.csproj +++ b/src/Workspaces/Remote/ServiceHubTest/Microsoft.CodeAnalysis.Remote.ServiceHub.UnitTests.csproj @@ -8,6 +8,7 @@ Microsoft.CodeAnalysis.UnitTests + diff --git a/src/Workspaces/Remote/ServiceHubTest/TelemetryLoggerTests.cs b/src/Workspaces/Remote/ServiceHubTest/TelemetryLoggerTests.cs index c44212acc8ada..3e06fc0844d1f 100644 --- a/src/Workspaces/Remote/ServiceHubTest/TelemetryLoggerTests.cs +++ b/src/Workspaces/Remote/ServiceHubTest/TelemetryLoggerTests.cs @@ -8,135 +8,87 @@ using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Telemetry; using Microsoft.VisualStudio.Telemetry; +using Microsoft.CodeAnalysis.UnitTests.Logging; using Roslyn.Test.Utilities; using Xunit; -namespace Microsoft.CodeAnalysis.UnitTests -{ - public class TelemetryLoggerTests - { - private class TestLogger : TelemetryLogger - { - public TestLogger(bool logDelta = false) - { - LogDelta = logDelta; - } - - protected override bool LogDelta { get; } +namespace Microsoft.CodeAnalysis.UnitTests; - public class TestScope - { - public readonly TelemetryEvent EndEvent; - public readonly LogType Type; - - public TestScope(TelemetryEvent endEvent, LogType type) - { - EndEvent = endEvent; - Type = type; - } - } - - public List PostedEvents = []; - public HashSet OpenedScopes = []; +public sealed class TelemetryLoggerTests +{ + private static IEnumerable InspectProperties(TelemetryEvent @event, string? keyToIgnoreValueInspection = null) + => @event.Properties.Select(p => $"{p.Key}={(keyToIgnoreValueInspection == p.Key ? string.Empty : InspectPropertyValue(p.Value))}"); - public override bool IsEnabled(FunctionId functionId) - => true; + private static string InspectPropertyValue(object? value) + => value switch + { + null => "", + TelemetryComplexProperty { Value: IEnumerable items } => $"Complex[{string.Join(",", items.Select(InspectPropertyValue))}]", + _ => value.ToString()! + }; - protected override void PostEvent(TelemetryEvent telemetryEvent) - { - PostedEvents.Add(telemetryEvent); - } + [Theory, CombinatorialData] + internal void IgnoredSeverity(LogLevel level) + { + var logger = new TestTelemetryLogger(); - protected override object Start(string eventName, LogType type) - { - var scope = new TestScope(new TelemetryEvent(eventName), type); - OpenedScopes.Add(scope); - return scope; - } + logger.Log(FunctionId.Debugging_EncSession_EditSession_EmitDeltaErrorId, LogMessage.Create("test", level)); + Assert.Equal((level < LogLevel.Information) ? 0 : 1, logger.PostedEvents.Count); + } - protected override void End(object scope, TelemetryResult result) - { - Assert.True(OpenedScopes.Remove((TestScope)scope)); - } + [Fact] + public void EventWithProperties() + { + var logger = new TestTelemetryLogger(); - protected override TelemetryEvent GetEndEvent(object scope) - => ((TestScope)scope).EndEvent; - } + logger.Log(FunctionId.Debugging_EncSession_EditSession_EmitDeltaErrorId, KeyValueLogMessage.Create(p => + { + p.Add("test1", 1); + p.Add("test2", new PiiValue(2)); + p.Add("test3", new object[] { 3, new PiiValue(4) }); + })); - private static IEnumerable InspectProperties(TelemetryEvent @event, string? keyToIgnoreValueInspection = null) - => @event.Properties.Select(p => $"{p.Key}={(keyToIgnoreValueInspection == p.Key ? string.Empty : InspectPropertyValue(p.Value))}"); + var postedEvent = logger.PostedEvents.Single(); - private static string InspectPropertyValue(object? value) - => value switch - { - null => "", - TelemetryComplexProperty { Value: IEnumerable items } => $"Complex[{string.Join(",", items.Select(InspectPropertyValue))}]", - _ => value.ToString()! - }; + Assert.Equal("vs/ide/vbcs/debugging/encsession/editsession/emitdeltaerrorid", postedEvent.Name); - [Theory, CombinatorialData] - internal void IgnoredSeverity(LogLevel level) + AssertEx.Equal(new[] { - var logger = new TestLogger(); + "vs.ide.vbcs.debugging.encsession.editsession.emitdeltaerrorid.test1=1", + "vs.ide.vbcs.debugging.encsession.editsession.emitdeltaerrorid.test2=PII(2)", + "vs.ide.vbcs.debugging.encsession.editsession.emitdeltaerrorid.test3=Complex[3,PII(4)]", + }, InspectProperties(postedEvent)); + } - logger.Log(FunctionId.Debugging_EncSession_EditSession_EmitDeltaErrorId, LogMessage.Create("test", level)); - Assert.Equal((level < LogLevel.Information) ? 0 : 1, logger.PostedEvents.Count); - } + [Theory, CombinatorialData] + public void LogBlockStartEnd(bool logDelta) + { + var logger = new TestTelemetryLogger(logDelta); - [Fact] - public void EventWithProperties() - { - var logger = new TestLogger(); + logger.LogBlockStart(FunctionId.Debugging_EncSession_EditSession_EmitDeltaErrorId, KeyValueLogMessage.Create(p => p.Add("test", "start"), logLevel: LogLevel.Information), blockId: 1, CancellationToken.None); - logger.Log(FunctionId.Debugging_EncSession_EditSession_EmitDeltaErrorId, KeyValueLogMessage.Create(p => - { - p.Add("test1", 1); - p.Add("test2", new PiiValue(2)); - p.Add("test3", new object[] { 3, new PiiValue(4) }); - })); + var scope = logger.OpenedScopes.Single(); + Assert.Equal(LogType.Trace, scope.Type); - var postedEvent = logger.PostedEvents.Single(); + logger.LogBlockEnd(FunctionId.Debugging_EncSession_EditSession_EmitDeltaErrorId, KeyValueLogMessage.Create(p => p.Add("test", "end")), blockId: 1, delta: 100, CancellationToken.None); - Assert.Equal("vs/ide/vbcs/debugging/encsession/editsession/emitdeltaerrorid", postedEvent.Name); + Assert.Equal("vs/ide/vbcs/debugging/encsession/editsession/emitdeltaerrorid", scope.EndEvent.Name); + if (logDelta) + { + // We don't inspect the property value for "Delta" (time of execution) as that value will vary each time. AssertEx.Equal(new[] { - "vs.ide.vbcs.debugging.encsession.editsession.emitdeltaerrorid.test1=1", - "vs.ide.vbcs.debugging.encsession.editsession.emitdeltaerrorid.test2=PII(2)", - "vs.ide.vbcs.debugging.encsession.editsession.emitdeltaerrorid.test3=Complex[3,PII(4)]", - }, InspectProperties(postedEvent)); + "vs.ide.vbcs.debugging.encsession.editsession.emitdeltaerrorid.test=end", + "vs.ide.vbcs.debugging.encsession.editsession.emitdeltaerrorid.delta=" + }, InspectProperties(scope.EndEvent, keyToIgnoreValueInspection: "vs.ide.vbcs.debugging.encsession.editsession.emitdeltaerrorid.delta")); } - - [Theory, CombinatorialData] - public void LogBlockStartEnd(bool logDelta) + else { - var logger = new TestLogger(logDelta); - - logger.LogBlockStart(FunctionId.Debugging_EncSession_EditSession_EmitDeltaErrorId, KeyValueLogMessage.Create(p => p.Add("test", "start"), logLevel: LogLevel.Information), blockId: 1, CancellationToken.None); - - var scope = logger.OpenedScopes.Single(); - Assert.Equal(LogType.Trace, scope.Type); - - logger.LogBlockEnd(FunctionId.Debugging_EncSession_EditSession_EmitDeltaErrorId, KeyValueLogMessage.Create(p => p.Add("test", "end")), blockId: 1, delta: 100, CancellationToken.None); - - Assert.Equal("vs/ide/vbcs/debugging/encsession/editsession/emitdeltaerrorid", scope.EndEvent.Name); - - if (logDelta) - { - // We don't inspect the property value for "Delta" (time of execution) as that value will vary each time. - AssertEx.Equal(new[] - { - "vs.ide.vbcs.debugging.encsession.editsession.emitdeltaerrorid.test=end", - "vs.ide.vbcs.debugging.encsession.editsession.emitdeltaerrorid.delta=" - }, InspectProperties(scope.EndEvent, keyToIgnoreValueInspection: "vs.ide.vbcs.debugging.encsession.editsession.emitdeltaerrorid.delta")); - } - else + AssertEx.Equal(new[] { - AssertEx.Equal(new[] - { - "vs.ide.vbcs.debugging.encsession.editsession.emitdeltaerrorid.test=end" - }, InspectProperties(scope.EndEvent)); - } + "vs.ide.vbcs.debugging.encsession.editsession.emitdeltaerrorid.test=end" + }, InspectProperties(scope.EndEvent)); } } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/IntervalTreeHelpers.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/IntervalTreeHelpers.cs index dcc6d66424f90..b6ede2a21c306 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/IntervalTreeHelpers.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/IntervalTreeHelpers.cs @@ -19,12 +19,12 @@ namespace Microsoft.CodeAnalysis.Shared.Collections; internal interface IIntervalTreeWitness where TIntervalTree : IIntervalTree { - public bool TryGetRoot(TIntervalTree tree, [NotNullWhen(true)] out TNode? root); - public bool TryGetLeftNode(TIntervalTree tree, TNode node, [NotNullWhen(true)] out TNode? leftNode); - public bool TryGetRightNode(TIntervalTree tree, TNode node, [NotNullWhen(true)] out TNode? rightNode); + bool TryGetRoot(TIntervalTree tree, [NotNullWhen(true)] out TNode? root); + bool TryGetLeftNode(TIntervalTree tree, TNode node, [NotNullWhen(true)] out TNode? leftNode); + bool TryGetRightNode(TIntervalTree tree, TNode node, [NotNullWhen(true)] out TNode? rightNode); - public T GetValue(TIntervalTree tree, TNode node); - public TNode GetMaxEndNode(TIntervalTree tree, TNode node); + T GetValue(TIntervalTree tree, TNode node); + TNode GetMaxEndNode(TIntervalTree tree, TNode node); } /// diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/SimpleMutableIntervalTree`2.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/SimpleMutableIntervalTree`2.cs index fe1fd19c494f8..c42a6b2646820 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/SimpleMutableIntervalTree`2.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/SimpleMutableIntervalTree`2.cs @@ -12,16 +12,14 @@ internal class SimpleMutableIntervalTree : MutableIntervalTree { private readonly TIntrospector _introspector; - public SimpleMutableIntervalTree(in TIntrospector introspector, IEnumerable? values) + public SimpleMutableIntervalTree(in TIntrospector introspector, IEnumerable? values = null) { _introspector = introspector; if (values != null) { foreach (var value in values) - { root = Insert(root, new Node(value), introspector); - } } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/TextSpanMutableIntervalTree.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/TextSpanMutableIntervalTree.cs index 0c8647b4e755f..4907f3af8e306 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/TextSpanMutableIntervalTree.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Collections/TextSpanMutableIntervalTree.cs @@ -26,4 +26,7 @@ public TextSpanMutableIntervalTree(params TextSpan[]? values) : this((IEnumerabl public bool HasIntervalThatIntersectsWith(TextSpan span) => this.HasIntervalThatIntersectsWith(span.Start, span.Length); + + public bool HasIntervalThatOverlapsWith(TextSpan span) + => this.HasIntervalThatOverlapsWith(span.Start, span.Length); } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs index 1430b8477c9dc..b0264e8e72673 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/FunctionId.cs @@ -645,6 +645,8 @@ internal enum FunctionId Copilot_Implement_NotImplementedException_Failed = 831, Copilot_Implement_NotImplementedException_Completed = 832, + Copilot_AnalyzeChange = 840, + Copilot_Rename = 851, VSCode_LanguageServer_Started = 860, diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/Logger.LogBlock.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/Logger.LogBlock.cs index 68d2da639566f..53d457e1b6aa6 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/Logger.LogBlock.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/Logger.LogBlock.cs @@ -16,12 +16,12 @@ internal static partial class Logger // Use an object pool since we may be logging up to 1-10k events/second private static readonly ObjectPool s_pool = new(() => new RoslynLogBlock(s_pool!), Math.Min(Environment.ProcessorCount * 8, 256)); - private static IDisposable CreateLogBlock(FunctionId functionId, LogMessage message, int blockId, CancellationToken cancellationToken) + public static IDisposable CreateLogBlock(ILogger logger, FunctionId functionId, LogMessage message, int blockId, CancellationToken cancellationToken) { - Contract.ThrowIfNull(s_currentLogger); + Contract.ThrowIfNull(logger); var block = s_pool.Allocate(); - block.Construct(s_currentLogger, functionId, message, blockId, cancellationToken); + block.Construct(logger, functionId, message, blockId, cancellationToken); return block; } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/Logger.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/Logger.cs index e90f69e761673..d1619ce5f6eba 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/Logger.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Log/Logger.cs @@ -163,8 +163,8 @@ public static IDisposable LogBlock(FunctionId functionId, CancellationToken toke /// simplest way to log a start and end pair with a simple context message which should be very cheap to create /// public static IDisposable LogBlock(FunctionId functionId, string? message, CancellationToken token, LogLevel logLevel = LogLevel.Trace) - => TryGetActiveLogger(functionId, out _) - ? CreateLogBlock(functionId, LogMessage.Create(message ?? "", logLevel), GetNextUniqueBlockId(), token) + => TryGetActiveLogger(functionId, out var logger) + ? CreateLogBlock(logger, functionId, LogMessage.Create(message ?? "", logLevel), GetNextUniqueBlockId(), token) : EmptyLogBlock.Instance; /// @@ -172,8 +172,8 @@ public static IDisposable LogBlock(FunctionId functionId, string? message, Cance /// the messageGetter should be cheap to create. in another word, it shouldn't capture any locals /// public static IDisposable LogBlock(FunctionId functionId, Func messageGetter, CancellationToken token, LogLevel logLevel = LogLevel.Trace) - => TryGetActiveLogger(functionId, out _) - ? CreateLogBlock(functionId, LogMessage.Create(messageGetter, logLevel), GetNextUniqueBlockId(), token) + => TryGetActiveLogger(functionId, out var logger) + ? CreateLogBlock(logger, functionId, LogMessage.Create(messageGetter, logLevel), GetNextUniqueBlockId(), token) : EmptyLogBlock.Instance; /// @@ -181,8 +181,8 @@ public static IDisposable LogBlock(FunctionId functionId, Func messageGe /// given arguments will be passed to the messageGetter so that it can create the context message without requiring lifted locals /// public static IDisposable LogBlock(FunctionId functionId, Func messageGetter, TArg arg, CancellationToken token, LogLevel logLevel = LogLevel.Trace) - => TryGetActiveLogger(functionId, out _) - ? CreateLogBlock(functionId, LogMessage.Create(messageGetter, arg, logLevel), GetNextUniqueBlockId(), token) + => TryGetActiveLogger(functionId, out var logger) + ? CreateLogBlock(logger, functionId, LogMessage.Create(messageGetter, arg, logLevel), GetNextUniqueBlockId(), token) : EmptyLogBlock.Instance; /// @@ -190,8 +190,8 @@ public static IDisposable LogBlock(FunctionId functionId, Func public static IDisposable LogBlock(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, CancellationToken token, LogLevel logLevel = LogLevel.Trace) - => TryGetActiveLogger(functionId, out _) - ? CreateLogBlock(functionId, LogMessage.Create(messageGetter, arg0, arg1, logLevel), GetNextUniqueBlockId(), token) + => TryGetActiveLogger(functionId, out var logger) + ? CreateLogBlock(logger, functionId, LogMessage.Create(messageGetter, arg0, arg1, logLevel), GetNextUniqueBlockId(), token) : EmptyLogBlock.Instance; /// @@ -199,8 +199,8 @@ public static IDisposable LogBlock(FunctionId functionId, Func public static IDisposable LogBlock(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, TArg2 arg2, CancellationToken token, LogLevel logLevel = LogLevel.Trace) - => TryGetActiveLogger(functionId, out _) - ? CreateLogBlock(functionId, LogMessage.Create(messageGetter, arg0, arg1, arg2, logLevel), GetNextUniqueBlockId(), token) + => TryGetActiveLogger(functionId, out var logger) + ? CreateLogBlock(logger, functionId, LogMessage.Create(messageGetter, arg0, arg1, arg2, logLevel), GetNextUniqueBlockId(), token) : EmptyLogBlock.Instance; /// @@ -208,15 +208,15 @@ public static IDisposable LogBlock(FunctionId functionId, F /// given arguments will be passed to the messageGetter so that it can create the context message without requiring lifted locals /// public static IDisposable LogBlock(FunctionId functionId, Func messageGetter, TArg0 arg0, TArg1 arg1, TArg2 arg2, TArg3 arg3, CancellationToken token, LogLevel logLevel = LogLevel.Trace) - => TryGetActiveLogger(functionId, out _) - ? CreateLogBlock(functionId, LogMessage.Create(messageGetter, arg0, arg1, arg2, arg3, logLevel), GetNextUniqueBlockId(), token) + => TryGetActiveLogger(functionId, out var logger) + ? CreateLogBlock(logger, functionId, LogMessage.Create(messageGetter, arg0, arg1, arg2, arg3, logLevel), GetNextUniqueBlockId(), token) : EmptyLogBlock.Instance; /// /// log a start and end pair with a context message. /// public static IDisposable LogBlock(FunctionId functionId, LogMessage logMessage, CancellationToken token) - => TryGetActiveLogger(functionId, out _) - ? CreateLogBlock(functionId, logMessage, GetNextUniqueBlockId(), token) + => TryGetActiveLogger(functionId, out var logger) + ? CreateLogBlock(logger, functionId, logMessage, GetNextUniqueBlockId(), token) : EmptyLogBlock.Instance; }