File: Diagnostics\Service\DiagnosticAnalyzerService_RemoteOrLocalDispatcher.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.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Text;
 
namespace Microsoft.CodeAnalysis.Diagnostics;
 
// this part contains the methods that will attempt to call out to OOP to do the work, and fall back
// to processing locally if it is not available (or we are already in OOP).
 
internal sealed partial class DiagnosticAnalyzerService : IDiagnosticAnalyzerService
{
    public async Task<ImmutableArray<DiagnosticData>> ForceRunCodeAnalysisDiagnosticsAsync(
        Project project, CancellationToken cancellationToken)
    {
        var client = await RemoteHostClient.TryGetClientAsync(project, cancellationToken).ConfigureAwait(false);
        if (client is not null)
        {
            var descriptors = await client.TryInvokeAsync<IRemoteDiagnosticAnalyzerService, ImmutableArray<DiagnosticData>>(
                project,
                (service, solution, cancellationToken) => service.ForceRunCodeAnalysisDiagnosticsAsync(
                    solution, project.Id, cancellationToken),
                cancellationToken).ConfigureAwait(false);
            return descriptors.HasValue ? descriptors.Value : [];
        }
 
        // Otherwise, fallback to computing in proc.
        return await ForceRunCodeAnalysisDiagnosticsInProcessAsync(project, cancellationToken).ConfigureAwait(false);
    }
 
    public async Task<ImmutableArray<DiagnosticDescriptor>> GetDiagnosticDescriptorsAsync(
        Solution solution, ProjectId projectId, AnalyzerReference analyzerReference, string language, CancellationToken cancellationToken)
    {
        // Attempt to compute this OOP.
        var client = await RemoteHostClient.TryGetClientAsync(solution.Services, cancellationToken).ConfigureAwait(false);
        if (client is not null &&
            analyzerReference is AnalyzerFileReference analyzerFileReference)
        {
            var descriptors = await client.TryInvokeAsync<IRemoteDiagnosticAnalyzerService, ImmutableArray<DiagnosticDescriptorData>>(
                solution,
                (service, solution, cancellationToken) => service.GetDiagnosticDescriptorsAsync(solution, projectId, analyzerFileReference.FullPath, language, cancellationToken),
                cancellationToken).ConfigureAwait(false);
            if (!descriptors.HasValue)
                return [];
 
            return descriptors.Value.SelectAsArray(d => d.ToDiagnosticDescriptor());
        }
 
        // Otherwise, fallback to computing in proc.
        return analyzerReference
            .GetAnalyzers(language)
            .SelectManyAsArray(this._analyzerInfoCache.GetDiagnosticDescriptors);
    }
 
    public async Task<ImmutableArray<string>> GetCompilationEndDiagnosticDescriptorIdsAsync(
        Solution solution, CancellationToken cancellationToken)
    {
        var client = await RemoteHostClient.TryGetClientAsync(solution.Services, cancellationToken).ConfigureAwait(false);
        if (client is not null)
        {
            var result = await client.TryInvokeAsync<IRemoteDiagnosticAnalyzerService, ImmutableArray<string>>(
                solution,
                (service, solution, cancellationToken) => service.GetCompilationEndDiagnosticDescriptorIdsAsync(
                    solution, cancellationToken),
                cancellationToken).ConfigureAwait(false);
 
            return result.HasValue ? result.Value : [];
        }
 
        using var _1 = PooledHashSet<string>.GetInstance(out var builder);
        using var _2 = PooledHashSet<(object Reference, string Language)>.GetInstance(out var seenAnalyzerReferencesByLanguage);
 
        foreach (var project in solution.Projects)
        {
            var analyzersPerReferenceMap = solution.SolutionState.Analyzers.CreateDiagnosticAnalyzersPerReference(project);
            foreach (var (analyzerReference, analyzers) in analyzersPerReferenceMap)
            {
                if (!seenAnalyzerReferencesByLanguage.Add((analyzerReference, project.Language)))
                    continue;
 
                foreach (var analyzer in analyzers)
                {
                    if (analyzer.IsCompilerAnalyzer())
                        continue;
 
                    foreach (var buildOnlyDescriptor in _analyzerInfoCache.GetCompilationEndDiagnosticDescriptors(analyzer))
                        builder.Add(buildOnlyDescriptor.Id);
                }
            }
        }
 
        return builder.ToImmutableArray();
    }
 
