From 75d02c10066b85adc0208b844b80ca91064ec111 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 21 Jul 2020 12:04:49 +1000 Subject: [PATCH] Create "Remove 'async' modifier" code fix (#45913) Co-authored-by: CyrusNajmabadi Co-authored-by: David Wengier --- .../PredefinedCodeFixProviderNames.cs | 1 + .../RemoveAsyncModifierTests.cs | 1143 +++++++++++++++++ .../RemoveAsyncModifierTests.vb | 308 +++++ ...arpMakeMethodSynchronousCodeFixProvider.cs | 54 +- .../RemoveAsyncModifierHelpers.cs | 65 + ...SharpRemoveAsyncModifierCodeFixProvider.cs | 73 ++ .../Core/Portable/FeaturesResources.resx | 3 + ...tractRemoveAsyncModifierCodeFixProvider.cs | 264 ++++ .../Portable/xlf/FeaturesResources.cs.xlf | 5 + .../Portable/xlf/FeaturesResources.de.xlf | 5 + .../Portable/xlf/FeaturesResources.es.xlf | 5 + .../Portable/xlf/FeaturesResources.fr.xlf | 5 + .../Portable/xlf/FeaturesResources.it.xlf | 5 + .../Portable/xlf/FeaturesResources.ja.xlf | 5 + .../Portable/xlf/FeaturesResources.ko.xlf | 5 + .../Portable/xlf/FeaturesResources.pl.xlf | 5 + .../Portable/xlf/FeaturesResources.pt-BR.xlf | 5 + .../Portable/xlf/FeaturesResources.ru.xlf | 5 + .../Portable/xlf/FeaturesResources.tr.xlf | 5 + .../xlf/FeaturesResources.zh-Hans.xlf | 5 + .../xlf/FeaturesResources.zh-Hant.xlf | 5 + .../RemoveAsyncModifierHelpers.vb | 55 + ...sicMakeMethodSynchronousCodeFixProvider.vb | 58 +- ...BasicRemoveAsyncModifierCodeFixProvider.vb | 60 + src/Test/Utilities/Portable/Traits/Traits.cs | 1 + .../CodeGeneration/CSharpSyntaxGenerator.cs | 32 + .../CodeGeneration/SyntaxGeneratorTests.cs | 30 + 27 files changed, 2111 insertions(+), 101 deletions(-) create mode 100644 src/EditorFeatures/CSharpTest/Diagnostics/RemoveAsyncModifier/RemoveAsyncModifierTests.cs create mode 100644 src/EditorFeatures/VisualBasicTest/Diagnostics/RemoveAsyncModifier/RemoveAsyncModifierTests.vb create mode 100644 src/Features/CSharp/Portable/MakeMethodSynchronous/RemoveAsyncModifierHelpers.cs create mode 100644 src/Features/CSharp/Portable/RemoveAsyncModifier/CSharpRemoveAsyncModifierCodeFixProvider.cs create mode 100644 src/Features/Core/Portable/RemoveAsyncModifier/AbstractRemoveAsyncModifierCodeFixProvider.cs create mode 100644 src/Features/VisualBasic/Portable/MakeMethodSynchronous/RemoveAsyncModifierHelpers.vb create mode 100644 src/Features/VisualBasic/Portable/RemoveAsyncModifier/VisualBasicRemoveAsyncModifierCodeFixProvider.vb diff --git a/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs b/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs index dd85185e022..181ff57705e 100644 --- a/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs +++ b/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs @@ -50,6 +50,7 @@ internal static class PredefinedCodeFixProviderNames public const string ReplaceDefaultLiteral = nameof(ReplaceDefaultLiteral); public const string RemoveUnnecessaryCast = nameof(RemoveUnnecessaryCast); public const string DeclareAsNullable = nameof(DeclareAsNullable); + public const string RemoveAsyncModifier = nameof(RemoveAsyncModifier); public const string RemoveUnnecessaryImports = nameof(RemoveUnnecessaryImports); public const string RemoveUnnecessaryAttributeSuppressions = nameof(RemoveUnnecessaryAttributeSuppressions); public const string RemoveUnnecessaryPragmaSuppressions = nameof(RemoveUnnecessaryPragmaSuppressions); diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/RemoveAsyncModifier/RemoveAsyncModifierTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/RemoveAsyncModifier/RemoveAsyncModifierTests.cs new file mode 100644 index 00000000000..7c6426c0a9e --- /dev/null +++ b/src/EditorFeatures/CSharpTest/Diagnostics/RemoveAsyncModifier/RemoveAsyncModifierTests.cs @@ -0,0 +1,1143 @@ +// 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.Test.Utilities; +using Microsoft.CodeAnalysis.Testing; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics.RemoveAsyncModifier +{ + using VerifyCS = Editor.UnitTests.CodeActions.CSharpCodeFixVerifier< + EmptyDiagnosticAnalyzer, + CodeAnalysis.CSharp.RemoveAsyncModifier.CSharpRemoveAsyncModifierCodeFixProvider>; + + public class RemoveAsyncModifierTests : CodeAnalysis.CSharp.Test.Utilities.CSharpTestBase + { + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task Method_Task_MultipleAndNested() + { + await VerifyCS.VerifyCodeFixAsync( +@" +using System; +using System.Threading.Tasks; + +class C +{ + async Task {|CS1998:Goo|}() + { + if (DateTime.Now.Ticks > 0) + { + return; + } + } + + async Task {|CS1998:Foo|}() + { + Console.WriteLine(1); + } + + async Task {|CS1998:Bar|}() + { + async Task {|CS1998:Baz|}() + { + Func> g = async () {|CS1998:=>|} 5; + } + } + + async Task {|CS1998:Tur|}() + { + async Task {|CS1998:Duck|}() + { + async Task {|CS1998:En|}() + { + return ""Developers!""; + } + + return ""Developers! Developers!""; + } + + return ""Developers! Developers! Developers!""; + } + + async Task {|CS1998:Nurk|}() + { + Func> f = async () {|CS1998:=>|} 4; + + if (DateTime.Now.Ticks > f().Result) + { + } + } +}", +@" +using System; +using System.Threading.Tasks; + +class C +{ + Task Goo() + { + if (DateTime.Now.Ticks > 0) + { + return Task.CompletedTask; + } + + return Task.CompletedTask; + } + + Task Foo() + { + Console.WriteLine(1); + return Task.CompletedTask; + } + + Task Bar() + { + Task Baz() + { + Func> g = () => Task.FromResult(5); + return Task.CompletedTask; + } + + return Task.CompletedTask; + } + + Task Tur() + { + Task Duck() + { + Task En() + { + return Task.FromResult(""Developers!""); + } + + return Task.FromResult(""Developers! Developers!""); + } + + return Task.FromResult(""Developers! Developers! Developers!""); + } + + Task Nurk() + { + Func> f = () => Task.FromResult(4); + + if (DateTime.Now.Ticks > f().Result) + { + } + + return Task.CompletedTask; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task Method_Task_EmptyBlockBody() + { + await VerifyCS.VerifyCodeFixAsync( +@" +using System.Threading.Tasks; + +class C +{ + async Task {|CS1998:Goo|}(){} +}", +@" +using System.Threading.Tasks; + +class C +{ + Task Goo() + { + return Task.CompletedTask; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task Method_Task_BlockBody() + { + await VerifyCS.VerifyCodeFixAsync( +@" +using System.Threading.Tasks; + +class C +{ + async Task {|CS1998:Goo|}() + { + if (System.DateTime.Now.Ticks > 0) + { + return; + } + } +}", +@" +using System.Threading.Tasks; + +class C +{ + Task Goo() + { + if (System.DateTime.Now.Ticks > 0) + { + return Task.CompletedTask; + } + + return Task.CompletedTask; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task Method_ValueTask_BlockBody() + { + var source = @" +using System.Threading.Tasks; + +class C +{ + async ValueTask {|CS1998:Goo|}() + { + if (System.DateTime.Now.Ticks > 0) + { + return; + } + } +}"; + + var expected = @" +using System.Threading.Tasks; + +class C +{ + ValueTask Goo() + { + if (System.DateTime.Now.Ticks > 0) + { + return new ValueTask(); + } + + return new ValueTask(); + } +}"; + + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.NetStandard.NetStandard21, + TestCode = source, + FixedCode = expected, + }.RunAsync(); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task Method_ValueTaskOfT_BlockBody() + { + var source = @" +using System.Threading.Tasks; + +class C +{ + async ValueTask {|CS1998:Goo|}() + { + if (System.DateTime.Now.Ticks > 0) + { + return 2; + } + + return 3; + } +}"; + var expected = @" +using System.Threading.Tasks; + +class C +{ + ValueTask Goo() + { + if (System.DateTime.Now.Ticks > 0) + { + return new ValueTask(2); + } + + return new ValueTask(3); + } +}"; + + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.NetStandard.NetStandard21, + TestCode = source, + FixedCode = expected, + }.RunAsync(); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task Method_ValueTask_ExpressionBody() + { + var source = @" +using System.Threading.Tasks; + +class C +{ + async ValueTask {|CS1998:Goo|}() => System.Console.WriteLine(1); +}"; + + var expected = @" +using System.Threading.Tasks; + +class C +{ + ValueTask Goo() + { + System.Console.WriteLine(1); + return new ValueTask(); + } +}"; + + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.NetStandard.NetStandard21, + TestCode = source, + FixedCode = expected, + }.RunAsync(); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task Method_ValueTaskOfT_ExpressionBody() + { + var source = @" +using System.Threading.Tasks; + +class C +{ + async ValueTask {|CS1998:Goo|}() => 3; +}"; + + var expected = @" +using System.Threading.Tasks; + +class C +{ + ValueTask Goo() => new ValueTask(3); +}"; + + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.NetStandard.NetStandard21, + TestCode = source, + FixedCode = expected, + }.RunAsync(); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task Method_Task_BlockBody_Throws() + { + await VerifyCS.VerifyCodeFixAsync( +@" +using System.Threading.Tasks; + +class C +{ + async Task {|CS1998:Goo|}() + { + if (System.DateTime.Now.Ticks > 0) + { + return; + } + + throw new System.ApplicationException(); + } +}", +@" +using System.Threading.Tasks; + +class C +{ + Task Goo() + { + if (System.DateTime.Now.Ticks > 0) + { + return Task.CompletedTask; + } + + throw new System.ApplicationException(); + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task Method_Task_BlockBody_WithLocalFunction() + { + await VerifyCS.VerifyCodeFixAsync( +@" +using System.Threading.Tasks; + +class C +{ + async Task {|CS1998:Goo|}() + { + if (GetTicks() > 0) + { + return; + } + + long GetTicks() + { + return System.DateTime.Now.Ticks; + } + } +}", +@" +using System.Threading.Tasks; + +class C +{ + Task Goo() + { + if (GetTicks() > 0) + { + return Task.CompletedTask; + } + + long GetTicks() + { + return System.DateTime.Now.Ticks; + } + + return Task.CompletedTask; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task Method_Task_BlockBody_WithLambda() + { + await VerifyCS.VerifyCodeFixAsync( +@" +using System.Threading.Tasks; + +class C +{ + async Task {|CS1998:Goo|}() + { + System.Func getTicks = () => { + return System.DateTime.Now.Ticks; + }; + + if (getTicks() > 0) + { + return; + } + + } +}", +@" +using System.Threading.Tasks; + +class C +{ + Task Goo() + { + System.Func getTicks = () => { + return System.DateTime.Now.Ticks; + }; + + if (getTicks() > 0) + { + return Task.CompletedTask; + } + + return Task.CompletedTask; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task Method_TaskOfT_BlockBody() + { + await VerifyCS.VerifyCodeFixAsync( +@" +using System.Threading.Tasks; + +class C +{ + async Task {|CS1998:Goo|}() + { + if (System.DateTime.Now.Ticks > 0) + { + return 2; + } + + return 3; + } +}", +@" +using System.Threading.Tasks; + +class C +{ + Task Goo() + { + if (System.DateTime.Now.Ticks > 0) + { + return Task.FromResult(2); + } + + return Task.FromResult(3); + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task Method_TaskOfT_ExpressionBody() + { + await VerifyCS.VerifyCodeFixAsync( +@" +using System.Threading.Tasks; + +class C +{ + async Task {|CS1998:Goo|}() => 2; +}", +@" +using System.Threading.Tasks; + +class C +{ + Task Goo() => Task.FromResult(2); +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task Method_Task_ExpressionBody() + { + await VerifyCS.VerifyCodeFixAsync( +@" +using System; +using System.Threading.Tasks; + +class C +{ + async Task {|CS1998:Goo|}() => Console.WriteLine(""Hello""); +}", +@" +using System; +using System.Threading.Tasks; + +class C +{ + Task Goo() + { + Console.WriteLine(""Hello""); + return Task.CompletedTask; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task LocalFunction_Task_BlockBody() + { + await VerifyCS.VerifyCodeFixAsync( +@"using System.Threading.Tasks; + +class C +{ + public void M1() + { + async Task {|CS1998:Goo|}() + { + if (System.DateTime.Now.Ticks > 0) + { + return; + } + } + } +}", +@"using System.Threading.Tasks; + +class C +{ + public void M1() + { + Task Goo() + { + if (System.DateTime.Now.Ticks > 0) + { + return Task.CompletedTask; + } + + return Task.CompletedTask; + } + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task LocalFunction_Task_ExpressionBody() + { + await VerifyCS.VerifyCodeFixAsync( +@"using System; +using System.Threading.Tasks; + +class C +{ + public void M1() + { + async Task {|CS1998:Goo|}() => Console.WriteLine(1); + } +}", +@"using System; +using System.Threading.Tasks; + +class C +{ + public void M1() + { + Task Goo() { Console.WriteLine(1); return Task.CompletedTask; } + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task LocalFunction_TaskOfT_BlockBody() + { + await VerifyCS.VerifyCodeFixAsync( +@"using System.Threading.Tasks; + +class C +{ + public void M1() + { + async Task {|CS1998:Goo|}() + { + return 1; + } + } +}", +@"using System.Threading.Tasks; + +class C +{ + public void M1() + { + Task Goo() + { + return Task.FromResult(1); + } + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task LocalFunction_TaskOfT_ExpressionBody() + { + await VerifyCS.VerifyCodeFixAsync( +@"using System.Threading.Tasks; + +class C +{ + public void M1() + { + async Task {|CS1998:Goo|}() => 1; + } +}", +@"using System.Threading.Tasks; + +class C +{ + public void M1() + { + Task Goo() => Task.FromResult(1); + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task AnonymousFunction_Task_BlockBody() + { + await VerifyCS.VerifyCodeFixAsync( +@"using System; +using System.Threading.Tasks; + +class C +{ + public void M1() + { + Func foo = (Func)async {|CS1998:delegate|} { + if (System.DateTime.Now.Ticks > 0) + { + return; + } + }; + } +}", +@"using System; +using System.Threading.Tasks; + +class C +{ + public void M1() + { + Func foo = (Func)delegate + { + if (System.DateTime.Now.Ticks > 0) + { + return Task.CompletedTask; + } + + return Task.CompletedTask; + }; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task AnonymousFunction_TaskOfT_BlockBody() + { + await VerifyCS.VerifyCodeFixAsync( +@"using System; +using System.Threading.Tasks; + +class C +{ + public void M1() + { + Func> foo = (Func>)async {|CS1998:delegate|} + { + return 1; + }; + } +}", +@"using System; +using System.Threading.Tasks; + +class C +{ + public void M1() + { + Func> foo = (Func>)delegate + { + return Task.FromResult(1); + }; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task SimpleLambda_TaskOfT_ExpressionBody() + { + await VerifyCS.VerifyCodeFixAsync( +@"using System; +using System.Threading.Tasks; + +class C +{ + public void M1() + { + Func> foo = async x {|CS1998:=>|} 1; + } +}", +@"using System; +using System.Threading.Tasks; + +class C +{ + public void M1() + { + Func> foo = x => Task.FromResult(1); + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task SimpleLambda_TaskOfT_BlockBody() + { + await VerifyCS.VerifyCodeFixAsync( +@"using System; +using System.Threading.Tasks; + +class C +{ + public void M1() + { + Func> foo = async x {|CS1998:=>|} { + return 1; + }; + } +}", +@"using System; +using System.Threading.Tasks; + +class C +{ + public void M1() + { + Func> foo = x => + { + return Task.FromResult(1); + }; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task SimpleLambda_Task_ExpressionBody() + { + await VerifyCS.VerifyCodeFixAsync( +@"using System; +using System.Threading.Tasks; + +class C +{ + public void M1() + { + Func foo = async x {|CS1998:=>|} Console.WriteLine(1); + } +}", +@"using System; +using System.Threading.Tasks; + +class C +{ + public void M1() + { + Func foo = x => { Console.WriteLine(1); return Task.CompletedTask; }; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task SimpleLambda_Task_BlockBody() + { + await VerifyCS.VerifyCodeFixAsync( +@"using System; +using System.Threading.Tasks; + +class C +{ + public void M1() + { + Func foo = async x {|CS1998:=>|} + { + if (System.DateTime.Now.Ticks > 0) + { + return; + } + }; + } +}", +@"using System; +using System.Threading.Tasks; + +class C +{ + public void M1() + { + Func foo = x => { + if (System.DateTime.Now.Ticks > 0) + { + return Task.CompletedTask; + } + + return Task.CompletedTask; + }; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task ParenthesisedLambda_TaskOfT_ExpressionBody() + { + await VerifyCS.VerifyCodeFixAsync( +@"using System; +using System.Threading.Tasks; + +class C +{ + public void M1() + { + Func> foo = async () {|CS1998:=>|} 1; + } +}", +@"using System; +using System.Threading.Tasks; + +class C +{ + public void M1() + { + Func> foo = () => Task.FromResult(1); + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task ParenthesisedLambda_TaskOfT_BlockBody() + { + await VerifyCS.VerifyCodeFixAsync( +@"using System; +using System.Threading.Tasks; + +class C +{ + public void M1() + { + Func> foo = async () {|CS1998:=>|} { + return 1; + }; + } +}", +@"using System; +using System.Threading.Tasks; + +class C +{ + public void M1() + { + Func> foo = () => + { + return Task.FromResult(1); + }; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task ParenthesisedLambda_Task_ExpressionBody() + { + await VerifyCS.VerifyCodeFixAsync( +@"using System; +using System.Threading.Tasks; + +class C +{ + public void M1() + { + Func foo = async () {|CS1998:=>|} Console.WriteLine(1); + } +}", +@"using System; +using System.Threading.Tasks; + +class C +{ + public void M1() + { + Func foo = () => { Console.WriteLine(1); return Task.CompletedTask; }; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task ParenthesisedLambda_Task_BlockBody() + { + await VerifyCS.VerifyCodeFixAsync( +@"using System; +using System.Threading.Tasks; + +class C +{ + public void M1() + { + Func foo = async () {|CS1998:=>|} + { + if (System.DateTime.Now.Ticks > 0) + { + return; + } + }; + } +}", +@"using System; +using System.Threading.Tasks; + +class C +{ + public void M1() + { + Func foo = () => { + if (System.DateTime.Now.Ticks > 0) + { + return Task.CompletedTask; + } + + return Task.CompletedTask; + }; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task Method_Task_BlockBody_FullyQualified() + { + await VerifyCS.VerifyCodeFixAsync( +@" +class C +{ + async System.Threading.Tasks.Task {|CS1998:Goo|}() + { + if (System.DateTime.Now.Ticks > 0) + { + return; + } + } +}", +@" +class C +{ + System.Threading.Tasks.Task Goo() + { + if (System.DateTime.Now.Ticks > 0) + { + return System.Threading.Tasks.Task.CompletedTask; + } + + return System.Threading.Tasks.Task.CompletedTask; + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task Method_TaskOfT_BlockBody_FullyQualified() + { + await VerifyCS.VerifyCodeFixAsync( +@" +class C +{ + async System.Threading.Tasks.Task {|CS1998:Goo|}() + { + if (System.DateTime.Now.Ticks > 0) + { + return 1; + } + + return 2; + } +}", + @" +class C +{ + System.Threading.Tasks.Task Goo() + { + if (System.DateTime.Now.Ticks > 0) + { + return System.Threading.Tasks.Task.FromResult(1); + } + + return System.Threading.Tasks.Task.FromResult(2); + } +}"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task IAsyncEnumerable_Missing() + { + var source = @" +using System.Threading.Tasks; +using System.Collections.Generic; + +class C +{ + async IAsyncEnumerable M() + { + yield return 1; + } +}" + AsyncStreamsTypes; + + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.NetStandard.NetStandard21, + TestCode = source, + ExpectedDiagnostics = + { + // /0/Test0.cs(7,33): warning CS1998: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread. + DiagnosticResult.CompilerWarning("CS1998").WithSpan(7, 33, 7, 34), + }, + FixedCode = source, + }.RunAsync(); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task Method_AsyncVoid_Missing() + { + var source = @" +using System.Threading.Tasks; + +class C +{ + async void M() + { + System.Console.WriteLine(1); + } +}"; + + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.NetStandard.NetStandard21, + TestCode = source, + ExpectedDiagnostics = + { + // /0/Test0.cs(6,16): warning CS1998: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread. + DiagnosticResult.CompilerWarning("CS1998").WithSpan(6, 16, 6, 17), + }, + FixedCode = source, + }.RunAsync(); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task ParenthesisedLambda_AsyncVoid_Missing() + { + var source = @" +using System; +using System.Threading.Tasks; + +class C +{ + void M() + { + Action a = async () => Console.WriteLine(1); + } +}"; + + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.NetStandard.NetStandard21, + TestCode = source, + ExpectedDiagnostics = + { + // /0/Test0.cs(9,29): warning CS1998: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread. + DiagnosticResult.CompilerWarning("CS1998").WithSpan(9, 29, 9, 31), + }, + FixedCode = source, + }.RunAsync(); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)] + public async Task SimpleLambda_AsyncVoid_Missing() + { + var source = @" +using System; +using System.Threading.Tasks; + +class C +{ + void M() + { + Action a = async x => Console.WriteLine(x); + } +}"; + + await new VerifyCS.Test + { + ReferenceAssemblies = ReferenceAssemblies.NetStandard.NetStandard21, + TestCode = source, + ExpectedDiagnostics = + { + // /0/Test0.cs(9,33): warning CS1998: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread. + DiagnosticResult.CompilerWarning("CS1998").WithSpan(9, 33, 9, 35), + }, + FixedCode = source, + }.RunAsync(); + } + } +} diff --git a/src/EditorFeatures/VisualBasicTest/Diagnostics/RemoveAsyncModifier/RemoveAsyncModifierTests.vb b/src/EditorFeatures/VisualBasicTest/Diagnostics/RemoveAsyncModifier/RemoveAsyncModifierTests.vb new file mode 100644 index 00000000000..cb6dc6383f5 --- /dev/null +++ b/src/EditorFeatures/VisualBasicTest/Diagnostics/RemoveAsyncModifier/RemoveAsyncModifierTests.vb @@ -0,0 +1,308 @@ +' 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. + +Imports Microsoft.CodeAnalysis.Testing +Imports VerifyVB = Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.VisualBasicCodeFixVerifier( + Of Microsoft.CodeAnalysis.Testing.EmptyDiagnosticAnalyzer, + Microsoft.CodeAnalysis.VisualBasic.RemoveAsyncModifier.VisualBasicRemoveAsyncModifierCodeFixProvider) + +Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics.RemoteAsyncModifier + Public Class RemoveAsyncModifierTests + + + Public Async Function Function_Task() As Task + Await VerifyVB.VerifyCodeFixAsync( +"Imports System.Threading.Tasks + +Class C + Async Function {|BC42356:Goo|}() As Task + If System.DateTime.Now.Ticks > 0 Then + Return + End If + + System.Console.WriteLine(1) + End Function +End Class", +"Imports System.Threading.Tasks + +Class C + Function {|BC42356:Goo|}() As Task + If System.DateTime.Now.Ticks > 0 Then + Return Task.CompletedTask + End If + + System.Console.WriteLine(1) + Return Task.CompletedTask + End Function +End Class") + End Function + + + Public Async Function Function_Task_Throws() As Task + Await VerifyVB.VerifyCodeFixAsync( +"Imports System.Threading.Tasks + +Class C + Async Function {|BC42356:Goo|}() As Task + System.Console.WriteLine(1) + + Throw New System.ApplicationException() + End Function +End Class", +"Imports System.Threading.Tasks + +Class C + Function {|BC42356:Goo|}() As Task + System.Console.WriteLine(1) + + Throw New System.ApplicationException() + End Function +End Class") + End Function + + + Public Async Function Function_Task_WithLambda() As Task + Await VerifyVB.VerifyCodeFixAsync( +"Imports System +Imports System.Threading.Tasks + +Class C + Async Function {|BC42356:Goo|}() As Task + System.Console.WriteLine(1) + + dim f as Func(of Integer) = + Function() + Return 1 + End Function + End Function +End Class", +"Imports System +Imports System.Threading.Tasks + +Class C + Function {|BC42356:Goo|}() As Task + System.Console.WriteLine(1) + + dim f as Func(of Integer) = + Function() + Return 1 + End Function + + Return Task.CompletedTask + End Function +End Class") + End Function + + + Public Async Function Function_TaskOfT() As Task + Await VerifyVB.VerifyCodeFixAsync( +"Imports System.Threading.Tasks + +Class C + Async Function {|BC42356:Goo|}() As Task(of Integer) + Return 1 + End Function +End Class", +"Imports System.Threading.Tasks + +Class C + Function {|BC42356:Goo|}() As Task(of Integer) + Return Task.FromResult(1) + End Function +End Class") + End Function + + + Public Async Function SingleLineFunctionLambda_Task() As Task + Await VerifyVB.VerifyCodeFixAsync( +"Imports System +Imports System.Threading.Tasks + +Class C + Sub Goo() + dim f as Func(of Task) = + Async {|BC42356:Function|}() 1 + End Sub +End Class", +"Imports System +Imports System.Threading.Tasks + +Class C + Sub Goo() + dim f as Func(of Task) = + Function() Task.FromResult(1) + + End Sub +End Class") + End Function + + + Public Async Function SingleLineFunctionLambda_TaskOfT() As Task + Await VerifyVB.VerifyCodeFixAsync( +"Imports System +Imports System.Threading.Tasks + +Class C + Sub Goo() + dim f as Func(of Task(Of Integer)) = + Async {|BC42356:Function|}() 1 + End Sub +End Class", +"Imports System +Imports System.Threading.Tasks + +Class C + Sub Goo() + dim f as Func(of Task(Of Integer)) = + Function() Task.FromResult(1) + + End Sub +End Class") + End Function + + + Public Async Function MultiLineFunctionLambda_Task() As Task + Await VerifyVB.VerifyCodeFixAsync( +"Imports System +Imports System.Threading.Tasks + +Class C + Sub Goo() + dim f as Func(of Task) = + Async {|BC42356:Function|}() + Console.WriteLine(1) + End Function + End Sub +End Class", +"Imports System +Imports System.Threading.Tasks + +Class C + Sub Goo() + dim f as Func(of Task) = + Function() + Console.WriteLine(1) + Return Task.CompletedTask + End Function + End Sub +End Class") + End Function + + + Public Async Function MultiLineFunctionLambda_TaskOfT() As Task + Await VerifyVB.VerifyCodeFixAsync( +"Imports System +Imports System.Threading.Tasks + +Class C + Sub Goo() + dim f as Func(of Task(Of Integer)) = + Async {|BC42356:Function|}() + Return 1 + End Function + End Sub +End Class", +"Imports System +Imports System.Threading.Tasks + +Class C + Sub Goo() + dim f as Func(of Task(Of Integer)) = + Function() + Return Task.FromResult(1) + End Function + End Sub +End Class") + End Function + + + Public Async Function Sub_Missing() As Task + Dim source = " +Imports System + +Class C + Async Sub {|BC42356:Goo|}() + Console.WriteLine(1) + End Sub +End Class" + Dim expected = " +Imports System + +Class C + Async Sub {|#0:Goo|}() + Console.WriteLine(1) + End Sub +End Class" + + Dim test = New VerifyVB.Test() + test.TestState.Sources.Add(source) + test.FixedState.Sources.Add(expected) + ' /0/Test0.vb(5) : warning BC42356: This async method lacks 'Await' operators and so will run synchronously. Consider using the 'Await' operator to await non-blocking API calls, or 'Await Task.Run(...)' to do CPU-bound work on a background thread. + test.FixedState.ExpectedDiagnostics.Add(DiagnosticResult.CompilerWarning("BC42356").WithSpan(5, 15, 5, 18)) + Await test.RunAsync() + End Function + + + Public Async Function MultiLineSubLambda_Task_Missing() As Task + Dim source = " +Imports System + +Class C + Sub Goo() + dim f as Action = + Async {|BC42356:Sub|}() + Console.WriteLine(1) + End Sub + End Sub +End Class" + Dim expected = " +Imports System + +Class C + Sub Goo() + dim f as Action = + Async {|#0:Sub|}() + Console.WriteLine(1) + End Sub + End Sub +End Class" + + Dim test = New VerifyVB.Test() + test.TestState.Sources.Add(source) + test.FixedState.Sources.Add(expected) + ' /0/Test0.vb(7) : warning BC42356: This async method lacks 'Await' operators and so will run synchronously. Consider using the 'Await' operator to await non-blocking API calls, or 'Await Task.Run(...)' to do CPU-bound work on a background thread. + test.FixedState.ExpectedDiagnostics.Add(DiagnosticResult.CompilerWarning("BC42356").WithSpan(7, 19, 7, 22)) + Await test.RunAsync() + End Function + + + Public Async Function SingleLineSubLambda_Task_Missing() As Task + Dim source = " +Imports System + +Class C + Sub Goo() + dim f as Action = + Async {|BC42356:Sub|}() Console.WriteLine(1) + End Sub +End Class" + Dim expected = " +Imports System + +Class C + Sub Goo() + dim f as Action = + Async {|#0:Sub|}() Console.WriteLine(1) + End Sub +End Class" + + Dim test = New VerifyVB.Test() + test.TestState.Sources.Add(source) + test.FixedState.Sources.Add(expected) + ' /0/Test0.vb(7) : warning BC42356: This async method lacks 'Await' operators and so will run synchronously. Consider using the 'Await' operator to await non-blocking API calls, or 'Await Task.Run(...)' to do CPU-bound work on a background thread. + test.FixedState.ExpectedDiagnostics.Add(DiagnosticResult.CompilerWarning("BC42356").WithSpan(7, 19, 7, 22)) + Await test.RunAsync() + End Function + End Class +End Namespace diff --git a/src/Features/CSharp/Portable/MakeMethodSynchronous/CSharpMakeMethodSynchronousCodeFixProvider.cs b/src/Features/CSharp/Portable/MakeMethodSynchronous/CSharpMakeMethodSynchronousCodeFixProvider.cs index b65931f7f83..ef8aa236b57 100644 --- a/src/Features/CSharp/Portable/MakeMethodSynchronous/CSharpMakeMethodSynchronousCodeFixProvider.cs +++ b/src/Features/CSharp/Portable/MakeMethodSynchronous/CSharpMakeMethodSynchronousCodeFixProvider.cs @@ -37,25 +37,22 @@ protected override SyntaxNode RemoveAsyncTokenAndFixReturnType(IMethodSymbol met { case MethodDeclarationSyntax method: return FixMethod(methodSymbolOpt, method, knownTypes); case LocalFunctionStatementSyntax localFunction: return FixLocalFunction(methodSymbolOpt, localFunction, knownTypes); - case AnonymousMethodExpressionSyntax method: return FixAnonymousMethod(method); - case ParenthesizedLambdaExpressionSyntax lambda: return FixParenthesizedLambda(lambda); - case SimpleLambdaExpressionSyntax lambda: return FixSimpleLambda(lambda); + case AnonymousMethodExpressionSyntax method: return RemoveAsyncModifierHelpers.WithoutAsyncModifier(method); + case ParenthesizedLambdaExpressionSyntax lambda: return RemoveAsyncModifierHelpers.WithoutAsyncModifier(lambda); + case SimpleLambdaExpressionSyntax lambda: return RemoveAsyncModifierHelpers.WithoutAsyncModifier(lambda); default: return node; } } - private static SyntaxNode FixMethod(IMethodSymbol methodSymbol, MethodDeclarationSyntax method, KnownTypes knownTypes) { var newReturnType = FixMethodReturnType(methodSymbol, method.ReturnType, knownTypes); - var newModifiers = FixMethodModifiers(method.Modifiers, ref newReturnType); - return method.WithReturnType(newReturnType).WithModifiers(newModifiers); + return RemoveAsyncModifierHelpers.WithoutAsyncModifier(method, newReturnType); } private static SyntaxNode FixLocalFunction(IMethodSymbol methodSymbol, LocalFunctionStatementSyntax localFunction, KnownTypes knownTypes) { var newReturnType = FixMethodReturnType(methodSymbol, localFunction.ReturnType, knownTypes); - var newModifiers = FixMethodModifiers(localFunction.Modifiers, ref newReturnType); - return localFunction.WithReturnType(newReturnType).WithModifiers(newModifiers); + return RemoveAsyncModifierHelpers.WithoutAsyncModifier(localFunction, newReturnType); } private static TypeSyntax FixMethodReturnType(IMethodSymbol methodSymbol, TypeSyntax returnTypeSyntax, KnownTypes knownTypes) @@ -86,46 +83,5 @@ private static TypeSyntax FixMethodReturnType(IMethodSymbol methodSymbol, TypeSy return newReturnType; } - - private static SyntaxTokenList FixMethodModifiers(SyntaxTokenList modifiers, ref TypeSyntax newReturnType) - { - var asyncTokenIndex = modifiers.IndexOf(SyntaxKind.AsyncKeyword); - SyntaxTokenList newModifiers; - if (asyncTokenIndex == 0) - { - // Have to move the trivia on the async token appropriately. - var asyncLeadingTrivia = modifiers[0].LeadingTrivia; - - if (modifiers.Count > 1) - { - // Move the trivia to the next modifier; - newModifiers = modifiers.Replace( - modifiers[1], - modifiers[1].WithPrependedLeadingTrivia(asyncLeadingTrivia)); - newModifiers = newModifiers.RemoveAt(0); - } - else - { - // move it to the return type. - newModifiers = modifiers.RemoveAt(0); - newReturnType = newReturnType.WithPrependedLeadingTrivia(asyncLeadingTrivia); - } - } - else - { - newModifiers = modifiers.RemoveAt(asyncTokenIndex); - } - - return newModifiers; - } - - private static SyntaxNode FixParenthesizedLambda(ParenthesizedLambdaExpressionSyntax lambda) - => lambda.WithAsyncKeyword(default).WithPrependedLeadingTrivia(lambda.AsyncKeyword.LeadingTrivia); - - private static SyntaxNode FixSimpleLambda(SimpleLambdaExpressionSyntax lambda) - => lambda.WithAsyncKeyword(default).WithPrependedLeadingTrivia(lambda.AsyncKeyword.LeadingTrivia); - - private static SyntaxNode FixAnonymousMethod(AnonymousMethodExpressionSyntax method) - => method.WithAsyncKeyword(default).WithPrependedLeadingTrivia(method.AsyncKeyword.LeadingTrivia); } } diff --git a/src/Features/CSharp/Portable/MakeMethodSynchronous/RemoveAsyncModifierHelpers.cs b/src/Features/CSharp/Portable/MakeMethodSynchronous/RemoveAsyncModifierHelpers.cs new file mode 100644 index 00000000000..1084018d073 --- /dev/null +++ b/src/Features/CSharp/Portable/MakeMethodSynchronous/RemoveAsyncModifierHelpers.cs @@ -0,0 +1,65 @@ +// 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 Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; + +namespace Microsoft.CodeAnalysis.CSharp.MakeMethodSynchronous +{ + internal static class RemoveAsyncModifierHelpers + { + internal static SyntaxNode WithoutAsyncModifier(MethodDeclarationSyntax method, TypeSyntax returnType) + { + var newModifiers = RemoveAsyncModifier(method.Modifiers, ref returnType); + return method.WithReturnType(returnType).WithModifiers(newModifiers); + } + + internal static SyntaxNode WithoutAsyncModifier(LocalFunctionStatementSyntax localFunction, TypeSyntax returnType) + { + var newModifiers = RemoveAsyncModifier(localFunction.Modifiers, ref returnType); + return localFunction.WithReturnType(returnType).WithModifiers(newModifiers); + } + + internal static SyntaxNode WithoutAsyncModifier(ParenthesizedLambdaExpressionSyntax lambda) + => lambda.WithAsyncKeyword(default).WithPrependedLeadingTrivia(lambda.AsyncKeyword.LeadingTrivia); + + internal static SyntaxNode WithoutAsyncModifier(SimpleLambdaExpressionSyntax lambda) + => lambda.WithAsyncKeyword(default).WithPrependedLeadingTrivia(lambda.AsyncKeyword.LeadingTrivia); + + internal static SyntaxNode WithoutAsyncModifier(AnonymousMethodExpressionSyntax method) + => method.WithAsyncKeyword(default).WithPrependedLeadingTrivia(method.AsyncKeyword.LeadingTrivia); + + private static SyntaxTokenList RemoveAsyncModifier(SyntaxTokenList modifiers, ref TypeSyntax newReturnType) + { + var asyncTokenIndex = modifiers.IndexOf(SyntaxKind.AsyncKeyword); + SyntaxTokenList newModifiers; + if (asyncTokenIndex == 0) + { + // Have to move the trivia on the async token appropriately. + var asyncLeadingTrivia = modifiers[0].LeadingTrivia; + + if (modifiers.Count > 1) + { + // Move the trivia to the next modifier; + newModifiers = modifiers.Replace( + modifiers[1], + modifiers[1].WithPrependedLeadingTrivia(asyncLeadingTrivia)); + newModifiers = newModifiers.RemoveAt(0); + } + else + { + // move it to the return type. + newModifiers = default; + newReturnType = newReturnType.WithPrependedLeadingTrivia(asyncLeadingTrivia); + } + } + else + { + newModifiers = modifiers.RemoveAt(asyncTokenIndex); + } + + return newModifiers; + } + } +} diff --git a/src/Features/CSharp/Portable/RemoveAsyncModifier/CSharpRemoveAsyncModifierCodeFixProvider.cs b/src/Features/CSharp/Portable/RemoveAsyncModifier/CSharpRemoveAsyncModifierCodeFixProvider.cs new file mode 100644 index 00000000000..5ceb043cadd --- /dev/null +++ b/src/Features/CSharp/Portable/RemoveAsyncModifier/CSharpRemoveAsyncModifierCodeFixProvider.cs @@ -0,0 +1,73 @@ +// 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. + +#nullable enable + +using System.Collections.Immutable; +using System.Composition; +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.MakeMethodSynchronous; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.RemoveAsyncModifier; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.RemoveAsyncModifier +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.RemoveAsyncModifier), Shared] + [ExtensionOrder(After = PredefinedCodeFixProviderNames.MakeMethodSynchronous)] + internal partial class CSharpRemoveAsyncModifierCodeFixProvider : AbstractRemoveAsyncModifierCodeFixProvider + { + private const string CS1998 = nameof(CS1998); // This async method lacks 'await' operators and will run synchronously. + + [ImportingConstructor] + [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")] + public CSharpRemoveAsyncModifierCodeFixProvider() + { + } + + public override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create(CS1998); + + protected override bool IsAsyncSupportingFunctionSyntax(SyntaxNode node) + => node.IsAsyncSupportingFunctionSyntax(); + + protected override SyntaxNode? ConvertToBlockBody(SyntaxNode node, ExpressionSyntax expressionBody) + { + var semicolonToken = SyntaxFactory.Token(SyntaxKind.SemicolonToken); + if (expressionBody.TryConvertToStatement(semicolonToken, createReturnStatementForExpression: false, out var statement)) + { + var block = SyntaxFactory.Block(statement); + return node switch + { + MethodDeclarationSyntax method => method.WithBody(block).WithExpressionBody(null).WithSemicolonToken(default), + LocalFunctionStatementSyntax localFunction => localFunction.WithBody(block).WithExpressionBody(null).WithSemicolonToken(default), + AnonymousFunctionExpressionSyntax anonymousFunction => anonymousFunction.WithBody(block).WithExpressionBody(null), + _ => throw ExceptionUtilities.Unreachable + }; + } + + return null; + } + + protected override SyntaxNode RemoveAsyncModifier(SyntaxGenerator generator, SyntaxNode methodLikeNode) + => methodLikeNode switch + { + MethodDeclarationSyntax method => RemoveAsyncModifierHelpers.WithoutAsyncModifier(method, method.ReturnType), + LocalFunctionStatementSyntax localFunction => RemoveAsyncModifierHelpers.WithoutAsyncModifier(localFunction, localFunction.ReturnType), + AnonymousMethodExpressionSyntax method => AnnotateBlock(generator, RemoveAsyncModifierHelpers.WithoutAsyncModifier(method)), + ParenthesizedLambdaExpressionSyntax lambda => AnnotateBlock(generator, RemoveAsyncModifierHelpers.WithoutAsyncModifier(lambda)), + SimpleLambdaExpressionSyntax lambda => AnnotateBlock(generator, RemoveAsyncModifierHelpers.WithoutAsyncModifier(lambda)), + _ => methodLikeNode, + }; + + // Block bodied lambdas and anonymous methods need to be formatted after changing their modifiers, or their indentation is broken + private static SyntaxNode AnnotateBlock(SyntaxGenerator generator, SyntaxNode node) + => generator.GetExpression(node) == null + ? node.WithAdditionalAnnotations(Formatter.Annotation) + : node; + } +} diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx index 73869a6e5fc..d82911e2e80 100644 --- a/src/Features/Core/Portable/FeaturesResources.resx +++ b/src/Features/Core/Portable/FeaturesResources.resx @@ -2783,4 +2783,7 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Make class 'abstract' + + Remove 'async' modifier + \ No newline at end of file diff --git a/src/Features/Core/Portable/RemoveAsyncModifier/AbstractRemoveAsyncModifierCodeFixProvider.cs b/src/Features/Core/Portable/RemoveAsyncModifier/AbstractRemoveAsyncModifierCodeFixProvider.cs new file mode 100644 index 00000000000..79d499bb9a1 --- /dev/null +++ b/src/Features/Core/Portable/RemoveAsyncModifier/AbstractRemoveAsyncModifierCodeFixProvider.cs @@ -0,0 +1,264 @@ +// 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. + +#nullable enable + +using System; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; +using KnownTypes = Microsoft.CodeAnalysis.MakeMethodAsynchronous.AbstractMakeMethodAsynchronousCodeFixProvider.KnownTypes; + +namespace Microsoft.CodeAnalysis.RemoveAsyncModifier +{ + internal abstract class AbstractRemoveAsyncModifierCodeFixProvider : SyntaxEditorBasedCodeFixProvider + where TReturnStatementSyntax : SyntaxNode + where TExpressionSyntax : SyntaxNode + { + internal sealed override CodeFixCategory CodeFixCategory => CodeFixCategory.Compile; + + protected abstract bool IsAsyncSupportingFunctionSyntax(SyntaxNode node); + protected abstract SyntaxNode RemoveAsyncModifier(SyntaxGenerator generator, SyntaxNode methodLikeNode); + protected abstract SyntaxNode? ConvertToBlockBody(SyntaxNode node, TExpressionSyntax expressionBody); + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var document = context.Document; + var cancellationToken = context.CancellationToken; + var compilation = await document.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + var knownTypes = new KnownTypes(compilation); + + var diagnostic = context.Diagnostics.First(); + var token = diagnostic.Location.FindToken(cancellationToken); + var node = token.GetAncestor(IsAsyncSupportingFunctionSyntax); + if (node == null) + { + return; + } + + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var methodSymbol = GetMethodSymbol(node, semanticModel, cancellationToken); + + if (methodSymbol == null) + { + return; + } + + if (ShouldOfferFix(methodSymbol.ReturnType, knownTypes)) + { + context.RegisterCodeFix( + new MyCodeAction(c => FixAsync(document, diagnostic, c)), + context.Diagnostics); + } + } + + protected sealed override async Task FixAllAsync( + Document document, ImmutableArray diagnostics, + SyntaxEditor editor, CancellationToken cancellationToken) + { + var generator = editor.Generator; + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var compilation = semanticModel.Compilation; + var knownTypes = new KnownTypes(compilation); + + // For fix all we need to do nested locals or lambdas first, so order the diagnostics by location descending + foreach (var diagnostic in diagnostics.OrderByDescending(d => d.Location.SourceSpan.Start)) + { + var token = diagnostic.Location.FindToken(cancellationToken); + var node = token.GetAncestor(IsAsyncSupportingFunctionSyntax); + if (node == null) + { + Debug.Fail("We should always be able to find the node from the diagnostic."); + continue; + } + + var methodSymbol = GetMethodSymbol(node, semanticModel, cancellationToken); + if (methodSymbol == null) + { + Debug.Fail("We should always be able to find the method symbol for the diagnostic."); + continue; + } + + // We might need to perform control flow analysis as part of the fix, so we need to do it on the original node + // so do it up front. Nothing in the fixer changes the reachability of the end of the method so this is safe + var controlFlow = GetControlFlowAnalysis(generator, semanticModel, node); + // If control flow couldn't be computed then its probably an empty block, which means we need to add a return anyway + var needsReturnStatementAdded = controlFlow == null || controlFlow.EndPointIsReachable; + + editor.ReplaceNode(node, + (updatedNode, generator) => RemoveAsyncModifier(generator, updatedNode, methodSymbol.ReturnType, knownTypes, needsReturnStatementAdded)); + } + } + + private static IMethodSymbol? GetMethodSymbol(SyntaxNode node, SemanticModel semanticModel, CancellationToken cancellationToken) + => semanticModel.GetSymbolInfo(node, cancellationToken).Symbol as IMethodSymbol ?? + semanticModel.GetDeclaredSymbol(node, cancellationToken) as IMethodSymbol; + + private static bool ShouldOfferFix(ITypeSymbol returnType, KnownTypes knownTypes) + => IsTaskType(returnType, knownTypes) + || returnType.OriginalDefinition.Equals(knownTypes._taskOfTType) + || returnType.OriginalDefinition.Equals(knownTypes._valueTaskOfTTypeOpt); + + private static bool IsTaskType(ITypeSymbol returnType, KnownTypes knownTypes) + => returnType.OriginalDefinition.Equals(knownTypes._taskType) + || returnType.OriginalDefinition.Equals(knownTypes._valueTaskType); + + private SyntaxNode RemoveAsyncModifier(SyntaxGenerator generator, SyntaxNode node, ITypeSymbol returnType, KnownTypes knownTypes, bool needsReturnStatementAdded) + { + node = RemoveAsyncModifier(generator, node); + + var expression = generator.GetExpression(node); + if (expression is TExpressionSyntax expressionBody) + { + if (IsTaskType(returnType, knownTypes)) + { + // We need to add a `return Task.CompletedTask;` so we have to convert to a block body + var blockBodiedNode = ConvertToBlockBody(node, expressionBody); + + // Expression bodied members can't have return statements so if we can't convert to a block + // body then we've done all we can + if (blockBodiedNode != null) + { + node = AddReturnStatement(generator, blockBodiedNode); + } + } + else + { + // For Task returning expression bodied methods we can just wrap the whole expression + var newExpressionBody = WrapExpressionWithTaskFromResult(generator, expressionBody, returnType, knownTypes); + node = generator.WithExpression(node, newExpressionBody); + } + } + else + { + if (IsTaskType(returnType, knownTypes)) + { + // If the end of the method isn't reachable, or there were no statements to analyze, then we + // need to add an explicit return + if (needsReturnStatementAdded) + { + node = AddReturnStatement(generator, node); + } + } + } + + node = ChangeReturnStatements(generator, node, returnType, knownTypes); + + return node; + } + + private static ControlFlowAnalysis? GetControlFlowAnalysis(SyntaxGenerator generator, SemanticModel semanticModel, SyntaxNode node) + { + var statements = generator.GetStatements(node); + if (statements.Count > 0) + { + return semanticModel.AnalyzeControlFlow(statements[0], statements[statements.Count - 1]); + } + + return null; + } + + private static SyntaxNode AddReturnStatement(SyntaxGenerator generator, SyntaxNode node) + => generator.WithStatements(node, generator.GetStatements(node).Concat(generator.ReturnStatement())); + + private SyntaxNode ChangeReturnStatements(SyntaxGenerator generator, SyntaxNode node, ITypeSymbol returnType, KnownTypes knownTypes) + { + var editor = new SyntaxEditor(node, generator); + + // Look for all return statements, but if we find a new node that can have the async modifier we stop + // because that will have its own diagnostic and fix, if applicable + var returns = node.DescendantNodes(n => n == node || !IsAsyncSupportingFunctionSyntax(n)).OfType(); + + foreach (var returnSyntax in returns) + { + var returnExpression = generator.SyntaxFacts.GetExpressionOfReturnStatement(returnSyntax); + if (returnExpression is null) + { + // Convert return; into return Task.CompletedTask; + var returnTaskCompletedTask = GetReturnTaskCompletedTaskStatement(generator, returnType, knownTypes); + editor.ReplaceNode(returnSyntax, returnTaskCompletedTask); + } + else + { + // Convert return ; into return Task.FromResult(); + var newExpression = WrapExpressionWithTaskFromResult(generator, returnExpression, returnType, knownTypes); + editor.ReplaceNode(returnExpression, newExpression); + } + } + + return editor.GetChangedRoot(); + } + + private static SyntaxNode GetReturnTaskCompletedTaskStatement(SyntaxGenerator generator, ITypeSymbol returnType, KnownTypes knownTypes) + { + SyntaxNode invocation; + if (returnType.OriginalDefinition.Equals(knownTypes._taskType)) + { + var taskTypeExpression = TypeExpressionForStaticMemberAccess(generator, knownTypes._taskType); + invocation = generator.MemberAccessExpression(taskTypeExpression, nameof(Task.CompletedTask)); + } + else + { + invocation = generator.ObjectCreationExpression(knownTypes._valueTaskType); + } + + var statement = generator.ReturnStatement(invocation); + return statement; + } + + private static SyntaxNode WrapExpressionWithTaskFromResult(SyntaxGenerator generator, SyntaxNode expression, ITypeSymbol returnType, KnownTypes knownTypes) + { + if (returnType.OriginalDefinition.Equals(knownTypes._taskOfTType)) + { + var taskTypeExpression = TypeExpressionForStaticMemberAccess(generator, knownTypes._taskType); + var taskFromResult = generator.MemberAccessExpression(taskTypeExpression, nameof(Task.FromResult)); + return generator.InvocationExpression(taskFromResult, expression.WithoutTrivia()).WithTriviaFrom(expression); + } + else + { + return generator.ObjectCreationExpression(returnType, expression); + } + } + + // Workaround for https://github.com/dotnet/roslyn/issues/43950 + // Copied from https://github.com/dotnet/roslyn-analyzers/blob/f24a5b42c85be6ee572f3a93bef223767fbefd75/src/Utilities/Workspaces/SyntaxGeneratorExtensions.cs#L68-L74 + private static SyntaxNode TypeExpressionForStaticMemberAccess(SyntaxGenerator generator, INamedTypeSymbol typeSymbol) + { + var qualifiedNameSyntaxKind = generator.QualifiedName(generator.IdentifierName("ignored"), generator.IdentifierName("ignored")).RawKind; + var memberAccessExpressionSyntaxKind = generator.MemberAccessExpression(generator.IdentifierName("ignored"), "ignored").RawKind; + + var typeExpression = generator.TypeExpression(typeSymbol); + return QualifiedNameToMemberAccess(qualifiedNameSyntaxKind, memberAccessExpressionSyntaxKind, typeExpression, generator); + + // Local function + static SyntaxNode QualifiedNameToMemberAccess(int qualifiedNameSyntaxKind, int memberAccessExpressionSyntaxKind, SyntaxNode expression, SyntaxGenerator generator) + { + if (expression.RawKind == qualifiedNameSyntaxKind) + { + var left = QualifiedNameToMemberAccess(qualifiedNameSyntaxKind, memberAccessExpressionSyntaxKind, expression.ChildNodes().First(), generator); + var right = expression.ChildNodes().Last(); + return generator.MemberAccessExpression(left, right); + } + + return expression; + } + } + + private class MyCodeAction : CodeAction.DocumentChangeAction + { + public MyCodeAction(Func> createChangedDocument) + : base(FeaturesResources.Remove_async_modifier, createChangedDocument, FeaturesResources.Remove_async_modifier) + { + } + } + } +} diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf index 43cf132819b..66b52f1603d 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf @@ -1790,6 +1790,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Podpisy souvisejících metod nalezené v metadatech se nebudou aktualizovat. + + Remove 'async' modifier + Remove 'async' modifier + + Replace '{0}' with '{1}' Místo {0} použijte {1} diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf index d3c83c43506..bd42c378a96 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf @@ -1790,6 +1790,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of In Metadaten gefundene ähnliche Methodensignaturen werden nicht aktualisiert. + + Remove 'async' modifier + Remove 'async' modifier + + Replace '{0}' with '{1}' "{0}" durch "{1}" ersetzen diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf index a2a9717caa0..5f2cc8be9f5 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf @@ -1790,6 +1790,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Las signaturas de método relacionadas encontradas en los metadatos no se actualizarán. + + Remove 'async' modifier + Remove 'async' modifier + + Replace '{0}' with '{1}' Reemplazar "{0}" por "{1}" diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf index b26e2a06757..9b3d809e5bb 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf @@ -1790,6 +1790,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Les signatures de méthode associées dans les métadonnées ne sont pas mises à jour. + + Remove 'async' modifier + Remove 'async' modifier + + Replace '{0}' with '{1}' Remplacer '{0}' par '{1}' diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf index 28014ba948d..6566e21b67f 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf @@ -1790,6 +1790,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Le firme del metodo correlate trovate nei metadati non verranno aggiornate. + + Remove 'async' modifier + Remove 'async' modifier + + Replace '{0}' with '{1}' Sostituisci '{0}' con '{1}' diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf index 7e319feb716..0d35775a759 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf @@ -1790,6 +1790,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of メタデータ内に検出される関連するメソッド シグネチャは更新されません。 + + Remove 'async' modifier + Remove 'async' modifier + + Replace '{0}' with '{1}' '{0}' を '{1}' に置き換える diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf index e81a8cae4dd..69209375074 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf @@ -1790,6 +1790,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of 메타데이터에서 찾은 관련 메서드 시그니처가 업데이트되지 않습니다. + + Remove 'async' modifier + Remove 'async' modifier + + Replace '{0}' with '{1}' '{0}'을(를) '{1}'(으)로 바꾸기 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf index 62c5d8bf5b4..526d2f518cf 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf @@ -1790,6 +1790,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Sygnatury powiązanych metod znalezione w metadanych nie zostaną zaktualizowane. + + Remove 'async' modifier + Remove 'async' modifier + + Replace '{0}' with '{1}' Zamień element „{0}” na element „{1}” diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf index 5d7add7c6a0..0a42fcd9a0c 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf @@ -1790,6 +1790,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of As assinaturas de método relacionadas encontradas nos metadados não serão atualizadas. + + Remove 'async' modifier + Remove 'async' modifier + + Replace '{0}' with '{1}' Substituir '{0}' por '{1}' diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf index 3665d17dca3..0d6e264a415 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf @@ -1790,6 +1790,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Связанные сигнатуры методов, найденные в метаданных, не будут обновлены. + + Remove 'async' modifier + Remove 'async' modifier + + Replace '{0}' with '{1}' Замените '{0}' на '{1}' diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf index 44431485c75..37cb55e08e3 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf @@ -1790,6 +1790,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Meta verilerde bulunan ilgili metot imzaları güncelleştirilmez. + + Remove 'async' modifier + Remove 'async' modifier + + Replace '{0}' with '{1}' '{0}' öğesini '{1}' ile değiştir diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf index 2bf9a2c3339..ef12d98aeea 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf @@ -1790,6 +1790,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of 不更新在元数据中发现的相关方法签名。 + + Remove 'async' modifier + Remove 'async' modifier + + Replace '{0}' with '{1}' 将 "{0}" 替换为 "{1}" diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf index 0c104859cde..48034358a7b 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf @@ -1790,6 +1790,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of 將不會更新中繼資料中所找到的相關方法簽章。 + + Remove 'async' modifier + Remove 'async' modifier + + Replace '{0}' with '{1}' 將 ‘{0}’ 取代為 ‘{1}' diff --git a/src/Features/VisualBasic/Portable/MakeMethodSynchronous/RemoveAsyncModifierHelpers.vb b/src/Features/VisualBasic/Portable/MakeMethodSynchronous/RemoveAsyncModifierHelpers.vb new file mode 100644 index 00000000000..1e8a145e3cb --- /dev/null +++ b/src/Features/VisualBasic/Portable/MakeMethodSynchronous/RemoveAsyncModifierHelpers.vb @@ -0,0 +1,55 @@ +' 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. + +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Namespace Microsoft.CodeAnalysis.VisualBasic.MakeMethodSynchronous + Friend Class RemoveAsyncModifierHelpers + Friend Shared Function RemoveAsyncKeyword(subOrFunctionStatement As MethodStatementSyntax) As MethodStatementSyntax + Dim modifiers = subOrFunctionStatement.Modifiers + Dim asyncTokenIndex = modifiers.IndexOf(SyntaxKind.AsyncKeyword) + + Dim newSubOrFunctionKeyword = subOrFunctionStatement.SubOrFunctionKeyword + Dim newModifiers As SyntaxTokenList + If asyncTokenIndex = 0 Then + ' Have to move the trivia on the async token appropriately. + Dim asyncLeadingTrivia = modifiers(0).LeadingTrivia + + If modifiers.Count > 1 Then + ' Move the trivia to the next modifier; + newModifiers = modifiers.Replace( + modifiers(1), + modifiers(1).WithPrependedLeadingTrivia(asyncLeadingTrivia)) + newModifiers = newModifiers.RemoveAt(0) + Else + ' move it to the 'sub' or 'function' keyword. + newModifiers = modifiers.RemoveAt(0) + newSubOrFunctionKeyword = newSubOrFunctionKeyword.WithPrependedLeadingTrivia(asyncLeadingTrivia) + End If + Else + newModifiers = modifiers.RemoveAt(asyncTokenIndex) + End If + + Dim newSubOrFunctionStatement = subOrFunctionStatement.WithModifiers(newModifiers).WithSubOrFunctionKeyword(newSubOrFunctionKeyword) + Return newSubOrFunctionStatement + End Function + + Friend Shared Function FixMultiLineLambdaExpression(node As MultiLineLambdaExpressionSyntax) As SyntaxNode + Dim header As LambdaHeaderSyntax = GetNewHeader(node) + Return node.WithSubOrFunctionHeader(header).WithLeadingTrivia(node.GetLeadingTrivia()) + End Function + + Friend Shared Function FixSingleLineLambdaExpression(node As SingleLineLambdaExpressionSyntax) As SingleLineLambdaExpressionSyntax + Dim header As LambdaHeaderSyntax = GetNewHeader(node) + Return node.WithSubOrFunctionHeader(header).WithLeadingTrivia(node.GetLeadingTrivia()) + End Function + + Private Shared Function GetNewHeader(node As LambdaExpressionSyntax) As LambdaHeaderSyntax + Dim header = DirectCast(node.SubOrFunctionHeader, LambdaHeaderSyntax) + Dim asyncKeywordIndex = header.Modifiers.IndexOf(SyntaxKind.AsyncKeyword) + Dim newHeader = header.WithModifiers(header.Modifiers.RemoveAt(asyncKeywordIndex)) + Return newHeader + End Function + End Class +End Namespace diff --git a/src/Features/VisualBasic/Portable/MakeMethodSynchronous/VisualBasicMakeMethodSynchronousCodeFixProvider.vb b/src/Features/VisualBasic/Portable/MakeMethodSynchronous/VisualBasicMakeMethodSynchronousCodeFixProvider.vb index c171c453219..8995cb486e4 100644 --- a/src/Features/VisualBasic/Portable/MakeMethodSynchronous/VisualBasicMakeMethodSynchronousCodeFixProvider.vb +++ b/src/Features/VisualBasic/Portable/MakeMethodSynchronous/VisualBasicMakeMethodSynchronousCodeFixProvider.vb @@ -39,11 +39,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.MakeMethodSynchronous If node.IsKind(SyntaxKind.SingleLineSubLambdaExpression) OrElse node.IsKind(SyntaxKind.SingleLineFunctionLambdaExpression) Then - Return FixSingleLineLambdaExpression(DirectCast(node, SingleLineLambdaExpressionSyntax)) + Return RemoveAsyncModifierHelpers.FixSingleLineLambdaExpression(DirectCast(node, SingleLineLambdaExpressionSyntax)) ElseIf node.IsKind(SyntaxKind.MultiLineSubLambdaExpression) OrElse node.IsKind(SyntaxKind.MultiLineFunctionLambdaExpression) Then - Return FixMultiLineLambdaExpression(DirectCast(node, MultiLineLambdaExpressionSyntax)) + Return RemoveAsyncModifierHelpers.FixMultiLineLambdaExpression(DirectCast(node, MultiLineLambdaExpressionSyntax)) ElseIf node.IsKind(SyntaxKind.SubBlock) Then Return FixSubBlock(DirectCast(node, MethodBlockSyntax)) Else @@ -59,7 +59,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.MakeMethodSynchronous If methodSymbol.ReturnType.OriginalDefinition.Equals(knownTypes._taskOfTType) Then Dim newAsClause = functionStatement.AsClause.WithType(methodSymbol.ReturnType.GetTypeArguments()(0).GenerateTypeSyntax()) Dim newFunctionStatement = functionStatement.WithAsClause(newAsClause) - newFunctionStatement = RemoveAsyncKeyword(newFunctionStatement) + newFunctionStatement = RemoveAsyncModifierHelpers.RemoveAsyncKeyword(newFunctionStatement) Return node.WithSubOrFunctionStatement(newFunctionStatement) ElseIf Equals(methodSymbol.ReturnType.OriginalDefinition, knownTypes._taskType) Then ' Convert this to a 'Sub' method. @@ -75,7 +75,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.MakeMethodSynchronous functionStatement.ImplementsClause) subStatement = subStatement.RemoveNode(subStatement.AsClause, SyntaxRemoveOptions.KeepTrailingTrivia) - subStatement = RemoveAsyncKeyword(subStatement) + subStatement = RemoveAsyncModifierHelpers.RemoveAsyncKeyword(subStatement) Dim endSubStatement = SyntaxFactory.EndSubStatement( node.EndSubOrFunctionStatement.EndKeyword, @@ -83,60 +83,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.MakeMethodSynchronous Return SyntaxFactory.SubBlock(subStatement, node.Statements, endSubStatement) Else - Dim newFunctionStatement = RemoveAsyncKeyword(functionStatement) + Dim newFunctionStatement = RemoveAsyncModifierHelpers.RemoveAsyncKeyword(functionStatement) Return node.WithSubOrFunctionStatement(newFunctionStatement) End If End Function Private Shared Function FixSubBlock(node As MethodBlockSyntax) As SyntaxNode - Dim newSubStatement = RemoveAsyncKeyword(node.SubOrFunctionStatement) + Dim newSubStatement = RemoveAsyncModifierHelpers.RemoveAsyncKeyword(node.SubOrFunctionStatement) Return node.WithSubOrFunctionStatement(newSubStatement) End Function - - Private Shared Function RemoveAsyncKeyword(subOrFunctionStatement As MethodStatementSyntax) As MethodStatementSyntax - Dim modifiers = subOrFunctionStatement.Modifiers - Dim asyncTokenIndex = modifiers.IndexOf(SyntaxKind.AsyncKeyword) - - Dim newSubOrFunctionKeyword = subOrFunctionStatement.SubOrFunctionKeyword - Dim newModifiers As SyntaxTokenList - If asyncTokenIndex = 0 Then - ' Have to move the trivia on the async token appropriately. - Dim asyncLeadingTrivia = modifiers(0).LeadingTrivia - - If modifiers.Count > 1 Then - ' Move the trivia to the next modifier; - newModifiers = modifiers.Replace( - modifiers(1), - modifiers(1).WithPrependedLeadingTrivia(asyncLeadingTrivia)) - newModifiers = newModifiers.RemoveAt(0) - Else - ' move it to the 'sub' or 'function' keyword. - newModifiers = modifiers.RemoveAt(0) - newSubOrFunctionKeyword = newSubOrFunctionKeyword.WithPrependedLeadingTrivia(asyncLeadingTrivia) - End If - Else - newModifiers = modifiers.RemoveAt(asyncTokenIndex) - End If - - Dim newSubOrFunctionStatement = subOrFunctionStatement.WithModifiers(newModifiers).WithSubOrFunctionKeyword(newSubOrFunctionKeyword) - Return newSubOrFunctionStatement - End Function - - Private Shared Function FixMultiLineLambdaExpression(node As MultiLineLambdaExpressionSyntax) As SyntaxNode - Dim header As LambdaHeaderSyntax = GetNewHeader(node) - Return node.WithSubOrFunctionHeader(header).WithLeadingTrivia(node.GetLeadingTrivia()) - End Function - - Private Shared Function FixSingleLineLambdaExpression(node As SingleLineLambdaExpressionSyntax) As SingleLineLambdaExpressionSyntax - Dim header As LambdaHeaderSyntax = GetNewHeader(node) - Return node.WithSubOrFunctionHeader(header).WithLeadingTrivia(node.GetLeadingTrivia()) - End Function - - Private Shared Function GetNewHeader(node As LambdaExpressionSyntax) As LambdaHeaderSyntax - Dim header = DirectCast(node.SubOrFunctionHeader, LambdaHeaderSyntax) - Dim asyncKeywordIndex = header.Modifiers.IndexOf(SyntaxKind.AsyncKeyword) - Dim newHeader = header.WithModifiers(header.Modifiers.RemoveAt(asyncKeywordIndex)) - Return newHeader - End Function End Class End Namespace diff --git a/src/Features/VisualBasic/Portable/RemoveAsyncModifier/VisualBasicRemoveAsyncModifierCodeFixProvider.vb b/src/Features/VisualBasic/Portable/RemoveAsyncModifier/VisualBasicRemoveAsyncModifierCodeFixProvider.vb new file mode 100644 index 00000000000..92fe66b2f40 --- /dev/null +++ b/src/Features/VisualBasic/Portable/RemoveAsyncModifier/VisualBasicRemoveAsyncModifierCodeFixProvider.vb @@ -0,0 +1,60 @@ +' 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. + +Imports System.Collections.Immutable +Imports System.Composition +Imports System.Diagnostics.CodeAnalysis +Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.Editing +Imports Microsoft.CodeAnalysis.RemoveAsyncModifier +Imports Microsoft.CodeAnalysis.VisualBasic.MakeMethodSynchronous +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Namespace Microsoft.CodeAnalysis.VisualBasic.RemoveAsyncModifier + + + Friend Class VisualBasicRemoveAsyncModifierCodeFixProvider + Inherits AbstractRemoveAsyncModifierCodeFixProvider(Of ReturnStatementSyntax, ExpressionSyntax) + + Private Const BC42356 As String = NameOf(BC42356) ' This async method lacks 'Await' operators and so will run synchronously. + + Private Shared ReadOnly s_diagnosticIds As ImmutableArray(Of String) = ImmutableArray.Create(BC42356) + + + + Public Sub New() + End Sub + + Public Overrides ReadOnly Property FixableDiagnosticIds As ImmutableArray(Of String) = s_diagnosticIds + + Protected Overrides Function IsAsyncSupportingFunctionSyntax(node As SyntaxNode) As Boolean + Return node.IsAsyncSupportedFunctionSyntax() + End Function + + Protected Overrides Function RemoveAsyncModifier(generator As SyntaxGenerator, methodLikeNode As SyntaxNode) As SyntaxNode + Dim methodBlock = TryCast(methodLikeNode, MethodBlockSyntax) + If methodBlock IsNot Nothing Then + Dim subOrFunctionStatement = methodBlock.SubOrFunctionStatement + Dim newSubOrFunctionStatement = RemoveAsyncModifierHelpers.RemoveAsyncKeyword(subOrFunctionStatement) + Return methodBlock.WithSubOrFunctionStatement(newSubOrFunctionStatement) + End If + + Dim multiLineLambda = TryCast(methodLikeNode, MultiLineLambdaExpressionSyntax) + If multiLineLambda IsNot Nothing Then + Return RemoveAsyncModifierHelpers.FixMultiLineLambdaExpression(multiLineLambda) + End If + + Dim singleLineLambda = TryCast(methodLikeNode, SingleLineLambdaExpressionSyntax) + If singleLineLambda IsNot Nothing Then + Return RemoveAsyncModifierHelpers.FixSingleLineLambdaExpression(singleLineLambda) + End If + + Return Nothing + End Function + + Protected Overrides Function ConvertToBlockBody(node As SyntaxNode, expressionBody As ExpressionSyntax) As SyntaxNode + Throw ExceptionUtilities.Unreachable + End Function + End Class +End Namespace diff --git a/src/Test/Utilities/Portable/Traits/Traits.cs b/src/Test/Utilities/Portable/Traits/Traits.cs index 5d246ebc3fb..d3f2de08efa 100644 --- a/src/Test/Utilities/Portable/Traits/Traits.cs +++ b/src/Test/Utilities/Portable/Traits/Traits.cs @@ -132,6 +132,7 @@ public static class Features public const string CodeActionsPopulateSwitch = "CodeActions.PopulateSwitch"; public const string CodeActionsPullMemberUp = "CodeActions.PullMemberUp"; public const string CodeActionsQualifyMemberAccess = "CodeActions.QualifyMemberAccess"; + public const string CodeActionsRemoveAsyncModifier = "CodeActions.RemoveAsyncModifier"; public const string CodeActionsRemoveByVal = "CodeActions.RemoveByVal"; public const string CodeActionsRemoveDocCommentNode = "CodeActions.RemoveDocCommentNode"; public const string CodeActionsRemoveInKeyword = "CodeActions.RemoveInKeyword"; diff --git a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs index e6d874a604d..386c547b95a 100644 --- a/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs +++ b/src/Workspaces/CSharp/Portable/CodeGeneration/CSharpSyntaxGenerator.cs @@ -2230,6 +2230,22 @@ public override SyntaxNode GetExpression(SyntaxNode declaration) } goto default; + case SyntaxKind.MethodDeclaration: + var method = (MethodDeclarationSyntax)declaration; + if (method.ExpressionBody != null) + { + return method.ExpressionBody.Expression; + } + goto default; + + case SyntaxKind.LocalFunctionStatement: + var local = (LocalFunctionStatementSyntax)declaration; + if (local.ExpressionBody != null) + { + return local.ExpressionBody.Expression; + } + goto default; + default: return GetEqualsValue(declaration)?.Value; } @@ -2266,6 +2282,22 @@ private static SyntaxNode WithExpressionInternal(SyntaxNode declaration, SyntaxN } goto default; + case SyntaxKind.MethodDeclaration: + var method = (MethodDeclarationSyntax)declaration; + if (method.ExpressionBody != null) + { + return ReplaceWithTrivia(method, method.ExpressionBody.Expression, expr); + } + goto default; + + case SyntaxKind.LocalFunctionStatement: + var local = (LocalFunctionStatementSyntax)declaration; + if (local.ExpressionBody != null) + { + return ReplaceWithTrivia(local, local.ExpressionBody.Expression, expr); + } + goto default; + default: var eq = GetEqualsValue(declaration); if (eq != null) diff --git a/src/Workspaces/CSharpTest/CodeGeneration/SyntaxGeneratorTests.cs b/src/Workspaces/CSharpTest/CodeGeneration/SyntaxGeneratorTests.cs index 9b34e39e0aa..cc67e8d95e2 100644 --- a/src/Workspaces/CSharpTest/CodeGeneration/SyntaxGeneratorTests.cs +++ b/src/Workspaces/CSharpTest/CodeGeneration/SyntaxGeneratorTests.cs @@ -2328,7 +2328,22 @@ public void TestGetExpression() Assert.Equal("x", Generator.GetExpression(Generator.ValueReturningLambdaExpression("p", Generator.IdentifierName("x"))).ToString()); Assert.Equal("x", Generator.GetExpression(Generator.VoidReturningLambdaExpression("p", Generator.IdentifierName("x"))).ToString()); + // identifier Assert.Null(Generator.GetExpression(Generator.IdentifierName("e"))); + + // expression bodied methods + var method = (MethodDeclarationSyntax)Generator.MethodDeclaration("p"); + method = method.WithBody(null).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + method = method.WithExpressionBody(SyntaxFactory.ArrowExpressionClause((ExpressionSyntax)Generator.IdentifierName("x"))); + + Assert.Equal("x", Generator.GetExpression(method).ToString()); + + // expression bodied local functions + var local = SyntaxFactory.LocalFunctionStatement(SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)), "p"); + local = local.WithBody(null).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + local = local.WithExpressionBody(SyntaxFactory.ArrowExpressionClause((ExpressionSyntax)Generator.IdentifierName("x"))); + + Assert.Equal("x", Generator.GetExpression(local).ToString()); } [Fact] @@ -2349,7 +2364,22 @@ public void TestWithExpression() Assert.Equal("y", Generator.GetExpression(Generator.WithExpression(Generator.ValueReturningLambdaExpression(Generator.IdentifierName("x")), Generator.IdentifierName("y"))).ToString()); Assert.Equal("y", Generator.GetExpression(Generator.WithExpression(Generator.VoidReturningLambdaExpression(Generator.IdentifierName("x")), Generator.IdentifierName("y"))).ToString()); + // identifier Assert.Null(Generator.GetExpression(Generator.WithExpression(Generator.IdentifierName("e"), Generator.IdentifierName("x")))); + + // expression bodied methods + var method = (MethodDeclarationSyntax)Generator.MethodDeclaration("p"); + method = method.WithBody(null).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + method = method.WithExpressionBody(SyntaxFactory.ArrowExpressionClause((ExpressionSyntax)Generator.IdentifierName("x"))); + + Assert.Equal("y", Generator.GetExpression(Generator.WithExpression(method, Generator.IdentifierName("y"))).ToString()); + + // expression bodied local functions + var local = SyntaxFactory.LocalFunctionStatement(SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)), "p"); + local = local.WithBody(null).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + local = local.WithExpressionBody(SyntaxFactory.ArrowExpressionClause((ExpressionSyntax)Generator.IdentifierName("x"))); + + Assert.Equal("y", Generator.GetExpression(Generator.WithExpression(local, Generator.IdentifierName("y"))).ToString()); } [Fact] -- GitLab