File: AddConstructorParametersFromMembers\AddConstructorParametersFromMembersCodeRefactoringProvider.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.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.Features.Intents;
using Microsoft.CodeAnalysis.GenerateFromMembers;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.AddConstructorParametersFromMembers;
 
using static GenerateFromMembersHelpers;
 
[ExportCodeRefactoringProvider(LanguageNames.CSharp, LanguageNames.VisualBasic,
    Name = PredefinedCodeRefactoringProviderNames.AddConstructorParametersFromMembers), Shared]
[ExtensionOrder(After = PredefinedCodeRefactoringProviderNames.GenerateConstructorFromMembers,
                Before = PredefinedCodeRefactoringProviderNames.GenerateOverrides)]
[IntentProvider(WellKnownIntents.AddConstructorParameter, LanguageNames.CSharp)]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed partial class AddConstructorParametersFromMembersCodeRefactoringProvider()
        : CodeRefactoringProvider, IIntentProvider
{
    public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
    {
        var (document, textSpan, cancellationToken) = context;
        if (document.Project.Solution.WorkspaceKind == WorkspaceKind.MiscellaneousFiles)
        {
            return;
        }
 
        var result = await AddConstructorParametersFromMembersAsync(document, textSpan, cancellationToken).ConfigureAwait(false);
        if (result == null)
        {
            return;
        }
 
        var actions = GetGroupedActions(result.Value);
        context.RegisterRefactorings(actions);
    }
 
    private static async Task<AddConstructorParameterResult?> AddConstructorParametersFromMembersAsync(
        Document document, TextSpan textSpan, CancellationToken cancellationToken)
    {
        using (Logger.LogBlock(FunctionId.Refactoring_GenerateFromMembers_AddConstructorParametersFromMembers, cancellationToken))
        {
            var info = await GetSelectedMemberInfoAsync(
                document,
                textSpan,
                allowPartialSelection: true,
                cancellationToken).ConfigureAwait(false);
 
            if (info != null)
            {
                var state = await State.GenerateAsync(info.SelectedMembers, document, cancellationToken).ConfigureAwait(false);
                if (state?.ConstructorCandidates != null && !state.ConstructorCandidates.IsEmpty)
                {
                    var contextInfo = await document.GetCodeGenerationInfoAsync(CodeGenerationContext.Default, cancellationToken).ConfigureAwait(false);
                    return CreateCodeActions(document, contextInfo, state);
                }
            }
 
            return null;
        }
    }
 
    private static ImmutableArray<CodeAction> GetGroupedActions(AddConstructorParameterResult result)
    {
        using var _ = ArrayBuilder<CodeAction>.GetInstance(out var actions);
        if (result.UseSubMenu)
        {
            if (!result.RequiredParameterActions.IsDefaultOrEmpty)
            {
                actions.Add(CodeAction.Create(
                    FeaturesResources.Add_parameter_to_constructor,
                    result.RequiredParameterActions.Cast<AddConstructorParametersCodeAction, CodeAction>(),
                    isInlinable: false));
            }
 
            actions.Add(CodeAction.Create(
                FeaturesResources.Add_optional_parameter_to_constructor,
                result.OptionalParameterActions.Cast<AddConstructorParametersCodeAction, CodeAction>(),
                isInlinable: false));
        }
        else
        {
            // Not using submenus, this means we have at most a single of each action.
            if (!result.RequiredParameterActions.IsDefaultOrEmpty)
            {
                actions.Add(result.RequiredParameterActions.Single());
            }
 
            actions.Add(result.OptionalParameterActions.Single());
        }
 
        return actions.ToImmutableAndClear();
    }
 
    private static AddConstructorParameterResult CreateCodeActions(Document document, CodeGenerationContextInfo info, State state)
    {
        using var _0 = ArrayBuilder<AddConstructorParametersCodeAction>.GetInstance(out var requiredParametersActions);
        using var _1 = ArrayBuilder<AddConstructorParametersCodeAction>.GetInstance(out var optionalParametersActions);
        var containingType = state.ContainingType;
 
        var useSubMenu = state.ConstructorCandidates.Length > 1;
        foreach (var constructorCandidate in state.ConstructorCandidates)
        {
            if (CanHaveRequiredParameters(constructorCandidate.Constructor.Parameters))
            {
                requiredParametersActions.Add(new AddConstructorParametersCodeAction(
                    document,
                    info,
                    constructorCandidate,
                    containingType,
                    constructorCandidate.MissingParameters,
                    useSubMenuName: useSubMenu));
            }
 
            optionalParametersActions.Add(GetOptionalContructorParametersCodeAction(
                document,
                info,
                constructorCandidate,
                containingType,
                useSubMenuName: useSubMenu));
        }
 
        return new AddConstructorParameterResult(requiredParametersActions.ToImmutable(), optionalParametersActions.ToImmutable(), useSubMenu);
 
        // local functions
        static bool CanHaveRequiredParameters(ImmutableArray<IParameterSymbol> parameters)
               => parameters.Length == 0 || !parameters.Last().IsOptional;
 
        static AddConstructorParametersCodeAction GetOptionalContructorParametersCodeAction(Document document, CodeGenerationContextInfo info, ConstructorCandidate constructorCandidate, INamedTypeSymbol containingType, bool useSubMenuName)
        {
            var missingOptionalParameters = constructorCandidate.MissingParameters.SelectAsArray(
                p => CodeGenerationSymbolFactory.CreateParameterSymbol(
                    attributes: default,
                    refKind: p.RefKind,
                    isParams: p.IsParams,
                    type: p.Type,
                    name: p.Name,
                    isOptional: true,
                    hasDefaultValue: true));
 
            return new AddConstructorParametersCodeAction(
                document, info, constructorCandidate, containingType, missingOptionalParameters, useSubMenuName);
        }
    }
 
    public async Task<ImmutableArray<IntentProcessorResult>> ComputeIntentAsync(
        Document priorDocument,
        TextSpan priorSelection,
        Document currentDocument,
        IntentDataProvider intentDataProvider,
        CancellationToken cancellationToken)
    {
        var addConstructorParametersResult = await AddConstructorParametersFromMembersAsync(priorDocument, priorSelection, cancellationToken).ConfigureAwait(false);
        if (addConstructorParametersResult == null)
        {
            return [];
        }
 
        var actions = addConstructorParametersResult.Value.RequiredParameterActions.Concat(addConstructorParametersResult.Value.OptionalParameterActions);
        if (actions.IsEmpty)
        {
            return [];
        }
 
        var results = new FixedSizeArrayBuilder<IntentProcessorResult>(actions.Length);
        foreach (var action in actions)
        {
            // Intents currently have no way to report progress.
            var changedSolution = await action.GetChangedSolutionInternalAsync(
                priorDocument.Project.Solution, CodeAnalysisProgress.None, postProcessChanges: true, cancellationToken).ConfigureAwait(false);
            Contract.ThrowIfNull(changedSolution);
            var intent = new IntentProcessorResult(changedSolution, [priorDocument.Id], action.Title, action.ActionName);
            results.Add(intent);
        }
 
        return results.MoveToImmutable();
    }
}