File: CodeFixesAndRefactorings\DefaultFixAllProviderHelpers.cs
Web Access
Project: src\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// 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.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CodeFixesAndRefactorings;
 
/// <summary>
/// Default implementation of a <see cref="FixAllProvider"/> that efficiently handles the dispatch logic for fixing
/// entire solutions.  Used by <see cref="BatchFixAllProvider"/> and <see cref="DocumentBasedFixAllProvider"/>.
/// </summary>
internal static class DefaultFixAllProviderHelpers
{
    public static async Task<CodeAction?> GetFixAsync<TFixAllContext>(
        string title,
        TFixAllContext fixAllContext,
        Func<TFixAllContext, ImmutableArray<TFixAllContext>, Task<Solution?>> fixAllContextsAsync)
        where TFixAllContext : IFixAllContext
    {
 
        // We're about to do a lot of computation to compute all the diagnostics needed and to perform the
        // changes.  Keep this solution alive on the OOP side so that we never drop it and then resync it
        // (which would cause us to drop/recreate compilations, skeletons and sg docs.
        using var _ = await RemoteKeepAliveSession.CreateAsync(fixAllContext.Solution, fixAllContext.CancellationToken).ConfigureAwait(false);
 
        var solution = fixAllContext.Scope switch
        {
            FixAllScope.Document or FixAllScope.ContainingMember or FixAllScope.ContainingType
                => await GetDocumentFixesAsync(fixAllContext, fixAllContextsAsync).ConfigureAwait(false),
            FixAllScope.Project => await GetProjectFixesAsync(fixAllContext, fixAllContextsAsync).ConfigureAwait(false),
            FixAllScope.Solution => await GetSolutionFixesAsync(fixAllContext, fixAllContextsAsync).ConfigureAwait(false),
            _ => throw ExceptionUtilities.UnexpectedValue(fixAllContext.Scope),
        };
 
        if (solution == null)
            return null;
 
        return CodeAction.Create(
            title, _ => Task.FromResult(solution));
    }
 
    private static Task<Solution?> GetDocumentFixesAsync<TFixAllContext>(
        TFixAllContext fixAllContext,
        Func<TFixAllContext, ImmutableArray<TFixAllContext>, Task<Solution?>> fixAllContextsAsync)
        where TFixAllContext : IFixAllContext
        => fixAllContextsAsync(fixAllContext, [fixAllContext]);
 
    private static Task<Solution?> GetProjectFixesAsync<TFixAllContext>(
        TFixAllContext fixAllContext,
        Func<TFixAllContext, ImmutableArray<TFixAllContext>, Task<Solution?>> fixAllContextsAsync)
        where TFixAllContext : IFixAllContext
        => fixAllContextsAsync(fixAllContext, [(TFixAllContext)fixAllContext.With((document: null, fixAllContext.Project))]);
 
    private static Task<Solution?> GetSolutionFixesAsync<TFixAllContext>(
        TFixAllContext fixAllContext,
        Func<TFixAllContext, ImmutableArray<TFixAllContext>, Task<Solution?>> fixAllContextsAsync)
        where TFixAllContext : IFixAllContext
    {
        var solution = fixAllContext.Solution;
        var dependencyGraph = solution.GetProjectDependencyGraph();
 
        // Walk through each project in topological order, determining and applying the diagnostics for each
        // project.  We do this in topological order so that the compilations for successive projects are readily
        // available as we just computed them for dependent projects.  If we were to do it out of order, we might
        // start with a project that has a ton of dependencies, and we'd spend an inordinate amount of time just
        // building the compilations for it before we could proceed.
        //
        // By processing one project at a time, we can also let go of a project once done with it, allowing us to
        // reclaim lots of the memory so we don't overload the system while processing a large solution.
        //
        // Note: we have to filter down to projects of the same language as the FixAllContext points at a
        // CodeFixProvider, and we can't call into providers of different languages with diagnostics from a
        // different language.
        var sortedProjects = dependencyGraph.GetTopologicallySortedProjects()
                                            .Select(solution.GetRequiredProject)
                                            .Where(p => p.Language == fixAllContext.Project.Language);
        return fixAllContextsAsync(
            fixAllContext,
            sortedProjects.SelectAsArray(p => (TFixAllContext)fixAllContext.With((document: null, project: p), scope: FixAllScope.Project)));
    }
}