File: src\Workspaces\SharedUtilitiesAndExtensions\Workspace\Core\CodeFixes\ForkingSyntaxEditorBasedCodeFixProvider.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.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
 
namespace Microsoft.CodeAnalysis.CodeFixes;
 
/// <summary>
/// Helper type for <see cref="CodeFixProvider"/>s that need to provide 'fix all' support in a document, by operate by
/// applying one fix at a time, then recomputing the work to do after that fix is applied.  While this is not generally
/// desirable from a performance perspective (due to the costs of forking a document after each fix), it is sometimes
/// necessary as individual fixes can impact the code so substantially that successive fixes may no longer apply, or may
/// have dramatically different data to work with before the fix.  For example, if one fix removes statements entirely
/// that another fix was contained in.
/// </summary>
internal abstract class ForkingSyntaxEditorBasedCodeFixProvider<TDiagnosticNode>
    : SyntaxEditorBasedCodeFixProvider
    where TDiagnosticNode : SyntaxNode
{
    protected abstract (string title, string equivalenceKey) GetTitleAndEquivalenceKey(CodeFixContext context);
 
    /// <summary>
    /// Subclasses must override this to actually provide the fix for a particular diagnostic.  The implementation will
    /// be passed the <em>current</em> <paramref name="document"/> (containing the changes from all prior fixes), the
    /// the <paramref name="diagnosticNode"/> in that document, for the current diagnostic being fixed.  And the <see
    /// cref="Diagnostic.Properties"/> for that diagnostic.  The diagnostic itself is not passed along as it was
    /// computed with respect to the original user document, and as such its <see cref="Diagnostic.Location"/> and <see
    /// cref="Diagnostic.AdditionalLocations"/> will not be correct.
    /// </summary>
    protected abstract Task FixAsync(
        Document document,
        SyntaxEditor editor,
        TDiagnosticNode diagnosticNode,
        ImmutableDictionary<string, string?> properties,
        CancellationToken cancellationToken);
 
    protected sealed override bool IncludeDiagnosticDuringFixAll(Diagnostic diagnostic)
        // Never try to fix the secondary diagnostics that were produced just to fade out code.
        => !diagnostic.Descriptor.ImmutableCustomTags().Contains(WellKnownDiagnosticTags.Unnecessary);
 
    public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
    {
        var (title, equivalenceKey) = GetTitleAndEquivalenceKey(context);
        RegisterCodeFix(context, title, equivalenceKey);
    }
 
    protected sealed override async Task FixAllAsync(
        Document document,
        ImmutableArray<Diagnostic> diagnostics,
        SyntaxEditor editor,
        CancellationToken cancellationToken)
    {
        var originalRoot = editor.OriginalRoot;
 
        using var _ = ArrayBuilder<(TDiagnosticNode diagnosticNode, Diagnostic diagnostic)>.GetInstance(out var originalNodes);
        foreach (var diagnostic in diagnostics)
        {
            var diagnosticNode = (TDiagnosticNode)originalRoot.FindNode(
                diagnostic.AdditionalLocations[0].SourceSpan, getInnermostNodeForTie: true);
            originalNodes.Push((diagnosticNode, diagnostic));
        }
 
        var solutionServices = document.Project.Solution.Services;
 
        // We're going to be continually editing this tree.  Track all the nodes we care about so we can find them
        // across each edit.
        var semanticDocument = await SemanticDocument.CreateAsync(
            document.WithSyntaxRoot(originalRoot.TrackNodes(originalNodes.Select(static t => t.diagnosticNode))),
            cancellationToken).ConfigureAwait(false);
 
        while (originalNodes.TryPop(out var tuple))
        {
            var (originalDiagnosticNode, diagnostic) = tuple;
            var currentRoot = semanticDocument.Root;
            var diagnosticNode = currentRoot.GetCurrentNodes(originalDiagnosticNode).Single();
 
            var subEditor = new SyntaxEditor(currentRoot, solutionServices);
 
            await FixAsync(
                semanticDocument.Document,
                subEditor,
                diagnosticNode,
                diagnostic.Properties,
                cancellationToken).ConfigureAwait(false);
 
            var changedRoot = subEditor.GetChangedRoot();
            if (currentRoot == changedRoot)
                continue;
 
            semanticDocument = await semanticDocument.WithSyntaxRootAsync(
                changedRoot, cancellationToken).ConfigureAwait(false);
        }
 
        editor.ReplaceNode(originalRoot, semanticDocument.Root);
    }
}