From fe9591c8dd91cc270c18f90540e882600865a6c6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 4 Nov 2025 00:34:17 +0000 Subject: [PATCH 1/9] Initial plan From 9909d56ee61a740fdf7636b05749e1829cbd9f1e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 4 Nov 2025 00:57:57 +0000 Subject: [PATCH 2/9] Fix introduce parameter refactoring for member initializer names Add check to prevent "Introduce parameter" refactoring from being offered on the left-hand side of member initializers in both anonymous objects (new { a = ... }) and regular object initializers (new Foo { X = ... }). - Added abstract method IsAnonymousObjectMemberDeclaratorNameIdentifier to check for anonymous object member names - Implemented the method in both C# and VB providers - Added tests to verify refactoring is not offered on member initializer names - Existing IsMemberInitializerNamedAssignmentIdentifier handles regular object initializers Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com> --- ...troduceParameterCodeRefactoringProvider.cs | 16 ++++++ .../IntroduceParameterTests.cs | 55 +++++++++++++++++++ ...troduceParameterCodeRefactoringProvider.cs | 15 ++++- ...troduceParameterCodeRefactoringProvider.vb | 18 ++++++ 4 files changed, 102 insertions(+), 2 deletions(-) diff --git a/src/Features/CSharp/Portable/IntroduceParameter/CSharpIntroduceParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/IntroduceParameter/CSharpIntroduceParameterCodeRefactoringProvider.cs index 1b291cdfd5564..006d4edb84154 100644 --- a/src/Features/CSharp/Portable/IntroduceParameter/CSharpIntroduceParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/IntroduceParameter/CSharpIntroduceParameterCodeRefactoringProvider.cs @@ -39,4 +39,20 @@ protected override bool IsDestructor(IMethodSymbol methodSymbol) protected override SyntaxNode UpdateArgumentListSyntax(SyntaxNode argumentList, SeparatedSyntaxList arguments) => ((ArgumentListSyntax)argumentList).WithArguments(arguments); + + protected override bool IsAnonymousObjectMemberDeclaratorNameIdentifier(SyntaxNode expression) + { + // Check if this expression is the name identifier in an anonymous object member declarator. + // In C#, the structure is: IdentifierNameSyntax -> NameEqualsSyntax -> AnonymousObjectMemberDeclaratorSyntax + // We want to return true when expression is the identifier on the left side of the '=' in something like 'new { a = value }' + if (expression is IdentifierNameSyntax identifier && + identifier.Parent is NameEqualsSyntax nameEquals && + nameEquals.Parent is AnonymousObjectMemberDeclaratorSyntax) + { + // Verify that the identifier is the Name part of NameEqualsSyntax (not some other part) + return nameEquals.Name == identifier; + } + + return false; + } } diff --git a/src/Features/CSharpTest/IntroduceParameter/IntroduceParameterTests.cs b/src/Features/CSharpTest/IntroduceParameter/IntroduceParameterTests.cs index e4e97c62f2829..ad0c2d481e29b 100644 --- a/src/Features/CSharpTest/IntroduceParameter/IntroduceParameterTests.cs +++ b/src/Features/CSharpTest/IntroduceParameter/IntroduceParameterTests.cs @@ -1818,4 +1818,59 @@ void M() } } """); + + [Fact] + public Task TestNotOnAnonymousObjectMemberName() + => TestMissingInRegularAndScriptAsync( + """ + class C + { + object M() => new + { + [|a|] = new { }, + }; + } + """); + + [Fact] + public Task TestNotOnAnonymousObjectMemberNameWithValue() + => TestMissingInRegularAndScriptAsync( + """ + class C + { + object M() => new + { + [|x|] = 5, + }; + } + """); + + [Fact] + public Task TestNotOnObjectInitializerMemberName() + => TestMissingInRegularAndScriptAsync( + """ + class Foo { public int X { get; set; } } + class C + { + Foo M() => new Foo + { + [|X|] = 5, + }; + } + """); + + [Fact] + public Task TestNotOnObjectInitializerMemberNameNested() + => TestMissingInRegularAndScriptAsync( + """ + class Foo { public int X { get; set; } public Foo Inner { get; set; } } + class C + { + Foo M() => new Foo + { + [|X|] = 5, + Inner = new Foo { X = 10 } + }; + } + """); } diff --git a/src/Features/Core/Portable/IntroduceParameter/AbstractIntroduceParameterCodeRefactoringProvider.cs b/src/Features/Core/Portable/IntroduceParameter/AbstractIntroduceParameterCodeRefactoringProvider.cs index b617025d192d3..1826264203e19 100644 --- a/src/Features/Core/Portable/IntroduceParameter/AbstractIntroduceParameterCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/IntroduceParameter/AbstractIntroduceParameterCodeRefactoringProvider.cs @@ -44,6 +44,7 @@ private enum IntroduceParameterCodeActionKind protected abstract SyntaxNode UpdateArgumentListSyntax(SyntaxNode argumentList, SeparatedSyntaxList arguments); protected abstract SyntaxNode? GetLocalDeclarationFromDeclarator(SyntaxNode variableDecl); protected abstract bool IsDestructor(IMethodSymbol methodSymbol); + protected abstract bool IsAnonymousObjectMemberDeclaratorNameIdentifier(SyntaxNode expression); public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { @@ -57,7 +58,7 @@ public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContex var generator = SyntaxGenerator.GetGenerator(document); var syntaxFacts = document.GetRequiredLanguageService(); - if (!IsValidExpression(expression, syntaxFacts)) + if (!IsValidExpression(expression, syntaxFacts, this)) return; var containingMethod = expression.FirstAncestorOrSelf(node => generator.GetParameterListNode(node) is not null); @@ -117,7 +118,7 @@ public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContex } } - private static bool IsValidExpression(SyntaxNode expression, ISyntaxFactsService syntaxFacts) + private bool IsValidExpression(SyntaxNode expression, ISyntaxFactsService syntaxFacts, AbstractIntroduceParameterCodeRefactoringProvider provider) { // Need to special case for highlighting of method types because they are also "contained" within a method, // but it does not make sense to introduce a parameter in that case. @@ -129,6 +130,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 (provider.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/Features/VisualBasic/Portable/IntroduceParameter/VisualBasicIntroduceParameterCodeRefactoringProvider.vb b/src/Features/VisualBasic/Portable/IntroduceParameter/VisualBasicIntroduceParameterCodeRefactoringProvider.vb index 2d45e87ac9528..f49fcbadb246b 100644 --- a/src/Features/VisualBasic/Portable/IntroduceParameter/VisualBasicIntroduceParameterCodeRefactoringProvider.vb +++ b/src/Features/VisualBasic/Portable/IntroduceParameter/VisualBasicIntroduceParameterCodeRefactoringProvider.vb @@ -34,5 +34,23 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.IntroduceParameter Protected Overrides Function IsDestructor(methodSymbol As IMethodSymbol) As Boolean Return methodSymbol.Name.Equals(WellKnownMemberNames.DestructorName) End Function + + Protected Overrides Function IsAnonymousObjectMemberDeclaratorNameIdentifier(expression As SyntaxNode) As Boolean + ' Check if this expression is the name identifier in an anonymous object member declarator. + ' In VB, the structure for anonymous objects with explicit names is: IdentifierNameSyntax -> NamedFieldInitializerSyntax + ' We want to return true when expression is the identifier on the left side in something like 'New With { .a = value }' + Dim identifier = TryCast(expression, IdentifierNameSyntax) + If identifier IsNot Nothing AndAlso TypeOf identifier.Parent Is NamedFieldInitializerSyntax Then + Dim namedFieldInit = DirectCast(identifier.Parent, NamedFieldInitializerSyntax) + ' Check if this is part of an anonymous object (not a regular object initializer) + ' Anonymous object initializers are inside AnonymousObjectCreationExpressionSyntax + If TypeOf namedFieldInit.Parent Is AnonymousObjectCreationExpressionSyntax Then + ' Verify that the identifier is the Name part of NamedFieldInitializerSyntax + Return namedFieldInit.Name Is identifier + End If + End If + + Return False + End Function End Class End Namespace From bd2c14fca9840cab82f78df553003f7c8c56fb87 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 4 Nov 2025 01:01:09 +0000 Subject: [PATCH 3/9] Refactor VB code to reduce nesting Simplified IsAnonymousObjectMemberDeclaratorNameIdentifier method in VB provider using early returns instead of nested if statements for better readability. Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com> --- ...troduceParameterCodeRefactoringProvider.vb | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/Features/VisualBasic/Portable/IntroduceParameter/VisualBasicIntroduceParameterCodeRefactoringProvider.vb b/src/Features/VisualBasic/Portable/IntroduceParameter/VisualBasicIntroduceParameterCodeRefactoringProvider.vb index f49fcbadb246b..8767d99b33e08 100644 --- a/src/Features/VisualBasic/Portable/IntroduceParameter/VisualBasicIntroduceParameterCodeRefactoringProvider.vb +++ b/src/Features/VisualBasic/Portable/IntroduceParameter/VisualBasicIntroduceParameterCodeRefactoringProvider.vb @@ -40,17 +40,23 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.IntroduceParameter ' In VB, the structure for anonymous objects with explicit names is: IdentifierNameSyntax -> NamedFieldInitializerSyntax ' We want to return true when expression is the identifier on the left side in something like 'New With { .a = value }' Dim identifier = TryCast(expression, IdentifierNameSyntax) - If identifier IsNot Nothing AndAlso TypeOf identifier.Parent Is NamedFieldInitializerSyntax Then - Dim namedFieldInit = DirectCast(identifier.Parent, NamedFieldInitializerSyntax) - ' Check if this is part of an anonymous object (not a regular object initializer) - ' Anonymous object initializers are inside AnonymousObjectCreationExpressionSyntax - If TypeOf namedFieldInit.Parent Is AnonymousObjectCreationExpressionSyntax Then - ' Verify that the identifier is the Name part of NamedFieldInitializerSyntax - Return namedFieldInit.Name Is identifier - End If + If identifier Is Nothing Then + Return False End If - Return False + Dim namedFieldInit = TryCast(identifier.Parent, NamedFieldInitializerSyntax) + If namedFieldInit Is Nothing Then + Return False + End If + + ' Check if this is part of an anonymous object (not a regular object initializer) + ' Anonymous object initializers are inside AnonymousObjectCreationExpressionSyntax + If Not (TypeOf namedFieldInit.Parent Is AnonymousObjectCreationExpressionSyntax) Then + Return False + End If + + ' Verify that the identifier is the Name part of NamedFieldInitializerSyntax + Return namedFieldInit.Name Is identifier End Function End Class End Namespace From 3c96fb4e5032ed8bc4061056ab9d10470431e48b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 3 Nov 2025 17:52:07 -0800 Subject: [PATCH 4/9] Update src/Features/VisualBasic/Portable/IntroduceParameter/VisualBasicIntroduceParameterCodeRefactoringProvider.vb --- .../VisualBasicIntroduceParameterCodeRefactoringProvider.vb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/VisualBasic/Portable/IntroduceParameter/VisualBasicIntroduceParameterCodeRefactoringProvider.vb b/src/Features/VisualBasic/Portable/IntroduceParameter/VisualBasicIntroduceParameterCodeRefactoringProvider.vb index 8767d99b33e08..db984da1caf72 100644 --- a/src/Features/VisualBasic/Portable/IntroduceParameter/VisualBasicIntroduceParameterCodeRefactoringProvider.vb +++ b/src/Features/VisualBasic/Portable/IntroduceParameter/VisualBasicIntroduceParameterCodeRefactoringProvider.vb @@ -51,7 +51,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.IntroduceParameter ' Check if this is part of an anonymous object (not a regular object initializer) ' Anonymous object initializers are inside AnonymousObjectCreationExpressionSyntax - If Not (TypeOf namedFieldInit.Parent Is AnonymousObjectCreationExpressionSyntax) Then + If TypeOf namedFieldInit.Parent IsNot AnonymousObjectCreationExpressionSyntax Then Return False End If From ec9d4ab27fe069c3e66efafbda7db5943d12637f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 3 Nov 2025 17:54:09 -0800 Subject: [PATCH 5/9] Simplify --- .../AbstractIntroduceParameterCodeRefactoringProvider.cs | 6 +++--- .../Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Features/Core/Portable/IntroduceParameter/AbstractIntroduceParameterCodeRefactoringProvider.cs b/src/Features/Core/Portable/IntroduceParameter/AbstractIntroduceParameterCodeRefactoringProvider.cs index 1826264203e19..f243605ffdebf 100644 --- a/src/Features/Core/Portable/IntroduceParameter/AbstractIntroduceParameterCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/IntroduceParameter/AbstractIntroduceParameterCodeRefactoringProvider.cs @@ -58,7 +58,7 @@ public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContex var generator = SyntaxGenerator.GetGenerator(document); var syntaxFacts = document.GetRequiredLanguageService(); - if (!IsValidExpression(expression, syntaxFacts, this)) + if (!IsValidExpression(expression, syntaxFacts)) return; var containingMethod = expression.FirstAncestorOrSelf(node => generator.GetParameterListNode(node) is not null); @@ -118,7 +118,7 @@ public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContex } } - private bool IsValidExpression(SyntaxNode expression, ISyntaxFactsService syntaxFacts, AbstractIntroduceParameterCodeRefactoringProvider provider) + private static bool IsValidExpression(SyntaxNode expression, ISyntaxFactsService syntaxFacts) { // Need to special case for highlighting of method types because they are also "contained" within a method, // but it does not make sense to introduce a parameter in that case. @@ -137,7 +137,7 @@ private bool IsValidExpression(SyntaxNode expression, ISyntaxFactsService syntax // 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 (provider.IsAnonymousObjectMemberDeclaratorNameIdentifier(expression)) + if (syntaxFacts.IsAnonymousObjectMemberDeclaratorNameIdentifier(expression)) return false; // Need to special case for expressions that are contained within a parameter or attribute argument 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); From fd4ad0fb2b7ea954bba803690a12e5295ecc3079 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 3 Nov 2025 17:57:22 -0800 Subject: [PATCH 6/9] Move to syntaxfacts --- ...troduceParameterCodeRefactoringProvider.cs | 28 ++----------------- ...troduceParameterCodeRefactoringProvider.cs | 1 - ...troduceParameterCodeRefactoringProvider.vb | 24 ---------------- .../Services/SyntaxFacts/CSharpSyntaxFacts.cs | 6 ++++ .../SyntaxFacts/VisualBasicSyntaxFacts.vb | 12 ++++++++ 5 files changed, 21 insertions(+), 50 deletions(-) diff --git a/src/Features/CSharp/Portable/IntroduceParameter/CSharpIntroduceParameterCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/IntroduceParameter/CSharpIntroduceParameterCodeRefactoringProvider.cs index 006d4edb84154..8b897db02030b 100644 --- a/src/Features/CSharp/Portable/IntroduceParameter/CSharpIntroduceParameterCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/IntroduceParameter/CSharpIntroduceParameterCodeRefactoringProvider.cs @@ -23,36 +23,14 @@ 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); - - protected override bool IsAnonymousObjectMemberDeclaratorNameIdentifier(SyntaxNode expression) - { - // Check if this expression is the name identifier in an anonymous object member declarator. - // In C#, the structure is: IdentifierNameSyntax -> NameEqualsSyntax -> AnonymousObjectMemberDeclaratorSyntax - // We want to return true when expression is the identifier on the left side of the '=' in something like 'new { a = value }' - if (expression is IdentifierNameSyntax identifier && - identifier.Parent is NameEqualsSyntax nameEquals && - nameEquals.Parent is AnonymousObjectMemberDeclaratorSyntax) - { - // Verify that the identifier is the Name part of NameEqualsSyntax (not some other part) - return nameEquals.Name == identifier; - } - - return false; - } } diff --git a/src/Features/Core/Portable/IntroduceParameter/AbstractIntroduceParameterCodeRefactoringProvider.cs b/src/Features/Core/Portable/IntroduceParameter/AbstractIntroduceParameterCodeRefactoringProvider.cs index f243605ffdebf..8858b9e0696a4 100644 --- a/src/Features/Core/Portable/IntroduceParameter/AbstractIntroduceParameterCodeRefactoringProvider.cs +++ b/src/Features/Core/Portable/IntroduceParameter/AbstractIntroduceParameterCodeRefactoringProvider.cs @@ -44,7 +44,6 @@ private enum IntroduceParameterCodeActionKind protected abstract SyntaxNode UpdateArgumentListSyntax(SyntaxNode argumentList, SeparatedSyntaxList arguments); protected abstract SyntaxNode? GetLocalDeclarationFromDeclarator(SyntaxNode variableDecl); protected abstract bool IsDestructor(IMethodSymbol methodSymbol); - protected abstract bool IsAnonymousObjectMemberDeclaratorNameIdentifier(SyntaxNode expression); public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { diff --git a/src/Features/VisualBasic/Portable/IntroduceParameter/VisualBasicIntroduceParameterCodeRefactoringProvider.vb b/src/Features/VisualBasic/Portable/IntroduceParameter/VisualBasicIntroduceParameterCodeRefactoringProvider.vb index db984da1caf72..2d45e87ac9528 100644 --- a/src/Features/VisualBasic/Portable/IntroduceParameter/VisualBasicIntroduceParameterCodeRefactoringProvider.vb +++ b/src/Features/VisualBasic/Portable/IntroduceParameter/VisualBasicIntroduceParameterCodeRefactoringProvider.vb @@ -34,29 +34,5 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.IntroduceParameter Protected Overrides Function IsDestructor(methodSymbol As IMethodSymbol) As Boolean Return methodSymbol.Name.Equals(WellKnownMemberNames.DestructorName) End Function - - Protected Overrides Function IsAnonymousObjectMemberDeclaratorNameIdentifier(expression As SyntaxNode) As Boolean - ' Check if this expression is the name identifier in an anonymous object member declarator. - ' In VB, the structure for anonymous objects with explicit names is: IdentifierNameSyntax -> NamedFieldInitializerSyntax - ' We want to return true when expression is the identifier on the left side in something like 'New With { .a = value }' - Dim identifier = TryCast(expression, IdentifierNameSyntax) - If identifier Is Nothing Then - Return False - End If - - Dim namedFieldInit = TryCast(identifier.Parent, NamedFieldInitializerSyntax) - If namedFieldInit Is Nothing Then - Return False - End If - - ' Check if this is part of an anonymous object (not a regular object initializer) - ' Anonymous object initializers are inside AnonymousObjectCreationExpressionSyntax - If TypeOf namedFieldInit.Parent IsNot AnonymousObjectCreationExpressionSyntax Then - Return False - End If - - ' Verify that the identifier is the Name part of NamedFieldInitializerSyntax - Return namedFieldInit.Name Is identifier - End Function End Class End Namespace diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs index 0aec2f4bf25f2..d0c9714df581d 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs @@ -667,6 +667,12 @@ public bool IsMemberInitializerNamedAssignmentIdentifier( return false; } + public bool IsAnonymousObjectMemberDeclaratorNameIdentifier([NotNullWhen(true)] SyntaxNode? expression) + => expression is IdentifierNameSyntax identifier && + identifier.Parent is NameEqualsSyntax nameEquals && + nameEquals.Parent is AnonymousObjectMemberDeclaratorSyntax && + 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/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb index c5afc220befc5..9e834f03f1059 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb @@ -672,6 +672,18 @@ 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) + + If TypeOf namedFieldInit?.Parent IsNot AnonymousObjectCreationExpressionSyntax Then + Return False + End If + + ' Verify that the identifier is the Name part of NamedFieldInitializerSyntax + Return 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 From 4af8df1f5a81ff59a0928513acbcb8ac2ac7def7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 3 Nov 2025 17:58:48 -0800 Subject: [PATCH 7/9] Simplify --- .../Services/SyntaxFacts/VisualBasicSyntaxFacts.vb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb index 9e834f03f1059..4e456fcac25b2 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb @@ -676,12 +676,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageService Dim identifier = TryCast(expression, IdentifierNameSyntax) Dim namedFieldInit = TryCast(identifier?.Parent, NamedFieldInitializerSyntax) - If TypeOf namedFieldInit?.Parent IsNot AnonymousObjectCreationExpressionSyntax Then - Return False - End If - - ' Verify that the identifier is the Name part of NamedFieldInitializerSyntax - Return namedFieldInit.Name Is identifier + 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 From 0cc1448e8e1069abb72b388dcf9675c0b4c4d31b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 4 Nov 2025 01:59:37 +0000 Subject: [PATCH 8/9] Address PR feedback: Use 'Goo' instead of 'Foo' and add WorkItem attributes - Renamed all instances of 'Foo' to 'Goo' in test classes - Added WorkItem attribute with issue URL to all new tests - Added using statement for Roslyn.Test.Utilities Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com> --- .../IntroduceParameterTests.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Features/CSharpTest/IntroduceParameter/IntroduceParameterTests.cs b/src/Features/CSharpTest/IntroduceParameter/IntroduceParameterTests.cs index ad0c2d481e29b..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; @@ -1819,7 +1820,7 @@ void M() } """); - [Fact] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81013")] public Task TestNotOnAnonymousObjectMemberName() => TestMissingInRegularAndScriptAsync( """ @@ -1832,7 +1833,7 @@ class C } """); - [Fact] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81013")] public Task TestNotOnAnonymousObjectMemberNameWithValue() => TestMissingInRegularAndScriptAsync( """ @@ -1845,31 +1846,31 @@ class C } """); - [Fact] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81013")] public Task TestNotOnObjectInitializerMemberName() => TestMissingInRegularAndScriptAsync( """ - class Foo { public int X { get; set; } } + class Goo { public int X { get; set; } } class C { - Foo M() => new Foo + Goo M() => new Goo { [|X|] = 5, }; } """); - [Fact] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/81013")] public Task TestNotOnObjectInitializerMemberNameNested() => TestMissingInRegularAndScriptAsync( """ - class Foo { public int X { get; set; } public Foo Inner { get; set; } } + class Goo { public int X { get; set; } public Goo Inner { get; set; } } class C { - Foo M() => new Foo + Goo M() => new Goo { [|X|] = 5, - Inner = new Foo { X = 10 } + Inner = new Goo { X = 10 } }; } """); From 6f3fb87648e915b09e0466c86663efb307dd08eb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 3 Nov 2025 18:00:38 -0800 Subject: [PATCH 9/9] Simplify --- .../Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs index d0c9714df581d..ab972009e8a36 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs @@ -668,9 +668,7 @@ public bool IsMemberInitializerNamedAssignmentIdentifier( } public bool IsAnonymousObjectMemberDeclaratorNameIdentifier([NotNullWhen(true)] SyntaxNode? expression) - => expression is IdentifierNameSyntax identifier && - identifier.Parent is NameEqualsSyntax nameEquals && - nameEquals.Parent is AnonymousObjectMemberDeclaratorSyntax && + => expression is IdentifierNameSyntax { Parent: NameEqualsSyntax { Parent: AnonymousObjectMemberDeclaratorSyntax } nameEquals } identifier && nameEquals.Name == identifier; public bool IsAnyInitializerExpression([NotNullWhen(true)] SyntaxNode? node, [NotNullWhen(true)] out SyntaxNode? creationExpression)