From 939422f978c7b2dd2cb78cd1a58d4341fe8d4d79 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 02:35:13 +0000 Subject: [PATCH 1/3] Initial plan From 5aa553e962427cb26f4f8a795393afdbe73b654b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 03:05:02 +0000 Subject: [PATCH 2/3] Fix SYSLIB1045 code fixer to skip extension blocks when adding partial modifier The fixer now correctly skips ExtensionBlockDeclarationSyntax nodes when: - Adding the partial modifier to ancestor types (extension blocks can't be partial) - Finding the parent type to place the generated regex property in Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> --- .../gen/UpgradeToGeneratedRegexCodeFixer.cs | 9 +++--- .../UpgradeToGeneratedRegexAnalyzerTests.cs | 30 +++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Text.RegularExpressions/gen/UpgradeToGeneratedRegexCodeFixer.cs b/src/libraries/System.Text.RegularExpressions/gen/UpgradeToGeneratedRegexCodeFixer.cs index a8548a3bd0bfce..4f21419199016b 100644 --- a/src/libraries/System.Text.RegularExpressions/gen/UpgradeToGeneratedRegexCodeFixer.cs +++ b/src/libraries/System.Text.RegularExpressions/gen/UpgradeToGeneratedRegexCodeFixer.cs @@ -104,8 +104,9 @@ operation is not (IInvocationOperation or IObjectCreationOperation)) } // Get the parent type declaration so that we can inspect its methods as well as check if we need to add the partial keyword. + // Skip extension blocks, as they can't be partial and can't contain generated regex members. SyntaxNode? typeDeclarationOrCompilationUnit = - nodeToFix.Ancestors().OfType().FirstOrDefault() ?? + nodeToFix.Ancestors().OfType().FirstOrDefault(t => t is not ExtensionBlockDeclarationSyntax) ?? await nodeToFix.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); // Calculate what name should be used for the generated static partial property. @@ -210,7 +211,7 @@ where modifier.Kind() is SyntaxKind.PublicKeyword or SyntaxKind.PrivateKeyword o SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))))); - var typeDeclarationOrCompilationUnit = nodeToFix.Ancestors().OfType().FirstOrDefault() ?? root; + var typeDeclarationOrCompilationUnit = nodeToFix.Ancestors().OfType().FirstOrDefault(t => t is not ExtensionBlockDeclarationSyntax) ?? root; ImmutableArray operationArguments = operation is IObjectCreationOperation objectCreation ? @@ -255,7 +256,7 @@ operation is not (IInvocationOperation or IObjectCreationOperation)) SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))))); - var typeDeclarationOrCompilationUnit = nodeToFix.Ancestors().OfType().FirstOrDefault() ?? root; + var typeDeclarationOrCompilationUnit = nodeToFix.Ancestors().OfType().FirstOrDefault(t => t is not ExtensionBlockDeclarationSyntax) ?? root; ImmutableArray operationArguments = operation is IObjectCreationOperation objectCreation ? @@ -342,7 +343,7 @@ private static Document TryAddNewMember( var trackedRoot = root.TrackNodes(parent is null ? [nodeToFix] : [nodeToFix, parent]); root = trackedRoot.ReplaceNodes( - trackedRoot.GetCurrentNode(nodeToFix)!.Ancestors().OfType(), + trackedRoot.GetCurrentNode(nodeToFix)!.Ancestors().OfType().Where(t => t is not ExtensionBlockDeclarationSyntax), (_, typeDeclaration) => typeDeclaration.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword)) ? typeDeclaration : diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/UpgradeToGeneratedRegexAnalyzerTests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/UpgradeToGeneratedRegexAnalyzerTests.cs index 061c34787063ba..535cac04b06f5b 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/UpgradeToGeneratedRegexAnalyzerTests.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/UpgradeToGeneratedRegexAnalyzerTests.cs @@ -969,6 +969,36 @@ public void Foo() await VerifyCS.VerifyCodeFixAsync(test, fixedSource); } + [Fact] + public async Task CodeFixSupportsExtensionMembers() + { + string test = @"using System.Text.RegularExpressions; + +static class Foo +{ + extension(string value) + { + public bool Test() => [|Regex.IsMatch|](value, @""\d+""); + } +} +"; + string fixedSource = @"using System.Text.RegularExpressions; + +static partial class Foo +{ + extension(string value) + { + public bool Test() => MyRegex.IsMatch(value); + } + + [GeneratedRegex(@""\d+"")] + private static partial Regex MyRegex { get; } +} +"; + + await VerifyCS.VerifyCodeFixAsync(test, fixedSource); + } + [Theory] [MemberData(nameof(InvocationTypes))] public async Task NoDiagnosticForRegexOptionsNonBacktracking(InvocationType invocationType) From 0de7f6896b16d604f8a2e5d4e07264a3344984ed Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 04:07:37 +0000 Subject: [PATCH 3/3] Add constructor-path test for extension blocks Split the extension member test into two: one for static invocation (Regex.IsMatch) and one for constructor (new Regex), covering both code paths through CreateGeneratedRegexProperty. Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> --- .../UpgradeToGeneratedRegexAnalyzerTests.cs | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/UpgradeToGeneratedRegexAnalyzerTests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/UpgradeToGeneratedRegexAnalyzerTests.cs index 535cac04b06f5b..24b7308fe93fd4 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/UpgradeToGeneratedRegexAnalyzerTests.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/UpgradeToGeneratedRegexAnalyzerTests.cs @@ -970,7 +970,7 @@ public void Foo() } [Fact] - public async Task CodeFixSupportsExtensionMembers() + public async Task CodeFixSupportsExtensionMembers_StaticInvocation() { string test = @"using System.Text.RegularExpressions; @@ -999,6 +999,36 @@ static partial class Foo await VerifyCS.VerifyCodeFixAsync(test, fixedSource); } + [Fact] + public async Task CodeFixSupportsExtensionMembers_Constructor() + { + string test = @"using System.Text.RegularExpressions; + +static class Foo +{ + extension(string value) + { + public Regex GetRegex() => [|new Regex|](@""\d+""); + } +} +"; + string fixedSource = @"using System.Text.RegularExpressions; + +static partial class Foo +{ + extension(string value) + { + public Regex GetRegex() => MyRegex; + } + + [GeneratedRegex(@""\d+"")] + private static partial Regex MyRegex { get; } +} +"; + + await VerifyCS.VerifyCodeFixAsync(test, fixedSource); + } + [Theory] [MemberData(nameof(InvocationTypes))] public async Task NoDiagnosticForRegexOptionsNonBacktracking(InvocationType invocationType)