diff --git a/src/Features/CSharp/Portable/IntroduceParameter/CSharpIntroduceParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/IntroduceParameter/CSharpIntroduceParameterCodeRefactoringProvider.cs index 1b291cdfd5564..8b897db02030b 100644 --- a/src/Features/CSharp/Portable/IntroduceParameter/CSharpIntroduceParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/IntroduceParameter/CSharpIntroduceParameterCodeRefactoringProvider.cs @@ -23,19 +23,13 @@ internal sealed partial class CSharpIntroduceParameterCodeRefactoringProvider() ArgumentSyntax> { protected override SyntaxNode GenerateExpressionFromOptionalParameter(IParameterSymbol parameterSymbol) - { - return ExpressionGenerator.GenerateExpression(parameterSymbol.Type, parameterSymbol.ExplicitDefaultValue, canUseFieldReference: true); - } + => ExpressionGenerator.GenerateExpression(parameterSymbol.Type, parameterSymbol.ExplicitDefaultValue, canUseFieldReference: true); protected override SyntaxNode? GetLocalDeclarationFromDeclarator(SyntaxNode variableDecl) - { - return variableDecl.Parent?.Parent as LocalDeclarationStatementSyntax; - } + => variableDecl.Parent?.Parent as LocalDeclarationStatementSyntax; protected override bool IsDestructor(IMethodSymbol methodSymbol) - { - return false; - } + => false; protected override SyntaxNode UpdateArgumentListSyntax(SyntaxNode argumentList, SeparatedSyntaxList arguments) => ((ArgumentListSyntax)argumentList).WithArguments(arguments); diff --git a/src/Features/CSharpTest/IntroduceParameter/IntroduceParameterTests.cs b/src/Features/CSharpTest/IntroduceParameter/IntroduceParameterTests.cs index e4e97c62f2829..497b08aeb9d96 100644 --- a/src/Features/CSharpTest/IntroduceParameter/IntroduceParameterTests.cs +++ b/src/Features/CSharpTest/IntroduceParameter/IntroduceParameterTests.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings; using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.IntroduceParameter; @@ -1818,4 +1819,59 @@ void M() } } """); + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81013")] + public Task TestNotOnAnonymousObjectMemberName() + => TestMissingInRegularAndScriptAsync( + """ + class C + { + object M() => new + { + [|a|] = new { }, + }; + } + """); + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81013")] + public Task TestNotOnAnonymousObjectMemberNameWithValue() + => TestMissingInRegularAndScriptAsync( + """ + class C + { + object M() => new + { + [|x|] = 5, + }; + } + """); + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81013")] + public Task TestNotOnObjectInitializerMemberName() + => TestMissingInRegularAndScriptAsync( + """ + class Goo { public int X { get; set; } } + class C + { + Goo M() => new Goo + { + [|X|] = 5, + }; + } + """); + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81013")] + public Task TestNotOnObjectInitializerMemberNameNested() + => TestMissingInRegularAndScriptAsync( + """ + class Goo { public int X { get; set; } public Goo Inner { get; set; } } + class C + { + Goo M() => new Goo + { + [|X|] = 5, + Inner = new Goo { X = 10 } + }; + } + """); } diff --git a/src/Features/Core/Portable/IntroduceParameter/AbstractIntroduceParameterCodeRefactoringProvider.cs b/src/Features/Core/Portable/IntroduceParameter/AbstractIntroduceParameterCodeRefactoringProvider.cs index b617025d192d3..8858b9e0696a4 100644 --- a/src/Features/Core/Portable/IntroduceParameter/AbstractIntroduceParameterCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/IntroduceParameter/AbstractIntroduceParameterCodeRefactoringProvider.cs @@ -129,6 +129,16 @@ private static bool IsValidExpression(SyntaxNode expression, ISyntaxFactsService if (syntaxFacts.IsNameOfAnyMemberAccessExpression(expression)) return false; + // Need to special case for the left-hand side of member initializers in regular objects (e.g., 'X' in 'new Foo { X = ... }') + // because it does not make sense to introduce a parameter for the property/member name itself. + if (syntaxFacts.IsMemberInitializerNamedAssignmentIdentifier(expression, out _)) + return false; + + // Need to special case for the left-hand side of member initializers in anonymous objects (e.g., 'a' in 'new { a = ... }'). + // This checks if the expression is the name identifier in an anonymous object member declarator. + if (syntaxFacts.IsAnonymousObjectMemberDeclaratorNameIdentifier(expression)) + return false; + // Need to special case for expressions that are contained within a parameter or attribute argument // because it is technically "contained" within a method, but does not make // sense to introduce. diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs index 0aec2f4bf25f2..ab972009e8a36 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs @@ -667,6 +667,10 @@ public bool IsMemberInitializerNamedAssignmentIdentifier( return false; } + public bool IsAnonymousObjectMemberDeclaratorNameIdentifier([NotNullWhen(true)] SyntaxNode? expression) + => expression is IdentifierNameSyntax { Parent: NameEqualsSyntax { Parent: AnonymousObjectMemberDeclaratorSyntax } nameEquals } identifier && + nameEquals.Name == identifier; + public bool IsAnyInitializerExpression([NotNullWhen(true)] SyntaxNode? node, [NotNullWhen(true)] out SyntaxNode? creationExpression) { if (node is InitializerExpressionSyntax diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs index b67f47b2bee0e..00e380fc10914 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs @@ -331,6 +331,7 @@ void GetPartsOfTupleExpression(SyntaxNode node, bool IsAttributeNamedArgumentIdentifier([NotNullWhen(true)] SyntaxNode? node); bool IsMemberInitializerNamedAssignmentIdentifier([NotNullWhen(true)] SyntaxNode? node, [NotNullWhen(true)] out SyntaxNode? initializedInstance); + bool IsAnonymousObjectMemberDeclaratorNameIdentifier([NotNullWhen(true)] SyntaxNode? node); bool IsAnyInitializerExpression([NotNullWhen(true)] SyntaxNode? node, [NotNullWhen(true)] out SyntaxNode? creationExpression); bool IsDirective([NotNullWhen(true)] SyntaxNode? node); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb index c5afc220befc5..4e456fcac25b2 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb @@ -672,6 +672,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageService Return False End Function + Public Function IsAnonymousObjectMemberDeclaratorNameIdentifier(expression As SyntaxNode) As Boolean Implements ISyntaxFacts.IsAnonymousObjectMemberDeclaratorNameIdentifier + Dim identifier = TryCast(expression, IdentifierNameSyntax) + Dim namedFieldInit = TryCast(identifier?.Parent, NamedFieldInitializerSyntax) + + Return TypeOf namedFieldInit?.Parent Is AnonymousObjectCreationExpressionSyntax AndAlso + namedFieldInit.Name Is identifier + End Function + Public Function IsAnyInitializerExpression(node As SyntaxNode, ByRef creationExpression As SyntaxNode) As Boolean Implements ISyntaxFacts.IsAnyInitializerExpression If TypeOf node Is CollectionInitializerSyntax Then If TypeOf node.Parent Is ArrayCreationExpressionSyntax Then