|
// 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.Composition;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.ChangeSignature;
using Microsoft.CodeAnalysis.CSharp.CodeGeneration;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.LanguageService;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Formatting.Rules;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
namespace Microsoft.CodeAnalysis.CSharp.ChangeSignature;
using static CSharpSyntaxTokens;
[ExportLanguageService(typeof(AbstractChangeSignatureService), LanguageNames.CSharp), Shared]
internal sealed class CSharpChangeSignatureService : AbstractChangeSignatureService
{
protected override SyntaxGenerator Generator => CSharpSyntaxGenerator.Instance;
protected override ISyntaxFacts SyntaxFacts => CSharpSyntaxFacts.Instance;
private static readonly ImmutableArray<SyntaxKind> _declarationKinds =
[
SyntaxKind.MethodDeclaration,
SyntaxKind.ConstructorDeclaration,
SyntaxKind.IndexerDeclaration,
SyntaxKind.DelegateDeclaration,
SyntaxKind.SimpleLambdaExpression,
SyntaxKind.ParenthesizedLambdaExpression,
SyntaxKind.LocalFunctionStatement,
SyntaxKind.RecordStructDeclaration,
SyntaxKind.RecordDeclaration,
SyntaxKind.StructDeclaration,
SyntaxKind.ClassDeclaration,
];
private static readonly ImmutableArray<SyntaxKind> _declarationAndInvocableKinds =
_declarationKinds.Concat(
[
SyntaxKind.InvocationExpression,
SyntaxKind.ElementAccessExpression,
SyntaxKind.ThisConstructorInitializer,
SyntaxKind.BaseConstructorInitializer,
SyntaxKind.ObjectCreationExpression,
SyntaxKind.ImplicitObjectCreationExpression,
SyntaxKind.Attribute,
SyntaxKind.NameMemberCref,
]);
private static readonly ImmutableArray<SyntaxKind> _updatableAncestorKinds =
[
SyntaxKind.ConstructorDeclaration,
SyntaxKind.IndexerDeclaration,
SyntaxKind.InvocationExpression,
SyntaxKind.ElementAccessExpression,
SyntaxKind.ThisConstructorInitializer,
SyntaxKind.BaseConstructorInitializer,
SyntaxKind.ObjectCreationExpression,
SyntaxKind.Attribute,
SyntaxKind.DelegateDeclaration,
SyntaxKind.SimpleLambdaExpression,
SyntaxKind.ParenthesizedLambdaExpression,
SyntaxKind.NameMemberCref,
];
private static readonly ImmutableArray<SyntaxKind> _updatableNodeKinds =
[
SyntaxKind.MethodDeclaration,
SyntaxKind.LocalFunctionStatement,
SyntaxKind.ConstructorDeclaration,
SyntaxKind.IndexerDeclaration,
SyntaxKind.InvocationExpression,
SyntaxKind.ElementAccessExpression,
SyntaxKind.ThisConstructorInitializer,
SyntaxKind.BaseConstructorInitializer,
SyntaxKind.ObjectCreationExpression,
SyntaxKind.ImplicitObjectCreationExpression,
SyntaxKind.Attribute,
SyntaxKind.DelegateDeclaration,
SyntaxKind.NameMemberCref,
SyntaxKind.AnonymousMethodExpression,
SyntaxKind.ParenthesizedLambdaExpression,
SyntaxKind.SimpleLambdaExpression,
SyntaxKind.RecordStructDeclaration,
SyntaxKind.RecordDeclaration,
SyntaxKind.StructDeclaration,
SyntaxKind.ClassDeclaration,
];
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public CSharpChangeSignatureService()
{
}
public override async Task<(ISymbol? symbol, int selectedIndex)> GetInvocationSymbolAsync(
Document document, int position, bool restrictToDeclarations, CancellationToken cancellationToken)
{
var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var token = root.FindToken(position != tree.Length ? position : Math.Max(0, position - 1));
// Allow the user to invoke Change-Sig if they've written: Goo(a, b, c);$$
if (token.Kind() == SyntaxKind.SemicolonToken && token.Parent is StatementSyntax)
{
token = token.GetPreviousToken();
position = token.Span.End;
}
if (token.Parent == null)
{
return default;
}
var matchingNode = GetMatchingNode(token.Parent, restrictToDeclarations);
if (matchingNode == null)
{
return default;
}
// Don't show change-signature in the random whitespace/trivia for code.
if (!matchingNode.Span.IntersectsWith(position))
{
return default;
}
// If we're actually on the declaration of some symbol, ensure that we're
// in a good location for that symbol (i.e. not in the attributes/constraints).
if (!InSymbolHeader(matchingNode, position))
{
return default;
}
var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var symbol = semanticModel.GetDeclaredSymbol(matchingNode, cancellationToken);
if (symbol != null)
{
var selectedIndex = TryGetSelectedIndexFromDeclaration(position, matchingNode);
return (symbol, selectedIndex);
}
if (matchingNode is ObjectCreationExpressionSyntax objectCreation &&
token.Parent.AncestorsAndSelf().Any(a => a == objectCreation.Type))
{
var typeSymbol = semanticModel.GetSymbolInfo(objectCreation.Type, cancellationToken).Symbol;
if (typeSymbol != null && typeSymbol.IsKind(SymbolKind.NamedType) && ((ITypeSymbol)typeSymbol).TypeKind == TypeKind.Delegate)
{
return (typeSymbol, 0);
}
}
var symbolInfo = semanticModel.GetSymbolInfo(matchingNode, cancellationToken);
var parameterIndex = 0;
// If we're being called on an invocation and not a definition we need to find the selected argument index based on the original definition.
var argumentList = matchingNode is ObjectCreationExpressionSyntax objCreation ? objCreation.ArgumentList
: matchingNode.GetAncestorOrThis<InvocationExpressionSyntax>()?.ArgumentList;
var argument = argumentList?.Arguments.FirstOrDefault(a => a.Span.Contains(position));
if (argument != null)
{
parameterIndex = GetParameterIndexFromInvocationArgument(argument, document, semanticModel, cancellationToken);
}
return (symbolInfo.Symbol ?? symbolInfo.CandidateSymbols.FirstOrDefault(), parameterIndex);
}
private static int TryGetSelectedIndexFromDeclaration(int position, SyntaxNode matchingNode)
{
var parameters = matchingNode.ChildNodes().OfType<BaseParameterListSyntax>().SingleOrDefault();
return parameters != null ? GetParameterIndex(parameters.Parameters, position) : 0;
}
private static SyntaxNode? GetMatchingNode(SyntaxNode node, bool restrictToDeclarations)
{
var matchKinds = restrictToDeclarations
? _declarationKinds
: _declarationAndInvocableKinds;
for (var current = node; current != null; current = current.Parent)
{
if (restrictToDeclarations &&
current.Kind() == SyntaxKind.Block || current.Kind() == SyntaxKind.ArrowExpressionClause)
{
return null;
}
if (matchKinds.Contains(current.Kind()))
{
return current;
}
}
return null;
}
private static bool InSymbolHeader(SyntaxNode matchingNode, int position)
{
// Caret has to be after the attributes if the symbol has any.
var lastAttributes = matchingNode.ChildNodes().LastOrDefault(n => n is AttributeListSyntax);
var start = lastAttributes?.GetLastToken().GetNextToken().SpanStart ??
matchingNode.SpanStart;
if (position < start)
{
return false;
}
// If the symbol has a parameter list, then the caret shouldn't be past the end of it.
var parameterList = matchingNode.ChildNodes().LastOrDefault(n => n is ParameterListSyntax);
if (parameterList != null)
{
return position <= parameterList.FullSpan.End;
}
// Case we haven't handled yet. Just assume we're in the header.
return true;
}
public override SyntaxNode? FindNodeToUpdate(Document document, SyntaxNode node)
{
if (_updatableNodeKinds.Contains(node.Kind()))
{
return node;
}
// TODO: file bug about this: var invocation = csnode.Ancestors().FirstOrDefault(a => a.Kind == SyntaxKind.InvocationExpression);
var matchingNode = node.AncestorsAndSelf().FirstOrDefault(n => _updatableAncestorKinds.Contains(n.Kind()));
if (matchingNode == null)
{
return null;
}
var nodeContainingOriginal = GetNodeContainingTargetNode(matchingNode);
if (nodeContainingOriginal == null)
{
return null;
}
return node.AncestorsAndSelf().Any(n => n == nodeContainingOriginal) ? matchingNode : null;
}
private static SyntaxNode? GetNodeContainingTargetNode(SyntaxNode matchingNode)
{
switch (matchingNode.Kind())
{
case SyntaxKind.InvocationExpression:
return ((InvocationExpressionSyntax)matchingNode).Expression;
case SyntaxKind.ElementAccessExpression:
return ((ElementAccessExpressionSyntax)matchingNode).ArgumentList;
case SyntaxKind.ObjectCreationExpression:
return ((ObjectCreationExpressionSyntax)matchingNode).Type;
case SyntaxKind.ConstructorDeclaration:
case SyntaxKind.IndexerDeclaration:
case SyntaxKind.ThisConstructorInitializer:
case SyntaxKind.BaseConstructorInitializer:
case SyntaxKind.Attribute:
case SyntaxKind.DelegateDeclaration:
case SyntaxKind.NameMemberCref:
return matchingNode;
default:
return null;
}
}
public override async Task<SyntaxNode> ChangeSignatureAsync(
Document document,
ISymbol declarationSymbol,
SyntaxNode potentiallyUpdatedNode,
SyntaxNode originalNode,
SignatureChange signaturePermutation,
CancellationToken cancellationToken)
{
var updatedNode = potentiallyUpdatedNode as CSharpSyntaxNode;
// Update <param> tags.
if (updatedNode?.Kind()
is SyntaxKind.MethodDeclaration
or SyntaxKind.ConstructorDeclaration
or SyntaxKind.IndexerDeclaration
or SyntaxKind.DelegateDeclaration
or SyntaxKind.RecordStructDeclaration
or SyntaxKind.RecordDeclaration
or SyntaxKind.StructDeclaration
or SyntaxKind.ClassDeclaration)
{
var updatedLeadingTrivia = await UpdateParamTagsInLeadingTriviaAsync(document, updatedNode, declarationSymbol, signaturePermutation, cancellationToken).ConfigureAwait(false);
if (updatedLeadingTrivia != default && !updatedLeadingTrivia.IsEmpty)
{
updatedNode = updatedNode.WithLeadingTrivia(updatedLeadingTrivia);
}
}
// Update declarations parameter lists
if (updatedNode is MethodDeclarationSyntax method)
{
var updatedParameters = UpdateDeclaration(method.ParameterList.Parameters, signaturePermutation, CreateNewParameterSyntax);
return method.WithParameterList(method.ParameterList.WithParameters(updatedParameters).WithAdditionalAnnotations(changeSignatureFormattingAnnotation));
}
if (updatedNode is TypeDeclarationSyntax { ParameterList: not null } typeWithParameters)
{
var updatedParameters = UpdateDeclaration(typeWithParameters.ParameterList.Parameters, signaturePermutation, CreateNewParameterSyntax);
return typeWithParameters.WithParameterList(typeWithParameters.ParameterList.WithParameters(updatedParameters).WithAdditionalAnnotations(changeSignatureFormattingAnnotation));
}
if (updatedNode is LocalFunctionStatementSyntax localFunction)
{
var updatedParameters = UpdateDeclaration(localFunction.ParameterList.Parameters, signaturePermutation, CreateNewParameterSyntax);
return localFunction.WithParameterList(localFunction.ParameterList.WithParameters(updatedParameters).WithAdditionalAnnotations(changeSignatureFormattingAnnotation));
}
if (updatedNode is ConstructorDeclarationSyntax constructor)
{
var updatedParameters = UpdateDeclaration(constructor.ParameterList.Parameters, signaturePermutation, CreateNewParameterSyntax);
return constructor.WithParameterList(constructor.ParameterList.WithParameters(updatedParameters).WithAdditionalAnnotations(changeSignatureFormattingAnnotation));
}
if (updatedNode is IndexerDeclarationSyntax indexer)
{
var updatedParameters = UpdateDeclaration(indexer.ParameterList.Parameters, signaturePermutation, CreateNewParameterSyntax);
return indexer.WithParameterList(indexer.ParameterList.WithParameters(updatedParameters).WithAdditionalAnnotations(changeSignatureFormattingAnnotation));
}
if (updatedNode is DelegateDeclarationSyntax delegateDeclaration)
{
var updatedParameters = UpdateDeclaration(delegateDeclaration.ParameterList.Parameters, signaturePermutation, CreateNewParameterSyntax);
return delegateDeclaration.WithParameterList(delegateDeclaration.ParameterList.WithParameters(updatedParameters).WithAdditionalAnnotations(changeSignatureFormattingAnnotation));
}
if (updatedNode is AnonymousMethodExpressionSyntax anonymousMethod)
{
// Delegates may omit parameters in C#
if (anonymousMethod.ParameterList == null)
{
return anonymousMethod;
}
var updatedParameters = UpdateDeclaration(anonymousMethod.ParameterList.Parameters, signaturePermutation, CreateNewParameterSyntax);
return anonymousMethod.WithParameterList(anonymousMethod.ParameterList.WithParameters(updatedParameters).WithAdditionalAnnotations(changeSignatureFormattingAnnotation));
}
if (updatedNode is SimpleLambdaExpressionSyntax lambda)
{
if (signaturePermutation.UpdatedConfiguration.ToListOfParameters().Any())
{
var updatedParameters = UpdateDeclaration([lambda.Parameter], signaturePermutation, CreateNewParameterSyntax);
return ParenthesizedLambdaExpression(
lambda.AsyncKeyword,
ParameterList(updatedParameters),
lambda.ArrowToken,
lambda.Body);
}
else
{
// No parameters. Change to a parenthesized lambda expression
var emptyParameterList = ParameterList()
.WithLeadingTrivia(lambda.Parameter.GetLeadingTrivia())
.WithTrailingTrivia(lambda.Parameter.GetTrailingTrivia());
return ParenthesizedLambdaExpression(lambda.AsyncKeyword, emptyParameterList, lambda.ArrowToken, lambda.Body);
}
}
if (updatedNode is ParenthesizedLambdaExpressionSyntax parenLambda)
{
var doNotSkipParameterType = parenLambda.ParameterList.Parameters.FirstOrDefault()?.Type != null;
var updatedParameters = UpdateDeclaration(
parenLambda.ParameterList.Parameters,
signaturePermutation,
p => CreateNewParameterSyntax(p, !doNotSkipParameterType));
return parenLambda.WithParameterList(parenLambda.ParameterList.WithParameters(updatedParameters));
}
// Handle references in crefs
if (updatedNode is NameMemberCrefSyntax nameMemberCref)
{
if (nameMemberCref.Parameters == null ||
!nameMemberCref.Parameters.Parameters.Any())
{
return nameMemberCref;
}
var newParameters = UpdateDeclaration(nameMemberCref.Parameters.Parameters, signaturePermutation, CreateNewCrefParameterSyntax);
var newCrefParameterList = nameMemberCref.Parameters.WithParameters(newParameters);
return nameMemberCref.WithParameters(newCrefParameterList);
}
var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
// Update reference site argument lists
if (updatedNode is InvocationExpressionSyntax invocation)
{
var symbolInfo = semanticModel.GetSymbolInfo((InvocationExpressionSyntax)originalNode, cancellationToken);
return invocation.WithArgumentList(
await UpdateArgumentListAsync(
declarationSymbol,
signaturePermutation,
invocation.ArgumentList,
symbolInfo.Symbol is IMethodSymbol { MethodKind: MethodKind.ReducedExtension },
IsParamsArrayExpanded(semanticModel, invocation, symbolInfo, cancellationToken),
document,
originalNode.SpanStart,
cancellationToken).ConfigureAwait(false));
}
// Handles both ObjectCreationExpressionSyntax and ImplicitObjectCreationExpressionSyntax
if (updatedNode is BaseObjectCreationExpressionSyntax objCreation)
{
if (objCreation.ArgumentList == null)
{
return updatedNode;
}
var symbolInfo = semanticModel.GetSymbolInfo((BaseObjectCreationExpressionSyntax)originalNode, cancellationToken);
return objCreation.WithArgumentList(
await UpdateArgumentListAsync(
declarationSymbol,
signaturePermutation,
objCreation.ArgumentList,
isReducedExtensionMethod: false,
IsParamsArrayExpanded(semanticModel, objCreation, symbolInfo, cancellationToken),
document,
originalNode.SpanStart,
cancellationToken).ConfigureAwait(false));
}
if (updatedNode is ConstructorInitializerSyntax constructorInit)
{
var symbolInfo = semanticModel.GetSymbolInfo((ConstructorInitializerSyntax)originalNode, cancellationToken);
return constructorInit.WithArgumentList(
await UpdateArgumentListAsync(
declarationSymbol,
signaturePermutation,
constructorInit.ArgumentList,
isReducedExtensionMethod: false,
IsParamsArrayExpanded(semanticModel, constructorInit, symbolInfo, cancellationToken),
document,
originalNode.SpanStart,
cancellationToken).ConfigureAwait(false));
}
if (updatedNode is ElementAccessExpressionSyntax elementAccess)
{
var symbolInfo = semanticModel.GetSymbolInfo((ElementAccessExpressionSyntax)originalNode, cancellationToken);
return elementAccess.WithArgumentList(
await UpdateArgumentListAsync(
declarationSymbol,
signaturePermutation,
elementAccess.ArgumentList,
isReducedExtensionMethod: false,
IsParamsArrayExpanded(semanticModel, elementAccess, symbolInfo, cancellationToken),
document,
originalNode.SpanStart,
cancellationToken).ConfigureAwait(false));
}
if (updatedNode is AttributeSyntax attribute)
{
var symbolInfo = semanticModel.GetSymbolInfo((AttributeSyntax)originalNode, cancellationToken);
if (attribute.ArgumentList == null)
{
return updatedNode;
}
return attribute.WithArgumentList(
await UpdateAttributeArgumentListAsync(
declarationSymbol,
signaturePermutation,
attribute.ArgumentList,
isReducedExtensionMethod: false,
IsParamsArrayExpanded(semanticModel, attribute, symbolInfo, cancellationToken),
document,
originalNode.SpanStart,
cancellationToken).ConfigureAwait(false));
}
Debug.Assert(false, "Unknown reference location");
return null;
}
private async Task<T> UpdateArgumentListAsync<T>(
ISymbol declarationSymbol,
SignatureChange signaturePermutation,
T argumentList,
bool isReducedExtensionMethod,
bool isParamsArrayExpanded,
Document document,
int position,
CancellationToken cancellationToken) where T : BaseArgumentListSyntax
{
// Reorders and removes arguments
// e.g. P(a, b, c) ==> P(c, a)
var newArguments = PermuteArgumentList(
declarationSymbol,
argumentList.Arguments,
signaturePermutation.WithoutAddedParameters(),
isReducedExtensionMethod);
// Adds new arguments into the updated list
// e.g. P(c, a) ==> P(x, c, a, y)
newArguments = await AddNewArgumentsToListAsync(
declarationSymbol,
newArguments,
argumentList.Arguments,
signaturePermutation,
isReducedExtensionMethod,
isParamsArrayExpanded,
generateAttributeArguments: false,
document,
position,
cancellationToken).ConfigureAwait(false);
return (T)argumentList
.WithArguments(newArguments)
.WithAdditionalAnnotations(changeSignatureFormattingAnnotation);
}
private async Task<AttributeArgumentListSyntax> UpdateAttributeArgumentListAsync(
ISymbol declarationSymbol,
SignatureChange signaturePermutation,
AttributeArgumentListSyntax argumentList,
bool isReducedExtensionMethod,
bool isParamsArrayExpanded,
Document document,
int position,
CancellationToken cancellationToken)
{
var newArguments = PermuteAttributeArgumentList(
declarationSymbol,
argumentList.Arguments,
signaturePermutation.WithoutAddedParameters());
newArguments = await AddNewArgumentsToListAsync(
declarationSymbol,
newArguments,
argumentList.Arguments,
signaturePermutation,
isReducedExtensionMethod,
isParamsArrayExpanded,
generateAttributeArguments: true,
document,
position,
cancellationToken).ConfigureAwait(false);
return argumentList
.WithArguments(newArguments)
.WithAdditionalAnnotations(changeSignatureFormattingAnnotation);
}
private static bool IsParamsArrayExpanded(SemanticModel semanticModel, SyntaxNode node, SymbolInfo symbolInfo, CancellationToken cancellationToken)
{
if (symbolInfo.Symbol == null)
{
return false;
}
int argumentCount;
bool lastArgumentIsNamed;
ExpressionSyntax lastArgumentExpression;
if (node is AttributeSyntax attribute)
{
if (attribute.ArgumentList == null)
{
return false;
}
argumentCount = attribute.ArgumentList.Arguments.Count;
lastArgumentIsNamed = attribute.ArgumentList.Arguments.LastOrDefault()?.NameColon != null ||
attribute.ArgumentList.Arguments.LastOrDefault()?.NameEquals != null;
var lastArgument = attribute.ArgumentList.Arguments.LastOrDefault();
if (lastArgument == null)
{
return false;
}
lastArgumentExpression = lastArgument.Expression;
}
else
{
BaseArgumentListSyntax? argumentList = node switch
{
InvocationExpressionSyntax invocation => invocation.ArgumentList,
BaseObjectCreationExpressionSyntax objectCreation => objectCreation.ArgumentList,
ConstructorInitializerSyntax constructorInitializer => constructorInitializer.ArgumentList,
ElementAccessExpressionSyntax elementAccess => elementAccess.ArgumentList,
_ => throw ExceptionUtilities.UnexpectedValue(node.Kind())
};
if (argumentList == null)
{
return false;
}
argumentCount = argumentList.Arguments.Count;
lastArgumentIsNamed = argumentList.Arguments.LastOrDefault()?.NameColon != null;
var lastArgument = argumentList.Arguments.LastOrDefault();
if (lastArgument == null)
{
return false;
}
lastArgumentExpression = lastArgument.Expression;
}
return IsParamsArrayExpandedHelper(symbolInfo.Symbol, argumentCount, lastArgumentIsNamed, semanticModel, lastArgumentExpression, cancellationToken);
}
private static ParameterSyntax CreateNewParameterSyntax(AddedParameter addedParameter)
=> CreateNewParameterSyntax(addedParameter, skipParameterType: false);
private static ParameterSyntax CreateNewParameterSyntax(AddedParameter addedParameter, bool skipParameterType)
{
var equalsValueClause = addedParameter.HasDefaultValue
? EqualsValueClause(ParseExpression(addedParameter.DefaultValue))
: null;
return Parameter(
attributeLists: default,
modifiers: default,
type: skipParameterType
? null
: addedParameter.Type.GenerateTypeSyntax(),
Identifier(addedParameter.Name),
@default: equalsValueClause);
}
private static CrefParameterSyntax CreateNewCrefParameterSyntax(AddedParameter addedParameter)
=> CrefParameter(type: addedParameter.Type.GenerateTypeSyntax())
.WithLeadingTrivia(ElasticSpace);
private SeparatedSyntaxList<T> UpdateDeclaration<T>(
SeparatedSyntaxList<T> list,
SignatureChange updatedSignature,
Func<AddedParameter, T> createNewParameterMethod) where T : SyntaxNode
{
var (parameters, separators) = base.UpdateDeclarationBase<T>(list, updatedSignature, createNewParameterMethod);
return SeparatedList(parameters, separators);
}
protected override T TransferLeadingWhitespaceTrivia<T>(T newArgument, SyntaxNode oldArgument)
{
var oldTrivia = oldArgument.GetLeadingTrivia();
var oldOnlyHasWhitespaceTrivia = oldTrivia.All(t => t.IsKind(SyntaxKind.WhitespaceTrivia));
var newTrivia = newArgument.GetLeadingTrivia();
var newOnlyHasWhitespaceTrivia = newTrivia.All(t => t.IsKind(SyntaxKind.WhitespaceTrivia));
if (oldOnlyHasWhitespaceTrivia && newOnlyHasWhitespaceTrivia)
{
newArgument = newArgument.WithLeadingTrivia(oldTrivia);
}
return newArgument;
}
private async Task<SeparatedSyntaxList<TArgumentSyntax>> AddNewArgumentsToListAsync<TArgumentSyntax>(
ISymbol declarationSymbol,
SeparatedSyntaxList<TArgumentSyntax> newArguments,
SeparatedSyntaxList<TArgumentSyntax> originalArguments,
SignatureChange signaturePermutation,
bool isReducedExtensionMethod,
bool isParamsArrayExpanded,
bool generateAttributeArguments,
Document document,
int position,
CancellationToken cancellationToken)
where TArgumentSyntax : SyntaxNode
{
var newArgumentList = await AddNewArgumentsToListAsync(
declarationSymbol, newArguments,
signaturePermutation, isReducedExtensionMethod,
isParamsArrayExpanded, generateAttributeArguments,
document, position, cancellationToken).ConfigureAwait(false);
return SeparatedList(
TransferLeadingWhitespaceTrivia(newArgumentList, originalArguments),
newArgumentList.GetSeparators());
}
private SeparatedSyntaxList<AttributeArgumentSyntax> PermuteAttributeArgumentList(
ISymbol declarationSymbol,
SeparatedSyntaxList<AttributeArgumentSyntax> arguments,
SignatureChange updatedSignature)
{
var newArguments = PermuteArguments(declarationSymbol, arguments.Select(a => UnifiedArgumentSyntax.Create(a)).ToImmutableArray(),
updatedSignature);
var numSeparatorsToSkip = arguments.Count - newArguments.Length;
// copy whitespace trivia from original position
var newArgumentsWithTrivia = TransferLeadingWhitespaceTrivia(
newArguments.Select(a => (AttributeArgumentSyntax)(UnifiedArgumentSyntax)a), arguments);
return SeparatedList(newArgumentsWithTrivia, GetSeparators(arguments, numSeparatorsToSkip));
}
private SeparatedSyntaxList<ArgumentSyntax> PermuteArgumentList(
ISymbol declarationSymbol,
SeparatedSyntaxList<ArgumentSyntax> arguments,
SignatureChange updatedSignature,
bool isReducedExtensionMethod = false)
{
var newArguments = PermuteArguments(
declarationSymbol,
arguments.Select(a => UnifiedArgumentSyntax.Create(a)).ToImmutableArray(),
updatedSignature,
isReducedExtensionMethod);
// copy whitespace trivia from original position
var newArgumentsWithTrivia = TransferLeadingWhitespaceTrivia(
newArguments.Select(a => (ArgumentSyntax)(UnifiedArgumentSyntax)a), arguments);
var numSeparatorsToSkip = arguments.Count - newArguments.Length;
return SeparatedList(newArgumentsWithTrivia, GetSeparators(arguments, numSeparatorsToSkip));
}
private ImmutableArray<T> TransferLeadingWhitespaceTrivia<T, U>(IEnumerable<T> newArguments, SeparatedSyntaxList<U> oldArguments)
where T : SyntaxNode
where U : SyntaxNode
{
var result = ImmutableArray.CreateBuilder<T>();
var index = 0;
foreach (var newArgument in newArguments)
{
result.Add(index < oldArguments.Count
? TransferLeadingWhitespaceTrivia(newArgument, oldArguments[index])
: newArgument);
index++;
}
return result.ToImmutableAndClear();
}
private async ValueTask<ImmutableArray<SyntaxTrivia>> UpdateParamTagsInLeadingTriviaAsync(
Document document, CSharpSyntaxNode node, ISymbol declarationSymbol, SignatureChange updatedSignature, CancellationToken cancellationToken)
{
if (!node.HasLeadingTrivia)
{
return [];
}
var paramNodes = node
.DescendantNodes(descendIntoTrivia: true)
.OfType<XmlElementSyntax>()
.Where(e => e.StartTag.Name.ToString() == DocumentationCommentXmlNames.ParameterElementName);
var permutedParamNodes = VerifyAndPermuteParamNodes(paramNodes, declarationSymbol, updatedSignature);
if (permutedParamNodes.IsEmpty)
{
return [];
}
var options = await document.GetLineFormattingOptionsAsync(cancellationToken).ConfigureAwait(false);
return GetPermutedDocCommentTrivia(node, permutedParamNodes, document.Project.Services, options);
}
private ImmutableArray<SyntaxNode> VerifyAndPermuteParamNodes(IEnumerable<XmlElementSyntax> paramNodes, ISymbol declarationSymbol, SignatureChange updatedSignature)
{
// Only reorder if count and order match originally.
var originalParameters = updatedSignature.OriginalConfiguration.ToListOfParameters();
var reorderedParameters = updatedSignature.UpdatedConfiguration.ToListOfParameters();
var declaredParameters = GetParameters(declarationSymbol);
if (paramNodes.Count() != declaredParameters.Length)
{
return [];
}
// No parameters originally, so no param nodes to permute.
if (declaredParameters.Length == 0)
{
return [];
}
var dictionary = new Dictionary<string, XmlElementSyntax>();
var i = 0;
foreach (var paramNode in paramNodes)
{
var nameAttribute = paramNode.StartTag.Attributes.FirstOrDefault(a => a.Name.ToString().Equals("name", StringComparison.OrdinalIgnoreCase));
if (nameAttribute == null)
{
return [];
}
var identifier = nameAttribute.DescendantNodes(descendIntoTrivia: true).OfType<IdentifierNameSyntax>().FirstOrDefault();
if (identifier == null || identifier.ToString() != declaredParameters.ElementAt(i).Name)
{
return [];
}
dictionary.Add(originalParameters[i].Name.ToString(), paramNode);
i++;
}
// Everything lines up, so permute them.
var permutedParams = ArrayBuilder<SyntaxNode>.GetInstance();
foreach (var parameter in reorderedParameters)
{
if (dictionary.TryGetValue(parameter.Name, out var permutedParam))
{
permutedParams.Add(permutedParam);
}
else
{
permutedParams.Add(XmlElement(
XmlElementStartTag(
XmlName("param"),
[XmlNameAttribute(parameter.Name)]),
XmlElementEndTag(XmlName("param"))));
}
}
return permutedParams.ToImmutableAndFree();
}
public override async Task<ImmutableArray<ISymbol>> DetermineCascadedSymbolsFromDelegateInvokeAsync(
IMethodSymbol symbol,
Document document,
CancellationToken cancellationToken)
{
var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
using var _ = ArrayBuilder<SyntaxNode>.GetInstance(out var convertedMethodNodes);
foreach (var node in root.DescendantNodes())
{
if (!node.IsKind(SyntaxKind.IdentifierName) ||
!semanticModel.GetMemberGroup(node, cancellationToken).Any())
{
continue;
}
var convertedType = (ISymbol?)semanticModel.GetTypeInfo(node, cancellationToken).ConvertedType;
convertedType = convertedType?.OriginalDefinition;
if (convertedType != null)
{
convertedType = await SymbolFinder.FindSourceDefinitionAsync(convertedType, document.Project.Solution, cancellationToken).ConfigureAwait(false)
?? convertedType;
}
if (Equals(convertedType, symbol.ContainingType))
convertedMethodNodes.Add(node);
}
var convertedMethodGroups = convertedMethodNodes
.Select(n => semanticModel.GetSymbolInfo(n, cancellationToken).Symbol)
.WhereNotNull()
.ToImmutableArray();
return convertedMethodGroups;
}
protected override ImmutableArray<AbstractFormattingRule> GetFormattingRules(Document document)
=> [.. Formatter.GetDefaultFormattingRules(document), new ChangeSignatureFormattingRule()];
protected override TArgumentSyntax AddNameToArgument<TArgumentSyntax>(TArgumentSyntax newArgument, string name)
{
return newArgument switch
{
ArgumentSyntax a => (TArgumentSyntax)(SyntaxNode)a.WithNameColon(NameColon(name)),
AttributeArgumentSyntax a => (TArgumentSyntax)(SyntaxNode)a.WithNameColon(NameColon(name)),
_ => throw ExceptionUtilities.UnexpectedValue(newArgument.Kind())
};
}
protected override TArgumentSyntax CreateExplicitParamsArrayFromIndividualArguments<TArgumentSyntax>(SeparatedSyntaxList<TArgumentSyntax> newArguments, int indexInExistingList, IParameterSymbol parameterSymbol)
{
RoslynDebug.Assert(parameterSymbol.IsParams);
// These arguments are part of a params array, and should not have any modifiers, making it okay to just use their expressions.
var listOfArguments = SeparatedList(newArguments.Skip(indexInExistingList).Select(a => ((ArgumentSyntax)(SyntaxNode)a).Expression), newArguments.GetSeparators().Skip(indexInExistingList));
var initializerExpression = InitializerExpression(SyntaxKind.ArrayInitializerExpression, listOfArguments);
var objectCreation = ArrayCreationExpression((ArrayTypeSyntax)parameterSymbol.Type.GenerateTypeSyntax(), initializerExpression);
return (TArgumentSyntax)(SyntaxNode)Argument(objectCreation);
}
protected override bool SupportsOptionalAndParamsArrayParametersSimultaneously()
{
return true;
}
protected override SyntaxToken CommaTokenWithElasticSpace()
=> CommaToken.WithTrailingTrivia(ElasticSpace);
protected override bool TryGetRecordPrimaryConstructor(INamedTypeSymbol typeSymbol, [NotNullWhen(true)] out IMethodSymbol? primaryConstructor)
=> typeSymbol.TryGetPrimaryConstructor(out primaryConstructor);
protected override ImmutableArray<IParameterSymbol> GetParameters(ISymbol declarationSymbol)
{
var declaredParameters = declarationSymbol.GetParameters();
if (declarationSymbol is INamedTypeSymbol namedTypeSymbol &&
namedTypeSymbol.TryGetPrimaryConstructor(out var primaryConstructor))
{
declaredParameters = primaryConstructor.Parameters;
}
return declaredParameters;
}
}
|