diff --git a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs index 990d405cd3d4d..efeaa71523050 100644 --- a/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/IDiagnosticAnalyzerService.cs @@ -22,9 +22,7 @@ internal interface IDiagnosticAnalyzerService : IWorkspaceService /// void RequestDiagnosticRefresh(); - /// - /// Force analyzes the given project by running all applicable analyzers on the project. - /// + /// Task> ForceAnalyzeProjectAsync(Project project, CancellationToken cancellationToken); /// diff --git a/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService.cs index 69624bb5d1e44..221d23597afde 100644 --- a/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService.cs @@ -58,6 +58,7 @@ internal sealed partial class DiagnosticAnalyzerService : IDiagnosticAnalyzerSer private readonly IDiagnosticsRefresher _diagnosticsRefresher; private readonly DiagnosticIncrementalAnalyzer _incrementalAnalyzer; private readonly DiagnosticAnalyzerInfoCache _analyzerInfoCache; + private readonly StateManager _stateManager; public DiagnosticAnalyzerService( IGlobalOptionService globalOptions, @@ -71,6 +72,7 @@ public DiagnosticAnalyzerService( GlobalOptions = globalOptions; _diagnosticsRefresher = diagnosticsRefresher; _incrementalAnalyzer = new DiagnosticIncrementalAnalyzer(this, _analyzerInfoCache, this.GlobalOptions); + _stateManager = new StateManager(_analyzerInfoCache); globalOptions.AddOptionChangedHandler(this, (_, _, e) => { @@ -122,8 +124,22 @@ public async Task> GetDiagnosticsForSpanAsync( document, range, shouldIncludeDiagnostic, priorityProvider, diagnosticKinds, cancellationToken).ConfigureAwait(false); } - public Task> ForceAnalyzeProjectAsync(Project project, CancellationToken cancellationToken) - => _incrementalAnalyzer.ForceAnalyzeProjectAsync(project, cancellationToken); + public async Task> ForceAnalyzeProjectAsync(Project project, CancellationToken cancellationToken) + { + var client = await RemoteHostClient.TryGetClientAsync(project, cancellationToken).ConfigureAwait(false); + if (client is not null) + { + var result = await client.TryInvokeAsync>( + project, + (service, solution, cancellationToken) => service.ForceAnalyzeProjectAsync(solution, project.Id, cancellationToken), + cancellationToken).ConfigureAwait(false); + + return result.HasValue ? result.Value : []; + } + + // No OOP connection. Compute in proc. + return await _incrementalAnalyzer.ForceAnalyzeProjectAsync(project, cancellationToken).ConfigureAwait(false); + } public Task> GetDiagnosticsForIdsAsync( Project project, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken) diff --git a/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.HostAnalyzerInfo.cs b/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.HostAnalyzerInfo.cs index beb57d39e625b..2c7481fe353d9 100644 --- a/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.HostAnalyzerInfo.cs +++ b/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.HostAnalyzerInfo.cs @@ -2,95 +2,90 @@ // 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 Microsoft.CodeAnalysis.PooledObjects; -using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Diagnostics; internal sealed partial class DiagnosticAnalyzerService { - private sealed partial class DiagnosticIncrementalAnalyzer + private sealed partial class StateManager { - private sealed partial class StateManager + private HostAnalyzerInfo GetOrCreateHostAnalyzerInfo( + SolutionState solution, ProjectState project, ProjectAnalyzerInfo projectAnalyzerInfo) { - private HostAnalyzerInfo GetOrCreateHostAnalyzerInfo( - SolutionState solution, ProjectState project, ProjectAnalyzerInfo projectAnalyzerInfo) + var key = new HostAnalyzerInfoKey(project.Language, project.HasSdkCodeStyleAnalyzers, solution.Analyzers.HostAnalyzerReferences); + // Some Host Analyzers may need to be treated as Project Analyzers so that they do not have access to the + // Host fallback options. These ids will be used when building up the Host and Project analyzer collections. + var referenceIdsToRedirect = GetReferenceIdsToRedirectAsProjectAnalyzers(solution, project); + var hostAnalyzerInfo = ImmutableInterlocked.GetOrAdd(ref _hostAnalyzerStateMap, key, CreateLanguageSpecificAnalyzerMap, (solution.Analyzers, referenceIdsToRedirect)); + return hostAnalyzerInfo.WithExcludedAnalyzers(projectAnalyzerInfo.SkippedAnalyzersInfo.SkippedAnalyzers); + + static HostAnalyzerInfo CreateLanguageSpecificAnalyzerMap(HostAnalyzerInfoKey arg, (HostDiagnosticAnalyzers HostAnalyzers, ImmutableHashSet ReferenceIdsToRedirect) state) { - var key = new HostAnalyzerInfoKey(project.Language, project.HasSdkCodeStyleAnalyzers, solution.Analyzers.HostAnalyzerReferences); - // Some Host Analyzers may need to be treated as Project Analyzers so that they do not have access to the - // Host fallback options. These ids will be used when building up the Host and Project analyzer collections. - var referenceIdsToRedirect = GetReferenceIdsToRedirectAsProjectAnalyzers(solution, project); - var hostAnalyzerInfo = ImmutableInterlocked.GetOrAdd(ref _hostAnalyzerStateMap, key, CreateLanguageSpecificAnalyzerMap, (solution.Analyzers, referenceIdsToRedirect)); - return hostAnalyzerInfo.WithExcludedAnalyzers(projectAnalyzerInfo.SkippedAnalyzersInfo.SkippedAnalyzers); - - static HostAnalyzerInfo CreateLanguageSpecificAnalyzerMap(HostAnalyzerInfoKey arg, (HostDiagnosticAnalyzers HostAnalyzers, ImmutableHashSet ReferenceIdsToRedirect) state) - { - var language = arg.Language; - var analyzersPerReference = state.HostAnalyzers.GetOrCreateHostDiagnosticAnalyzersPerReference(language); + var language = arg.Language; + var analyzersPerReference = state.HostAnalyzers.GetOrCreateHostDiagnosticAnalyzersPerReference(language); + + var (hostAnalyzerCollection, projectAnalyzerCollection) = GetAnalyzerCollections(analyzersPerReference, state.ReferenceIdsToRedirect); + var (hostAnalyzers, allAnalyzers) = PartitionAnalyzers(projectAnalyzerCollection, hostAnalyzerCollection, includeWorkspacePlaceholderAnalyzers: true); - var (hostAnalyzerCollection, projectAnalyzerCollection) = GetAnalyzerCollections(analyzersPerReference, state.ReferenceIdsToRedirect); - var (hostAnalyzers, allAnalyzers) = PartitionAnalyzers(projectAnalyzerCollection, hostAnalyzerCollection, includeWorkspacePlaceholderAnalyzers: true); + return new HostAnalyzerInfo(hostAnalyzers, allAnalyzers); + } - return new HostAnalyzerInfo(hostAnalyzers, allAnalyzers); + static (IEnumerable> HostAnalyzerCollection, IEnumerable> ProjectAnalyzerCollection) GetAnalyzerCollections( + ImmutableDictionary> analyzersPerReference, + ImmutableHashSet referenceIdsToRedirectAsProjectAnalyzers) + { + if (referenceIdsToRedirectAsProjectAnalyzers.IsEmpty) + { + return (analyzersPerReference.Values, []); } - static (IEnumerable> HostAnalyzerCollection, IEnumerable> ProjectAnalyzerCollection) GetAnalyzerCollections( - ImmutableDictionary> analyzersPerReference, - ImmutableHashSet referenceIdsToRedirectAsProjectAnalyzers) + var hostAnalyzerCollection = ArrayBuilder>.GetInstance(); + var projectAnalyzerCollection = ArrayBuilder>.GetInstance(); + + foreach (var (referenceId, analyzers) in analyzersPerReference) { - if (referenceIdsToRedirectAsProjectAnalyzers.IsEmpty) + if (referenceIdsToRedirectAsProjectAnalyzers.Contains(referenceId)) { - return (analyzersPerReference.Values, []); + projectAnalyzerCollection.Add(analyzers); } - - var hostAnalyzerCollection = ArrayBuilder>.GetInstance(); - var projectAnalyzerCollection = ArrayBuilder>.GetInstance(); - - foreach (var (referenceId, analyzers) in analyzersPerReference) + else { - if (referenceIdsToRedirectAsProjectAnalyzers.Contains(referenceId)) - { - projectAnalyzerCollection.Add(analyzers); - } - else - { - hostAnalyzerCollection.Add(analyzers); - } + hostAnalyzerCollection.Add(analyzers); } - - return (hostAnalyzerCollection.ToImmutableAndFree(), projectAnalyzerCollection.ToImmutableAndFree()); } + + return (hostAnalyzerCollection.ToImmutableAndFree(), projectAnalyzerCollection.ToImmutableAndFree()); } + } - private static ImmutableHashSet GetReferenceIdsToRedirectAsProjectAnalyzers( - SolutionState solution, ProjectState project) + private static ImmutableHashSet GetReferenceIdsToRedirectAsProjectAnalyzers( + SolutionState solution, ProjectState project) + { + if (project.HasSdkCodeStyleAnalyzers) { - if (project.HasSdkCodeStyleAnalyzers) - { - // When a project uses CodeStyle analyzers added by the SDK, we remove them in favor of the - // Features analyzers. We need to then treat the Features analyzers as Project analyzers so - // they do not get access to the Host fallback options. - return GetFeaturesAnalyzerReferenceIds(solution.Analyzers); - } - - return []; + // When a project uses CodeStyle analyzers added by the SDK, we remove them in favor of the + // Features analyzers. We need to then treat the Features analyzers as Project analyzers so + // they do not get access to the Host fallback options. + return GetFeaturesAnalyzerReferenceIds(solution.Analyzers); + } - static ImmutableHashSet GetFeaturesAnalyzerReferenceIds(HostDiagnosticAnalyzers hostAnalyzers) - { - var builder = ImmutableHashSet.CreateBuilder(); + return []; - foreach (var analyzerReference in hostAnalyzers.HostAnalyzerReferences) - { - if (analyzerReference.IsFeaturesAnalyzer()) - builder.Add(analyzerReference.Id); - } + static ImmutableHashSet GetFeaturesAnalyzerReferenceIds(HostDiagnosticAnalyzers hostAnalyzers) + { + var builder = ImmutableHashSet.CreateBuilder(); - return builder.ToImmutable(); + foreach (var analyzerReference in hostAnalyzers.HostAnalyzerReferences) + { + if (analyzerReference.IsFeaturesAnalyzer()) + builder.Add(analyzerReference.Id); } + + return builder.ToImmutable(); } } } diff --git a/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs b/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs index d1214f5e8b2d3..2bb757a8e4f06 100644 --- a/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs +++ b/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs @@ -13,96 +13,93 @@ namespace Microsoft.CodeAnalysis.Diagnostics; internal sealed partial class DiagnosticAnalyzerService { - private sealed partial class DiagnosticIncrementalAnalyzer + private sealed partial class StateManager { - private sealed partial class StateManager + private readonly struct ProjectAnalyzerInfo { - private readonly struct ProjectAnalyzerInfo - { - public static readonly ProjectAnalyzerInfo Default = new( - analyzerReferences: [], - analyzers: [], - SkippedHostAnalyzersInfo.Empty); + public static readonly ProjectAnalyzerInfo Default = new( + analyzerReferences: [], + analyzers: [], + SkippedHostAnalyzersInfo.Empty); - public readonly IReadOnlyList AnalyzerReferences; + public readonly IReadOnlyList AnalyzerReferences; - public readonly ImmutableHashSet Analyzers; + public readonly ImmutableHashSet Analyzers; - public readonly SkippedHostAnalyzersInfo SkippedAnalyzersInfo; + public readonly SkippedHostAnalyzersInfo SkippedAnalyzersInfo; - internal ProjectAnalyzerInfo( - IReadOnlyList analyzerReferences, - ImmutableHashSet analyzers, - SkippedHostAnalyzersInfo skippedAnalyzersInfo) - { - AnalyzerReferences = analyzerReferences; - Analyzers = analyzers; - SkippedAnalyzersInfo = skippedAnalyzersInfo; - } + internal ProjectAnalyzerInfo( + IReadOnlyList analyzerReferences, + ImmutableHashSet analyzers, + SkippedHostAnalyzersInfo skippedAnalyzersInfo) + { + AnalyzerReferences = analyzerReferences; + Analyzers = analyzers; + SkippedAnalyzersInfo = skippedAnalyzersInfo; } + } - private ProjectAnalyzerInfo? TryGetProjectAnalyzerInfo(ProjectState project) + private ProjectAnalyzerInfo? TryGetProjectAnalyzerInfo(ProjectState project) + { + // check if the analyzer references have changed since the last time we updated the map: + // No need to use _projectAnalyzerStateMapGuard during reads of _projectAnalyzerStateMap + if (_projectAnalyzerStateMap.TryGetValue(project.Id, out var entry) && + entry.AnalyzerReferences.SequenceEqual(project.AnalyzerReferences)) { - // check if the analyzer references have changed since the last time we updated the map: - // No need to use _projectAnalyzerStateMapGuard during reads of _projectAnalyzerStateMap - if (_projectAnalyzerStateMap.TryGetValue(project.Id, out var entry) && - entry.AnalyzerReferences.SequenceEqual(project.AnalyzerReferences)) - { - return entry; - } - - return null; + return entry; } - private async Task GetOrCreateProjectAnalyzerInfoAsync(SolutionState solution, ProjectState project, CancellationToken cancellationToken) - => TryGetProjectAnalyzerInfo(project) ?? await UpdateProjectAnalyzerInfoAsync(solution, project, cancellationToken).ConfigureAwait(false); + return null; + } + + private async Task GetOrCreateProjectAnalyzerInfoAsync(SolutionState solution, ProjectState project, CancellationToken cancellationToken) + => TryGetProjectAnalyzerInfo(project) ?? await UpdateProjectAnalyzerInfoAsync(solution, project, cancellationToken).ConfigureAwait(false); - private ProjectAnalyzerInfo CreateProjectAnalyzerInfo(SolutionState solution, ProjectState project) + private ProjectAnalyzerInfo CreateProjectAnalyzerInfo(SolutionState solution, ProjectState project) + { + if (project.AnalyzerReferences.Count == 0) { - if (project.AnalyzerReferences.Count == 0) - { - return ProjectAnalyzerInfo.Default; - } + return ProjectAnalyzerInfo.Default; + } - var solutionAnalyzers = solution.Analyzers; - var analyzersPerReference = solutionAnalyzers.CreateProjectDiagnosticAnalyzersPerReference(project); - if (analyzersPerReference.Count == 0) - { - return ProjectAnalyzerInfo.Default; - } + var solutionAnalyzers = solution.Analyzers; + var analyzersPerReference = solutionAnalyzers.CreateProjectDiagnosticAnalyzersPerReference(project); + if (analyzersPerReference.Count == 0) + { + return ProjectAnalyzerInfo.Default; + } - var (newHostAnalyzers, newAllAnalyzers) = PartitionAnalyzers( - analyzersPerReference.Values, hostAnalyzerCollection: [], includeWorkspacePlaceholderAnalyzers: false); + var (newHostAnalyzers, newAllAnalyzers) = PartitionAnalyzers( + analyzersPerReference.Values, hostAnalyzerCollection: [], includeWorkspacePlaceholderAnalyzers: false); - // We passed an empty array for 'hostAnalyzeCollection' above, and we specifically asked to not include - // workspace placeholder analyzers. So we should never get host analyzers back here. - Contract.ThrowIfTrue(newHostAnalyzers.Count > 0); + // We passed an empty array for 'hostAnalyzeCollection' above, and we specifically asked to not include + // workspace placeholder analyzers. So we should never get host analyzers back here. + Contract.ThrowIfTrue(newHostAnalyzers.Count > 0); - var skippedAnalyzersInfo = solutionAnalyzers.GetSkippedAnalyzersInfo(project, _analyzerInfoCache); - return new ProjectAnalyzerInfo(project.AnalyzerReferences, newAllAnalyzers, skippedAnalyzersInfo); - } + var skippedAnalyzersInfo = solutionAnalyzers.GetSkippedAnalyzersInfo(project, _analyzerInfoCache); + return new ProjectAnalyzerInfo(project.AnalyzerReferences, newAllAnalyzers, skippedAnalyzersInfo); + } - /// - /// Updates the map to the given project snapshot. - /// - private async Task UpdateProjectAnalyzerInfoAsync( - SolutionState solution, ProjectState project, CancellationToken cancellationToken) + /// + /// Updates the map to the given project snapshot. + /// + private async Task UpdateProjectAnalyzerInfoAsync( + SolutionState solution, ProjectState project, CancellationToken cancellationToken) + { + // This code is called concurrently for a project, so the guard prevents duplicated effort calculating StateSets. + using (await _projectAnalyzerStateMapGuard.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) { - // This code is called concurrently for a project, so the guard prevents duplicated effort calculating StateSets. - using (await _projectAnalyzerStateMapGuard.DisposableWaitAsync(cancellationToken).ConfigureAwait(false)) - { - var projectAnalyzerInfo = TryGetProjectAnalyzerInfo(project); + var projectAnalyzerInfo = TryGetProjectAnalyzerInfo(project); - if (projectAnalyzerInfo == null) - { - projectAnalyzerInfo = CreateProjectAnalyzerInfo(solution, project); - - // update cache. - _projectAnalyzerStateMap = _projectAnalyzerStateMap.SetItem(project.Id, projectAnalyzerInfo.Value); - } + if (projectAnalyzerInfo == null) + { + projectAnalyzerInfo = CreateProjectAnalyzerInfo(solution, project); - return projectAnalyzerInfo.Value; + // update cache. + _projectAnalyzerStateMap = _projectAnalyzerStateMap.SetItem(project.Id, projectAnalyzerInfo.Value); } + + return projectAnalyzerInfo.Value; } } } diff --git a/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs b/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs index d669779e61ac7..d94a2822b1a49 100644 --- a/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs +++ b/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.cs @@ -13,91 +13,88 @@ namespace Microsoft.CodeAnalysis.Diagnostics; internal sealed partial class DiagnosticAnalyzerService { - private sealed partial class DiagnosticIncrementalAnalyzer + /// + /// This is in charge of anything related to + /// + private sealed partial class StateManager(DiagnosticAnalyzerInfoCache analyzerInfoCache) { + private readonly DiagnosticAnalyzerInfoCache _analyzerInfoCache = analyzerInfoCache; + /// - /// This is in charge of anything related to + /// Analyzers supplied by the host (IDE). These are built-in to the IDE, the compiler, or from an installed IDE extension (VSIX). + /// Maps language name to the analyzers and their state. /// - private sealed partial class StateManager(DiagnosticAnalyzerInfoCache analyzerInfoCache) - { - private readonly DiagnosticAnalyzerInfoCache _analyzerInfoCache = analyzerInfoCache; + private ImmutableDictionary _hostAnalyzerStateMap = ImmutableDictionary.Empty; - /// - /// Analyzers supplied by the host (IDE). These are built-in to the IDE, the compiler, or from an installed IDE extension (VSIX). - /// Maps language name to the analyzers and their state. - /// - private ImmutableDictionary _hostAnalyzerStateMap = ImmutableDictionary.Empty; + /// + /// Analyzers referenced by the project via a PackageReference. Updates are protected by _projectAnalyzerStateMapGuard. + /// ImmutableDictionary used to present a safe, non-immutable view to users. + /// + private ImmutableDictionary _projectAnalyzerStateMap = ImmutableDictionary.Empty; - /// - /// Analyzers referenced by the project via a PackageReference. Updates are protected by _projectAnalyzerStateMapGuard. - /// ImmutableDictionary used to present a safe, non-immutable view to users. - /// - private ImmutableDictionary _projectAnalyzerStateMap = ImmutableDictionary.Empty; + /// + /// Guard around updating _projectAnalyzerStateMap. This is used in UpdateProjectStateSets to avoid + /// duplicated calculations for a project during contentious calls. + /// + private readonly SemaphoreSlim _projectAnalyzerStateMapGuard = new(initialCount: 1); - /// - /// Guard around updating _projectAnalyzerStateMap. This is used in UpdateProjectStateSets to avoid - /// duplicated calculations for a project during contentious calls. - /// - private readonly SemaphoreSlim _projectAnalyzerStateMapGuard = new(initialCount: 1); + /// + /// Return s for the given . + /// + public async Task> GetOrCreateAnalyzersAsync( + SolutionState solution, ProjectState project, CancellationToken cancellationToken) + { + var hostAnalyzerInfo = await GetOrCreateHostAnalyzerInfoAsync(solution, project, cancellationToken).ConfigureAwait(false); + var projectAnalyzerInfo = await GetOrCreateProjectAnalyzerInfoAsync(solution, project, cancellationToken).ConfigureAwait(false); + return hostAnalyzerInfo.OrderedAllAnalyzers.AddRange(projectAnalyzerInfo.Analyzers); + } - /// - /// Return s for the given . - /// - public async Task> GetOrCreateAnalyzersAsync( - SolutionState solution, ProjectState project, CancellationToken cancellationToken) - { - var hostAnalyzerInfo = await GetOrCreateHostAnalyzerInfoAsync(solution, project, cancellationToken).ConfigureAwait(false); - var projectAnalyzerInfo = await GetOrCreateProjectAnalyzerInfoAsync(solution, project, cancellationToken).ConfigureAwait(false); - return hostAnalyzerInfo.OrderedAllAnalyzers.AddRange(projectAnalyzerInfo.Analyzers); - } + public async Task GetOrCreateHostAnalyzerInfoAsync( + SolutionState solution, ProjectState project, CancellationToken cancellationToken) + { + var projectAnalyzerInfo = await GetOrCreateProjectAnalyzerInfoAsync(solution, project, cancellationToken).ConfigureAwait(false); + return GetOrCreateHostAnalyzerInfo(solution, project, projectAnalyzerInfo); + } + + private static (ImmutableHashSet hostAnalyzers, ImmutableHashSet allAnalyzers) PartitionAnalyzers( + IEnumerable> projectAnalyzerCollection, + IEnumerable> hostAnalyzerCollection, + bool includeWorkspacePlaceholderAnalyzers) + { + using var _1 = PooledHashSet.GetInstance(out var hostAnalyzers); + using var _2 = PooledHashSet.GetInstance(out var allAnalyzers); - public async Task GetOrCreateHostAnalyzerInfoAsync( - SolutionState solution, ProjectState project, CancellationToken cancellationToken) + if (includeWorkspacePlaceholderAnalyzers) { - var projectAnalyzerInfo = await GetOrCreateProjectAnalyzerInfoAsync(solution, project, cancellationToken).ConfigureAwait(false); - return GetOrCreateHostAnalyzerInfo(solution, project, projectAnalyzerInfo); + hostAnalyzers.Add(FileContentLoadAnalyzer.Instance); + hostAnalyzers.Add(GeneratorDiagnosticsPlaceholderAnalyzer.Instance); + allAnalyzers.Add(FileContentLoadAnalyzer.Instance); + allAnalyzers.Add(GeneratorDiagnosticsPlaceholderAnalyzer.Instance); } - private static (ImmutableHashSet hostAnalyzers, ImmutableHashSet allAnalyzers) PartitionAnalyzers( - IEnumerable> projectAnalyzerCollection, - IEnumerable> hostAnalyzerCollection, - bool includeWorkspacePlaceholderAnalyzers) + foreach (var analyzers in projectAnalyzerCollection) { - using var _1 = PooledHashSet.GetInstance(out var hostAnalyzers); - using var _2 = PooledHashSet.GetInstance(out var allAnalyzers); - - if (includeWorkspacePlaceholderAnalyzers) - { - hostAnalyzers.Add(FileContentLoadAnalyzer.Instance); - hostAnalyzers.Add(GeneratorDiagnosticsPlaceholderAnalyzer.Instance); - allAnalyzers.Add(FileContentLoadAnalyzer.Instance); - allAnalyzers.Add(GeneratorDiagnosticsPlaceholderAnalyzer.Instance); - } - - foreach (var analyzers in projectAnalyzerCollection) + foreach (var analyzer in analyzers) { - foreach (var analyzer in analyzers) - { - Debug.Assert(analyzer != FileContentLoadAnalyzer.Instance && analyzer != GeneratorDiagnosticsPlaceholderAnalyzer.Instance); - allAnalyzers.Add(analyzer); - } + Debug.Assert(analyzer != FileContentLoadAnalyzer.Instance && analyzer != GeneratorDiagnosticsPlaceholderAnalyzer.Instance); + allAnalyzers.Add(analyzer); } + } - foreach (var analyzers in hostAnalyzerCollection) + foreach (var analyzers in hostAnalyzerCollection) + { + foreach (var analyzer in analyzers) { - foreach (var analyzer in analyzers) - { - Debug.Assert(analyzer != FileContentLoadAnalyzer.Instance && analyzer != GeneratorDiagnosticsPlaceholderAnalyzer.Instance); - allAnalyzers.Add(analyzer); - hostAnalyzers.Add(analyzer); - } + Debug.Assert(analyzer != FileContentLoadAnalyzer.Instance && analyzer != GeneratorDiagnosticsPlaceholderAnalyzer.Instance); + allAnalyzers.Add(analyzer); + hostAnalyzers.Add(analyzer); } - - return (hostAnalyzers.ToImmutableHashSet(), allAnalyzers.ToImmutableHashSet()); } - private readonly record struct HostAnalyzerInfoKey( - string Language, bool HasSdkCodeStyleAnalyzers, IReadOnlyList AnalyzerReferences); + return (hostAnalyzers.ToImmutableHashSet(), allAnalyzers.ToImmutableHashSet()); } + + private readonly record struct HostAnalyzerInfoKey( + string Language, bool HasSdkCodeStyleAnalyzers, IReadOnlyList AnalyzerReferences); } } diff --git a/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.cs index c494e0311a478..d2e2922371c09 100644 --- a/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.cs @@ -21,7 +21,6 @@ internal sealed partial class DiagnosticAnalyzerService private sealed partial class DiagnosticIncrementalAnalyzer { private readonly DiagnosticAnalyzerTelemetry _telemetry = new(); - private readonly StateManager _stateManager; private readonly InProcOrRemoteHostAnalyzerRunner _diagnosticAnalyzerRunner; private readonly IncrementalMemberEditAnalyzer _incrementalMemberEditAnalyzer = new(); @@ -37,16 +36,16 @@ public DiagnosticIncrementalAnalyzer( AnalyzerService = analyzerService; GlobalOptions = globalOptionService; - _stateManager = new StateManager(analyzerInfoCache); - _diagnosticAnalyzerRunner = new InProcOrRemoteHostAnalyzerRunner(analyzerInfoCache, analyzerService.Listener); } + private StateManager StateManager => this.AnalyzerService._stateManager; + internal IGlobalOptionService GlobalOptions { get; } internal DiagnosticAnalyzerInfoCache DiagnosticAnalyzerInfoCache => _diagnosticAnalyzerRunner.AnalyzerInfoCache; public Task> GetAnalyzersForTestingPurposesOnlyAsync(Project project, CancellationToken cancellationToken) - => _stateManager.GetOrCreateAnalyzersAsync(project.Solution.SolutionState, project.State, cancellationToken); + => StateManager.GetOrCreateAnalyzersAsync(project.Solution.SolutionState, project.State, cancellationToken); private static string GetProjectLogMessage(Project project, ImmutableArray analyzers) => $"project: ({project.Id}), ({string.Join(Environment.NewLine, analyzers.Select(a => a.ToString()))})"; diff --git a/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index 3109ad434bd13..f5537daebc8bb 100644 --- a/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -61,9 +61,9 @@ private async Task> ProduceProjectDiagnosticsAsyn using var _ = ArrayBuilder.GetInstance(out var builder); var solution = project.Solution; - var analyzersForProject = await _stateManager.GetOrCreateAnalyzersAsync( + var analyzersForProject = await StateManager.GetOrCreateAnalyzersAsync( solution.SolutionState, project.State, cancellationToken).ConfigureAwait(false); - var hostAnalyzerInfo = await _stateManager.GetOrCreateHostAnalyzerInfoAsync( + var hostAnalyzerInfo = await StateManager.GetOrCreateHostAnalyzerInfoAsync( solution.SolutionState, project.State, cancellationToken).ConfigureAwait(false); var analyzers = analyzersForProject.WhereAsArray(a => ShouldIncludeAnalyzer(project, a)); diff --git a/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs b/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs index f29b1d10632c4..dd42ee832573a 100644 --- a/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs +++ b/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs @@ -60,12 +60,12 @@ public async Task> GetDiagnosticsForSpanAsync( var project = document.Project; var solutionState = project.Solution.SolutionState; - var unfilteredAnalyzers = await _stateManager + var unfilteredAnalyzers = await StateManager .GetOrCreateAnalyzersAsync(solutionState, project.State, cancellationToken) .ConfigureAwait(false); var analyzers = unfilteredAnalyzers .WhereAsArray(a => DocumentAnalysisExecutor.IsAnalyzerEnabledForProject(a, document.Project, GlobalOptions)); - var hostAnalyzerInfo = await _stateManager.GetOrCreateHostAnalyzerInfoAsync(solutionState, project.State, cancellationToken).ConfigureAwait(false); + var hostAnalyzerInfo = await StateManager.GetOrCreateHostAnalyzerInfoAsync(solutionState, project.State, cancellationToken).ConfigureAwait(false); // Note that some callers, such as diagnostic tagger, might pass in a range equal to the entire document span. // We clear out range for such cases as we are computing full document diagnostics. @@ -224,7 +224,7 @@ async Task ComputeDocumentDiagnosticsAsync( analyzers = filteredAnalyzers.ToImmutable(); - var hostAnalyzerInfo = await _stateManager.GetOrCreateHostAnalyzerInfoAsync(solutionState, project.State, cancellationToken).ConfigureAwait(false); + var hostAnalyzerInfo = await StateManager.GetOrCreateHostAnalyzerInfoAsync(solutionState, project.State, cancellationToken).ConfigureAwait(false); var projectAnalyzers = analyzers.WhereAsArray(static (a, info) => !info.IsHostAnalyzer(a), hostAnalyzerInfo); var hostAnalyzers = analyzers.WhereAsArray(static (a, info) => info.IsHostAnalyzer(a), hostAnalyzerInfo); diff --git a/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs index f017acab6d9ca..8c16885e9fdca 100644 --- a/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -76,8 +76,8 @@ public async Task> ForceAnalyzeProjectAsync(Proje async Task<(Checksum checksum, ImmutableArray analyzers, ImmutableDictionary diagnosticAnalysisResults)> ComputeForceAnalyzeProjectAsync() { var solutionState = project.Solution.SolutionState; - var allAnalyzers = await _stateManager.GetOrCreateAnalyzersAsync(solutionState, projectState, cancellationToken).ConfigureAwait(false); - var hostAnalyzerInfo = await _stateManager.GetOrCreateHostAnalyzerInfoAsync(solutionState, projectState, cancellationToken).ConfigureAwait(false); + var allAnalyzers = await StateManager.GetOrCreateAnalyzersAsync(solutionState, projectState, cancellationToken).ConfigureAwait(false); + var hostAnalyzerInfo = await StateManager.GetOrCreateHostAnalyzerInfoAsync(solutionState, projectState, cancellationToken).ConfigureAwait(false); var fullSolutionAnalysisAnalyzers = allAnalyzers.WhereAsArray( static (analyzer, arg) => IsCandidateForFullSolutionAnalysis( diff --git a/src/Workspaces/Core/Portable/Diagnostics/IRemoteDiagnosticAnalyzerService.cs b/src/Workspaces/Core/Portable/Diagnostics/IRemoteDiagnosticAnalyzerService.cs index de19dad4f749b..2365b25d274a1 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/IRemoteDiagnosticAnalyzerService.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/IRemoteDiagnosticAnalyzerService.cs @@ -12,7 +12,13 @@ namespace Microsoft.CodeAnalysis.Diagnostics; internal interface IRemoteDiagnosticAnalyzerService { + /// + /// Force analyzes the given project by running all applicable analyzers on the project. + /// + ValueTask> ForceAnalyzeProjectAsync(Checksum solutionChecksum, ProjectId projectId, CancellationToken cancellationToken); + ValueTask CalculateDiagnosticsAsync(Checksum solutionChecksum, DiagnosticArguments arguments, CancellationToken cancellationToken); + ValueTask> GetSourceGeneratorDiagnosticsAsync(Checksum solutionChecksum, ProjectId projectId, CancellationToken cancellationToken); ValueTask ReportAnalyzerPerformanceAsync(ImmutableArray snapshot, int unitCount, bool forSpanAnalysis, CancellationToken cancellationToken); diff --git a/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/RemoteDiagnosticAnalyzerService.cs b/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/RemoteDiagnosticAnalyzerService.cs index 3dfbd428e0c71..4c35590421bb8 100644 --- a/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/RemoteDiagnosticAnalyzerService.cs +++ b/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/RemoteDiagnosticAnalyzerService.cs @@ -75,6 +75,22 @@ public async ValueTask CalculateDiagnosti } } + public ValueTask> ForceAnalyzeProjectAsync( + Checksum solutionChecksum, + ProjectId projectId, + CancellationToken cancellationToken) + { + return RunWithSolutionAsync( + solutionChecksum, + async solution => + { + var project = solution.GetRequiredProject(projectId); + var service = solution.Services.GetRequiredService(); + return await service.ForceAnalyzeProjectAsync(project, cancellationToken).ConfigureAwait(false); + }, + cancellationToken); + } + public ValueTask> GetSourceGeneratorDiagnosticsAsync(Checksum solutionChecksum, ProjectId projectId, CancellationToken cancellationToken) { return RunWithSolutionAsync(