File: DiagnosticAnalyzer\AnalysisResultBuilder.cs
Web Access
Project: src\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis)
// 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.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.Diagnostics.Telemetry;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Diagnostics
{
    /// <summary>
    /// Stores the results of analyzer execution:
    /// 1. Local and non-local diagnostics, per-analyzer.
    /// 2. Analyzer execution times, if requested.
    /// </summary>
    internal sealed class AnalysisResultBuilder
    {
        private static readonly ImmutableDictionary<string, OneOrMany<AdditionalText>> s_emptyPathToAdditionalTextMap =
            ImmutableDictionary<string, OneOrMany<AdditionalText>>.Empty.WithComparers(PathUtilities.Comparer);
 
        private readonly object _gate = new object();
        private readonly Dictionary<DiagnosticAnalyzer, TimeSpan>? _analyzerExecutionTimeOpt;
        private readonly HashSet<DiagnosticAnalyzer> _completedAnalyzersForCompilation;
        private readonly Dictionary<SyntaxTree, HashSet<DiagnosticAnalyzer>> _completedSyntaxAnalyzersByTree;
        private readonly Dictionary<SyntaxTree, HashSet<DiagnosticAnalyzer>> _completedSemanticAnalyzersByTree;
        private readonly Dictionary<AdditionalText, HashSet<DiagnosticAnalyzer>> _completedSyntaxAnalyzersByAdditionalFile;
        private readonly Dictionary<DiagnosticAnalyzer, AnalyzerActionCounts> _analyzerActionCounts;
        private readonly ImmutableDictionary<string, OneOrMany<AdditionalText>> _pathToAdditionalTextMap;
 
        private Dictionary<SyntaxTree, Dictionary<DiagnosticAnalyzer, ImmutableArray<Diagnostic>.Builder>>? _localSemanticDiagnosticsOpt = null;
        private Dictionary<SyntaxTree, Dictionary<DiagnosticAnalyzer, ImmutableArray<Diagnostic>.Builder>>? _localSyntaxDiagnosticsOpt = null;
        private Dictionary<AdditionalText, Dictionary<DiagnosticAnalyzer, ImmutableArray<Diagnostic>.Builder>>? _localAdditionalFileDiagnosticsOpt = null;
        private Dictionary<DiagnosticAnalyzer, ImmutableArray<Diagnostic>.Builder>? _nonLocalDiagnosticsOpt = null;
 
        internal AnalysisResultBuilder(bool logAnalyzerExecutionTime, ImmutableArray<DiagnosticAnalyzer> analyzers, ImmutableArray<AdditionalText> additionalFiles)
        {
            _analyzerExecutionTimeOpt = logAnalyzerExecutionTime ? CreateAnalyzerExecutionTimeMap(analyzers) : null;
            _completedAnalyzersForCompilation = new HashSet<DiagnosticAnalyzer>();
            _completedSyntaxAnalyzersByTree = new Dictionary<SyntaxTree, HashSet<DiagnosticAnalyzer>>();
            _completedSemanticAnalyzersByTree = new Dictionary<SyntaxTree, HashSet<DiagnosticAnalyzer>>();
            _completedSyntaxAnalyzersByAdditionalFile = new Dictionary<AdditionalText, HashSet<DiagnosticAnalyzer>>();
            _analyzerActionCounts = new Dictionary<DiagnosticAnalyzer, AnalyzerActionCounts>(analyzers.Length);
            _pathToAdditionalTextMap = CreatePathToAdditionalTextMap(additionalFiles);
        }
 
        private static Dictionary<DiagnosticAnalyzer, TimeSpan> CreateAnalyzerExecutionTimeMap(ImmutableArray<DiagnosticAnalyzer> analyzers)
        {
            var map = new Dictionary<DiagnosticAnalyzer, TimeSpan>(analyzers.Length);
            foreach (var analyzer in analyzers)
            {
                map[analyzer] = default;
            }
 
            return map;
        }
 
        private static ImmutableDictionary<string, OneOrMany<AdditionalText>> CreatePathToAdditionalTextMap(ImmutableArray<AdditionalText> additionalFiles)
        {
            if (additionalFiles.IsEmpty)
            {
                return s_emptyPathToAdditionalTextMap;
            }
 
            var builder = ImmutableDictionary.CreateBuilder<string, OneOrMany<AdditionalText>>(PathUtilities.Comparer);
            foreach (var file in additionalFiles)
            {
                // Null file path for additional files is not possible from IDE or command line compiler host.
                // However, it is possible from custom third party analysis hosts.
                // Ensure we handle it gracefully
                var path = file.Path ?? string.Empty;
 
                // Handle multiple additional files with same path.
                if (builder.TryGetValue(path, out var value))
                {
                    value = value.Add(file);
                }
                else
                {
                    value = new OneOrMany<AdditionalText>(file);
                }
 
                builder[path] = value;
            }
 
            return builder.ToImmutable();
        }
 
        public TimeSpan GetAnalyzerExecutionTime(DiagnosticAnalyzer analyzer)
        {
            Debug.Assert(_analyzerExecutionTimeOpt != null);
 
            lock (_gate)
            {
                return _analyzerExecutionTimeOpt[analyzer];
            }
        }
 
        private HashSet<DiagnosticAnalyzer>? GetCompletedAnalyzersForFile_NoLock(SourceOrAdditionalFile filterFile, bool syntax)
        {
            if (filterFile.SourceTree is { } tree)
            {
                var completedAnalyzersByTree = syntax ? _completedSyntaxAnalyzersByTree : _completedSemanticAnalyzersByTree;
                if (completedAnalyzersByTree.TryGetValue(tree, out var completedAnalyzersForTree))
                {
                    return completedAnalyzersForTree;
                }
            }
            else if (filterFile.AdditionalFile is { } additionalFile)
            {
                if (_completedSyntaxAnalyzersByAdditionalFile.TryGetValue(additionalFile, out var completedAnalyzersForFile))
                {
                    return completedAnalyzersForFile;
                }
            }
            else
            {
                throw ExceptionUtilities.Unreachable();
            }
 
            return null;
        }
 
        private void AddCompletedAnalyzerForFile_NoLock(SourceOrAdditionalFile filterFile, bool syntax, DiagnosticAnalyzer analyzer)
        {
            var completedAnalyzers = new HashSet<DiagnosticAnalyzer> { analyzer };
            if (filterFile.SourceTree is { } tree)
            {
                var completedAnalyzersByTree = syntax ? _completedSyntaxAnalyzersByTree : _completedSemanticAnalyzersByTree;
                completedAnalyzersByTree.Add(tree, completedAnalyzers);
            }
            else if (filterFile.AdditionalFile is { } additionalFile)
            {
                _completedSyntaxAnalyzersByAdditionalFile.Add(additionalFile, completedAnalyzers);
            }
            else
            {
                throw ExceptionUtilities.Unreachable();
            }
        }
 
        /// <summary>
        /// Filters down the given <paramref name="analyzers"/> to only retain the analyzers which have
        /// not completed execution. If the <paramref name="filterScope"/> is non-null, then return
        /// the analyzers which have not fully exected on the filterScope. Otherwise, return the analyzers
        /// which have not fully executed on the entire compilation.
        /// </summary>
        /// <param name="analyzers">Analyzers to be filtered.</param>
        /// <param name="filterScope">Optional scope for filtering.</param>
        /// <returns>
        /// Analyzers which have not fully executed on the given <paramref name="filterScope"/>, if non-null,
        /// or the entire compilation, if <paramref name="filterScope"/> is null.
        /// </returns>
        public ImmutableArray<DiagnosticAnalyzer> GetPendingAnalyzers(ImmutableArray<DiagnosticAnalyzer> analyzers, (SourceOrAdditionalFile file, bool syntax)? filterScope)
        {
            lock (_gate)
            {
                // If we have a non-null filter scope, then fetch the set of analyzers that have
                // already completed execution on this filter scope.
                var completedAnalyzersForFile = filterScope.HasValue
                    ? GetCompletedAnalyzersForFile_NoLock(filterScope.Value.file, filterScope.Value.syntax)
                    : null;
 
                return analyzers.WhereAsArray(
                    static (analyzer, arg) =>
                    {
                        // If the analyzer has not executed for the entire compilation, or we are computing
                        // pending analyzers for a specific filterScope and the analyzer has not executed on
                        // this filter scope, then we add the analyzer to pending analyzers.
                        if (!arg.self._completedAnalyzersForCompilation.Contains(analyzer) &&
                            (arg.completedAnalyzersForFile == null || !arg.completedAnalyzersForFile.Contains(analyzer)))
                        {
                            return true;
                        }
 
                        return false;
                    },
                    (self: this, completedAnalyzersForFile));
            }
        }
 
        public void ApplySuppressionsAndStoreAnalysisResult(AnalysisScope analysisScope, AnalyzerDriver driver, Compilation compilation, Func<DiagnosticAnalyzer, AnalyzerActionCounts> getAnalyzerActionCounts, CancellationToken cancellationToken)
        {
            foreach (var analyzer in analysisScope.Analyzers)
            {
                // Dequeue reported analyzer diagnostics from the driver and store them in our maps.
                var syntaxDiagnostics = driver.DequeueLocalDiagnosticsAndApplySuppressions(analyzer, syntax: true, compilation: compilation, cancellationToken);
                var semanticDiagnostics = driver.DequeueLocalDiagnosticsAndApplySuppressions(analyzer, syntax: false, compilation: compilation, cancellationToken);
                var compilationDiagnostics = driver.DequeueNonLocalDiagnosticsAndApplySuppressions(analyzer, compilation, cancellationToken);
 
                lock (_gate)
                {
                    if (_completedAnalyzersForCompilation.Contains(analyzer))
                    {
                        // Already stored full analysis result for this analyzer.
                        continue;
                    }
 
                    // Determine if we have computed fully syntax/semantic diagnostics
                    // for a specific filter file or the entire compilation for this analyzer.
                    // If we have full diagnostics for the filter file/compilation, we add this analyzer to
                    // the corresponding completed analyzers set to avoid re-executing this analyzer
                    // for future diagnostic requests on this analysis scope.
                    bool fullSyntaxDiagnosticsForTree = false;
                    bool fullSyntaxDiagnosticsForAdditionalFile = false;
                    bool fullSemanticDiagnosticsForTree = false;
                    bool fullCompilationDiagnostics = false;
 
                    // Check if we computed syntax/semantic diagnostics for a specific filter file only.
                    if (analysisScope.FilterFileOpt.HasValue)
                    {
                        var completedAnalyzersForFile = GetCompletedAnalyzersForFile_NoLock(analysisScope.FilterFileOpt.Value, analysisScope.IsSyntacticSingleFileAnalysis);
                        if (completedAnalyzersForFile?.Contains(analyzer) == true)
                        {
                            // Already stored analysis result for this analyzer for the analyzed file.
                            continue;
                        }
                        else if (!analysisScope.FilterSpanOpt.HasValue && !analysisScope.OriginalFilterSpan.HasValue)
                        {
                            // We have complete analysis result for this file.
                            // Mark this file as completely analyzed for this analyzer.
                            if (completedAnalyzersForFile != null)
                            {
                                completedAnalyzersForFile.Add(analyzer);
                            }
                            else
                            {
                                AddCompletedAnalyzerForFile_NoLock(analysisScope.FilterFileOpt.Value, analysisScope.IsSyntacticSingleFileAnalysis, analyzer);
                            }
 
                            // Set the appropriate full diagnostics for tree/additional file flag.
                            if (analysisScope.IsSyntacticSingleFileAnalysis)
                            {
                                if (analysisScope.FilterFileOpt.Value.SourceTree != null)
                                {
                                    fullSyntaxDiagnosticsForTree = true;
                                }
                                else
                                {
                                    fullSyntaxDiagnosticsForAdditionalFile = true;
                                }
                            }
                            else
                            {
                                fullSemanticDiagnosticsForTree = true;
                            }
                        }
                    }
                    else
                    {
                        Debug.Assert(!analysisScope.FilterSpanOpt.HasValue);
 
                        _completedAnalyzersForCompilation.Add(analyzer);
                        fullCompilationDiagnostics = true;
                        fullSyntaxDiagnosticsForTree = true;
                        fullSyntaxDiagnosticsForAdditionalFile = true;
                        fullSemanticDiagnosticsForTree = true;
                    }
 
                    // Finally, update the appropriate syntax/semantic/compilation diagnostic maps to store the
                    // computed diagnostics. If we have full diagnostics for the filter file/compilation,
                    // we overwrite the diagnostics in the map.
                    // Otherwise, we have computed partial diagnostics for a file span, so we just
                    // append to the previously computed and stored diagnostics.
 
                    if (!syntaxDiagnostics.IsEmpty)
                    {
                        UpdateLocalDiagnostics_NoLock(analyzer, syntaxDiagnostics, fullSyntaxDiagnosticsForTree, getSourceTree, ref _localSyntaxDiagnosticsOpt);
                        UpdateLocalDiagnostics_NoLock(analyzer, syntaxDiagnostics, fullSyntaxDiagnosticsForAdditionalFile, getAdditionalTextKey, ref _localAdditionalFileDiagnosticsOpt);
                    }
 
                    if (!semanticDiagnostics.IsEmpty)
                    {
                        UpdateLocalDiagnostics_NoLock(analyzer, semanticDiagnostics, fullSemanticDiagnosticsForTree, getSourceTree, ref _localSemanticDiagnosticsOpt);
                    }
 
                    if (!compilationDiagnostics.IsEmpty)
                    {
                        UpdateNonLocalDiagnostics_NoLock(analyzer, compilationDiagnostics, fullCompilationDiagnostics);
                    }
 
                    if (_analyzerExecutionTimeOpt != null)
                    {
                        var timeSpan = driver.ResetAnalyzerExecutionTime(analyzer);
                        _analyzerExecutionTimeOpt[analyzer] = fullCompilationDiagnostics ?
                            timeSpan :
                            _analyzerExecutionTimeOpt[analyzer] + timeSpan;
                    }
 
                    if (!_analyzerActionCounts.ContainsKey(analyzer))
                    {
                        _analyzerActionCounts.Add(analyzer, getAnalyzerActionCounts(analyzer));
                    }
                }
            }
 
            static SyntaxTree? getSourceTree(Diagnostic diagnostic)
                => diagnostic.Location.SourceTree;
 
            AdditionalText? getAdditionalTextKey(Diagnostic diagnostic)
            {
                // Fetch the first additional file that matches diagnostic location.
                if (diagnostic.Location is ExternalFileLocation externalFileLocation)
                {
                    if (_pathToAdditionalTextMap.TryGetValue(externalFileLocation.GetLineSpan().Path, out var additionalTexts))
                    {
                        foreach (var additionalText in additionalTexts)
                        {
                            if (analysisScope.AdditionalFiles.Contains(additionalText))
                            {
                                return additionalText;
                            }
                        }
                    }
                }
 
                return null;
            }
        }
 
        private void UpdateLocalDiagnostics_NoLock<TKey>(
            DiagnosticAnalyzer analyzer,
            ImmutableArray<Diagnostic> diagnostics,
            bool overwrite,
            Func<Diagnostic, TKey?> getKeyFunc,
            ref Dictionary<TKey, Dictionary<DiagnosticAnalyzer, ImmutableArray<Diagnostic>.Builder>>? lazyLocalDiagnostics)
            where TKey : class
        {
            if (diagnostics.IsEmpty)
            {
                return;
            }
 
            lazyLocalDiagnostics = lazyLocalDiagnostics ?? new Dictionary<TKey, Dictionary<DiagnosticAnalyzer, ImmutableArray<Diagnostic>.Builder>>();
 
            foreach (var diagsByKey in diagnostics.GroupBy(getKeyFunc))
            {
                var key = diagsByKey.Key;
                if (key is null)
                {
                    continue;
                }
 
                Dictionary<DiagnosticAnalyzer, ImmutableArray<Diagnostic>.Builder>? allDiagnostics;
                if (!lazyLocalDiagnostics.TryGetValue(key, out allDiagnostics))
                {
                    allDiagnostics = new Dictionary<DiagnosticAnalyzer, ImmutableArray<Diagnostic>.Builder>();
                    lazyLocalDiagnostics[key] = allDiagnostics;
                }
 
                ImmutableArray<Diagnostic>.Builder? analyzerDiagnostics;
                if (!allDiagnostics.TryGetValue(analyzer, out analyzerDiagnostics))
                {
                    analyzerDiagnostics = ImmutableArray.CreateBuilder<Diagnostic>();
                    allDiagnostics[analyzer] = analyzerDiagnostics;
                }
 
                UpdateDiagnosticsCore_NoLock(analyzerDiagnostics, diagsByKey, overwrite);
            }
        }
 
        private void UpdateNonLocalDiagnostics_NoLock(DiagnosticAnalyzer analyzer, ImmutableArray<Diagnostic> diagnostics, bool overwrite)
        {
            if (diagnostics.IsEmpty)
            {
                return;
            }
 
            _nonLocalDiagnosticsOpt = _nonLocalDiagnosticsOpt ?? new Dictionary<DiagnosticAnalyzer, ImmutableArray<Diagnostic>.Builder>();
 
            ImmutableArray<Diagnostic>.Builder? currentDiagnostics;
            if (!_nonLocalDiagnosticsOpt.TryGetValue(analyzer, out currentDiagnostics))
            {
                currentDiagnostics = ImmutableArray.CreateBuilder<Diagnostic>();
                _nonLocalDiagnosticsOpt[analyzer] = currentDiagnostics;
            }
 
            UpdateDiagnosticsCore_NoLock(currentDiagnostics, diagnostics, overwrite);
        }
 
        private static void UpdateDiagnosticsCore_NoLock(ImmutableArray<Diagnostic>.Builder currentDiagnostics, IEnumerable<Diagnostic> diagnostics, bool overwrite)
        {
            if (overwrite)
            {
                currentDiagnostics.Clear();
            }
            else
            {
                // Always de-dupe diagnostic to add
                diagnostics = diagnostics.Where(d => !currentDiagnostics.Contains(d));
            }
 
            currentDiagnostics.AddRange(diagnostics);
        }
 
        internal ImmutableArray<Diagnostic> GetDiagnostics(AnalysisScope analysisScope, bool getLocalDiagnostics, bool getNonLocalDiagnostics)
        {
            lock (_gate)
            {
                return GetDiagnostics_NoLock(analysisScope, getLocalDiagnostics, getNonLocalDiagnostics);
            }
        }
 
        private ImmutableArray<Diagnostic> GetDiagnostics_NoLock(AnalysisScope analysisScope, bool getLocalDiagnostics, bool getNonLocalDiagnostics)
        {
            Debug.Assert(getLocalDiagnostics || getNonLocalDiagnostics);
 
            var builder = ImmutableArray.CreateBuilder<Diagnostic>();
            if (getLocalDiagnostics)
            {
                if (!analysisScope.IsSingleFileAnalysis)
                {
                    AddAllLocalDiagnostics_NoLock(_localSyntaxDiagnosticsOpt, analysisScope, builder);
                    AddAllLocalDiagnostics_NoLock(_localSemanticDiagnosticsOpt, analysisScope, builder);
                    AddAllLocalDiagnostics_NoLock(_localAdditionalFileDiagnosticsOpt, analysisScope, builder);
                }
                else if (analysisScope.IsSyntacticSingleFileAnalysis)
                {
                    AddLocalDiagnosticsForPartialAnalysis_NoLock(_localSyntaxDiagnosticsOpt, analysisScope, builder);
                    AddLocalDiagnosticsForPartialAnalysis_NoLock(_localAdditionalFileDiagnosticsOpt, analysisScope, builder);
                }
                else
                {
                    AddLocalDiagnosticsForPartialAnalysis_NoLock(_localSemanticDiagnosticsOpt, analysisScope, builder);
                }
            }
 
            if (getNonLocalDiagnostics && _nonLocalDiagnosticsOpt != null)
            {
                AddDiagnostics_NoLock(_nonLocalDiagnosticsOpt, analysisScope.Analyzers, builder);
            }
 
            return builder.ToImmutableArray();
        }
 
        private static void AddAllLocalDiagnostics_NoLock<TKey>(
            Dictionary<TKey, Dictionary<DiagnosticAnalyzer, ImmutableArray<Diagnostic>.Builder>>? lazyLocalDiagnostics,
            AnalysisScope analysisScope,
            ImmutableArray<Diagnostic>.Builder builder)
            where TKey : class
        {
            if (lazyLocalDiagnostics != null)
            {
                foreach (var localDiagsByTree in lazyLocalDiagnostics.Values)
                {
                    AddDiagnostics_NoLock(localDiagsByTree, analysisScope.Analyzers, builder);
                }
            }
        }
 
        private static void AddLocalDiagnosticsForPartialAnalysis_NoLock(
            Dictionary<SyntaxTree, Dictionary<DiagnosticAnalyzer, ImmutableArray<Diagnostic>.Builder>>? localDiagnostics,
            AnalysisScope analysisScope,
            ImmutableArray<Diagnostic>.Builder builder)
            => AddLocalDiagnosticsForPartialAnalysis_NoLock(localDiagnostics, analysisScope.FilterFileOpt!.Value.SourceTree, analysisScope.Analyzers, builder);
 
        private static void AddLocalDiagnosticsForPartialAnalysis_NoLock(
            Dictionary<AdditionalText, Dictionary<DiagnosticAnalyzer, ImmutableArray<Diagnostic>.Builder>>? localDiagnostics,
            AnalysisScope analysisScope,
            ImmutableArray<Diagnostic>.Builder builder)
            => AddLocalDiagnosticsForPartialAnalysis_NoLock(localDiagnostics, analysisScope.FilterFileOpt!.Value.AdditionalFile, analysisScope.Analyzers, builder);
 
        private static void AddLocalDiagnosticsForPartialAnalysis_NoLock<TKey>(
            Dictionary<TKey, Dictionary<DiagnosticAnalyzer, ImmutableArray<Diagnostic>.Builder>>? localDiagnostics,
            TKey? key,
            ImmutableArray<DiagnosticAnalyzer> analyzers,
            ImmutableArray<Diagnostic>.Builder builder)
            where TKey : class
        {
            Dictionary<DiagnosticAnalyzer, ImmutableArray<Diagnostic>.Builder>? diagnosticsForTree;
            if (key != null && localDiagnostics != null && localDiagnostics.TryGetValue(key, out diagnosticsForTree))
            {
                AddDiagnostics_NoLock(diagnosticsForTree, analyzers, builder);
            }
        }
 
        private static void AddDiagnostics_NoLock(
            Dictionary<DiagnosticAnalyzer, ImmutableArray<Diagnostic>.Builder> diagnostics,
            ImmutableArray<DiagnosticAnalyzer> analyzers,
            ImmutableArray<Diagnostic>.Builder builder)
        {
            Debug.Assert(diagnostics != null);
 
            foreach (var analyzer in analyzers)
            {
                ImmutableArray<Diagnostic>.Builder? diagnosticsByAnalyzer;
                if (diagnostics.TryGetValue(analyzer, out diagnosticsByAnalyzer))
                {
                    builder.AddRange(diagnosticsByAnalyzer);
                }
            }
        }
 
        internal AnalysisResult ToAnalysisResult(ImmutableArray<DiagnosticAnalyzer> analyzers, AnalysisScope analysisScope, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
 
            ImmutableDictionary<SyntaxTree, ImmutableDictionary<DiagnosticAnalyzer, ImmutableArray<Diagnostic>>> localSyntaxDiagnostics;
            ImmutableDictionary<SyntaxTree, ImmutableDictionary<DiagnosticAnalyzer, ImmutableArray<Diagnostic>>> localSemanticDiagnostics;
            ImmutableDictionary<AdditionalText, ImmutableDictionary<DiagnosticAnalyzer, ImmutableArray<Diagnostic>>> localAdditionalFileDiagnostics;
            ImmutableDictionary<DiagnosticAnalyzer, ImmutableArray<Diagnostic>> nonLocalDiagnostics;
 
            var analyzersSet = analyzers.ToImmutableHashSet();
            Func<Diagnostic, bool> shouldInclude = analysisScope.ShouldInclude;
            lock (_gate)
            {
                localSyntaxDiagnostics = GetImmutable(analyzersSet, shouldInclude, _localSyntaxDiagnosticsOpt);
                localSemanticDiagnostics = GetImmutable(analyzersSet, shouldInclude, _localSemanticDiagnosticsOpt);
                localAdditionalFileDiagnostics = GetImmutable(analyzersSet, shouldInclude, _localAdditionalFileDiagnosticsOpt);
                nonLocalDiagnostics = GetImmutable(analyzersSet, shouldInclude, _nonLocalDiagnosticsOpt);
            }
 
            cancellationToken.ThrowIfCancellationRequested();
            var analyzerTelemetryInfo = GetTelemetryInfo(analyzers);
            return new AnalysisResult(analyzers, localSyntaxDiagnostics, localSemanticDiagnostics, localAdditionalFileDiagnostics, nonLocalDiagnostics, analyzerTelemetryInfo);
        }
 
        private static ImmutableDictionary<TKey, ImmutableDictionary<DiagnosticAnalyzer, ImmutableArray<Diagnostic>>> GetImmutable<TKey>(
            ImmutableHashSet<DiagnosticAnalyzer> analyzers,
            Func<Diagnostic, bool> shouldInclude,
            Dictionary<TKey, Dictionary<DiagnosticAnalyzer, ImmutableArray<Diagnostic>.Builder>>? localDiagnosticsOpt)
            where TKey : class
        {
            if (localDiagnosticsOpt == null)
            {
                return ImmutableDictionary<TKey, ImmutableDictionary<DiagnosticAnalyzer, ImmutableArray<Diagnostic>>>.Empty;
            }
 
            var builder = ImmutableDictionary.CreateBuilder<TKey, ImmutableDictionary<DiagnosticAnalyzer, ImmutableArray<Diagnostic>>>();
            var perTreeBuilder = ImmutableDictionary.CreateBuilder<DiagnosticAnalyzer, ImmutableArray<Diagnostic>>();
 
            foreach (var diagnosticsByTree in localDiagnosticsOpt)
            {
                var key = diagnosticsByTree.Key;
                foreach (var diagnosticsByAnalyzer in diagnosticsByTree.Value)
                {
                    if (analyzers.Contains(diagnosticsByAnalyzer.Key))
                    {
                        var diagnostics = diagnosticsByAnalyzer.Value.Where(shouldInclude).ToImmutableArray();
                        if (!diagnostics.IsEmpty)
                        {
                            perTreeBuilder.Add(diagnosticsByAnalyzer.Key, diagnostics);
                        }
                    }
                }
 
                builder.Add(key, perTreeBuilder.ToImmutable());
                perTreeBuilder.Clear();
            }
 
            return builder.ToImmutable();
        }
 
        private static ImmutableDictionary<DiagnosticAnalyzer, ImmutableArray<Diagnostic>> GetImmutable(
            ImmutableHashSet<DiagnosticAnalyzer> analyzers,
            Func<Diagnostic, bool> shouldInclude,
            Dictionary<DiagnosticAnalyzer, ImmutableArray<Diagnostic>.Builder>? nonLocalDiagnosticsOpt)
        {
            if (nonLocalDiagnosticsOpt == null)
            {
                return ImmutableDictionary<DiagnosticAnalyzer, ImmutableArray<Diagnostic>>.Empty;
            }
 
            var builder = ImmutableDictionary.CreateBuilder<DiagnosticAnalyzer, ImmutableArray<Diagnostic>>();
            foreach (var diagnosticsByAnalyzer in nonLocalDiagnosticsOpt)
            {
                if (analyzers.Contains(diagnosticsByAnalyzer.Key))
                {
                    var diagnostics = diagnosticsByAnalyzer.Value.Where(shouldInclude).ToImmutableArray();
                    if (!diagnostics.IsEmpty)
                    {
                        builder.Add(diagnosticsByAnalyzer.Key, diagnostics);
                    }
                }
            }
 
            return builder.ToImmutable();
        }
 
        private ImmutableDictionary<DiagnosticAnalyzer, AnalyzerTelemetryInfo> GetTelemetryInfo(
            ImmutableArray<DiagnosticAnalyzer> analyzers)
        {
            var builder = ImmutableDictionary.CreateBuilder<DiagnosticAnalyzer, AnalyzerTelemetryInfo>();
 
            lock (_gate)
            {
                foreach (var analyzer in analyzers)
                {
                    if (!_analyzerActionCounts.TryGetValue(analyzer, out var actionCounts))
                    {
                        actionCounts = AnalyzerActionCounts.Empty;
                    }
 
                    var suppressionActionCounts = analyzer is DiagnosticSuppressor ? 1 : 0;
                    var executionTime = _analyzerExecutionTimeOpt != null ? _analyzerExecutionTimeOpt[analyzer] : default;
                    var telemetryInfo = new AnalyzerTelemetryInfo(actionCounts, suppressionActionCounts, executionTime);
                    builder.Add(analyzer, telemetryInfo);
                }
            }
 
            return builder.ToImmutable();
        }
    }
}