    public async Task<ImmutableDictionary<string, ImmutableArray<DiagnosticDescriptor>>> GetDiagnosticDescriptorsPerReferenceAsync(
        Solution solution, ProjectId? projectId, CancellationToken cancellationToken)
    {
        var client = await RemoteHostClient.TryGetClientAsync(solution.Services, cancellationToken).ConfigureAwait(false);
        if (client is not null)
        {
            var map = await client.TryInvokeAsync<IRemoteDiagnosticAnalyzerService, ImmutableDictionary<string, ImmutableArray<DiagnosticDescriptorData>>>(
                solution,
                (service, solution, cancellationToken) => service.GetDiagnosticDescriptorsPerReferenceAsync(
                    solution, projectId, cancellationToken),
                cancellationToken).ConfigureAwait(false);
            if (!map.HasValue)
                return ImmutableDictionary<string, ImmutableArray<DiagnosticDescriptor>>.Empty;
 
            return map.Value.ToImmutableDictionary(
                kvp => kvp.Key,
                kvp => kvp.Value.SelectAsArray(d => d.ToDiagnosticDescriptor()));
        }
 
        return solution.SolutionState.Analyzers.GetDiagnosticDescriptorsPerReference(
            this._analyzerInfoCache, solution.GetProject(projectId));
    }
 
    public async Task<ImmutableArray<DiagnosticData>> GetDiagnosticsForIdsAsync(
        Project project, ImmutableArray<DocumentId> documentIds, ImmutableHashSet<string>? diagnosticIds, AnalyzerFilter analyzerFilter, bool includeLocalDocumentDiagnostics, CancellationToken cancellationToken)
    {
        var client = await RemoteHostClient.TryGetClientAsync(project, cancellationToken).ConfigureAwait(false);
        if (client is not null)
        {
            var result = await client.TryInvokeAsync<IRemoteDiagnosticAnalyzerService, ImmutableArray<DiagnosticData>>(
                project,
                (service, solution, cancellationToken) => service.GetDiagnosticsForIdsAsync(
                    solution, project.Id, documentIds, diagnosticIds, analyzerFilter, includeLocalDocumentDiagnostics, cancellationToken),
                cancellationToken).ConfigureAwait(false);
            return result.HasValue ? result.Value : [];
        }
 
        return await GetDiagnosticsForIdsInProcessAsync(
            project, documentIds, diagnosticIds,
            analyzerFilter,
            includeLocalDocumentDiagnostics,
            cancellationToken).ConfigureAwait(false);
    }
 
    public async Task<ImmutableArray<DiagnosticData>> GetProjectDiagnosticsForIdsAsync(
        Project project,
        ImmutableHashSet<string>? diagnosticIds,
        AnalyzerFilter analyzerFilter,
        CancellationToken cancellationToken)
    {
        var client = await RemoteHostClient.TryGetClientAsync(project, cancellationToken).ConfigureAwait(false);
        if (client is not null)
        {
            var result = await client.TryInvokeAsync<IRemoteDiagnosticAnalyzerService, ImmutableArray<DiagnosticData>>(
                project,
                (service, solution, cancellationToken) => service.GetProjectDiagnosticsForIdsAsync(
                    solution, project.Id, diagnosticIds, analyzerFilter, cancellationToken),
                cancellationToken).ConfigureAwait(false);
            return result.HasValue ? result.Value : [];
        }
 
        return await GetProjectDiagnosticsForIdsInProcessAsync(
            project, diagnosticIds, analyzerFilter, cancellationToken).ConfigureAwait(false);
    }
 
    public async Task<bool> IsAnyDiagnosticIdDeprioritizedAsync(
        Project project, ImmutableArray<string> diagnosticIds, CancellationToken cancellationToken)
    {
        var client = await RemoteHostClient.TryGetClientAsync(project, cancellationToken).ConfigureAwait(false);
        if (client is not null)
        {
            var result = await client.TryInvokeAsync<IRemoteDiagnosticAnalyzerService, bool>(
                project,
                (service, solution, cancellationToken) => service.IsAnyDiagnosticIdDeprioritizedAsync(
                    solution, project.Id, diagnosticIds, cancellationToken),
                cancellationToken).ConfigureAwait(false);
            return result.HasValue && result.Value;
        }
 
        return await IsAnyDeprioritizedDiagnosticIdInProcessAsync(
            project, diagnosticIds, cancellationToken).ConfigureAwait(false);
    }
 
    public async Task<ImmutableArray<DiagnosticData>> GetDiagnosticsForSpanAsync(
        TextDocument document,
        TextSpan? range,
        DiagnosticIdFilter diagnosticIdFilter,
        CodeActionRequestPriority? priority,
        DiagnosticKind diagnosticKind,
        CancellationToken cancellationToken)
    {
        var client = await RemoteHostClient.TryGetClientAsync(document.Project, cancellationToken).ConfigureAwait(false);
        if (client is not null)
        {
            var result = await client.TryInvokeAsync<IRemoteDiagnosticAnalyzerService, ImmutableArray<DiagnosticData>>(
                document.Project,
                (service, solution, cancellationToken) => service.GetDiagnosticsForSpanAsync(
                    solution, document.Id, range, diagnosticIdFilter, priority, diagnosticKind, cancellationToken),
                cancellationToken).ConfigureAwait(false);
            return result.HasValue ? result.Value : [];
        }
 
        return await GetDiagnosticsForSpanInProcessAsync(
            document, range, diagnosticIdFilter, priority, diagnosticKind, cancellationToken).ConfigureAwait(false);
    }
}