diff --git a/Compilers.sln b/Compilers.sln index 0a19a8b853aee..c27f64deec044 100644 --- a/Compilers.sln +++ b/Compilers.sln @@ -169,8 +169,8 @@ Global src\Workspaces\SharedUtilitiesAndExtensions\Compiler\CSharp\CSharpCompilerExtensions.projitems*{21b239d0-d144-430f-a394-c066d58ee267}*SharedItemsImports = 5 src\Workspaces\SharedUtilitiesAndExtensions\Workspace\CSharp\CSharpWorkspaceExtensions.projitems*{21b239d0-d144-430f-a394-c066d58ee267}*SharedItemsImports = 5 src\Compilers\VisualBasic\BasicAnalyzerDriver\BasicAnalyzerDriver.projitems*{2523d0e6-df32-4a3e-8ae0-a19bffae2ef6}*SharedItemsImports = 5 - src\Compilers\VisualBasic\CommandLine\VbcCommandLine.projitems*{48c93f90-8776-4847-96d8-127b896d6c80}*SharedItemsImports = 5 - src\Compilers\CSharp\CommandLine\CscCommandLine.projitems*{4b45ca0c-03a0-400f-b454-3d4bcb16af38}*SharedItemsImports = 5 + src\Compilers\VisualBasic\vbc\VbcCommandLine.projitems*{48c93f90-8776-4847-96d8-127b896d6c80}*SharedItemsImports = 5 + src\Compilers\CSharp\csc\CscCommandLine.projitems*{4b45ca0c-03a0-400f-b454-3d4bcb16af38}*SharedItemsImports = 5 src\Compilers\CSharp\CSharpAnalyzerDriver\CSharpAnalyzerDriver.projitems*{54e08bf5-f819-404f-a18d-0ab9ea81ea04}*SharedItemsImports = 13 src\Workspaces\SharedUtilitiesAndExtensions\Compiler\VisualBasic\VisualBasicCompilerExtensions.projitems*{57ca988d-f010-4bf2-9a2e-07d6dcd2ff2c}*SharedItemsImports = 5 src\Workspaces\SharedUtilitiesAndExtensions\Workspace\VisualBasic\VisualBasicWorkspaceExtensions.projitems*{57ca988d-f010-4bf2-9a2e-07d6dcd2ff2c}*SharedItemsImports = 5 @@ -178,14 +178,14 @@ Global src\Dependencies\PooledObjects\Microsoft.CodeAnalysis.PooledObjects.projitems*{5f8d2414-064a-4b3a-9b42-8e2a04246be5}*SharedItemsImports = 5 src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Core\CompilerExtensions.projitems*{5f8d2414-064a-4b3a-9b42-8e2a04246be5}*SharedItemsImports = 5 src\Workspaces\SharedUtilitiesAndExtensions\Workspace\Core\WorkspaceExtensions.projitems*{5f8d2414-064a-4b3a-9b42-8e2a04246be5}*SharedItemsImports = 5 - src\Compilers\Server\CommandLine\VBCSCompilerCommandLine.projitems*{869e3b79-4e91-45fd-ba37-56dbd2f34721}*SharedItemsImports = 5 - src\Compilers\Server\CommandLine\VBCSCompilerCommandLine.projitems*{9508f118-f62e-4c16-a6f4-7c3b56e166ad}*SharedItemsImports = 5 + src\Compilers\Server\VBCSCompiler\VBCSCompilerCommandLine.projitems*{869e3b79-4e91-45fd-ba37-56dbd2f34721}*SharedItemsImports = 5 + src\Compilers\Server\VBCSCompiler\VBCSCompilerCommandLine.projitems*{9508f118-f62e-4c16-a6f4-7c3b56e166ad}*SharedItemsImports = 5 src\Compilers\CSharp\CSharpAnalyzerDriver\CSharpAnalyzerDriver.projitems*{b501a547-c911-4a05-ac6e-274a50dff30e}*SharedItemsImports = 5 - src\Compilers\CSharp\CommandLine\CscCommandLine.projitems*{b5a27411-77ff-4c43-b0e3-fe09fba5f887}*SharedItemsImports = 5 + src\Compilers\CSharp\csc\CscCommandLine.projitems*{b5a27411-77ff-4c43-b0e3-fe09fba5f887}*SharedItemsImports = 5 src\Dependencies\PooledObjects\Microsoft.CodeAnalysis.PooledObjects.projitems*{c1930979-c824-496b-a630-70f5369a636f}*SharedItemsImports = 13 src\Compilers\Core\AnalyzerDriver\AnalyzerDriver.projitems*{d0bc9be7-24f6-40ca-8dc6-fcb93bd44b34}*SharedItemsImports = 13 src\Dependencies\CodeAnalysis.Debugging\Microsoft.CodeAnalysis.Debugging.projitems*{d73adf7d-2c1c-42ae-b2ab-edc9497e4b71}*SharedItemsImports = 13 - src\Compilers\VisualBasic\CommandLine\VbcCommandLine.projitems*{e58ee9d7-1239-4961-a0c1-f9ec3952c4c1}*SharedItemsImports = 5 + src\Compilers\VisualBasic\vbc\VbcCommandLine.projitems*{e58ee9d7-1239-4961-a0c1-f9ec3952c4c1}*SharedItemsImports = 5 src\Compilers\VisualBasic\BasicAnalyzerDriver\BasicAnalyzerDriver.projitems*{e8f0baa5-7327-43d1-9a51-644e81ae55f1}*SharedItemsImports = 13 src\Dependencies\Collections\Microsoft.CodeAnalysis.Collections.projitems*{e919dd77-34f8-4f57-8058-4d3ff4c2b241}*SharedItemsImports = 13 EndGlobalSection diff --git a/eng/config/PublishData.json b/eng/config/PublishData.json index 3ffdb1eea7b49..6cf86c7c3f10c 100644 --- a/eng/config/PublishData.json +++ b/eng/config/PublishData.json @@ -143,6 +143,16 @@ "vsMajorVersion": 17, "insertionCreateDraftPR": true, "insertionTitlePrefix": "[d17.3p3]" + }, + "release/dev17.4-vs-deps": { + "nugetKind": [ + "Shipping", + "NonShipping" + ], + "vsBranch": "main", + "vsMajorVersion": 17, + "insertionCreateDraftPR": true, + "insertionTitlePrefix": "[d17.4p1]" } } } diff --git a/src/EditorFeatures/CSharpTest/CodeRefactorings/UseRecursivePatterns/UseRecursivePatternsRefactoringFixAllTests.cs b/src/EditorFeatures/CSharpTest/CodeRefactorings/UseRecursivePatterns/UseRecursivePatternsRefactoringFixAllTests.cs new file mode 100644 index 0000000000000..d40bb1fa123fc --- /dev/null +++ b/src/EditorFeatures/CSharpTest/CodeRefactorings/UseRecursivePatterns/UseRecursivePatternsRefactoringFixAllTests.cs @@ -0,0 +1,692 @@ +// 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.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp.CodeRefactorings.UseRecursivePatterns; +using Microsoft.CodeAnalysis.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings.UseRecursivePatterns +{ + [Trait(Traits.Feature, Traits.Features.CodeActionsUseRecursivePatterns)] + [Trait(Traits.Feature, Traits.Features.CodeActionsFixAllOccurrences)] + public class UseRecursivePatternsRefactoringFixAllTests : AbstractCSharpCodeActionTest + { + protected override CodeRefactoringProvider CreateCodeRefactoringProvider(Workspace workspace, TestParameters parameters) + => new UseRecursivePatternsCodeRefactoringProvider(); + + [Fact] + public async Task UseRecursivePatterns_FixAllInDocument() + { + await TestInRegularAndScriptAsync(@" +namespace NS +{ + class C : B + { + void M1() + { + if (n == a.b.c.d {|FixAllInDocument:|}&& a.b.c.a == n) + { + } + + if (this.P1 < 1 && 2 >= this.P2) + { + } + + if (!B1 && B2) + { + } + } + } + + class D : C + { + void M2() + { + _ = this switch + { + { a: var x } when x is { b: n } => 0 + }; + + switch (this) + { + case { a: var x } when x is { b: n }: + break; + } + } + } + + class B + { + public const C n = null; + public C a, b, c, d; + public int P1, P2, P3; + public bool B1, B2; + public C CP1, CP2; + public static C SCP1, SCP2; + public static int SP1, SP2; + public C m() { return null; } + } +}", @" +namespace NS +{ + class C : B + { + void M1() + { + if (a.b.c is { d: n, a: n }) + { + } + + if (this is { P1: < 1, P2: <= 2 }) + { + } + + if (this is { B1: false, B2: true }) + { + } + } + } + + class D : C + { + void M2() + { + _ = this switch + { + { a: { b: n } x } => 0 + }; + + switch (this) + { + case { a: { b: n } x }: + break; + } + } + } + + class B + { + public const C n = null; + public C a, b, c, d; + public int P1, P2, P3; + public bool B1, B2; + public C CP1, CP2; + public static C SCP1, SCP2; + public static int SP1, SP2; + public C m() { return null; } + } +}"); + } + + [Fact] + public async Task UseRecursivePatterns_FixAllInProject() + { + await TestInRegularAndScriptAsync(@" + + + +namespace NS +{ + class C : B + { + void M1() + { + _ = this switch + { + { a: var x } {|FixAllInProject:|}when x is { b: n } => 0 + }; + + switch (this) + { + case { a: var x } when x is { b: n }: + break; + } + } + } +} + + +namespace NS +{ + class D : C + { + void M2() + { + _ = this switch + { + { a: var x } when x is { b: n } => 0 + }; + + switch (this) + { + case { a: var x } when x is { b: n }: + break; + } + } + } +} + + +namespace NS +{ + class B + { + public const C n = null; + public C a, b, c, d; + public int P1, P2, P3; + public bool B1, B2; + public C CP1, CP2; + public static C SCP1, SCP2; + public static int SP1, SP2; + public C m() { return null; } + } +} + + + + +namespace NS +{ + class E : C + { + void M3() + { + _ = this switch + { + { a: var x } when x is { b: n } => 0 + }; + + switch (this) + { + case { a: var x } when x is { b: n }: + break; + } + } + } +} + + + +", @" + + + +namespace NS +{ + class C : B + { + void M1() + { + _ = this switch + { + { a: { b: n } x } => 0 + }; + + switch (this) + { + case { a: { b: n } x }: + break; + } + } + } +} + + +namespace NS +{ + class D : C + { + void M2() + { + _ = this switch + { + { a: { b: n } x } => 0 + }; + + switch (this) + { + case { a: { b: n } x }: + break; + } + } + } +} + + +namespace NS +{ + class B + { + public const C n = null; + public C a, b, c, d; + public int P1, P2, P3; + public bool B1, B2; + public C CP1, CP2; + public static C SCP1, SCP2; + public static int SP1, SP2; + public C m() { return null; } + } +} + + + + +namespace NS +{ + class E : C + { + void M3() + { + _ = this switch + { + { a: var x } when x is { b: n } => 0 + }; + + switch (this) + { + case { a: var x } when x is { b: n }: + break; + } + } + } +} + + +"); + } + + [Fact] + public async Task UseRecursivePatterns_FixAllInSolution() + { + await TestInRegularAndScriptAsync(@" + + + +namespace NS +{ + class C : B + { + void M1() + { + _ = this switch + { + { a: var x } {|FixAllInSolution:|}when x is { b: n } => 0 + }; + + switch (this) + { + case { a: var x } when x is { b: n }: + break; + } + } + } +} + + +namespace NS +{ + class D : C + { + void M2() + { + _ = this switch + { + { a: var x } when x is { b: n } => 0 + }; + + switch (this) + { + case { a: var x } when x is { b: n }: + break; + } + } + } +} + + +namespace NS +{ + class B + { + public const C n = null; + public C a, b, c, d; + public int P1, P2, P3; + public bool B1, B2; + public C CP1, CP2; + public static C SCP1, SCP2; + public static int SP1, SP2; + public C m() { return null; } + } +} + + + + +namespace NS +{ + class E : C + { + void M3() + { + _ = this switch + { + { a: var x } when x is { b: n } => 0 + }; + + switch (this) + { + case { a: var x } when x is { b: n }: + break; + } + } + } +} + + + +", @" + + + +namespace NS +{ + class C : B + { + void M1() + { + _ = this switch + { + { a: { b: n } x } => 0 + }; + + switch (this) + { + case { a: { b: n } x }: + break; + } + } + } +} + + +namespace NS +{ + class D : C + { + void M2() + { + _ = this switch + { + { a: { b: n } x } => 0 + }; + + switch (this) + { + case { a: { b: n } x }: + break; + } + } + } +} + + +namespace NS +{ + class B + { + public const C n = null; + public C a, b, c, d; + public int P1, P2, P3; + public bool B1, B2; + public C CP1, CP2; + public static C SCP1, SCP2; + public static int SP1, SP2; + public C m() { return null; } + } +} + + + + +namespace NS +{ + class E : C + { + void M3() + { + _ = this switch + { + { a: { b: n } x } => 0 + }; + + switch (this) + { + case { a: { b: n } x }: + break; + } + } + } +} + + +"); + } + + [Fact] + public async Task UseRecursivePatterns_FixAllInContainingMember() + { + await TestInRegularAndScriptAsync(@" +namespace NS +{ + class C : B + { + void M1() + { + if (n == a.b.c.d {|FixAllInContainingMember:|}&& a.b.c.a == n) + { + } + + if (this.P1 < 1 && 2 >= this.P2) + { + } + + if (!B1 && B2) + { + } + } + } + + class D : C + { + void M2() + { + _ = this switch + { + { a: var x } when x is { b: n } => 0 + }; + + switch (this) + { + case { a: var x } when x is { b: n }: + break; + } + } + } + + class B + { + public const C n = null; + public C a, b, c, d; + public int P1, P2, P3; + public bool B1, B2; + public C CP1, CP2; + public static C SCP1, SCP2; + public static int SP1, SP2; + public C m() { return null; } + } +}", @" +namespace NS +{ + class C : B + { + void M1() + { + if (a.b.c is { d: n, a: n }) + { + } + + if (this is { P1: < 1, P2: <= 2 }) + { + } + + if (this is { B1: false, B2: true }) + { + } + } + } + + class D : C + { + void M2() + { + _ = this switch + { + { a: var x } when x is { b: n } => 0 + }; + + switch (this) + { + case { a: var x } when x is { b: n }: + break; + } + } + } + + class B + { + public const C n = null; + public C a, b, c, d; + public int P1, P2, P3; + public bool B1, B2; + public C CP1, CP2; + public static C SCP1, SCP2; + public static int SP1, SP2; + public C m() { return null; } + } +}"); + } + + [Fact] + public async Task UseRecursivePatterns_FixAllInContainingType() + { + await TestInRegularAndScriptAsync(@" +namespace NS +{ + class C : B + { + void M1() + { + if (n == a.b.c.d {|FixAllInContainingType:|}&& a.b.c.a == n) + { + } + } + + void M2() + { + if (this.P1 < 1 && 2 >= this.P2) + { + } + + if (!B1 && B2) + { + } + } + } + + class D : C + { + void M3() + { + _ = this switch + { + { a: var x } when x is { b: n } => 0 + }; + + switch (this) + { + case { a: var x } when x is { b: n }: + break; + } + } + } + + class B + { + public const C n = null; + public C a, b, c, d; + public int P1, P2, P3; + public bool B1, B2; + public C CP1, CP2; + public static C SCP1, SCP2; + public static int SP1, SP2; + public C m() { return null; } + } +}", @" +namespace NS +{ + class C : B + { + void M1() + { + if (a.b.c is { d: n, a: n }) + { + } + } + + void M2() + { + if (this is { P1: < 1, P2: <= 2 }) + { + } + + if (this is { B1: false, B2: true }) + { + } + } + } + + class D : C + { + void M3() + { + _ = this switch + { + { a: var x } when x is { b: n } => 0 + }; + + switch (this) + { + case { a: var x } when x is { b: n }: + break; + } + } + } + + class B + { + public const C n = null; + public C a, b, c, d; + public int P1, P2, P3; + public bool B1, B2; + public C CP1, CP2; + public static C SCP1, SCP2; + public static int SP1, SP2; + public C m() { return null; } + } +}"); + } + } +} diff --git a/src/Features/CSharp/Portable/CodeRefactorings/UseRecursivePatterns/UseRecursivePatternsCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/CodeRefactorings/UseRecursivePatterns/UseRecursivePatternsCodeRefactoringProvider.cs index 3f3a371d406e5..fcb2f43263337 100644 --- a/src/Features/CSharp/Portable/CodeRefactorings/UseRecursivePatterns/UseRecursivePatternsCodeRefactoringProvider.cs +++ b/src/Features/CSharp/Portable/CodeRefactorings/UseRecursivePatterns/UseRecursivePatternsCodeRefactoringProvider.cs @@ -11,6 +11,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.CSharp.CodeGeneration; using Microsoft.CodeAnalysis.CSharp.Extensions; @@ -21,6 +22,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Simplification; +using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.CodeRefactorings.UseRecursivePatterns @@ -29,17 +31,19 @@ namespace Microsoft.CodeAnalysis.CSharp.CodeRefactorings.UseRecursivePatterns using static SyntaxKind; [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.UseRecursivePatterns), Shared] - internal sealed class UseRecursivePatternsCodeRefactoringProvider : CodeRefactoringProvider + internal sealed class UseRecursivePatternsCodeRefactoringProvider : SyntaxEditorBasedCodeRefactoringProvider { private static readonly PatternSyntax s_trueConstantPattern = ConstantPattern(LiteralExpression(TrueLiteralExpression)); private static readonly PatternSyntax s_falseConstantPattern = ConstantPattern(LiteralExpression(FalseLiteralExpression)); [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] public UseRecursivePatternsCodeRefactoringProvider() { } + protected override ImmutableArray SupportedFixAllScopes => AllFixAllScopes; + public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { var (document, textSpan, cancellationToken) = context; @@ -77,6 +81,17 @@ public override async Task ComputeRefactoringsAsync(CodeRefactoringContext conte _ => null }; + private static bool IsFixableNode(SyntaxNode node) + => node switch + { + BinaryExpressionSyntax(LogicalAndExpression) => true, + CasePatternSwitchLabelSyntax { WhenClause: { } whenClause } => true, + SwitchExpressionArmSyntax { WhenClause: { } whenClause } => true, + WhenClauseSyntax { Parent: CasePatternSwitchLabelSyntax } => true, + WhenClauseSyntax { Parent: SwitchExpressionArmSyntax } => true, + _ => false + }; + private static Func? CombineLogicalAndOperands(BinaryExpressionSyntax logicalAnd, SemanticModel model) { if (TryDetermineReceiver(logicalAnd.Left, model) is not var (leftReceiver, leftTarget, leftFlipped) || @@ -503,5 +518,44 @@ when canConvertToSubpattern(name, arg) && !memberAccess.Expression.IsKind(Syntax } } } + + protected override async Task FixAllAsync( + Document document, + ImmutableArray fixAllSpans, + SyntaxEditor editor, + CodeActionOptionsProvider optionsProvider, + string? equivalenceKey, + CancellationToken cancellationToken) + { + // Get all the descendant nodes to refactor. + var nodes = editor.OriginalRoot.DescendantNodes().Where(IsFixableNode); + + // We're going to be continually editing this tree. Track all the nodes we + // care about so we can find them across each edit. + document = document.WithSyntaxRoot(editor.OriginalRoot.TrackNodes(nodes)); + + // Process all nodes to refactor in reverse to ensure nested nodes + // are processed before the outer nodes to refactor. + foreach (var originalNode in nodes.Reverse()) + { + // Only process nodes fully within a fixAllSpan + if (!fixAllSpans.Any(fixAllSpan => fixAllSpan.Contains(originalNode.Span))) + continue; + + // Get current root, current node to refactor and semantic model. + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var currentNode = root.GetCurrentNodes(originalNode).SingleOrDefault(); + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + var replacementFunc = GetReplacementFunc(currentNode, semanticModel); + if (replacementFunc == null) + continue; + + document = document.WithSyntaxRoot(replacementFunc(root)); + } + + var updatedRoot = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + editor.ReplaceNode(editor.OriginalRoot, updatedRoot); + } } }