File: ChangeSignature\AbstractChangeSignatureService.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.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeCleanup;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.FindSymbols.Finders;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Formatting.Rules;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Recommendations;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Simplification;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.ChangeSignature;
 
internal abstract class AbstractChangeSignatureService : ILanguageService
{
    protected readonly SyntaxAnnotation ChangeSignatureFormattingAnnotation = new("ChangeSignatureFormatting");
 
    /// <summary>
    /// Determines the symbol on which we are invoking ReorderParameters
    /// </summary>
    public abstract Task<(ISymbol? symbol, int selectedIndex)> GetInvocationSymbolAsync(Document document, int position, bool restrictToDeclarations, CancellationToken cancellationToken);
 
    /// <summary>
    /// Given a SyntaxNode for which we want to reorder parameters/arguments, find the 
    /// SyntaxNode of a kind where we know how to reorder parameters/arguments.
    /// </summary>
    public abstract SyntaxNode? FindNodeToUpdate(Document document, SyntaxNode node);
 
    public abstract Task<ImmutableArray<ISymbol>> DetermineCascadedSymbolsFromDelegateInvokeAsync(
        IMethodSymbol symbol, Document document, CancellationToken cancellationToken);
 
    public abstract SyntaxNode ChangeSignature(
        SemanticDocument document,
        ISymbol declarationSymbol,
        SyntaxNode potentiallyUpdatedNode,
        SyntaxNode originalNode,
        SignatureChange signaturePermutation,
        LineFormattingOptions lineFormattingOptions,
        CancellationToken cancellationToken);
 
    protected abstract ImmutableArray<AbstractFormattingRule> GetFormattingRules(Document document);
 
    protected abstract T TransferLeadingWhitespaceTrivia<T>(T newArgument, SyntaxNode oldArgument) where T : SyntaxNode;
 
    protected abstract SyntaxToken CommaTokenWithElasticSpace();
 
    /// <summary>
    /// For some Foo(int x, params int[] p), this helps convert the "1, 2, 3" in Foo(0, 1, 2, 3)
    /// to "new int[] { 1, 2, 3 }" in Foo(0, new int[] { 1, 2, 3 });
    /// </summary>
    protected abstract TArgumentSyntax CreateExplicitParamsArrayFromIndividualArguments<TArgumentSyntax>(SeparatedSyntaxList<TArgumentSyntax> newArguments, int startingIndex, IParameterSymbol parameterSymbol)
        where TArgumentSyntax : SyntaxNode;
 
    protected abstract TArgumentSyntax AddNameToArgument<TArgumentSyntax>(TArgumentSyntax argument, string name)
        where TArgumentSyntax : SyntaxNode;
 
    /// <summary>
    /// Only some languages support:
    ///   - Optional parameters and params arrays simultaneously in declarations
    ///   - Passing the params array as a named argument
    /// </summary>
    protected abstract bool SupportsOptionalAndParamsArrayParametersSimultaneously();
 
    protected abstract bool TryGetRecordPrimaryConstructor(INamedTypeSymbol typeSymbol, [NotNullWhen(true)] out IMethodSymbol? primaryConstructor);
 
    /// <summary>
    /// A temporarily hack that should be removed once/if https://github.com/dotnet/roslyn/issues/53092 is fixed.
    /// </summary>
    protected abstract ImmutableArray<IParameterSymbol> GetParameters(ISymbol declarationSymbol);
 
    protected abstract SyntaxGenerator Generator { get; }
    protected abstract ISyntaxFacts SyntaxFacts { get; }
 
    public async Task<ImmutableArray<ChangeSignatureCodeAction>> GetChangeSignatureCodeActionAsync(Document document, TextSpan span, CancellationToken cancellationToken)
    {
        var context = await GetChangeSignatureContextAsync(document, span.Start, restrictToDeclarations: true, cancellationToken).ConfigureAwait(false);
 
        return context is ChangeSignatureAnalysisSucceededContext changeSignatureAnalyzedSucceedContext
            ? [new ChangeSignatureCodeAction(this, changeSignatureAnalyzedSucceedContext)]
            : ImmutableArray<ChangeSignatureCodeAction>.Empty;
    }
 
