File: Diagnostics\Service\EngineV2\DiagnosticIncrementalAnalyzer.Executor.cs
Web Access
Project: src\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features)
// 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.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics.Telemetry;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Workspaces.Diagnostics;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Diagnostics;
 
internal sealed partial class DiagnosticAnalyzerService
{
    private sealed partial class DiagnosticIncrementalAnalyzer
    {
        /// <summary>
        /// Return all diagnostics that belong to given project for the given <see cref="DiagnosticAnalyzer"/> either
        /// from cache or by calculating them.
        /// </summary>
        private async Task<ImmutableDictionary<DiagnosticAnalyzer, DiagnosticAnalysisResult>> ComputeDiagnosticAnalysisResultsAsync(
            CompilationWithAnalyzersPair? compilationWithAnalyzers,
            Project project,
            ImmutableArray<DocumentDiagnosticAnalyzer> analyzers,
            CancellationToken cancellationToken)
        {
            using (Logger.LogBlock(FunctionId.Diagnostics_ProjectDiagnostic, GetProjectLogMessage, project, analyzers, cancellationToken))
            {
                try
                {
                    var result = await ComputeDiagnosticsForAnalyzersAsync(analyzers).ConfigureAwait(false);
 
                    // If project is not loaded successfully, get rid of any semantic errors from compiler analyzer.
                    // Note: In the past when project was not loaded successfully we did not run any analyzers on the project.
                    // Now we run analyzers but filter out some information. So on such projects, there will be some perf degradation.
                    result = await RemoveCompilerSemanticErrorsIfProjectNotLoadedAsync(result).ConfigureAwait(false);
 
                    return result;
                }
                catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken))
                {
                    throw ExceptionUtilities.Unreachable();
                }
            }
 
            async Task<ImmutableDictionary<DiagnosticAnalyzer, DiagnosticAnalysisResult>> RemoveCompilerSemanticErrorsIfProjectNotLoadedAsync(
                ImmutableDictionary<DiagnosticAnalyzer, DiagnosticAnalysisResult> result)
            {
                // see whether solution is loaded successfully
                var projectLoadedSuccessfully = await project.HasSuccessfullyLoadedAsync(cancellationToken).ConfigureAwait(false);
                if (projectLoadedSuccessfully)
                {
                    return result;
                }
 
                var compilerAnalyzer = project.Solution.SolutionState.Analyzers.GetCompilerDiagnosticAnalyzer(project.Language);
                if (compilerAnalyzer == null)
                {
                    // this language doesn't support compiler analyzer
                    return result;
                }
 
                if (!result.TryGetValue(compilerAnalyzer, out var analysisResult))
                {
                    // no result from compiler analyzer
                    return result;
                }
 
                Logger.Log(FunctionId.Diagnostics_ProjectDiagnostic, p => $"Failed to Load Successfully ({p.FilePath ?? p.Name})", project);
 
                // get rid of any result except syntax from compiler analyzer result
                var newCompilerAnalysisResult = analysisResult.DropExceptSyntax();
 
                // return new result
                return result.SetItem(compilerAnalyzer, newCompilerAnalysisResult);
            }
 
            // <summary>
            // Calculate all diagnostics for a given project using analyzers referenced by the project and specified IDE analyzers.
            // </summary>
            async Task<ImmutableDictionary<DiagnosticAnalyzer, DiagnosticAnalysisResult>> ComputeDiagnosticsForAnalyzersAsync(
                ImmutableArray<DocumentDiagnosticAnalyzer> ideAnalyzers)
            {
                try
                {
                    var result = ImmutableDictionary<DiagnosticAnalyzer, DiagnosticAnalysisResult>.Empty;
 
                    // can be null if given project doesn't support compilation.
                    if (compilationWithAnalyzers?.ProjectAnalyzers.Length > 0
                        || compilationWithAnalyzers?.HostAnalyzers.Length > 0)
                    {
                        // calculate regular diagnostic analyzers diagnostics
                        var resultMap = await _diagnosticAnalyzerRunner.AnalyzeProjectAsync(
                            project, compilationWithAnalyzers, logPerformanceInfo: false, getTelemetryInfo: true, cancellationToken).ConfigureAwait(false);
 
                        result = resultMap.AnalysisResult;
 
                        // record telemetry data
                        UpdateAnalyzerTelemetryData(resultMap.TelemetryInfo);
                    }
 
                    // check whether there is IDE specific project diagnostic analyzer
                    return await MergeProjectDiagnosticAnalyzerDiagnosticsAsync(ideAnalyzers, result).ConfigureAwait(false);
                }
                catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken))
                {
                    throw ExceptionUtilities.Unreachable();
                }
            }
 
            async Task<ImmutableDictionary<DiagnosticAnalyzer, DiagnosticAnalysisResult>> MergeProjectDiagnosticAnalyzerDiagnosticsAsync(
                ImmutableArray<DocumentDiagnosticAnalyzer> ideAnalyzers,
                ImmutableDictionary<DiagnosticAnalyzer, DiagnosticAnalysisResult> result)
            {
                try
                {
                    var compilation = compilationWithAnalyzers?.HostCompilation;
 
                    foreach (var documentAnalyzer in ideAnalyzers)
                    {
                        var builder = new DiagnosticAnalysisResultBuilder(project);
 
                        foreach (var textDocument in project.AdditionalDocuments.Concat(project.Documents))
                        {
                            var tree = textDocument is Document document
                                ? await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false)
                                : null;
                            var syntaxDiagnostics = await DocumentAnalysisExecutor.ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(documentAnalyzer, textDocument, AnalysisKind.Syntax, compilation, tree, cancellationToken).ConfigureAwait(false);
                            var semanticDiagnostics = await DocumentAnalysisExecutor.ComputeDocumentDiagnosticAnalyzerDiagnosticsAsync(documentAnalyzer, textDocument, AnalysisKind.Semantic, compilation, tree, cancellationToken).ConfigureAwait(false);
 
                            if (tree != null)
                            {
                                builder.AddSyntaxDiagnostics(tree, syntaxDiagnostics);
                                builder.AddSemanticDiagnostics(tree, semanticDiagnostics);
                            }
                            else
                            {
                                builder.AddExternalSyntaxDiagnostics(textDocument.Id, syntaxDiagnostics);
                                builder.AddExternalSemanticDiagnostics(textDocument.Id, semanticDiagnostics);
                            }
                        }
 
                        // merge the result to existing one.
                        // there can be existing one from compiler driver with empty set. overwrite it with
                        // ide one.
                        result = result.SetItem(documentAnalyzer, DiagnosticAnalysisResult.CreateFromBuilder(builder));
                    }
 
                    return result;
                }
                catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken))
                {
                    throw ExceptionUtilities.Unreachable();
                }
            }
 
            void UpdateAnalyzerTelemetryData(ImmutableDictionary<DiagnosticAnalyzer, AnalyzerTelemetryInfo> telemetry)
            {
                foreach (var (analyzer, telemetryInfo) in telemetry)
                {
                    var isTelemetryCollectionAllowed = DiagnosticAnalyzerInfoCache.IsTelemetryCollectionAllowed(analyzer);
                    _telemetry.UpdateAnalyzerActionsTelemetry(analyzer, telemetryInfo, isTelemetryCollectionAllowed);
                }
            }
        }
    }
}