diff --git a/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs b/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs index eac1b135fb8c0c44a05a0f6d9c4d5dc79a17fb79..2855017098e1b151a518dba24284477eb372124d 100644 --- a/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs +++ b/src/Features/Core/Portable/Diagnostics/DefaultDiagnosticAnalyzerService.cs @@ -147,7 +147,7 @@ private async Task AnalyzeForKindAsync(TextDocument document, AnalysisKind kind, /// /// The intended audience for this API is for ones that pefer simplicity over performance such as document that belong to misc project. /// this doesn't cache nor use cache for anything. it will re-caculate new diagnostics every time for the given document. - /// it will not persist any data on disk nor use OOP to calcuate the data. + /// it will not persist any data on disk nor use OOP to calculate the data. /// /// This should never be used when performance is a big concern. for such context, use much complex API from IDiagnosticAnalyzerService /// that provide all kinds of knobs/cache/persistency/OOP to get better perf over simplicity. diff --git a/src/Features/Core/Portable/Diagnostics/DocumentAnalysisExecutor.cs b/src/Features/Core/Portable/Diagnostics/DocumentAnalysisExecutor.cs index 6525aa679b64561b62e0b21b2145381d618f2502..7d4fff67e164ad7aca8875e53ace8c959ed426c9 100644 --- a/src/Features/Core/Portable/Diagnostics/DocumentAnalysisExecutor.cs +++ b/src/Features/Core/Portable/Diagnostics/DocumentAnalysisExecutor.cs @@ -121,7 +121,7 @@ public async Task> ComputeDiagnosticsAsync(Diagnosti } } - if (document == null && !(textDocument is AdditionalDocument)) + if (document == null && textDocument is not AdditionalDocument) { // We currently support document analysis only for source documents and additional documents. return SpecializedCollections.EmptyEnumerable(); @@ -182,7 +182,7 @@ private async Task> GetSyntaxDiagnosticsAsync(Dia if (isCompilerAnalyzer) { - if (!(AnalysisScope.TextDocument is Document)) + if (AnalysisScope.TextDocument is not Document) { return ImmutableArray.Empty; } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs index 0740ab6954fa3f5155bbaf447cfc027bbc500b9a..e48eafa0dd5f79c5b4a5660563ee8516e69ef652 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.InProcOrRemoteHostAnalyzerRunner.cs @@ -74,19 +74,15 @@ internal class InProcOrRemoteHostAnalyzerRunner { Contract.ThrowIfFalse(!compilationWithAnalyzers.Analyzers.IsEmpty); - var workspace = project.Solution.Workspace; - if (workspace.Services.GetService() is { } service && - await service.TryGetRemoteHostClientAsync(cancellationToken).ConfigureAwait(false) is { } remoteHostClient) + var remoteHostClient = await RemoteHostClient.TryGetClientAsync(project, cancellationToken).ConfigureAwait(false); + if (remoteHostClient != null) { return await AnalyzeOutOfProcAsync(documentAnalysisScope, project, compilationWithAnalyzers, remoteHostClient, forceExecuteAllAnalyzers, logPerformanceInfo, getTelemetryInfo, cancellationToken).ConfigureAwait(false); } - // We need to execute InProc due to one of the following reasons: - // 1. The host doesn't support RemoteHostService (such as under unit test) OR - // 2. Remote host is not running (this can happen if remote host is disabled). return await AnalyzeInProcAsync(documentAnalysisScope, project, compilationWithAnalyzers, - client: null, logPerformanceInfo, getTelemetryInfo, cancellationToken).ConfigureAwait(false); + client: null, logPerformanceInfo, getTelemetryInfo, cancellationToken).ConfigureAwait(false); } } @@ -115,9 +111,10 @@ await service.TryGetRemoteHostClientAsync(cancellationToken).ConfigureAwait(fals var skippedAnalyzersInfo = project.GetSkippedAnalyzersInfo(AnalyzerInfoCache); // get compiler result builder map - var builderMap = analysisResult.ToResultBuilderMap( + var builderMap = await analysisResult.ToResultBuilderMapAsync( additionalPragmaSuppressionDiagnostics, documentAnalysisScope, project, version, - compilationWithAnalyzers.Compilation, analyzers, skippedAnalyzersInfo, compilationWithAnalyzers.AnalysisOptions.ReportSuppressedDiagnostics, cancellationToken); + compilationWithAnalyzers.Compilation, analyzers, skippedAnalyzersInfo, + compilationWithAnalyzers.AnalysisOptions.ReportSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); var result = builderMap.ToImmutableDictionary(kv => kv.Key, kv => DiagnosticAnalysisResult.CreateFromBuilder(kv.Value)); var telemetry = getTelemetryInfo @@ -140,16 +137,14 @@ await service.TryGetRemoteHostClientAsync(cancellationToken).ConfigureAwait(fals try { + // +1 for project itself + var count = documentAnalysisScope != null ? 1 : project.DocumentIds.Count + 1; + await client.RunRemoteAsync( WellKnownServiceHubService.CodeAnalysis, nameof(IRemoteDiagnosticAnalyzerService.ReportAnalyzerPerformance), solution: null, - new object[] - { - analysisResult.AnalyzerTelemetryInfo.ToAnalyzerPerformanceInfo(AnalyzerInfoCache), - // +1 for project itself - documentAnalysisScope != null ? 1 : project.DocumentIds.Count + 1 - }, + new object[] { analysisResult.AnalyzerTelemetryInfo.ToAnalyzerPerformanceInfo(AnalyzerInfoCache), count }, callbackTarget: null, cancellationToken).ConfigureAwait(false); } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs index 70409a07e5f7c3ad141697359518d2d6ab301d71..466627a25ce9c556867e784d5ffdcc6748d3efae 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs @@ -40,7 +40,6 @@ internal partial class DiagnosticIncrementalAnalyzer : IIncrementalAnalyzer2 internal DiagnosticAnalyzerService AnalyzerService { get; } internal Workspace Workspace { get; } internal IPersistentStorageService PersistentStorageService { get; } - internal DiagnosticAnalyzerInfoCache DiagnosticAnalyzerInfoCache => _diagnosticAnalyzerRunner.AnalyzerInfoCache; public DiagnosticIncrementalAnalyzer( DiagnosticAnalyzerService analyzerService, @@ -64,6 +63,8 @@ internal partial class DiagnosticIncrementalAnalyzer : IIncrementalAnalyzer2 _projectCompilationsWithAnalyzers = new ConditionalWeakTable(); } + internal DiagnosticAnalyzerInfoCache DiagnosticAnalyzerInfoCache => _diagnosticAnalyzerRunner.AnalyzerInfoCache; + public bool IsCompilationEndAnalyzer(DiagnosticAnalyzer diagnosticAnalyzer, Project project, Compilation compilation) => DiagnosticAnalyzerInfoCache.IsCompilationEndAnalyzer(diagnosticAnalyzer, project, compilation) == true; diff --git a/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs b/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs index 74564c9c510dd056bacaaf7beb1fb1732597ab94..f03b89df23c3638c230cd83d9b1ffe6d6c47b20f 100644 --- a/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs +++ b/src/Workspaces/Core/Portable/Diagnostics/Extensions.cs @@ -132,7 +132,7 @@ private static string GetAssemblyQualifiedName(Type type) return type.AssemblyQualifiedName ?? throw ExceptionUtilities.UnexpectedValue(type); } - public static ImmutableDictionary ToResultBuilderMap( + public static async Task> ToResultBuilderMapAsync( this AnalysisResult analysisResult, ImmutableArray additionalPragmaSuppressionDiagnostics, DocumentAnalysisScope? documentAnalysisScope, @@ -144,17 +144,17 @@ private static string GetAssemblyQualifiedName(Type type) bool includeSuppressedDiagnostics, CancellationToken cancellationToken) { - SyntaxTree? filterTree = null; - AdditionalText? filterAdditionalFile = null; + SyntaxTree? treeToAnalyze = null; + AdditionalText? additionalFileToAnalyze = null; if (documentAnalysisScope != null) { if (documentAnalysisScope.TextDocument is Document document) { - filterTree = document.GetSyntaxTreeSynchronously(cancellationToken); + treeToAnalyze = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); } else { - filterAdditionalFile = documentAnalysisScope.AdditionalFile; + additionalFileToAnalyze = documentAnalysisScope.AdditionalFile; } } @@ -175,35 +175,35 @@ private static string GetAssemblyQualifiedName(Type type) if (documentAnalysisScope != null) { - RoslynDebug.Assert(filterTree != null || filterAdditionalFile != null); - var filterSpan = documentAnalysisScope.Span; + RoslynDebug.Assert(treeToAnalyze != null || additionalFileToAnalyze != null); + var spanToAnalyze = documentAnalysisScope.Span; var kind = documentAnalysisScope.Kind; ImmutableDictionary>? diagnosticsByAnalyzerMap; switch (kind) { case AnalysisKind.Syntax: - if (filterTree != null) + if (treeToAnalyze != null) { - if (analysisResult.SyntaxDiagnostics.TryGetValue(filterTree, out diagnosticsByAnalyzerMap)) + if (analysisResult.SyntaxDiagnostics.TryGetValue(treeToAnalyze, out diagnosticsByAnalyzerMap)) { AddAnalyzerDiagnosticsToResult(analyzer, diagnosticsByAnalyzerMap, ref result, compilation, - filterTree, additionalDocumentId: null, filterSpan, AnalysisKind.Syntax, diagnosticIdsToFilter, includeSuppressedDiagnostics); + treeToAnalyze, additionalDocumentId: null, spanToAnalyze, AnalysisKind.Syntax, diagnosticIdsToFilter, includeSuppressedDiagnostics); } } - else if (analysisResult.AdditionalFileDiagnostics.TryGetValue(filterAdditionalFile!, out diagnosticsByAnalyzerMap)) + else if (analysisResult.AdditionalFileDiagnostics.TryGetValue(additionalFileToAnalyze!, out diagnosticsByAnalyzerMap)) { AddAnalyzerDiagnosticsToResult(analyzer, diagnosticsByAnalyzerMap, ref result, compilation, - tree: null, documentAnalysisScope.TextDocument.Id, filterSpan, AnalysisKind.Syntax, diagnosticIdsToFilter, includeSuppressedDiagnostics); + tree: null, documentAnalysisScope.TextDocument.Id, spanToAnalyze, AnalysisKind.Syntax, diagnosticIdsToFilter, includeSuppressedDiagnostics); } break; case AnalysisKind.Semantic: - if (analysisResult.SemanticDiagnostics.TryGetValue(filterTree!, out diagnosticsByAnalyzerMap)) + if (analysisResult.SemanticDiagnostics.TryGetValue(treeToAnalyze!, out diagnosticsByAnalyzerMap)) { AddAnalyzerDiagnosticsToResult(analyzer, diagnosticsByAnalyzerMap, ref result, compilation, - filterTree, additionalDocumentId: null, filterSpan, AnalysisKind.Semantic, diagnosticIdsToFilter, includeSuppressedDiagnostics); + treeToAnalyze, additionalDocumentId: null, spanToAnalyze, AnalysisKind.Semantic, diagnosticIdsToFilter, includeSuppressedDiagnostics); } break; @@ -244,10 +244,10 @@ private static string GetAssemblyQualifiedName(Type type) { if (documentAnalysisScope != null) { - if (filterTree != null) + if (treeToAnalyze != null) { - var diagnostics = additionalPragmaSuppressionDiagnostics.WhereAsArray(d => d.Location.SourceTree == filterTree); - AddDiagnosticsToResult(diagnostics, ref result, compilation, filterTree, additionalDocumentId: null, + var diagnostics = additionalPragmaSuppressionDiagnostics.WhereAsArray(d => d.Location.SourceTree == treeToAnalyze); + AddDiagnosticsToResult(diagnostics, ref result, compilation, treeToAnalyze, additionalDocumentId: null, documentAnalysisScope!.Span, AnalysisKind.Semantic, diagnosticIdsToFilter, includeSuppressedDiagnostics); } } diff --git a/src/Workspaces/Remote/Core/Diagnostics/DiagnosticComputer.cs b/src/Workspaces/Remote/Core/Diagnostics/DiagnosticComputer.cs index 77ef5d327d7f3e80ff39a50f755d13b9c42aea7e..e547326cca69cfc18a99a1e4bc62f100d76b57f3 100644 --- a/src/Workspaces/Remote/Core/Diagnostics/DiagnosticComputer.cs +++ b/src/Workspaces/Remote/Core/Diagnostics/DiagnosticComputer.cs @@ -24,6 +24,19 @@ namespace Microsoft.CodeAnalysis.Remote.Diagnostics { internal class DiagnosticComputer { + /// + /// Cache of and a map from analyzer IDs to s + /// for all analyzers for each project. + /// The instance is shared between all the following document analyses modes for the project: + /// 1. Span-based analysis for active document (lightbulb) + /// 2. Background analysis for active and open documents. + /// + /// NOTE: We do not re-use this cache for project analysis as it leads to significant memory increase in the OOP process, + /// and CWT does not seem to drop entries until ForceGC happens. + /// + private static readonly ConditionalWeakTable s_compilationWithAnalyzersCache + = new ConditionalWeakTable(); + private readonly TextDocument? _document; private readonly Project _project; private readonly TextSpan? _span; @@ -55,10 +68,10 @@ internal class DiagnosticComputer bool getTelemetryInfo, CancellationToken cancellationToken) { - var compilationWithAnalyzersData = await CompilationWithAnalyzersData.GetOrCreateAsync( + var (compilationWithAnalyzers, analyzerToIdMap) = await GetOrCreateCompilationWithAnalyzersAsync( _project, isDocumentAnalysis: _document != null, cancellationToken).ConfigureAwait(false); - var analyzers = GetAnalyzers(compilationWithAnalyzersData.AnalyzerToIdMap, analyzerIds); + var analyzers = GetAnalyzers(analyzerToIdMap, analyzerIds); if (analyzers.IsEmpty) { return DiagnosticAnalysisResultMap.Empty; @@ -67,12 +80,13 @@ internal class DiagnosticComputer var cacheService = _project.Solution.Workspace.Services.GetRequiredService(); using var cache = cacheService.EnableCaching(_project.Id); var skippedAnalyzersInfo = _project.GetSkippedAnalyzersInfo(_analyzerInfoCache); - return await AnalyzeAsync(compilationWithAnalyzersData, analyzers, skippedAnalyzersInfo, + return await AnalyzeAsync(compilationWithAnalyzers, analyzerToIdMap, analyzers, skippedAnalyzersInfo, reportSuppressedDiagnostics, logPerformanceInfo, getTelemetryInfo, cancellationToken).ConfigureAwait(false); } private async Task> AnalyzeAsync( - CompilationWithAnalyzersData compilationWithAnalyzersData, + CompilationWithAnalyzers compilationWithAnalyzers, + BidirectionalMap analyzerToIdMap, ImmutableArray analyzers, SkippedHostAnalyzersInfo skippedAnalyzersInfo, bool reportSuppressedDiagnostics, @@ -80,7 +94,6 @@ internal class DiagnosticComputer bool getTelemetryInfo, CancellationToken cancellationToken) { - var compilationWithAnalyzers = compilationWithAnalyzersData.CompilationWithAnalyzers; var documentAnalysisScope = _document != null ? new DocumentAnalysisScope(_document, _span, analyzers, _analysisKind!.Value) : null; @@ -96,12 +109,11 @@ internal class DiagnosticComputer _performanceTracker.AddSnapshot(analysisResult.AnalyzerTelemetryInfo.ToAnalyzerPerformanceInfo(_analyzerInfoCache), unitCount); } - var builderMap = analysisResult.ToResultBuilderMap( + var builderMap = await analysisResult.ToResultBuilderMapAsync( additionalPragmaSuppressionDiagnostics, documentAnalysisScope, _project, VersionStamp.Default, compilationWithAnalyzers.Compilation, - analyzers, skippedAnalyzersInfo, reportSuppressedDiagnostics, cancellationToken); + analyzers, skippedAnalyzersInfo, reportSuppressedDiagnostics, cancellationToken).ConfigureAwait(false); - var analyzerToIdMap = compilationWithAnalyzersData.AnalyzerToIdMap; var result = builderMap.ToImmutableDictionary(kv => GetAnalyzerId(analyzerToIdMap, kv.Key), kv => kv.Value); var telemetry = getTelemetryInfo ? GetTelemetryInfo(analysisResult, analyzers, analyzerToIdMap) @@ -165,108 +177,101 @@ private static ImmutableArray GetAnalyzers(BidirectionalMap< return builder.ToImmutable(); } - private sealed class CompilationWithAnalyzersData + private static async Task<(CompilationWithAnalyzers compilationWithAnalyzers, BidirectionalMap analyzerToIdMap)> GetOrCreateCompilationWithAnalyzersAsync( + Project project, + bool isDocumentAnalysis, + CancellationToken cancellationToken) { - /// - /// Cache of and a map from analyzer IDs to s - /// for all analyzers for each project. - /// The instance is shared between all the following document analyses modes for the project: - /// 1. Span-based analysis for active document (lightbulb) - /// 2. Background analysis for active and open documents. - /// - /// NOTE: We do not re-use this cache for project analysis as it leads to significant memory increase in the OOP process, - /// and CWT does not seem to drop entries until ForceGC happens. - /// - private static readonly ConditionalWeakTable s_cache - = new ConditionalWeakTable(); - - private CompilationWithAnalyzersData(CompilationWithAnalyzers compilationWithAnalyzers, BidirectionalMap analyzerToIdMap) - { - CompilationWithAnalyzers = compilationWithAnalyzers; - AnalyzerToIdMap = analyzerToIdMap; - } - - public BidirectionalMap AnalyzerToIdMap { get; } - public CompilationWithAnalyzers CompilationWithAnalyzers { get; } + var cacheEntry = await GetOrCreateCacheEntryAsync().ConfigureAwait(false); + return (cacheEntry.CompilationWithAnalyzers, cacheEntry.AnalyzerToIdMap); - public static async Task GetOrCreateAsync( - Project project, - bool isDocumentAnalysis, - CancellationToken cancellationToken) + async Task GetOrCreateCacheEntryAsync() { if (!isDocumentAnalysis) { // Only use cache for document analysis. - return await CreateAsync(project, cancellationToken).ConfigureAwait(false); + return await CreateCompilationWithAnalyzersCacheEntryAsync(project, cancellationToken).ConfigureAwait(false); } - if (s_cache.TryGetValue(project, out var data)) + if (s_compilationWithAnalyzersCache.TryGetValue(project, out var data)) { return data; } - data = await CreateAsync(project, cancellationToken).ConfigureAwait(false); - return s_cache.GetValue(project, _ => data); + data = await CreateCompilationWithAnalyzersCacheEntryAsync(project, cancellationToken).ConfigureAwait(false); + return s_compilationWithAnalyzersCache.GetValue(project, _ => data); } + } - private static async Task CreateAsync(Project project, CancellationToken cancellationToken) + private static async Task CreateCompilationWithAnalyzersCacheEntryAsync(Project project, CancellationToken cancellationToken) + { + // We could consider creating a service so that we don't do this repeatedly if this shows up as perf cost + using var pooledObject = SharedPools.Default>().GetPooledObject(); + using var pooledMap = SharedPools.Default>().GetPooledObject(); + var referenceSet = pooledObject.Object; + var analyzerMapBuilder = pooledMap.Object; + + // This follows what we do in DiagnosticAnalyzerInfoCache.CheckAnalyzerReferenceIdentity + using var _ = ArrayBuilder.GetInstance(out var analyzerBuilder); + foreach (var reference in project.Solution.AnalyzerReferences.Concat(project.AnalyzerReferences)) { - // We could consider creating a service so that we don't do this repeatedly if this shows up as perf cost - using var pooledObject = SharedPools.Default>().GetPooledObject(); - using var pooledMap = SharedPools.Default>().GetPooledObject(); - var referenceSet = pooledObject.Object; - var analyzerMapBuilder = pooledMap.Object; - - // This follows what we do in DiagnosticAnalyzerInfoCache.CheckAnalyzerReferenceIdentity - using var _ = ArrayBuilder.GetInstance(out var analyzerBuilder); - foreach (var reference in project.Solution.AnalyzerReferences.Concat(project.AnalyzerReferences)) + if (!referenceSet.Add(reference.Id)) { - if (!referenceSet.Add(reference.Id)) - { - continue; - } - - var analyzers = reference.GetAnalyzers(project.Language); - analyzerBuilder.AddRange(analyzers); - analyzerMapBuilder.AppendAnalyzerMap(analyzers); + continue; } - var compilationWithAnalyzers = await CreateCompilationWithAnalyzerAsync( - project, analyzerBuilder.ToImmutable(), cancellationToken).ConfigureAwait(false); - var analyzerToIdMap = new BidirectionalMap(analyzerMapBuilder); + var analyzers = reference.GetAnalyzers(project.Language); + analyzerBuilder.AddRange(analyzers); + analyzerMapBuilder.AppendAnalyzerMap(analyzers); + } - return new CompilationWithAnalyzersData(compilationWithAnalyzers, analyzerToIdMap); + var compilationWithAnalyzers = await CreateCompilationWithAnalyzerAsync( + project, analyzerBuilder.ToImmutable(), cancellationToken).ConfigureAwait(false); + var analyzerToIdMap = new BidirectionalMap(analyzerMapBuilder); - static async Task CreateCompilationWithAnalyzerAsync( - Project project, - ImmutableArray analyzers, - CancellationToken cancellationToken) - { - // Always run analyzers concurrently in OOP - const bool concurrentAnalysis = true; - - // Get original compilation - var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); - - // Fork compilation with concurrent build. this is okay since WithAnalyzers will fork compilation - // anyway to attach event queue. This should make compiling compilation concurrent and make things - // faster - compilation = compilation.WithOptions(compilation.Options.WithConcurrentBuild(concurrentAnalysis)); - - // Run analyzers concurrently, with performance logging and reporting suppressed diagnostics. - // This allows all client requests with or without performance data and/or suppressed diagnostics to be satisfied. - // TODO: can we support analyzerExceptionFilter in remote host? - // right now, host doesn't support watson, we might try to use new NonFatal watson API? - var analyzerOptions = new CompilationWithAnalyzersOptions( - options: new WorkspaceAnalyzerOptions(project.AnalyzerOptions, project.Solution), - onAnalyzerException: null, - analyzerExceptionFilter: null, - concurrentAnalysis: concurrentAnalysis, - logAnalyzerExecutionTime: true, - reportSuppressedDiagnostics: true); - - return compilation.WithAnalyzers(analyzers, analyzerOptions); - } + return new CompilationWithAnalyzersCacheEntry(compilationWithAnalyzers, analyzerToIdMap); + + static async Task CreateCompilationWithAnalyzerAsync( + Project project, + ImmutableArray analyzers, + CancellationToken cancellationToken) + { + // Always run analyzers concurrently in OOP + const bool concurrentAnalysis = true; + + // Get original compilation + var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); + + // Fork compilation with concurrent build. this is okay since WithAnalyzers will fork compilation + // anyway to attach event queue. This should make compiling compilation concurrent and make things + // faster + compilation = compilation.WithOptions(compilation.Options.WithConcurrentBuild(concurrentAnalysis)); + + // Run analyzers concurrently, with performance logging and reporting suppressed diagnostics. + // This allows all client requests with or without performance data and/or suppressed diagnostics to be satisfied. + // TODO: can we support analyzerExceptionFilter in remote host? + // right now, host doesn't support watson, we might try to use new NonFatal watson API? + var analyzerOptions = new CompilationWithAnalyzersOptions( + options: new WorkspaceAnalyzerOptions(project.AnalyzerOptions, project.Solution), + onAnalyzerException: null, + analyzerExceptionFilter: null, + concurrentAnalysis: concurrentAnalysis, + logAnalyzerExecutionTime: true, + reportSuppressedDiagnostics: true); + + return compilation.WithAnalyzers(analyzers, analyzerOptions); + } + } + + private sealed class CompilationWithAnalyzersCacheEntry + { + public CompilationWithAnalyzers CompilationWithAnalyzers { get; } + public BidirectionalMap AnalyzerToIdMap { get; } + + public CompilationWithAnalyzersCacheEntry(CompilationWithAnalyzers compilationWithAnalyzers, BidirectionalMap analyzerToIdMap) + { + CompilationWithAnalyzers = compilationWithAnalyzers; + AnalyzerToIdMap = analyzerToIdMap; } } }