File: CodeRefactorings\FixAllOccurences\DocumentBasedFixAllProvider.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.Shared.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
using FixAllScope = Microsoft.CodeAnalysis.CodeFixes.FixAllScope;
 
namespace Microsoft.CodeAnalysis.CodeRefactorings;
 
/// <summary>
/// Provides a base class to write a <see cref="FixAllProvider"/> for refactorings that fixes 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 fixing large solutions in an efficient manner.  Projects are serially
/// processed, with all the documents in the project being processed in parallel. 
/// <see cref="FixAllAsync(FixAllContext, Document, Optional{ImmutableArray{TextSpan}})"/> 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 DocumentBasedFixAllProvider(ImmutableArray<FixAllScope> supportedFixAllScopes) : FixAllProvider
{
    private readonly ImmutableArray<FixAllScope> _supportedFixAllScopes = supportedFixAllScopes;
 
    protected DocumentBasedFixAllProvider()
        : this(DefaultSupportedFixAllScopes)
    {
    }
 
    /// <summary>
    /// Produce a suitable title for the fix-all <see cref="CodeAction"/> this type creates in <see
    /// cref="GetFixAsync(FixAllContext)"/>.  Override this if customizing that title is desired.
    /// </summary>
    protected virtual string GetFixAllTitle(FixAllContext fixAllContext)
        => fixAllContext.GetDefaultFixAllTitle();
 
    /// <summary>
    /// Apply fix all operation for the code refactoring in the <see cref="FixAllContext.Document"/>
    /// for the given <paramref name="fixAllContext"/>.  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="fixAllContext">The context for the Fix All operation.</param>
    /// <param name="document">The document to fix.</param>
    /// <param name="fixAllSpans">The spans to fix in the document. If not specified, entire document needs to be fixedd.</param>
    /// <returns>
    /// <para>The new <see cref="Document"/> representing the content fixed document.</para>
    /// <para>-or-</para>
    /// <para><see langword="null"/>, if no changes were made to the document.</para>
    /// </returns>
    protected abstract Task<Document?> FixAllAsync(FixAllContext fixAllContext, Document document, Optional<ImmutableArray<TextSpan>> fixAllSpans);
 
    public sealed override IEnumerable<FixAllScope> GetSupportedFixAllScopes()
        => _supportedFixAllScopes;
 
    public sealed override Task<CodeAction?> GetFixAsync(FixAllContext fixAllContext)
        => DefaultFixAllProviderHelpers.GetFixAsync(
            fixAllContext.GetDefaultFixAllTitle(), fixAllContext, FixAllContextsHelperAsync);
 
    private Task<Solution?> FixAllContextsHelperAsync(FixAllContext originalFixAllContext, ImmutableArray<FixAllContext> fixAllContexts)
        => DocumentBasedFixAllProviderHelpers.FixAllContextsAsync(
            originalFixAllContext,
            fixAllContexts,
            originalFixAllContext.Progress,
            this.GetFixAllTitle(originalFixAllContext),
            GetFixedDocumentsAsync);
 
    /// <summary>
    /// Attempts to apply fix 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 GetFixedDocumentsAsync(
        FixAllContext fixAllContext, Func<Document, Document?, ValueTask> onDocumentFixed)
    {
        Contract.ThrowIfFalse(fixAllContext.Scope is FixAllScope.Document or FixAllScope.Project
            or FixAllScope.ContainingMember or FixAllScope.ContainingType);
 
        var cancellationToken = fixAllContext.CancellationToken;
 
        // Process all documents in parallel to get the change for each doc.
        var documentsAndSpansToFix = await fixAllContext.GetFixAllSpansAsync(cancellationToken).ConfigureAwait(false);
 
        await RoslynParallel.ForEachAsync(
            source: documentsAndSpansToFix,
            cancellationToken,
            async (tuple, cancellationToken) =>
            {
                var (document, spans) = tuple;
                var newDocument = await this.FixAllAsync(fixAllContext, document, spans).ConfigureAwait(false);
                await onDocumentFixed(document, newDocument).ConfigureAwait(false);
            }).ConfigureAwait(false);
    }
}