|
// 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.Composition;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.InitializeParameter;
using Microsoft.CodeAnalysis.Operations;
namespace Microsoft.CodeAnalysis.CSharp.InitializeParameter
{
[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.InitializeMemberFromParameter), Shared]
[ExtensionOrder(Before = nameof(CSharpAddParameterCheckCodeRefactoringProvider))]
[ExtensionOrder(Before = PredefinedCodeRefactoringProviderNames.Wrapping)]
internal class CSharpInitializeMemberFromParameterCodeRefactoringProvider :
AbstractInitializeMemberFromParameterCodeRefactoringProvider<
BaseTypeDeclarationSyntax,
ParameterSyntax,
StatementSyntax,
ExpressionSyntax>
{
[ImportingConstructor]
[SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")]
public CSharpInitializeMemberFromParameterCodeRefactoringProvider()
{
}
protected override bool IsFunctionDeclaration(SyntaxNode node)
=> InitializeParameterHelpers.IsFunctionDeclaration(node);
protected override SyntaxNode? TryGetLastStatement(IBlockOperation? blockStatement)
=> InitializeParameterHelpers.TryGetLastStatement(blockStatement);
protected override void InsertStatement(SyntaxEditor editor, SyntaxNode functionDeclaration, bool returnsVoid, SyntaxNode? statementToAddAfter, StatementSyntax statement)
=> InitializeParameterHelpers.InsertStatement(editor, functionDeclaration, returnsVoid, statementToAddAfter, statement);
protected override bool IsImplicitConversion(Compilation compilation, ITypeSymbol source, ITypeSymbol destination)
=> InitializeParameterHelpers.IsImplicitConversion(compilation, source, destination);
// Fields are always private by default in C#.
protected override Accessibility DetermineDefaultFieldAccessibility(INamedTypeSymbol containingType)
=> Accessibility.Private;
// Properties are always private by default in C#.
protected override Accessibility DetermineDefaultPropertyAccessibility()
=> Accessibility.Private;
protected override SyntaxNode GetBody(SyntaxNode functionDeclaration)
=> InitializeParameterHelpers.GetBody(functionDeclaration);
protected override SyntaxNode? GetAccessorBody(IMethodSymbol accessor, CancellationToken cancellationToken)
{
var node = accessor.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken);
if (node is AccessorDeclarationSyntax accessorDeclaration)
return accessorDeclaration.ExpressionBody ?? (SyntaxNode?)accessorDeclaration.Body;
// `int Age => ...;`
if (node is ArrowExpressionClauseSyntax arrowExpression)
return arrowExpression;
return null;
}
protected override SyntaxNode RemoveThrowNotImplemented(SyntaxNode node)
{
if (node is PropertyDeclarationSyntax propertyDeclaration)
{
if (propertyDeclaration.ExpressionBody != null)
{
var result = propertyDeclaration
.WithExpressionBody(null)
.WithSemicolonToken(default)
.AddAccessorListAccessors(SyntaxFactory
.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)))
.WithTrailingTrivia(propertyDeclaration.SemicolonToken.TrailingTrivia)
.WithAdditionalAnnotations(Formatter.Annotation);
return result;
}
if (propertyDeclaration.AccessorList != null)
{
var accessors = propertyDeclaration.AccessorList.Accessors.Select(RemoveThrowNotImplemented);
return propertyDeclaration.WithAccessorList(
propertyDeclaration.AccessorList.WithAccessors(SyntaxFactory.List(accessors)));
}
}
return node;
}
private static AccessorDeclarationSyntax RemoveThrowNotImplemented(AccessorDeclarationSyntax accessorDeclaration)
{
var result = accessorDeclaration
.WithExpressionBody(null)
.WithBody(null)
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken));
return result.WithTrailingTrivia(accessorDeclaration.Body?.GetTrailingTrivia() ?? accessorDeclaration.SemicolonToken.TrailingTrivia);
}
protected override bool TryUpdateTupleAssignment(
IBlockOperation? blockStatement,
IParameterSymbol parameter,
ISymbol fieldOrProperty,
SyntaxEditor editor)
{
if (blockStatement is null)
return false;
foreach (var (tupleLeft, tupleRight) in TryGetAssignmentExpressions(blockStatement))
{
if (tupleLeft.Syntax is TupleExpressionSyntax tupleLeftSyntax &&
tupleRight.Syntax is TupleExpressionSyntax tupleRightSyntax)
{
var generator = editor.Generator;
foreach (var (sibling, before) in GetSiblingParameters(parameter))
{
if (TryFindSiblingAssignment(tupleLeft, tupleRight, sibling, out var index))
{
// If we found assignment to a parameter before us, then add after that.
var insertionPosition = before ? index + 1 : index;
var left = (ArgumentSyntax)generator.Argument(generator.MemberAccessExpression(generator.ThisExpression(), generator.IdentifierName(fieldOrProperty.Name)));
var right = (ArgumentSyntax)generator.Argument(generator.IdentifierName(parameter.Name));
editor.ReplaceNode(
tupleLeftSyntax,
tupleLeftSyntax.WithArguments(tupleLeftSyntax.Arguments.Insert(insertionPosition, left)));
editor.ReplaceNode(
tupleRightSyntax,
tupleRightSyntax.WithArguments(tupleRightSyntax.Arguments.Insert(insertionPosition, right)));
return true;
}
}
}
}
return false;
}
private static bool TryFindSiblingAssignment(
ITupleOperation tupleLeft, ITupleOperation tupleRight, IParameterSymbol sibling, out int index)
{
for (int i = 0, n = tupleLeft.Elements.Length; i < n; i++)
{
// rhs tuple has to directly reference the sibling parameter. lhs has to be a reference to a field/prop in this type.
if (tupleRight.Elements[i] is IParameterReferenceOperation parameterReference && sibling.Equals(parameterReference.Parameter) &&
IsFieldOrPropertyReference(tupleLeft.Elements[i], sibling.ContainingType, out _))
{
index = i;
return true;
}
}
index = -1;
return false;
}
private static IEnumerable<(ITupleOperation targetTuple, ITupleOperation valueTuple)> TryGetAssignmentExpressions(IBlockOperation blockOperation)
{
foreach (var operation in blockOperation.Operations)
{
if (TryGetPartsOfTupleAssignmentOperation(operation, out var targetTuple, out var valueTuple))
yield return (targetTuple, valueTuple);
}
}
}
}
|