File: Diagnostics\Service\DiagnosticAnalyzerService_CoreAnalyze.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.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Threading;
using Microsoft.CodeAnalysis.Workspaces.Diagnostics;
using Roslyn.Utilities;
using RoslynLogger = Microsoft.CodeAnalysis.Internal.Log.Logger;
 
namespace Microsoft.CodeAnalysis.Diagnostics;
 
internal sealed partial class DiagnosticAnalyzerService
{
    private async Task<DiagnosticAnalysisResultMap<DiagnosticAnalyzer, DiagnosticAnalysisResult>> AnalyzeInProcessAsync(
        DocumentAnalysisScope? documentAnalysisScope,
        Project project,
        CompilationWithAnalyzers compilationWithAnalyzers,
        bool logPerformanceInfo,
        bool getTelemetryInfo,
        CancellationToken cancellationToken)
    {
        var result = await AnalyzeAsync().ConfigureAwait(false);
        Debug.Assert(getTelemetryInfo || result.TelemetryInfo.IsEmpty);
        return result;
 
        async Task<DiagnosticAnalysisResultMap<DiagnosticAnalyzer, DiagnosticAnalysisResult>> AnalyzeAsync()
        {
            var analysisResult = await GetAnalysisResultAsync().ConfigureAwait(false);
            var additionalPragmaSuppressionDiagnostics = await GetPragmaSuppressionAnalyzerDiagnosticsAsync().ConfigureAwait(false);
 
            if (logPerformanceInfo)
            {
                // if remote host is there, report performance data
                var asyncToken = _listener.BeginAsyncOperation(nameof(AnalyzeInProcessAsync));
                var _ = Task.Run(
                    () => ReportAnalyzerPerformance(analysisResult),
                    cancellationToken).CompletesAsyncOperation(asyncToken);
            }
 
            var analyzers = documentAnalysisScope?.Analyzers ?? compilationWithAnalyzers.Analyzers;
            var skippedAnalyzersInfo = project.Solution.SolutionState.Analyzers.GetSkippedAnalyzersInfo(project.State, _analyzerInfoCache);
 
            // get compiler result builder map
            var builderMap = ImmutableDictionary<DiagnosticAnalyzer, DiagnosticAnalysisResultBuilder>.Empty;
            if (analysisResult is not null)
            {
                var map = await analysisResult.ToResultBuilderMapAsync(
                    additionalPragmaSuppressionDiagnostics, documentAnalysisScope, project,
                    analyzers, skippedAnalyzersInfo, cancellationToken).ConfigureAwait(false);
                builderMap = builderMap.AddRange(map);
            }
 
            var result = builderMap.ToImmutableDictionary(kv => kv.Key, kv => DiagnosticAnalysisResult.CreateFromBuilder(kv.Value));
            var telemetry = ImmutableDictionary<DiagnosticAnalyzer, AnalyzerTelemetryInfo>.Empty;
            if (getTelemetryInfo && analysisResult is not null)
            {
                telemetry = analysisResult.AnalyzerTelemetryInfo;
            }
 
            return DiagnosticAnalysisResultMap.Create(result, telemetry);
        }
 
        void ReportAnalyzerPerformance(AnalysisResult analysisResult)
        {
            try
            {
                // +1 for project itself
                var count = documentAnalysisScope != null ? 1 : project.DocumentIds.Count + 1;
                var forSpanAnalysis = documentAnalysisScope?.Span.HasValue ?? false;
 
                ImmutableArray<AnalyzerPerformanceInfo> performanceInfo = [];
                if (analysisResult is not null)
                {
                    performanceInfo = performanceInfo.AddRange(analysisResult.AnalyzerTelemetryInfo.ToAnalyzerPerformanceInfo(_analyzerInfoCache));
                }
 
                using (RoslynLogger.LogBlock(FunctionId.CodeAnalysisService_ReportAnalyzerPerformance, cancellationToken))
                {
                    var service = project.Solution.Services.GetService<IPerformanceTrackerService>();
                    service?.AddSnapshot(performanceInfo, count, forSpanAnalysis);
                }
            }
            catch (Exception ex) when (FatalError.ReportAndCatchUnlessCanceled(ex, cancellationToken))
            {
                // ignore all, this is fire and forget method
            }
        }
 
        async Task<AnalysisResult> GetAnalysisResultAsync()
        {
            if (documentAnalysisScope == null)
            {
                return await compilationWithAnalyzers.GetAnalysisResultAsync(cancellationToken).ConfigureAwait(false);
            }
 
            Debug.Assert(documentAnalysisScope.Analyzers.ToSet().IsSubsetOf(compilationWithAnalyzers.Analyzers));
 
            switch (documentAnalysisScope.Kind)
            {
                case AnalysisKind.Syntax:
                    if (documentAnalysisScope.TextDocument is Document document)
                    {
                        var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
                        return await compilationWithAnalyzers.GetAnalysisResultAsync(tree, documentAnalysisScope.Span, documentAnalysisScope.Analyzers, cancellationToken).ConfigureAwait(false);
                    }
                    else
                    {
                        return await compilationWithAnalyzers.GetAnalysisResultAsync(documentAnalysisScope.AdditionalFile, documentAnalysisScope.Span, documentAnalysisScope.Analyzers, cancellationToken).ConfigureAwait(false);
                    }
 
                case AnalysisKind.Semantic:
                    var model = await ((Document)documentAnalysisScope.TextDocument).GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
                    return await compilationWithAnalyzers.GetAnalysisResultAsync(model, documentAnalysisScope.Span, documentAnalysisScope.Analyzers, cancellationToken).ConfigureAwait(false);
 
                default:
                    throw ExceptionUtilities.UnexpectedValue(documentAnalysisScope.Kind);
            }
        }
 
        async Task<ImmutableArray<Diagnostic>> GetPragmaSuppressionAnalyzerDiagnosticsAsync()
        {
            var hostAnalyzerInfo = this.GetOrCreateHostAnalyzerInfo_OnlyCallInProcess(project);
            var analyzers = documentAnalysisScope?.Analyzers ?? compilationWithAnalyzers.Analyzers;
 
            // NOTE: It is unclear why we filter down to host analyzers here, instead of just using all the specified
            // analyzers.  This behavior is historical, and we are preserving it for now.  However, it may be incorrect
            // and could be changed in the future if we find a scenario that requires it.
            var hostAnalyzers = analyzers.WhereAsArray(static (a, info) => info.IsHostAnalyzer(a), hostAnalyzerInfo);
 
            var suppressionAnalyzer = hostAnalyzers.OfType<IPragmaSuppressionsAnalyzer>().FirstOrDefault();
            if (suppressionAnalyzer == null)
                return [];
 
            if (documentAnalysisScope != null)
            {
                if (documentAnalysisScope.TextDocument is not Document document)
                    return [];
 
                using var _ = ArrayBuilder<Diagnostic>.GetInstance(out var diagnosticsBuilder);
                await AnalyzeDocumentAsync(
                    compilationWithAnalyzers, _analyzerInfoCache, suppressionAnalyzer,
                    document, documentAnalysisScope.Span, diagnosticsBuilder.Add, cancellationToken).ConfigureAwait(false);
                return diagnosticsBuilder.ToImmutableAndClear();
            }
            else
            {
                if (compilationWithAnalyzers.AnalysisOptions.ConcurrentAnalysis)
                {
                    return await ProducerConsumer<Diagnostic>.RunParallelAsync(
                        source: project.GetAllRegularAndSourceGeneratedDocumentsAsync(cancellationToken),
                        produceItems: static async (document, callback, args, cancellationToken) =>
                        {
                            var (compilationWithAnalyzers, analyzerInfoCache, suppressionAnalyzer) = args;
                            await AnalyzeDocumentAsync(
                                compilationWithAnalyzers, analyzerInfoCache, suppressionAnalyzer,
                                document, span: null, callback, cancellationToken).ConfigureAwait(false);
                        },
                        args: (compilationWithAnalyzers, _analyzerInfoCache, suppressionAnalyzer),
                        cancellationToken).ConfigureAwait(false);
                }
                else
                {
                    using var _ = ArrayBuilder<Diagnostic>.GetInstance(out var diagnosticsBuilder);
                    await foreach (var document in project.GetAllRegularAndSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false))
                    {
                        await AnalyzeDocumentAsync(
                            compilationWithAnalyzers, _analyzerInfoCache, suppressionAnalyzer,
                            document, span: null, diagnosticsBuilder.Add, cancellationToken).ConfigureAwait(false);
                    }
 
                    return diagnosticsBuilder.ToImmutableAndClear();
                }
            }
        }
 
        static async Task AnalyzeDocumentAsync(
            CompilationWithAnalyzers hostCompilationWithAnalyzers,
            DiagnosticAnalyzerInfoCache analyzerInfoCache,
            IPragmaSuppressionsAnalyzer suppressionAnalyzer,
            Document document,
            TextSpan? span,
            Action<Diagnostic> reportDiagnostic,
            CancellationToken cancellationToken)
        {
            var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
            await suppressionAnalyzer.AnalyzeAsync(
                semanticModel, span, hostCompilationWithAnalyzers, analyzerInfoCache.GetDiagnosticDescriptors, reportDiagnostic, cancellationToken).ConfigureAwait(false);
        }
    }
}