    internal async Task<ChangeSignatureAnalyzedContext> GetChangeSignatureContextAsync(
        Document document, int position, bool restrictToDeclarations, CancellationToken cancellationToken)
    {
        var (symbol, selectedIndex) = await GetInvocationSymbolAsync(
            document, position, restrictToDeclarations, cancellationToken).ConfigureAwait(false);
 
        // Cross-language symbols will show as metadata, so map it to source if possible.
        symbol = SymbolFinder.FindSourceDefinition(symbol, document.Project.Solution, cancellationToken) ?? symbol;
        if (symbol == null)
            return new CannotChangeSignatureAnalyzedContext(ChangeSignatureFailureKind.IncorrectKind);
 
        if (symbol is IMethodSymbol method)
        {
            var containingType = method.ContainingType;
 
            if (method.Name == WellKnownMemberNames.DelegateBeginInvokeName &&
                containingType != null &&
                containingType.IsDelegateType() &&
                containingType.DelegateInvokeMethod != null)
            {
                symbol = containingType.DelegateInvokeMethod;
            }
        }
 
        if (symbol is IEventSymbol ev)
        {
            symbol = ev.Type;
        }
 
        if (symbol is INamedTypeSymbol typeSymbol)
        {
            if (typeSymbol.IsDelegateType() && typeSymbol.DelegateInvokeMethod != null)
            {
                symbol = typeSymbol.DelegateInvokeMethod;
            }
            else if (TryGetRecordPrimaryConstructor(typeSymbol, out var primaryConstructor))
            {
                symbol = primaryConstructor;
            }
        }
 
        if (!symbol.MatchesKind(SymbolKind.Method, SymbolKind.Property))
        {
            return new CannotChangeSignatureAnalyzedContext(ChangeSignatureFailureKind.IncorrectKind);
        }
 
        if (symbol.Locations.Any(static loc => loc.IsInMetadata))
        {
            return new CannotChangeSignatureAnalyzedContext(ChangeSignatureFailureKind.DefinedInMetadata);
        }
 
        // This should be called after the metadata check above to avoid looking for nodes in metadata.
        var declarationLocation = symbol.Locations.FirstOrDefault();
        if (declarationLocation == null)
        {
            return new CannotChangeSignatureAnalyzedContext(ChangeSignatureFailureKind.DefinedInMetadata);
        }
 
        var solution = document.Project.Solution;
        var declarationDocument = solution.GetRequiredDocument(declarationLocation.SourceTree!);
        var declarationChangeSignatureService = declarationDocument.GetRequiredLanguageService<AbstractChangeSignatureService>();
 
        int positionForTypeBinding;
        var reference = symbol.DeclaringSyntaxReferences.FirstOrDefault();
 
        if (reference != null)
        {
            var syntax = await reference.GetSyntaxAsync(cancellationToken).ConfigureAwait(false);
            positionForTypeBinding = syntax.SpanStart;
        }
        else
        {
            // There may be no declaring syntax reference, for example delegate Invoke methods.
            // The user may need to fully-qualify type names, including the type(s) defined in
            // this document.
            positionForTypeBinding = 0;
        }
 
        var parameterConfiguration = ParameterConfiguration.Create(
            GetParameters(symbol).Select(p => new ExistingParameter(p)).ToImmutableArray<Parameter>(),
            symbol.IsExtensionMethod(), selectedIndex);
 
        var semanticDocument = await SemanticDocument.CreateAsync(declarationDocument, cancellationToken).ConfigureAwait(false);
        return new ChangeSignatureAnalysisSucceededContext(
            semanticDocument, positionForTypeBinding, symbol, parameterConfiguration);
    }
 
    internal async Task<ChangeSignatureResult> ChangeSignatureWithContextAsync(ChangeSignatureAnalyzedContext context, ChangeSignatureOptionsResult? options, CancellationToken cancellationToken)
    {
        return context switch
        {
            ChangeSignatureAnalysisSucceededContext changeSignatureAnalyzedSucceedContext => await GetChangeSignatureResultAsync(changeSignatureAnalyzedSucceedContext, options, cancellationToken).ConfigureAwait(false),
            CannotChangeSignatureAnalyzedContext cannotChangeSignatureAnalyzedContext => new ChangeSignatureResult(succeeded: false, changeSignatureFailureKind: cannotChangeSignatureAnalyzedContext.CannotChangeSignatureReason),
            _ => throw ExceptionUtilities.Unreachable(),
        };
 
        async Task<ChangeSignatureResult> GetChangeSignatureResultAsync(ChangeSignatureAnalysisSucceededContext context, ChangeSignatureOptionsResult? options, CancellationToken cancellationToken)
        {
            if (options == null)
            {
                return new ChangeSignatureResult(succeeded: false);
            }
 
            var (updatedSolution, confirmationMessage) = await CreateUpdatedSolutionAsync(context, options, cancellationToken).ConfigureAwait(false);
            return new ChangeSignatureResult(updatedSolution != null, updatedSolution, context.Symbol.ToDisplayString(), context.Symbol.GetGlyph(), options.PreviewChanges, confirmationMessage: confirmationMessage);
        }
    }
 
    /// <returns>Returns <c>null</c> if the operation is cancelled.</returns>
    internal static ChangeSignatureOptionsResult? GetChangeSignatureOptions(ChangeSignatureAnalyzedContext context)
    {
        if (context is not ChangeSignatureAnalysisSucceededContext succeededContext)
        {
            return null;
        }
 
        var changeSignatureOptionsService = succeededContext.Solution.Services.GetRequiredService<IChangeSignatureOptionsService>();
 
        return changeSignatureOptionsService.GetChangeSignatureOptions(
            succeededContext.Document, succeededContext.PositionForTypeBinding, succeededContext.Symbol, succeededContext.ParameterConfiguration);
    }
 
    private static async Task<ImmutableArray<ReferencedSymbol>> FindChangeSignatureReferencesAsync(
        ISymbol symbol,
        Solution solution,
        CancellationToken cancellationToken)
    {
        using (Logger.LogBlock(FunctionId.FindReference_ChangeSignature, cancellationToken))
        {
            var streamingProgress = new StreamingProgressCollector();
 
            var engine = new FindReferencesSearchEngine(
                solution,
                documents: null,
                [.. ReferenceFinders.DefaultReferenceFinders, DelegateInvokeMethodReferenceFinder.Instance],
                streamingProgress,
                FindReferencesSearchOptions.Default);
 
            await engine.FindReferencesAsync(symbol, cancellationToken).ConfigureAwait(false);
            return streamingProgress.GetReferencedSymbols();
        }
    }
 
