diff --git a/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService.cs
index c7068a7efadf4..8fea27df82f53 100644
--- a/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService.cs
+++ b/src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService.cs
@@ -46,6 +46,13 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
}
}
+///
+/// Only implementation of . Note: all methods in this class
+/// should attempt to run in OOP as soon as possible. This is not always easy, especially if the apis
+/// involve working with in-memory data structures that are not serializable. In those cases, we should
+/// do all that work in-proc, and then send the results to OOP for further processing. Examples of this
+/// are apis that take in a delegate callback to determine which analyzers to actually execute.
+///
internal sealed partial class DiagnosticAnalyzerService : IDiagnosticAnalyzerService
{
private static readonly Option2 s_crashOnAnalyzerException = new("dotnet_crash_on_analyzer_exception", defaultValue: false);
@@ -433,6 +440,44 @@ async Task IsCandidateForDeprioritizationBasedOnRegisteredActionsAsync(Dia
}
}
+ public async Task> ComputeDiagnosticsAsync(
+ TextDocument document,
+ TextSpan? range,
+ ImmutableArray allAnalyzers,
+ ImmutableArray syntaxAnalyzers,
+ ImmutableArray semanticSpanAnalyzers,
+ ImmutableArray semanticDocumentAnalyzers,
+ bool incrementalAnalysis,
+ bool logPerformanceInfo,
+ CancellationToken cancellationToken)
+ {
+ if (allAnalyzers.Length == 0)
+ return [];
+
+ var client = await RemoteHostClient.TryGetClientAsync(document.Project, cancellationToken).ConfigureAwait(false);
+ if (client is not null)
+ {
+ var allAnalyzerIds = allAnalyzers.Select(a => a.GetAnalyzerId()).ToImmutableHashSet();
+ var syntaxAnalyzersIds = syntaxAnalyzers.Select(a => a.GetAnalyzerId()).ToImmutableHashSet();
+ var semanticSpanAnalyzersIds = semanticSpanAnalyzers.Select(a => a.GetAnalyzerId()).ToImmutableHashSet();
+ var semanticDocumentAnalyzersIds = semanticDocumentAnalyzers.Select(a => a.GetAnalyzerId()).ToImmutableHashSet();
+
+ var result = await client.TryInvokeAsync>(
+ document.Project,
+ (service, solution, cancellationToken) => service.ComputeDiagnosticsAsync(
+ solution, document.Id, range,
+ allAnalyzerIds, syntaxAnalyzersIds, semanticSpanAnalyzersIds, semanticDocumentAnalyzersIds,
+ incrementalAnalysis, logPerformanceInfo, cancellationToken),
+ cancellationToken).ConfigureAwait(false);
+
+ return result.HasValue ? result.Value : [];
+ }
+
+ return await _incrementalAnalyzer.ComputeDiagnosticsAsync(
+ document, range, allAnalyzers, syntaxAnalyzers, semanticSpanAnalyzers, semanticDocumentAnalyzers,
+ incrementalAnalysis, logPerformanceInfo, cancellationToken).ConfigureAwait(false);
+ }
+
private sealed class DiagnosticAnalyzerComparer : IEqualityComparer
{
public static readonly DiagnosticAnalyzerComparer Instance = new();
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 569477bcc3789..db7b0a56f5026 100644
--- a/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs
+++ b/src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs
@@ -60,13 +60,11 @@ public async Task> GetDiagnosticsForSpanAsync(
var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false);
var project = document.Project;
- var solutionState = project.Solution.SolutionState;
var unfilteredAnalyzers = await StateManager
- .GetOrCreateAnalyzersAsync(solutionState, project.State, cancellationToken)
+ .GetOrCreateAnalyzersAsync(project.Solution.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);
+ .WhereAsArray(a => DocumentAnalysisExecutor.IsAnalyzerEnabledForProject(a, project, GlobalOptions));
// 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.
@@ -75,20 +73,30 @@ public async Task> GetDiagnosticsForSpanAsync(
// We log performance info when we are computing diagnostics for a span
var logPerformanceInfo = range.HasValue;
- var compilationWithAnalyzers = await GetOrCreateCompilationWithAnalyzersAsync(
- document.Project, analyzers, hostAnalyzerInfo, AnalyzerService.CrashOnAnalyzerException, cancellationToken).ConfigureAwait(false);
// If we are computing full document diagnostics, we will attempt to perform incremental
// member edit analysis. This analysis is currently only enabled with LSP pull diagnostics.
- var incrementalAnalysis = !range.HasValue
- && document is Document { SupportsSyntaxTree: true };
+ var incrementalAnalysis = range is null && document is Document { SupportsSyntaxTree: true };
- using var _ = ArrayBuilder.GetInstance(out var list);
- await GetAsync(list).ConfigureAwait(false);
+ using var _1 = PooledHashSet.GetInstance(out var deprioritizationCandidates);
- return list.ToImmutableAndClear();
+ deprioritizationCandidates.AddRange(await this.AnalyzerService.GetDeprioritizationCandidatesAsync(
+ project, analyzers, cancellationToken).ConfigureAwait(false));
+
+ var (syntaxAnalyzers, semanticSpanAnalyzers, semanticDocumentAnalyzers) = GetAllAnalyzers();
+ syntaxAnalyzers = FilterAnalyzers(syntaxAnalyzers, AnalysisKind.Syntax, range, deprioritizationCandidates);
+ semanticSpanAnalyzers = FilterAnalyzers(semanticSpanAnalyzers, AnalysisKind.Semantic, range, deprioritizationCandidates);
+ semanticDocumentAnalyzers = FilterAnalyzers(semanticDocumentAnalyzers, AnalysisKind.Semantic, span: null, deprioritizationCandidates);
+
+ var allDiagnostics = await this.AnalyzerService.ComputeDiagnosticsAsync(
+ document, range, analyzers, syntaxAnalyzers, semanticSpanAnalyzers, semanticDocumentAnalyzers,
+ incrementalAnalysis, logPerformanceInfo,
+ cancellationToken).ConfigureAwait(false);
+ return allDiagnostics.WhereAsArray(ShouldInclude);
- async Task GetAsync(ArrayBuilder list)
+ (ImmutableArray syntaxAnalyzers,
+ ImmutableArray semanticSpanAnalyzers,
+ ImmutableArray semanticDocumentAnalyzers) GetAllAnalyzers()
{
try
{
@@ -153,11 +161,10 @@ async Task GetAsync(ArrayBuilder list)
}
}
- await ComputeDocumentDiagnosticsAsync(syntaxAnalyzers.ToImmutable(), AnalysisKind.Syntax, range, list, incrementalAnalysis: false, cancellationToken).ConfigureAwait(false);
- await ComputeDocumentDiagnosticsAsync(semanticSpanBasedAnalyzers.ToImmutable(), AnalysisKind.Semantic, range, list, incrementalAnalysis, cancellationToken).ConfigureAwait(false);
- await ComputeDocumentDiagnosticsAsync(semanticDocumentBasedAnalyzers.ToImmutable(), AnalysisKind.Semantic, span: null, list, incrementalAnalysis: false, cancellationToken).ConfigureAwait(false);
-
- return;
+ return (
+ syntaxAnalyzers.ToImmutableAndClear(),
+ semanticSpanBasedAnalyzers.ToImmutableAndClear(),
+ semanticDocumentBasedAnalyzers.ToImmutableAndClear());
}
catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken))
{
@@ -193,22 +200,13 @@ static bool ShouldIncludeAnalyzer(
return true;
}
- async Task ComputeDocumentDiagnosticsAsync(
+ ImmutableArray FilterAnalyzers(
ImmutableArray analyzers,
AnalysisKind kind,
TextSpan? span,
- ArrayBuilder builder,
- bool incrementalAnalysis,
- CancellationToken cancellationToken)
+ HashSet deprioritizationCandidates)
{
- Debug.Assert(!incrementalAnalysis || kind == AnalysisKind.Semantic);
- Debug.Assert(!incrementalAnalysis || analyzers.All(analyzer => analyzer.SupportsSpanBasedSemanticDiagnosticAnalysis()));
-
using var _1 = ArrayBuilder.GetInstance(analyzers.Length, out var filteredAnalyzers);
- using var _2 = PooledHashSet.GetInstance(out var deprioritizationCandidates);
-
- deprioritizationCandidates.AddRange(await this.AnalyzerService.GetDeprioritizationCandidatesAsync(
- project, analyzers, cancellationToken).ConfigureAwait(false));
foreach (var analyzer in analyzers)
{
@@ -223,43 +221,7 @@ async Task ComputeDocumentDiagnosticsAsync(
filteredAnalyzers.Add(analyzer);
}
- if (filteredAnalyzers.Count == 0)
- return;
-
- analyzers = filteredAnalyzers.ToImmutable();
-
- var projectAnalyzers = analyzers.WhereAsArray(static (a, info) => !info.IsHostAnalyzer(a), hostAnalyzerInfo);
- var hostAnalyzers = analyzers.WhereAsArray(static (a, info) => info.IsHostAnalyzer(a), hostAnalyzerInfo);
- var analysisScope = new DocumentAnalysisScope(document, span, projectAnalyzers, hostAnalyzers, kind);
- var executor = new DocumentAnalysisExecutor(analysisScope, compilationWithAnalyzers, _diagnosticAnalyzerRunner, logPerformanceInfo);
- var version = await GetDiagnosticVersionAsync(document.Project, cancellationToken).ConfigureAwait(false);
-
- ImmutableDictionary> diagnosticsMap;
- if (incrementalAnalysis)
- {
- using var _3 = TelemetryLogging.LogBlockTimeAggregatedHistogram(FunctionId.RequestDiagnostics_Summary, $"Pri{priorityProvider.Priority.GetPriorityInt()}.Incremental");
-
- diagnosticsMap = await _incrementalMemberEditAnalyzer.ComputeDiagnosticsAsync(
- executor,
- analyzers,
- version,
- cancellationToken).ConfigureAwait(false);
- }
- else
- {
- using var _3 = TelemetryLogging.LogBlockTimeAggregatedHistogram(FunctionId.RequestDiagnostics_Summary, $"Pri{priorityProvider.Priority.GetPriorityInt()}.Document");
-
- diagnosticsMap = await ComputeDocumentDiagnosticsCoreAsync(executor, cancellationToken).ConfigureAwait(false);
- }
-
- foreach (var analyzer in analyzers)
- {
- var diagnostics = diagnosticsMap[analyzer];
- builder.AddRange(diagnostics.Where(ShouldInclude));
- }
-
- if (incrementalAnalysis)
- _incrementalMemberEditAnalyzer.UpdateDocumentWithCachedDiagnostics((Document)document);
+ return filteredAnalyzers.ToImmutableAndClear();
}
bool TryDeprioritizeAnalyzer(
@@ -309,5 +271,62 @@ bool ShouldInclude(DiagnosticData diagnostic)
&& (shouldIncludeDiagnostic == null || shouldIncludeDiagnostic(diagnostic.Id));
}
}
+
+ public async Task> ComputeDiagnosticsAsync(
+ TextDocument document,
+ TextSpan? range,
+ ImmutableArray allAnalyzers,
+ ImmutableArray syntaxAnalyzers,
+ ImmutableArray semanticSpanAnalyzers,
+ ImmutableArray semanticDocumentAnalyzers,
+ bool incrementalAnalysis,
+ bool logPerformanceInfo,
+ CancellationToken cancellationToken)
+ {
+ // We log performance info when we are computing diagnostics for a span
+ var project = document.Project;
+
+ var hostAnalyzerInfo = await StateManager.GetOrCreateHostAnalyzerInfoAsync(
+ project.Solution.SolutionState, project.State, cancellationToken).ConfigureAwait(false);
+ var compilationWithAnalyzers = await GetOrCreateCompilationWithAnalyzersAsync(
+ document.Project, allAnalyzers, hostAnalyzerInfo, AnalyzerService.CrashOnAnalyzerException, cancellationToken).ConfigureAwait(false);
+
+ using var _ = ArrayBuilder.GetInstance(out var list);
+
+ await ComputeDocumentDiagnosticsAsync(syntaxAnalyzers, AnalysisKind.Syntax, range, incrementalAnalysis: false).ConfigureAwait(false);
+ await ComputeDocumentDiagnosticsAsync(semanticSpanAnalyzers, AnalysisKind.Semantic, range, incrementalAnalysis).ConfigureAwait(false);
+ await ComputeDocumentDiagnosticsAsync(semanticDocumentAnalyzers, AnalysisKind.Semantic, span: null, incrementalAnalysis: false).ConfigureAwait(false);
+
+ return list.ToImmutableAndClear();
+
+ async Task ComputeDocumentDiagnosticsAsync(
+ ImmutableArray analyzers,
+ AnalysisKind kind,
+ TextSpan? span,
+ bool incrementalAnalysis)
+ {
+ if (analyzers.Length == 0)
+ return;
+
+ Debug.Assert(!incrementalAnalysis || kind == AnalysisKind.Semantic);
+ Debug.Assert(!incrementalAnalysis || analyzers.All(analyzer => analyzer.SupportsSpanBasedSemanticDiagnosticAnalysis()));
+
+ var projectAnalyzers = analyzers.WhereAsArray(static (a, info) => !info.IsHostAnalyzer(a), hostAnalyzerInfo);
+ var hostAnalyzers = analyzers.WhereAsArray(static (a, info) => info.IsHostAnalyzer(a), hostAnalyzerInfo);
+ var analysisScope = new DocumentAnalysisScope(document, span, projectAnalyzers, hostAnalyzers, kind);
+ var executor = new DocumentAnalysisExecutor(analysisScope, compilationWithAnalyzers, _diagnosticAnalyzerRunner, logPerformanceInfo);
+ var version = await GetDiagnosticVersionAsync(document.Project, cancellationToken).ConfigureAwait(false);
+
+ var computeTask = incrementalAnalysis
+ ? _incrementalMemberEditAnalyzer.ComputeDiagnosticsAsync(executor, analyzers, version, cancellationToken)
+ : ComputeDocumentDiagnosticsCoreAsync(executor, cancellationToken);
+ var diagnosticsMap = await computeTask.ConfigureAwait(false);
+
+ if (incrementalAnalysis)
+ _incrementalMemberEditAnalyzer.UpdateDocumentWithCachedDiagnostics((Document)document);
+
+ list.AddRange(diagnosticsMap.SelectMany(kvp => kvp.Value));
+ }
+ }
}
}
diff --git a/src/Workspaces/Core/Portable/Diagnostics/IRemoteDiagnosticAnalyzerService.cs b/src/Workspaces/Core/Portable/Diagnostics/IRemoteDiagnosticAnalyzerService.cs
index 2a9840c22fef5..ea009d943211a 100644
--- a/src/Workspaces/Core/Portable/Diagnostics/IRemoteDiagnosticAnalyzerService.cs
+++ b/src/Workspaces/Core/Portable/Diagnostics/IRemoteDiagnosticAnalyzerService.cs
@@ -8,6 +8,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.Diagnostics;
@@ -30,6 +31,17 @@ ValueTask> GetDeprioritizationCandidatesAsync(
Checksum solutionChecksum, ProjectId projectId, ImmutableHashSet analyzerIds, CancellationToken cancellationToken);
ValueTask CalculateDiagnosticsAsync(Checksum solutionChecksum, DiagnosticArguments arguments, CancellationToken cancellationToken);
+
+ ValueTask> ComputeDiagnosticsAsync(
+ Checksum solutionChecksum, DocumentId documentId, TextSpan? range,
+ ImmutableHashSet allAnalyzerIds,
+ ImmutableHashSet syntaxAnalyzersIds,
+ ImmutableHashSet semanticSpanAnalyzersIds,
+ ImmutableHashSet semanticDocumentAnalyzersIds,
+ bool incrementalAnalysis,
+ bool logPerformanceInfo,
+ CancellationToken cancellationToken);
+
ValueTask> ProduceProjectDiagnosticsAsync(
Checksum solutionChecksum, ProjectId projectId,
ImmutableHashSet analyzerIds,
diff --git a/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/RemoteDiagnosticAnalyzerService.cs b/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/RemoteDiagnosticAnalyzerService.cs
index ed78aa2e3ed87..f714c306c32d0 100644
--- a/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/RemoteDiagnosticAnalyzerService.cs
+++ b/src/Workspaces/Remote/ServiceHub/Services/DiagnosticAnalyzer/RemoteDiagnosticAnalyzerService.cs
@@ -10,10 +10,10 @@
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Internal.Log;
-using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Remote.Diagnostics;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Telemetry;
+using Microsoft.CodeAnalysis.Text;
using RoslynLogger = Microsoft.CodeAnalysis.Internal.Log.Logger;
namespace Microsoft.CodeAnalysis.Remote;
@@ -302,4 +302,35 @@ public ValueTask> GetDeprioritizationCandidatesAsync(
},
cancellationToken);
}
+
+ public ValueTask> ComputeDiagnosticsAsync(
+ Checksum solutionChecksum, DocumentId documentId, TextSpan? range,
+ ImmutableHashSet allAnalyzerIds,
+ ImmutableHashSet syntaxAnalyzersIds,
+ ImmutableHashSet semanticSpanAnalyzersIds,
+ ImmutableHashSet semanticDocumentAnalyzersIds,
+ bool incrementalAnalysis,
+ bool logPerformanceInfo,
+ CancellationToken cancellationToken)
+ {
+ return RunWithSolutionAsync(
+ solutionChecksum,
+ async solution =>
+ {
+ var document = solution.GetRequiredTextDocument(documentId);
+ var service = (DiagnosticAnalyzerService)solution.Services.GetRequiredService();
+
+ var allProjectAnalyzers = await service.GetProjectAnalyzersAsync(document.Project, cancellationToken).ConfigureAwait(false);
+
+ var allAnalyzers = allProjectAnalyzers.FilterAnalyzers(allAnalyzerIds);
+ var syntaxAnalyzers = allProjectAnalyzers.FilterAnalyzers(syntaxAnalyzersIds);
+ var semanticSpanAnalyzers = allProjectAnalyzers.FilterAnalyzers(semanticSpanAnalyzersIds);
+ var semanticDocumentAnalyzers = allProjectAnalyzers.FilterAnalyzers(semanticDocumentAnalyzersIds);
+
+ return await service.ComputeDiagnosticsAsync(
+ document, range, allAnalyzers, syntaxAnalyzers, semanticSpanAnalyzers, semanticDocumentAnalyzers,
+ incrementalAnalysis, logPerformanceInfo, cancellationToken).ConfigureAwait(false);
+ },
+ cancellationToken);
+ }
}