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..510662171a7a9 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,21 +124,102 @@ 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(
+ public async Task> GetDiagnosticsForIdsAsync(
Project project, DocumentId? documentId, ImmutableHashSet? diagnosticIds, Func? shouldIncludeAnalyzer, bool includeLocalDocumentDiagnostics, bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken)
{
- return _incrementalAnalyzer.GetDiagnosticsForIdsAsync(project, documentId, diagnosticIds, shouldIncludeAnalyzer, includeLocalDocumentDiagnostics, includeNonLocalDocumentDiagnostics, cancellationToken);
+ var analyzers = await GetDiagnosticAnalyzersAsync(
+ project, diagnosticIds, shouldIncludeAnalyzer, cancellationToken).ConfigureAwait(false);
+
+ return await ProduceProjectDiagnosticsAsync(
+ project, analyzers, diagnosticIds,
+ // Ensure we compute and return diagnostics for both the normal docs and the additional docs in this
+ // project if no specific document id was requested.
+ documentId != null ? [documentId] : [.. project.DocumentIds, .. project.AdditionalDocumentIds],
+ includeLocalDocumentDiagnostics,
+ includeNonLocalDocumentDiagnostics,
+ // return diagnostics specific to one project or document
+ includeProjectNonLocalResult: documentId == null,
+ cancellationToken).ConfigureAwait(false);
}
- public Task> GetProjectDiagnosticsForIdsAsync(
+ public async Task> GetProjectDiagnosticsForIdsAsync(
Project project, ImmutableHashSet? diagnosticIds,
Func? shouldIncludeAnalyzer,
bool includeNonLocalDocumentDiagnostics, CancellationToken cancellationToken)
{
- return _incrementalAnalyzer.GetProjectDiagnosticsForIdsAsync(project, diagnosticIds, shouldIncludeAnalyzer, includeNonLocalDocumentDiagnostics, cancellationToken);
+ var analyzers = await GetDiagnosticAnalyzersAsync(
+ project, diagnosticIds, shouldIncludeAnalyzer, cancellationToken).ConfigureAwait(false);
+
+ return await ProduceProjectDiagnosticsAsync(
+ project, analyzers, diagnosticIds,
+ documentIds: [],
+ includeLocalDocumentDiagnostics: false,
+ includeNonLocalDocumentDiagnostics: includeNonLocalDocumentDiagnostics,
+ includeProjectNonLocalResult: true,
+ cancellationToken).ConfigureAwait(false);
+ }
+
+ private Task> ProduceProjectDiagnosticsAsync(
+ Project project,
+ ImmutableArray analyzers,
+ ImmutableHashSet? diagnosticIds,
+ IReadOnlyList documentIds,
+ bool includeLocalDocumentDiagnostics,
+ bool includeNonLocalDocumentDiagnostics,
+ bool includeProjectNonLocalResult,
+ CancellationToken cancellationToken)
+ {
+ return _incrementalAnalyzer.ProduceProjectDiagnosticsAsync(
+ project, analyzers, diagnosticIds, documentIds,
+ includeLocalDocumentDiagnostics,
+ includeNonLocalDocumentDiagnostics,
+ includeProjectNonLocalResult,
+ cancellationToken);
+ }
+
+ private async Task> GetDiagnosticAnalyzersAsync(
+ Project project,
+ ImmutableHashSet? diagnosticIds,
+ Func? shouldIncludeAnalyzer,
+ CancellationToken cancellationToken)
+ {
+ var analyzersForProject = await _stateManager.GetOrCreateAnalyzersAsync(
+ project.Solution.SolutionState, project.State, cancellationToken).ConfigureAwait(false);
+
+ var analyzers = analyzersForProject.WhereAsArray(a => ShouldIncludeAnalyzer(project, a));
+
+ return analyzers;
+
+ bool ShouldIncludeAnalyzer(Project project, DiagnosticAnalyzer analyzer)
+ {
+ if (!DocumentAnalysisExecutor.IsAnalyzerEnabledForProject(analyzer, project, this.GlobalOptions))
+ return false;
+
+ if (shouldIncludeAnalyzer != null && !shouldIncludeAnalyzer(analyzer))
+ return false;
+
+ if (diagnosticIds != null && _analyzerInfoCache.GetDiagnosticDescriptors(analyzer).All(d => !diagnosticIds.Contains(d.Id)))
+ return false;
+
+ return true;
+ }
}
public async Task> GetDiagnosticDescriptorsAsync(
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