    private async Task<(Solution updatedSolution, string? confirmationMessage)> CreateUpdatedSolutionAsync(
        ChangeSignatureAnalysisSucceededContext context, ChangeSignatureOptionsResult options, CancellationToken cancellationToken)
    {
        var telemetryTimer = Stopwatch.StartNew();
 
        var currentSolution = context.Solution;
        var declaredSymbol = context.Symbol;
 
        var nodesToUpdate = new Dictionary<DocumentId, List<SyntaxNode>>();
        var definitionToUse = new Dictionary<SyntaxNode, ISymbol>();
 
        string? confirmationMessage = null;
 
        var symbols = await FindChangeSignatureReferencesAsync(
            declaredSymbol, context.Solution, cancellationToken).ConfigureAwait(false);
 
        var declaredSymbolParametersCount = GetParameters(declaredSymbol).Length;
 
        var telemetryNumberOfDeclarationsToUpdate = 0;
        var telemetryNumberOfReferencesToUpdate = 0;
 
        foreach (var symbol in symbols)
        {
            var methodSymbol = symbol.Definition as IMethodSymbol;
 
            if (methodSymbol is { MethodKind: MethodKind.PropertyGet or MethodKind.PropertySet })
            {
                continue;
            }
 
            if (symbol.Definition.Kind == SymbolKind.NamedType)
            {
                continue;
            }
 
            if (symbol.Definition.Locations.Any(static loc => loc.IsInMetadata))
            {
                confirmationMessage = FeaturesResources.This_symbol_has_related_definitions_or_references_in_metadata_Changing_its_signature_may_result_in_build_errors_Do_you_want_to_continue;
                continue;
            }
 
            var symbolWithSyntacticParameters = symbol.Definition;
            var symbolWithSemanticParameters = symbol.Definition;
 
            var includeDefinitionLocations = true;
 
            if (symbol.Definition.Kind == SymbolKind.Field)
            {
                includeDefinitionLocations = false;
            }
 
            if (symbolWithSyntacticParameters is IEventSymbol eventSymbol)
            {
                if (eventSymbol.Type is INamedTypeSymbol type && type.DelegateInvokeMethod != null)
                {
                    symbolWithSemanticParameters = type.DelegateInvokeMethod;
                }
                else
                {
                    continue;
                }
            }
 
            if (methodSymbol != null)
            {
                if (methodSymbol.MethodKind == MethodKind.DelegateInvoke)
                {
                    symbolWithSyntacticParameters = methodSymbol.ContainingType;
                }
 
                if (methodSymbol.Name == WellKnownMemberNames.DelegateBeginInvokeName &&
                    methodSymbol.ContainingType != null &&
                    methodSymbol.ContainingType.IsDelegateType())
                {
                    includeDefinitionLocations = false;
                }
 
                // We update delegates which may have different signature.
                // It seems it is enough for now to compare delegates by parameter count only.
                if (methodSymbol.Parameters.Length != declaredSymbolParametersCount)
                {
                    includeDefinitionLocations = false;
                }
            }
 
            // Find and annotate all the relevant definitions
            if (includeDefinitionLocations)
            {
                foreach (var def in symbolWithSyntacticParameters.Locations)
                {
                    if (!TryGetNodeWithEditableSignatureOrAttributes(def, currentSolution, out var nodeToUpdate, out var documentId))
                    {
                        continue;
                    }
 
                    if (!nodesToUpdate.ContainsKey(documentId))
                    {
                        nodesToUpdate.Add(documentId, []);
                    }
 
                    telemetryNumberOfDeclarationsToUpdate++;
                    AddUpdatableNodeToDictionaries(nodesToUpdate, documentId, nodeToUpdate, definitionToUse, symbolWithSemanticParameters);
                }
            }
 
            // Find and annotate all the relevant references
            foreach (var location in symbol.Locations)
            {
                if (location.Location.IsInMetadata)
                {
                    confirmationMessage = FeaturesResources.This_symbol_has_related_definitions_or_references_in_metadata_Changing_its_signature_may_result_in_build_errors_Do_you_want_to_continue;
                    continue;
                }
 
                if (!TryGetNodeWithEditableSignatureOrAttributes(location.Location, currentSolution, out var nodeToUpdate2, out var documentId2))
                {
                    continue;
                }
 
                if (!nodesToUpdate.ContainsKey(documentId2))
                {
                    nodesToUpdate.Add(documentId2, []);
                }
 
                telemetryNumberOfReferencesToUpdate++;
                AddUpdatableNodeToDictionaries(nodesToUpdate, documentId2, nodeToUpdate2, definitionToUse, symbolWithSemanticParameters);
            }
        }
 
        // Construct all the relevant syntax trees from the base solution
        var updatedRoots = new Dictionary<DocumentId, SyntaxNode>();
        foreach (var docId in nodesToUpdate.Keys)
        {
            var doc = currentSolution.GetRequiredDocument(docId);
            var updater = doc.Project.Services.GetRequiredService<AbstractChangeSignatureService>();
            var root = await doc.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
            if (root is null)
            {
                throw new NotSupportedException(WorkspaceExtensionsResources.Document_does_not_support_syntax_trees);
            }
 
            var nodes = nodesToUpdate[docId];
            var semanticDocument = await SemanticDocument.CreateAsync(doc, cancellationToken).ConfigureAwait(false);
            var lineFormattingOptions = await doc.GetLineFormattingOptionsAsync(cancellationToken).ConfigureAwait(false);
 
            var newRoot = root.ReplaceNodes(nodes, (originalNode, potentiallyUpdatedNode) =>
            {
                return updater.ChangeSignature(
                    semanticDocument,
                    definitionToUse[originalNode],
                    potentiallyUpdatedNode,
                    originalNode,
                    UpdateSignatureChangeToIncludeExtraParametersFromTheDeclarationSymbol(definitionToUse[originalNode], options.UpdatedSignature),
                    lineFormattingOptions,
                    cancellationToken);
            });
 
            var annotatedNodes = newRoot.GetAnnotatedNodes<SyntaxNode>(syntaxAnnotation: ChangeSignatureFormattingAnnotation);
            var formattingOptions = await doc.GetSyntaxFormattingOptionsAsync(cancellationToken).ConfigureAwait(false);
 
            var formattedRoot = Formatter.Format(
                newRoot,
                ChangeSignatureFormattingAnnotation,
                doc.Project.Solution.Services,
                options: formattingOptions,
                rules: GetFormattingRules(doc),
                cancellationToken: CancellationToken.None);
 
            updatedRoots[docId] = formattedRoot;
        }
 
        // Update the documents using the updated syntax trees
        var changedDocuments = await ProducerConsumer<(DocumentId documentId, SyntaxNode newRoot)>.RunParallelAsync(
            source: nodesToUpdate.Keys,
            produceItems: static async (docId, callback, args, cancellationToken) =>
            {
                var (currentSolution, updatedRoots, context) = args;
                var updatedDoc = currentSolution.GetRequiredDocument(docId).WithSyntaxRoot(updatedRoots[docId]);
                var cleanupOptions = await updatedDoc.GetCodeCleanupOptionsAsync(cancellationToken).ConfigureAwait(false);
 
                var docWithImports = await ImportAdder.AddImportsFromSymbolAnnotationAsync(updatedDoc, cleanupOptions.AddImportOptions, cancellationToken).ConfigureAwait(false);
                var reducedDoc = await Simplifier.ReduceAsync(docWithImports, Simplifier.Annotation, cleanupOptions.SimplifierOptions, cancellationToken: cancellationToken).ConfigureAwait(false);
                var formattedDoc = await Formatter.FormatAsync(reducedDoc, SyntaxAnnotation.ElasticAnnotation, cleanupOptions.FormattingOptions, cancellationToken).ConfigureAwait(false);
 
                callback((formattedDoc.Id, await formattedDoc.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false)));
            },
            args: (currentSolution, updatedRoots, context),
            cancellationToken).ConfigureAwait(false);
 
