File: AbstractChangeNamespaceToMatchFolderCodeFixProvider.CustomFixAllProvider.cs
Web Access
Project: ..\..\..\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.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixesAndRefactorings;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CodeFixes.MatchFolderAndNamespace
{
    /// <summary>
    /// Custom fix all provider for namespace sync. Does fix all on per document level. Since
    /// multiple documents may be updated when changing a single namespace, it happens 
    /// on a sequential level instead of batch fixing and merging the changes. This prevents
    /// collissions that the batch fixer won't handle correctly but is slower.
    /// </summary>
    internal abstract partial class AbstractChangeNamespaceToMatchFolderCodeFixProvider
    {
        private class CustomFixAllProvider : FixAllProvider
        {
            public static readonly CustomFixAllProvider Instance = new();
 
            public override async Task<CodeAction?> GetFixAsync(FixAllContext fixAllContext)
            {
                var diagnostics = fixAllContext.Scope switch
                {
                    FixAllScope.Document when fixAllContext.Document is not null => await fixAllContext.GetDocumentDiagnosticsAsync(fixAllContext.Document).ConfigureAwait(false),
                    FixAllScope.Project => await fixAllContext.GetAllDiagnosticsAsync(fixAllContext.Project).ConfigureAwait(false),
                    FixAllScope.Solution => await GetSolutionDiagnosticsAsync(fixAllContext).ConfigureAwait(false),
                    _ => default
                };
 
                if (diagnostics.IsDefaultOrEmpty)
                    return null;
 
                var title = fixAllContext.GetDefaultFixAllTitle();
                return CodeAction.Create(
                    title,
                    cancellationToken => FixAllByDocumentAsync(
                        fixAllContext.Project.Solution,
                        diagnostics,
                        fixAllContext.GetProgressTracker(),
#if CODE_STYLE
                        CodeActionOptions.DefaultProvider,
#else
                        fixAllContext.State.CodeActionOptionsProvider,
#endif
                        cancellationToken),
                    title);
 
                static async Task<ImmutableArray<Diagnostic>> GetSolutionDiagnosticsAsync(FixAllContext fixAllContext)
                {
                    var diagnostics = ImmutableArray.CreateBuilder<Diagnostic>();
 
                    foreach (var project in fixAllContext.Solution.Projects)
                    {
                        var projectDiagnostics = await fixAllContext.GetAllDiagnosticsAsync(project).ConfigureAwait(false);
                        diagnostics.AddRange(projectDiagnostics);
                    }
 
                    return diagnostics.ToImmutable();
                }
            }
 
            private static async Task<Solution> FixAllByDocumentAsync(
                Solution solution,
                ImmutableArray<Diagnostic> diagnostics,
                IProgressTracker progressTracker,
                CodeActionOptionsProvider options,
                CancellationToken cancellationToken)
            {
                // Use documentId instead of tree here because the
                // FixAsync call can modify more than one document per call. The
                // important thing is that the fix works on fixing the namespaces in a single document,
                // but references in other documents will be updated to be correct. Id will remain
                // across this mutation, but lookup via SyntaxTree directly will not work because
                // the tree won't be the same.
                var documentIdToDiagnosticsMap = diagnostics
                    .GroupBy(diagnostic => diagnostic.Location.SourceTree)
                    .Where(group => group.Key is not null)
                    .ToImmutableDictionary(group => solution.GetRequiredDocument(group.Key!).Id, group => group.ToImmutableArray());
 
                var newSolution = solution;
 
                progressTracker.AddItems(documentIdToDiagnosticsMap.Count);
 
                foreach (var (documentId, diagnosticsInTree) in documentIdToDiagnosticsMap)
                {
                    var document = newSolution.GetRequiredDocument(documentId);
                    using var _ = progressTracker.ItemCompletedScope(document.Name);
 
                    newSolution = await FixAllInDocumentAsync(document, diagnosticsInTree, options, cancellationToken).ConfigureAwait(false);
                }
 
                return newSolution;
            }
        }
    }
}