File: UseNamedArguments\AbstractUseNamedArgumentsCodeRefactoringProvider.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.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.Shared.Extensions;
 
namespace Microsoft.CodeAnalysis.UseNamedArguments;
 
internal abstract class AbstractUseNamedArgumentsCodeRefactoringProvider : CodeRefactoringProvider
{
    protected interface IAnalyzer
    {
        Task ComputeRefactoringsAsync(CodeRefactoringContext context, SyntaxNode root);
    }
 
    protected abstract class Analyzer<TBaseArgumentSyntax, TSimpleArgumentSyntax, TArgumentListSyntax> : IAnalyzer
        where TBaseArgumentSyntax : SyntaxNode
        where TSimpleArgumentSyntax : TBaseArgumentSyntax
        where TArgumentListSyntax : SyntaxNode
    {
        public async Task ComputeRefactoringsAsync(
            CodeRefactoringContext context, SyntaxNode root)
        {
            var (document, textSpan, cancellationToken) = context;
 
            // We allow empty nodes here to find VB implicit arguments.
            var potentialArguments = await document.GetRelevantNodesAsync<TBaseArgumentSyntax>(textSpan, allowEmptyNodes: true, cancellationToken).ConfigureAwait(false);
            var argument = potentialArguments.FirstOrDefault(n => n.Parent is TArgumentListSyntax) as TSimpleArgumentSyntax;
            if (argument == null)
            {
                return;
            }
 
            if (!IsPositionalArgument(argument))
            {
                return;
            }
 
            var receiver = GetReceiver(argument);
            if (receiver == null)
            {
                return;
            }
 
            if (receiver.ContainsDiagnostics)
            {
                return;
            }
 
            var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
 
            var symbol = semanticModel.GetSymbolInfo(receiver, cancellationToken).Symbol;
            if (symbol == null)
            {
                return;
            }
 
            var parameters = symbol.GetParameters();
            if (parameters.IsDefaultOrEmpty)
            {
                return;
            }
 
            if (argument.Parent is not TArgumentListSyntax argumentList)
            {
                return;
            }
 
            var arguments = GetArguments(argumentList);
            var argumentCount = arguments.Count;
            var argumentIndex = arguments.IndexOf(argument);
            if (argumentIndex >= parameters.Length)
            {
                return;
            }
 
            if (!IsLegalToAddNamedArguments(parameters, argumentCount))
            {
                return;
            }
 
            if (IsImplicitIndexOrRangeIndexer(parameters, argument, semanticModel))
            {
                return;
            }
 
            var potentialArgumentsToName = 0;
            for (var i = argumentIndex; i < argumentCount; i++)
            {
                if (arguments[i] is not TSimpleArgumentSyntax simpleArgumet)
                {
                    return;
                }
                else if (IsPositionalArgument(simpleArgumet))
                {
                    potentialArgumentsToName++;
                }
            }
 
            var argumentName = parameters[argumentIndex].Name;
 
            if (SupportsNonTrailingNamedArguments(root.SyntaxTree.Options) &&
                potentialArgumentsToName > 1)
            {
                context.RegisterRefactoring(
                    CodeAction.Create(
                        string.Format(FeaturesResources.Add_argument_name_0, argumentName),
                        c => AddNamedArgumentsAsync(root, document, argument, parameters, argumentIndex, includingTrailingArguments: false),
                        nameof(FeaturesResources.Add_argument_name_0) + "_" + argumentName),
                    argument.Span);
 
                context.RegisterRefactoring(
                    CodeAction.Create(
                        string.Format(FeaturesResources.Add_argument_name_0_including_trailing_arguments, argumentName),
                        c => AddNamedArgumentsAsync(root, document, argument, parameters, argumentIndex, includingTrailingArguments: true),
                        nameof(FeaturesResources.Add_argument_name_0_including_trailing_arguments) + "_" + argumentName),
                    argument.Span);
            }
            else
            {
                context.RegisterRefactoring(
                    CodeAction.Create(
                        string.Format(FeaturesResources.Add_argument_name_0, argumentName),
                        c => AddNamedArgumentsAsync(root, document, argument, parameters, argumentIndex, includingTrailingArguments: true),
                        nameof(FeaturesResources.Add_argument_name_0) + "_" + argumentName),
                    argument.Span);
            }
        }
 
        private Task<Document> AddNamedArgumentsAsync(
            SyntaxNode root,
            Document document,
            TSimpleArgumentSyntax firstArgument,
            ImmutableArray<IParameterSymbol> parameters,
            int index,
            bool includingTrailingArguments)
        {
            var argumentList = (TArgumentListSyntax)firstArgument.Parent!;
            var newArgumentList = GetOrSynthesizeNamedArguments(parameters, argumentList, index, includingTrailingArguments);
            var newRoot = root.ReplaceNode(argumentList, newArgumentList);
            return Task.FromResult(document.WithSyntaxRoot(newRoot));
        }
 
        private TArgumentListSyntax GetOrSynthesizeNamedArguments(
            ImmutableArray<IParameterSymbol> parameters, TArgumentListSyntax argumentList,
            int index, bool includingTrailingArguments)
        {
            var arguments = GetArguments(argumentList);
            var namedArguments = arguments
                .Select((argument, i) => ShouldAddName(argument, i)
                    ? WithName((TSimpleArgumentSyntax)argument.WithoutTrivia(), parameters[i].Name).WithTriviaFrom(argument)
                    : argument);
 
            return WithArguments(argumentList, namedArguments, arguments.GetSeparators());
 
            // local functions
 
            bool ShouldAddName(TBaseArgumentSyntax argument, int currentIndex)
            {
                if (currentIndex > index && !includingTrailingArguments)
                {
                    return false;
                }
 
                return currentIndex >= index && argument is TSimpleArgumentSyntax s && IsPositionalArgument(s);
            }
        }
 
        protected abstract TArgumentListSyntax WithArguments(
            TArgumentListSyntax argumentList, IEnumerable<TBaseArgumentSyntax> namedArguments, IEnumerable<SyntaxToken> separators);
 
        protected abstract bool IsLegalToAddNamedArguments(ImmutableArray<IParameterSymbol> parameters, int argumentCount);
        protected abstract TSimpleArgumentSyntax WithName(TSimpleArgumentSyntax argument, string name);
        protected abstract bool IsPositionalArgument(TSimpleArgumentSyntax argument);
        protected abstract SeparatedSyntaxList<TBaseArgumentSyntax> GetArguments(TArgumentListSyntax argumentList);
        protected abstract SyntaxNode? GetReceiver(SyntaxNode argument);
        protected abstract bool SupportsNonTrailingNamedArguments(ParseOptions options);
        protected abstract bool IsImplicitIndexOrRangeIndexer(ImmutableArray<IParameterSymbol> parameters, TBaseArgumentSyntax argument, SemanticModel semanticModel);
    }
 
    private readonly IAnalyzer _argumentAnalyzer;
    private readonly IAnalyzer _attributeArgumentAnalyzer;
 
    protected AbstractUseNamedArgumentsCodeRefactoringProvider(
        IAnalyzer argumentAnalyzer,
        IAnalyzer attributeArgumentAnalyzer)
    {
        _argumentAnalyzer = argumentAnalyzer;
        _attributeArgumentAnalyzer = attributeArgumentAnalyzer;
    }
 
    public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
    {
        var (document, _, cancellationToken) = context;
        if (document.Project.Solution.WorkspaceKind == WorkspaceKind.MiscellaneousFiles)
        {
            return;
        }
 
        var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
 
        await _argumentAnalyzer.ComputeRefactoringsAsync(context, root).ConfigureAwait(false);
 
        if (_attributeArgumentAnalyzer != null)
        {
            await _attributeArgumentAnalyzer.ComputeRefactoringsAsync(context, root).ConfigureAwait(false);
        }
    }
}