        currentSolution = currentSolution.WithDocumentSyntaxRoots(changedDocuments);
 
        telemetryTimer.Stop();
        ChangeSignatureLogger.LogCommitInformation(telemetryNumberOfDeclarationsToUpdate, telemetryNumberOfReferencesToUpdate, telemetryTimer.Elapsed);
 
        return (currentSolution, confirmationMessage);
    }
 
#nullable disable
 
    private static void AddUpdatableNodeToDictionaries(Dictionary<DocumentId, List<SyntaxNode>> nodesToUpdate, DocumentId documentId, SyntaxNode nodeToUpdate, Dictionary<SyntaxNode, ISymbol> definitionToUse, ISymbol symbolWithSemanticParameters)
    {
        nodesToUpdate[documentId].Add(nodeToUpdate);
        if (definitionToUse.TryGetValue(nodeToUpdate, out var sym) && sym != symbolWithSemanticParameters)
        {
            Debug.Assert(false, "Change Signature: Attempted to modify node twice with different semantic parameters.");
        }
 
        definitionToUse[nodeToUpdate] = symbolWithSemanticParameters;
    }
 
    private static bool TryGetNodeWithEditableSignatureOrAttributes(Location location, Solution solution, out SyntaxNode nodeToUpdate, out DocumentId documentId)
    {
        var tree = location.SourceTree;
        documentId = solution.GetDocumentId(tree);
        var document = solution.GetDocument(documentId);
 
        var root = tree.GetRoot();
        var node = root.FindNode(location.SourceSpan, findInsideTrivia: true, getInnermostNodeForTie: true);
        var updater = document.GetLanguageService<AbstractChangeSignatureService>();
        nodeToUpdate = updater.FindNodeToUpdate(document, node);
 
        return nodeToUpdate != null;
    }
 
    protected ImmutableArray<IUnifiedArgumentSyntax> PermuteArguments(
        ISymbol declarationSymbol,
        ImmutableArray<IUnifiedArgumentSyntax> arguments,
        SignatureChange updatedSignature,
        bool isReducedExtensionMethod = false)
    {
        // 1. Determine which parameters are permutable
        var declarationParameters = GetParameters(declarationSymbol);
        var declarationParametersToPermute = GetParametersToPermute(arguments, declarationParameters, isReducedExtensionMethod);
        var argumentsToPermute = arguments.Take(declarationParametersToPermute.Length).ToList();
 
        // 2. Create an argument to parameter map, and a parameter to index map for the sort.
        var argumentToParameterMap = new Dictionary<IUnifiedArgumentSyntax, IParameterSymbol>();
        var parameterToIndexMap = new Dictionary<IParameterSymbol, int>();
 
        for (var i = 0; i < declarationParametersToPermute.Length; i++)
        {
            var decl = declarationParametersToPermute[i];
            var arg = argumentsToPermute[i];
 
            argumentToParameterMap[arg] = decl;
            var originalIndex = declarationParameters.IndexOf(decl);
 
            var updatedIndex = updatedSignature.GetUpdatedIndex(originalIndex);
 
            // If there's no value, then we may be handling a method with more parameters than the original symbol (like BeginInvoke).
            parameterToIndexMap[decl] = updatedIndex ?? -1;
        }
 
        // 3. Sort the arguments that need to be reordered
        argumentsToPermute.Sort((a1, a2) => { return parameterToIndexMap[argumentToParameterMap[a1]].CompareTo(parameterToIndexMap[argumentToParameterMap[a2]]); });
 
        // 4. Add names to arguments where necessary.
        var newArguments = ArrayBuilder<IUnifiedArgumentSyntax>.GetInstance();
        var expectedIndex = 0 + (isReducedExtensionMethod ? 1 : 0);
        var seenNamedArgument = false;
 
        // Holds the params array argument so it can be
        // added at the end.
        IUnifiedArgumentSyntax paramsArrayArgument = null;
 
        foreach (var argument in argumentsToPermute)
        {
            var param = argumentToParameterMap[argument];
            var actualIndex = updatedSignature.GetUpdatedIndex(declarationParameters.IndexOf(param));
 
            if (!actualIndex.HasValue)
            {
                continue;
            }
 
            if (!param.IsParams)
            {
                // If seen a named argument before, add names for subsequent ones.
                if ((seenNamedArgument || actualIndex != expectedIndex) && !argument.IsNamed)
                {
                    newArguments.Add(argument.WithName(param.Name).WithAdditionalAnnotations(Formatter.Annotation));
                    seenNamedArgument = true;
                }
                else
                {
                    newArguments.Add(argument);
                }
            }
            else
            {
                paramsArrayArgument = argument;
            }
 
            seenNamedArgument |= argument.IsNamed;
            expectedIndex++;
        }
 
        // 5. Add the params argument with the first value:
        if (paramsArrayArgument != null)
        {
            var param = argumentToParameterMap[paramsArrayArgument];
            if (seenNamedArgument && !paramsArrayArgument.IsNamed)
            {
                newArguments.Add(paramsArrayArgument.WithName(param.Name).WithAdditionalAnnotations(Formatter.Annotation));
                seenNamedArgument = true;
            }
            else
            {
                newArguments.Add(paramsArrayArgument);
            }
        }
 
        // 6. Add the remaining arguments. These will already have names or be params arguments, but may have been removed.
        var removedParams = updatedSignature.OriginalConfiguration.ParamsParameter != null && updatedSignature.UpdatedConfiguration.ParamsParameter == null;
        for (var i = declarationParametersToPermute.Length; i < arguments.Length; i++)
        {
            if (!arguments[i].IsNamed && removedParams && i >= updatedSignature.UpdatedConfiguration.ToListOfParameters().Length)
            {
                break;
            }
 
            if (!arguments[i].IsNamed || updatedSignature.UpdatedConfiguration.ToListOfParameters().Any(static (p, arg) => p.Name == arg.arguments[arg.i].GetName(), (arguments, i)))
            {
                newArguments.Add(arguments[i]);
            }
        }
 
        return newArguments.ToImmutableAndFree();
    }
 
    /// <summary>
    /// Sometimes signature changes can cascade from a declaration with m parameters to one with n > m parameters, such as
    /// delegate Invoke methods (m) and delegate BeginInvoke methods (n = m + 2). This method adds on those extra parameters
    /// to the base <see cref="SignatureChange"/>.
    /// </summary>
    private SignatureChange UpdateSignatureChangeToIncludeExtraParametersFromTheDeclarationSymbol(ISymbol declarationSymbol, SignatureChange updatedSignature)
    {
        var realParameters = GetParameters(declarationSymbol);
        if (realParameters.Length > updatedSignature.OriginalConfiguration.ToListOfParameters().Length)
        {
            var originalConfigurationParameters = updatedSignature.OriginalConfiguration.ToListOfParameters();
            var updatedConfigurationParameters = updatedSignature.UpdatedConfiguration.ToListOfParameters();
 
            var bonusParameters = realParameters.Skip(originalConfigurationParameters.Length);
 
            var originalConfigurationParametersWithExtraParameters = originalConfigurationParameters.AddRange(bonusParameters.Select(p => new ExistingParameter(p)));
            var updatedConfigurationParametersWithExtraParameters = updatedConfigurationParameters.AddRange(bonusParameters.Select(p => new ExistingParameter(p)));
 
            updatedSignature = new SignatureChange(
                ParameterConfiguration.Create(originalConfigurationParametersWithExtraParameters, updatedSignature.OriginalConfiguration.ThisParameter != null, selectedIndex: 0),
                ParameterConfiguration.Create(updatedConfigurationParametersWithExtraParameters, updatedSignature.OriginalConfiguration.ThisParameter != null, selectedIndex: 0));
        }
 
        return updatedSignature;
    }
 
    private static ImmutableArray<IParameterSymbol> GetParametersToPermute(
        ImmutableArray<IUnifiedArgumentSyntax> arguments,
        ImmutableArray<IParameterSymbol> originalParameters,
        bool isReducedExtensionMethod)
    {
        var position = -1 + (isReducedExtensionMethod ? 1 : 0);
        var parametersToPermute = ArrayBuilder<IParameterSymbol>.GetInstance();
 
        foreach (var argument in arguments)
        {
            if (argument.IsNamed)
            {
                var name = argument.GetName();
 
                // TODO: file bug for var match = originalParameters.FirstOrDefault(p => p.Name == <ISymbol here>);
                var match = originalParameters.FirstOrDefault(p => p.Name == name);
                if (match == null || originalParameters.IndexOf(match) <= position)
                {
                    break;
                }
                else
                {
                    position = originalParameters.IndexOf(match);
                    parametersToPermute.Add(match);
                }
            }
            else
            {
                position++;
 
                if (position >= originalParameters.Length)
                {
                    break;
                }
 
                parametersToPermute.Add(originalParameters[position]);
            }
        }
 
        return parametersToPermute.ToImmutableAndFree();
    }
 
    /// <summary>
    /// Given the cursor position, find which parameter is selected.
    /// Returns 0 as the default value. Note that the ChangeSignature dialog adjusts the selection for
    /// the `this` parameter in extension methods (the selected index won't remain 0).
    /// </summary>
    protected static int GetParameterIndex<TNode>(SeparatedSyntaxList<TNode> parameters, int position)
        where TNode : SyntaxNode
    {
        if (parameters.Count == 0)
        {
            return 0;
        }
 
        if (position < parameters.Span.Start)
        {
            return 0;
        }
 
        if (position > parameters.Span.End)
        {
            return 0;
        }
 
        for (var i = 0; i < parameters.Count - 1; i++)
        {
            // `$$,` points to the argument before the separator
            // but `,$$` points to the argument following the separator
            if (position <= parameters.GetSeparator(i).Span.Start)
            {
                return i;
            }
        }
 
        return parameters.Count - 1;
    }
 
    protected (ImmutableArray<T> parameters, ImmutableArray<SyntaxToken> separators) UpdateDeclarationBase<T>(
        SeparatedSyntaxList<T> list,
        SignatureChange updatedSignature,
        Func<AddedParameter, T> createNewParameterMethod) where T : SyntaxNode
    {
        var originalParameters = updatedSignature.OriginalConfiguration.ToListOfParameters();
        var reorderedParameters = updatedSignature.UpdatedConfiguration.ToListOfParameters();
 
        var numAddedParameters = 0;
 
        // Iterate through the list of new parameters and combine any
        // preexisting parameters with added parameters to construct
        // the full updated list.
        var newParameters = ImmutableArray.CreateBuilder<T>();
        for (var index = 0; index < reorderedParameters.Length; index++)
        {
            var newParam = reorderedParameters[index];
            if (newParam is ExistingParameter existingParameter)
            {
                var pos = originalParameters.IndexOf(p => p is ExistingParameter ep && ep.Symbol.Equals(existingParameter.Symbol));
                var param = list[pos];
 
                if (index < list.Count)
                {
                    param = TransferLeadingWhitespaceTrivia(param, list[index]);
                }
                else
                {
                    param = param.WithLeadingTrivia();
                }
 
                newParameters.Add(param);
            }
            else
            {
                // Added parameter
                var newParameter = createNewParameterMethod((AddedParameter)newParam);
 
                if (index < list.Count)
                {
                    newParameter = TransferLeadingWhitespaceTrivia(newParameter, list[index]);
                }
                else
                {
                    newParameter = newParameter.WithLeadingTrivia();
                }
 
                newParameters.Add(newParameter);
                numAddedParameters++;
            }
        }
 
        // (a,b,c)
        // Adding X parameters, need to add X separators.
        var numSeparatorsToSkip = originalParameters.Length - reorderedParameters.Length;
 
        if (originalParameters.Length == 0)
        {
            // () 
            // Adding X parameters, need to add X-1 separators.
            numSeparatorsToSkip++;
        }
 
        return (newParameters.ToImmutable(), GetSeparators(list, numSeparatorsToSkip));
    }
 
    protected ImmutableArray<SyntaxToken> GetSeparators<T>(SeparatedSyntaxList<T> arguments, int numSeparatorsToSkip) where T : SyntaxNode
    {
        var count = arguments.SeparatorCount - numSeparatorsToSkip;
        if (count < 0)
            return [];
 
        var separators = new FixedSizeArrayBuilder<SyntaxToken>(count);
 
        for (var i = 0; i < count; i++)
        {
            separators.Add(i < arguments.SeparatorCount
                ? arguments.GetSeparator(i)
                : CommaTokenWithElasticSpace());
        }
 
        return separators.MoveToImmutable();
    }
 
    protected virtual SeparatedSyntaxList<TArgumentSyntax> AddNewArgumentsToList<TArgumentSyntax>(
        SemanticDocument document,
        ISymbol declarationSymbol,
        SeparatedSyntaxList<TArgumentSyntax> newArguments,
        SignatureChange signaturePermutation,
        bool isReducedExtensionMethod,
        bool isParamsArrayExpanded,
        bool generateAttributeArguments,
        int position,
        CancellationToken cancellationToken)
        where TArgumentSyntax : SyntaxNode
    {
        var fullList = ArrayBuilder<TArgumentSyntax>.GetInstance();
        var separators = ArrayBuilder<SyntaxToken>.GetInstance();
 
        var updatedParameters = signaturePermutation.UpdatedConfiguration.ToListOfParameters();
 
        var indexInListOfPreexistingArguments = 0;
 
        var seenNamedArguments = false;
        var seenOmitted = false;
        var paramsHandled = false;
 
        for (var i = 0; i < updatedParameters.Length; i++)
        {
            // Skip this parameter in list of arguments for extension method calls but not for reduced ones.
            if (updatedParameters[i] != signaturePermutation.UpdatedConfiguration.ThisParameter
                || !isReducedExtensionMethod)
            {
                var parameters = GetParameters(declarationSymbol);
                if (updatedParameters[i] is AddedParameter addedParameter)
                {
                    // Omitting an argument only works in some languages, depending on whether
                    // there is a params array. We sometimes need to reinterpret an requested 
                    // omitted parameter as one with a TODO requested.
                    var forcedCallsiteErrorDueToParamsArray = addedParameter.CallSiteKind == CallSiteKind.Omitted &&
                        parameters.LastOrDefault()?.IsParams == true &&
                        !SupportsOptionalAndParamsArrayParametersSimultaneously();
 
                    var isCallsiteActuallyOmitted = addedParameter.CallSiteKind == CallSiteKind.Omitted && !forcedCallsiteErrorDueToParamsArray;
                    var isCallsiteActuallyTODO = addedParameter.CallSiteKind == CallSiteKind.Todo || forcedCallsiteErrorDueToParamsArray;
 
                    if (isCallsiteActuallyOmitted)
                    {
                        seenOmitted = true;
                        seenNamedArguments = true;
                        continue;
                    }
 
                    var expression = GenerateInferredCallsiteExpression(
                        document, position, addedParameter, cancellationToken);
 
                    if (expression == null)
                    {
                        // If we tried to infer the expression but failed, use a TODO instead.
                        isCallsiteActuallyTODO |= addedParameter.CallSiteKind == CallSiteKind.Inferred;
 
                        expression = Generator.ParseExpression(isCallsiteActuallyTODO ? "TODO" : addedParameter.CallSiteValue);
                    }
 
                    // TODO: Need to be able to specify which kind of attribute argument it is to the SyntaxGenerator.
                    // https://github.com/dotnet/roslyn/issues/43354
                    var argument = generateAttributeArguments
                        ? (TArgumentSyntax)Generator.AttributeArgument(
                            name: seenNamedArguments || addedParameter.CallSiteKind == CallSiteKind.ValueWithName ? addedParameter.Name : null,
                            expression: expression)
                        : (TArgumentSyntax)Generator.Argument(
                            name: seenNamedArguments || addedParameter.CallSiteKind == CallSiteKind.ValueWithName ? addedParameter.Name : null,
                            refKind: RefKind.None,
                            expression: expression);
 
                    fullList.Add(argument);
                    separators.Add(CommaTokenWithElasticSpace());
                }
                else
                {
                    if (indexInListOfPreexistingArguments == parameters.Length - 1 &&
                        parameters[indexInListOfPreexistingArguments].IsParams)
                    {
                        // Handling params array
                        if (seenOmitted)
                        {
                            // Need to ensure the params array is an actual array, and that the argument is named.
                            if (isParamsArrayExpanded)
                            {
                                var newArgument = CreateExplicitParamsArrayFromIndividualArguments(newArguments, indexInListOfPreexistingArguments, parameters[indexInListOfPreexistingArguments]);
                                newArgument = AddNameToArgument(newArgument, parameters[indexInListOfPreexistingArguments].Name);
                                fullList.Add(newArgument);
                            }
                            else if (indexInListOfPreexistingArguments < newArguments.Count)
                            {
                                var newArgument = newArguments[indexInListOfPreexistingArguments];
                                newArgument = AddNameToArgument(newArgument, parameters[indexInListOfPreexistingArguments].Name);
                                fullList.Add(newArgument);
                            }
 
                            paramsHandled = true;
                        }
                        else
                        {
                            // Normal case. Handled later.
                        }
                    }
                    else if (indexInListOfPreexistingArguments < newArguments.Count)
                    {
                        if (SyntaxFacts.IsNamedArgument(newArguments[indexInListOfPreexistingArguments]))
                        {
                            seenNamedArguments = true;
                        }
 
                        if (indexInListOfPreexistingArguments < newArguments.SeparatorCount)
                        {
                            separators.Add(newArguments.GetSeparator(indexInListOfPreexistingArguments));
                        }
 
                        var newArgument = newArguments[indexInListOfPreexistingArguments];
 
                        if (seenNamedArguments && !SyntaxFacts.IsNamedArgument(newArgument))
                        {
                            newArgument = AddNameToArgument(newArgument, parameters[indexInListOfPreexistingArguments].Name);
                        }
 
                        fullList.Add(newArgument);
                        indexInListOfPreexistingArguments++;
                    }
                }
            }
        }
 
        if (!paramsHandled)
        {
            // Add the rest of existing parameters, e.g. from the params argument.
            while (indexInListOfPreexistingArguments < newArguments.Count)
            {
                if (indexInListOfPreexistingArguments < newArguments.SeparatorCount)
                {
                    separators.Add(newArguments.GetSeparator(indexInListOfPreexistingArguments));
                }
 
                fullList.Add(newArguments[indexInListOfPreexistingArguments++]);
            }
        }
 
        if (fullList.Count == separators.Count && separators.Count != 0)
        {
            separators.Remove(separators.Last());
        }
 
        return Generator.SeparatedList(fullList.ToImmutableAndFree(), separators.ToImmutableAndFree());
    }
 
    private SyntaxNode GenerateInferredCallsiteExpression(
        SemanticDocument document,
        int position,
        AddedParameter addedParameter,
        CancellationToken cancellationToken)
    {
        if (addedParameter.CallSiteKind != CallSiteKind.Inferred || !addedParameter.TypeBinds)
        {
            return null;
        }
 
        var semanticModel = document.SemanticModel;
        var recommender = document.GetRequiredLanguageService<IRecommendationService>();
 
        var recommendationOptions = new RecommendationServiceOptions()
        {
            HideAdvancedMembers = false,
            FilterOutOfScopeLocals = true,
        };
 
        var context = document.GetRequiredLanguageService<ISyntaxContextService>().CreateContext(
            document.Document, semanticModel, position, cancellationToken);
        var recommendations = recommender.GetRecommendedSymbolsInContext(context, recommendationOptions, cancellationToken).NamedSymbols;
 
        var sourceSymbols = recommendations.Where(r => r.IsNonImplicitAndFromSource());
 
        // For locals, prefer the one with the closest declaration. Because we used the Recommender,
        // we do not have to worry about filtering out inaccessible locals.
        // TODO: Support range variables here as well: https://github.com/dotnet/roslyn/issues/44689
        var orderedLocalAndParameterSymbols = sourceSymbols
            .Where(s => s.IsKind(SymbolKind.Local) || s.IsKind(SymbolKind.Parameter))
            .OrderByDescending(s => s.Locations.First().SourceSpan.Start);
 
        // No particular ordering preference for properties/fields.
        var orderedPropertiesAndFields = sourceSymbols
            .Where(s => s.IsKind(SymbolKind.Property) || s.IsKind(SymbolKind.Field));
 
        var fullyOrderedSymbols = orderedLocalAndParameterSymbols.Concat(orderedPropertiesAndFields);
 
        foreach (var symbol in fullyOrderedSymbols)
        {
            var symbolType = symbol.GetSymbolType();
            if (symbolType == null)
            {
                continue;
            }
 
            if (semanticModel.Compilation.ClassifyCommonConversion(symbolType, addedParameter.Type).IsImplicit)
            {
                return Generator.IdentifierName(symbol.Name);
            }
        }
 
        return null;
    }
 
    protected ImmutableArray<SyntaxTrivia> GetPermutedDocCommentTrivia(SyntaxNode node, ImmutableArray<SyntaxNode> permutedParamNodes, LanguageServices services, LineFormattingOptions options)
    {
        var updatedLeadingTrivia = ImmutableArray.CreateBuilder<SyntaxTrivia>();
        var index = 0;
 
        var syntaxFacts = services.GetRequiredService<ISyntaxFactsService>();
 
        foreach (var trivia in node.GetLeadingTrivia())
        {
            if (!trivia.HasStructure)
            {
                updatedLeadingTrivia.Add(trivia);
                continue;
            }
 
            var structuredTrivia = trivia.GetStructure();
            if (!syntaxFacts.IsDocumentationComment(structuredTrivia))
            {
                updatedLeadingTrivia.Add(trivia);
                continue;
            }
 
            var updatedNodeList = ArrayBuilder<SyntaxNode>.GetInstance();
            var structuredContent = syntaxFacts.GetContentFromDocumentationCommentTriviaSyntax(trivia);
            for (var i = 0; i < structuredContent.Count; i++)
            {
                var content = structuredContent[i];
                if (!syntaxFacts.IsParameterNameXmlElementSyntax(content))
                {
                    updatedNodeList.Add(content);
                    continue;
                }
 
                // Found a param tag, so insert the next one from the reordered list
                if (index < permutedParamNodes.Length)
                {
                    updatedNodeList.Add(permutedParamNodes[index].WithLeadingTrivia(content.GetLeadingTrivia()).WithTrailingTrivia(content.GetTrailingTrivia()));
                    index++;
                }
                else
                {
                    // Inspecting a param element that we are deleting but not replacing.
                }
            }
 
            var newDocComments = Generator.DocumentationCommentTriviaWithUpdatedContent(trivia, updatedNodeList.ToImmutableAndFree());
            newDocComments = newDocComments.WithLeadingTrivia(structuredTrivia.GetLeadingTrivia()).WithTrailingTrivia(structuredTrivia.GetTrailingTrivia());
            var newTrivia = Generator.Trivia(newDocComments);
            updatedLeadingTrivia.Add(newTrivia);
        }
 
        using var _ = ArrayBuilder<SyntaxNode>.GetInstance(out var extraNodeList);
        while (index < permutedParamNodes.Length)
        {
            extraNodeList.Add(permutedParamNodes[index]);
            index++;
        }
 
        if (extraNodeList.Any())
        {
            var extraDocComments = Generator.DocumentationCommentTrivia(
                extraNodeList,
                node.GetTrailingTrivia(),
                options.NewLine);
            var newTrivia = Generator.Trivia(extraDocComments);
 
            updatedLeadingTrivia.Add(newTrivia);
        }
 
        return updatedLeadingTrivia.ToImmutableAndClear();
    }
 
    protected static bool IsParamsArrayExpandedHelper(ISymbol symbol, int argumentCount, bool lastArgumentIsNamed, SemanticModel semanticModel, SyntaxNode lastArgumentExpression, CancellationToken cancellationToken)
    {
        if (symbol is IMethodSymbol methodSymbol && methodSymbol.Parameters.LastOrDefault()?.IsParams == true)
        {
            if (argumentCount > methodSymbol.Parameters.Length)
            {
                return true;
            }
 
            if (argumentCount == methodSymbol.Parameters.Length)
            {
                if (lastArgumentIsNamed)
                {
                    // If the last argument is named, then it cannot be part of an expanded params array.
                    return false;
                }
                else
                {
                    var fromType = semanticModel.GetTypeInfo(lastArgumentExpression, cancellationToken);
                    var toType = methodSymbol.Parameters.Last().Type;
                    return !semanticModel.Compilation.HasImplicitConversion(fromType.Type, toType);
                }
            }
        }
 
        return false;
    }
 
    protected static int GetParameterIndexFromInvocationArgument(SyntaxNode argument, Document document, SemanticModel semanticModel, CancellationToken cancellationToken)
    {
        var semanticFacts = document.GetRequiredLanguageService<ISemanticFactsService>();
        var parameter = semanticFacts.FindParameterForArgument(semanticModel, argument, cancellationToken);
        if (parameter is null)
            return 0;
 
        // If we're in the invocation of an extension method that is called via this.Method(params). The 'this'
        // argument has an ordinal value of -1 but change signature is expecting all params to start at 0 (including
        // the 'this' param).
        return parameter.ContainingSymbol.IsReducedExtension()
            ? parameter.Ordinal + 1
            : parameter.Ordinal;
    }
}