File: CodeFixesAndRefactorings\AbstractFixAllCodeAction.cs
Web Access
Project: src\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.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CodeFixesAndRefactorings;
 
/// <summary>
/// Fix all code action for a code action registered by
/// a <see cref="CodeFixes.CodeFixProvider"/> or a <see cref="CodeRefactorings.CodeRefactoringProvider"/>.
/// </summary>
internal sealed class RefactorOrFixAllCodeAction(
    IRefactorOrFixAllState refactorOrFixAllState,
    bool showPreviewChangesDialog,
    string? title = null,
    string? message = null) : CodeAction
{
    private static readonly ISet<string> s_predefinedProviderNames =
        typeof(PredefinedCodeFixProviderNames).GetTypeInfo().DeclaredFields.Concat(typeof(PredefinedCodeRefactoringProviderNames).GetTypeInfo().DeclaredFields)
            .Where(field => field.IsStatic)
            .Select(field => (string)field.GetValue(null)!)
            .ToSet();
 
    private bool _showPreviewChangesDialog = showPreviewChangesDialog;
 
    public IRefactorOrFixAllState RefactorOrFixAllState { get; } = refactorOrFixAllState;
 
    // We don't need to post process changes here as the inner code action created for Fix multiple code fix already executes.
    internal sealed override CodeActionCleanup Cleanup => CodeActionCleanup.None;
 
    /// <summary>
    /// Creates a new <see cref="IRefactorOrFixAllContext"/> with the given parameters.
    /// </summary>
    private static IRefactorOrFixAllContext CreateFixAllContext(IRefactorOrFixAllState state, IProgress<CodeAnalysisProgress> progressTracker, CancellationToken cancellationToken)
    {
        return state switch
        {
            FixAllState fixAllState => new FixAllContext(fixAllState, progressTracker, cancellationToken),
            RefactorAllState refactorAllState => new RefactorAllContext(refactorAllState, progressTracker, cancellationToken),
            _ => throw ExceptionUtilities.UnexpectedValue(state),
        };
    }
 
    public override string Title
        => title ?? (this.RefactorOrFixAllState.Scope switch
        {
            FixAllScope.Document => FeaturesResources.Document,
            FixAllScope.Project => FeaturesResources.Project,
            FixAllScope.Solution => FeaturesResources.Solution,
            FixAllScope.ContainingMember => FeaturesResources.Containing_Member,
            FixAllScope.ContainingType => FeaturesResources.Containing_Type,
            _ => throw ExceptionUtilities.UnexpectedValue(this.RefactorOrFixAllState.Scope),
        });
 
    internal override string Message => message ?? FeaturesResources.Computing_fix_all_occurrences_code_fix;
 
    protected sealed override Task<ImmutableArray<CodeActionOperation>> ComputeOperationsAsync(
        IProgress<CodeAnalysisProgress> progressTracker, CancellationToken cancellationToken)
    {
        cancellationToken.ThrowIfCancellationRequested();
        FixAllLogger.LogState(RefactorOrFixAllState, IsInternalProvider(RefactorOrFixAllState));
 
        var service = RefactorOrFixAllState.Project.Solution.Services.GetRequiredService<IFixAllGetFixesService>();
 
        var fixAllContext = CreateFixAllContext(RefactorOrFixAllState, progressTracker, cancellationToken);
        progressTracker.Report(CodeAnalysisProgress.Description(fixAllContext.GetDefaultTitle()));
 
        return service.GetFixAllOperationsAsync(fixAllContext, _showPreviewChangesDialog);
    }
 
    protected sealed override Task<Solution?> GetChangedSolutionAsync(
        IProgress<CodeAnalysisProgress> progressTracker, CancellationToken cancellationToken)
    {
        cancellationToken.ThrowIfCancellationRequested();
        FixAllLogger.LogState(RefactorOrFixAllState, IsInternalProvider(RefactorOrFixAllState));
 
        var service = RefactorOrFixAllState.Project.Solution.Services.GetRequiredService<IFixAllGetFixesService>();
 
        var fixAllContext = CreateFixAllContext(RefactorOrFixAllState, progressTracker, cancellationToken);
        progressTracker.Report(CodeAnalysisProgress.Description(fixAllContext.GetDefaultTitle()));
 
        return service.GetFixAllChangedSolutionAsync(fixAllContext);
    }
 
    /// <summary>
    /// Determine if the <see cref="IRefactorOrFixAllState.Provider"/> is an internal first-party provider or not.
    /// </summary>
    private static bool IsInternalProvider(IRefactorOrFixAllState fixAllState)
    {
        var exportAttributes = fixAllState.Provider.GetType().GetTypeInfo().GetCustomAttributes(typeof(ExportCodeFixProviderAttribute), false);
        if (exportAttributes?.FirstOrDefault() is ExportCodeFixProviderAttribute codeFixAttribute)
        {
            return !string.IsNullOrEmpty(codeFixAttribute.Name)
                && s_predefinedProviderNames.Contains(codeFixAttribute.Name);
        }
 
        exportAttributes = fixAllState.Provider.GetType().GetTypeInfo().GetCustomAttributes(typeof(ExportCodeRefactoringProviderAttribute), false);
        if (exportAttributes?.FirstOrDefault() is ExportCodeRefactoringProviderAttribute codeRefactoringAttribute)
        {
            return !string.IsNullOrEmpty(codeRefactoringAttribute.Name)
                && s_predefinedProviderNames.Contains(codeRefactoringAttribute.Name);
        }
 
        return false;
    }
 
    // internal for testing purposes.
    internal TestAccessor GetTestAccessor()
        => new(this);
 
    // internal for testing purposes.
    internal readonly struct TestAccessor
    {
        private readonly RefactorOrFixAllCodeAction _fixAllCodeAction;
 
        internal TestAccessor(RefactorOrFixAllCodeAction fixAllCodeAction)
            => _fixAllCodeAction = fixAllCodeAction;
 
        /// <summary>
        /// Gets a reference to <see cref="_showPreviewChangesDialog"/>, which can be read or written by test code.
        /// </summary>
        public ref bool ShowPreviewChangesDialog
            => ref _fixAllCodeAction._showPreviewChangesDialog;
    }
}