File: CodeRefactorings\FixAllOccurences\DocumentBasedRefactorAllProvider.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.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixesAndRefactorings;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CodeRefactorings;
 
/// <summary>
/// Provides a base class to write a <see cref="RefactorAllProvider"/> for refactorings that refactor documents
/// independently. This type should be used in the case where the code refactoring(s) only affect individual <see
/// cref="Document"/>s.
/// </summary>
/// <remarks>
/// This type provides suitable logic for refactorings large solutions in an efficient manner.  Projects are serially
/// processed, with all the documents in the project being processed in parallel. <see cref="RefactorAllAsync"/> is
/// invoked for each document for implementors to process.
///
/// TODO: Make public, tracked with https://github.com/dotnet/roslyn/issues/60703
/// </remarks>
internal abstract class DocumentBasedRefactorAllProvider(ImmutableArray<RefactorAllScope> supportedRefactorAllScopes)
    : RefactorAllProvider
{
    private readonly ImmutableArray<RefactorAllScope> _supportedRefactorAllScopes = supportedRefactorAllScopes;
 
    protected DocumentBasedRefactorAllProvider()
        : this(DefaultSupportedRefactorAllScopes)
    {
    }
 
    /// <summary>
    /// Produce a suitable title for the refactor-all <see cref="CodeAction"/> this type creates in <see
    /// cref="GetRefactoringAsync(RefactorAllContext)"/>.  Override this if customizing that title is desired.
    /// </summary>
    protected virtual string GetRefactorAllTitle(RefactorAllContext refactorAllContext)
        => refactorAllContext.GetDefaultRefactorAllTitle();
 
    /// <summary>
    /// Apply refactor all operation for the code refactoring in the <see cref="RefactorAllContext.Document"/> for the
    /// given <paramref name="refactorAllContext"/>.  The document returned will only be examined for its content (e.g.
    /// it's <see cref="SyntaxTree"/> or <see cref="SourceText"/>.  No other aspects of document (like it's properties),
    /// or changes to the <see cref="Project"/> or <see cref="Solution"/> it points at will be considered.
    /// </summary>
    /// <param name="refactorAllContext">The context for the Refactor All operation.</param>
    /// <param name="document">The document to refactor.</param>
    /// <param name="refactorAllSpans">The spans to refactor in the document. If not specified, entire document needs to be refactored.</param>
    /// <returns>
    /// <para>The new <see cref="Document"/> representing the content refactored document.</para>
    /// <para>-or-</para>
    /// <para><see langword="null"/>, if no changes were made to the document.</para>
    /// </returns>
    protected abstract Task<Document?> RefactorAllAsync(
        RefactorAllContext refactorAllContext, Document document, Optional<ImmutableArray<TextSpan>> refactorAllSpans);
 
    public sealed override IEnumerable<RefactorAllScope> GetSupportedRefactorAllScopes()
        => _supportedRefactorAllScopes;
 
    public sealed override Task<CodeAction?> GetRefactoringAsync(RefactorAllContext refactorAllContext)
        => DefaultFixAllProviderHelpers.GetFixAsync(
            refactorAllContext.GetDefaultRefactorAllTitle(), refactorAllContext, RefactorAllContextsHelperAsync);
 
    private Task<Solution?> RefactorAllContextsHelperAsync(RefactorAllContext originalRefactorAllContext, ImmutableArray<RefactorAllContext> refactorAllContexts)
        => DocumentBasedFixAllProviderHelpers.FixAllContextsAsync(
            originalRefactorAllContext,
            refactorAllContexts,
            originalRefactorAllContext.Progress,
            this.GetRefactorAllTitle(originalRefactorAllContext),
            GetRefactoredDocumentsAsync);
 
    /// <summary>
    /// Attempts to apply refactor all operations. Returning, for each updated document, either the new syntax root for
    /// that document or its new text.  Syntax roots are returned for documents that support them, and are used to
    /// perform a final cleanup pass for formatting/simplification/etc.  Text is returned for documents that don't
    /// support syntax.
    /// </summary>
    private async Task GetRefactoredDocumentsAsync(
        RefactorAllContext refactorAllContext, Func<Document, Document?, ValueTask> onDocumentRefactored)
    {
        Contract.ThrowIfFalse(refactorAllContext.Scope is RefactorAllScope.Document or RefactorAllScope.Project
            or RefactorAllScope.ContainingMember or RefactorAllScope.ContainingType);
 
        var cancellationToken = refactorAllContext.CancellationToken;
 
        // Process all documents in parallel to get the change for each doc.
        var documentsAndSpansToRefactor = await refactorAllContext.GetRefactorAllSpansAsync(cancellationToken).ConfigureAwait(false);
 
        await Parallel.ForEachAsync(
            source: documentsAndSpansToRefactor,
            cancellationToken,
            async (tuple, cancellationToken) =>
            {
                var (document, spans) = tuple;
                var newDocument = await this.RefactorAllAsync(refactorAllContext, document, spans).ConfigureAwait(false);
                await onDocumentRefactored(document, newDocument).ConfigureAwait(false);
            }).ConfigureAwait(false);
    }
}