未验证 提交 75d02c10 编写于 作者: D David Wengier 提交者: GitHub

Create "Remove 'async' modifier" code fix (#45913)

Co-authored-by: NCyrusNajmabadi <cyrus.najmabadi@gmail.com>
Co-authored-by: NDavid Wengier <dawengie@microsoft.com>
上级 f2ea4a57
......@@ -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);
......
' 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
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)>
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
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)>
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
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)>
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
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)>
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
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)>
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
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)>
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
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)>
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
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)>
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
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)>
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
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)>
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
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveAsyncModifier)>
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
......@@ -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);
}
}
// 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;
}
}
}
// 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<ReturnStatementSyntax, ExpressionSyntax>
{
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<string> 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;
}
}
......@@ -2783,4 +2783,7 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
<data name="Make_class_abstract" xml:space="preserve">
<value>Make class 'abstract'</value>
</data>
<data name="Remove_async_modifier" xml:space="preserve">
<value>Remove 'async' modifier</value>
</data>
</root>
\ No newline at end of file
// 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<TReturnStatementSyntax, TExpressionSyntax> : 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<Diagnostic> 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<T> 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<TReturnStatementSyntax>();
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 <expr>; into return Task.FromResult(<expr>);
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<CancellationToken, Task<Document>> createChangedDocument)
: base(FeaturesResources.Remove_async_modifier, createChangedDocument, FeaturesResources.Remove_async_modifier)
{
}
}
}
}
......@@ -1790,6 +1790,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
<target state="translated">Podpisy souvisejících metod nalezené v metadatech se nebudou aktualizovat.</target>
<note />
</trans-unit>
<trans-unit id="Remove_async_modifier">
<source>Remove 'async' modifier</source>
<target state="new">Remove 'async' modifier</target>
<note />
</trans-unit>
<trans-unit id="Replace_0_with_1">
<source>Replace '{0}' with '{1}' </source>
<target state="translated">Místo {0} použijte {1}</target>
......
......@@ -1790,6 +1790,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
<target state="translated">In Metadaten gefundene ähnliche Methodensignaturen werden nicht aktualisiert.</target>
<note />
</trans-unit>
<trans-unit id="Remove_async_modifier">
<source>Remove 'async' modifier</source>
<target state="new">Remove 'async' modifier</target>
<note />
</trans-unit>
<trans-unit id="Replace_0_with_1">
<source>Replace '{0}' with '{1}' </source>
<target state="translated">"{0}" durch "{1}" ersetzen</target>
......
......@@ -1790,6 +1790,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
<target state="translated">Las signaturas de método relacionadas encontradas en los metadatos no se actualizarán.</target>
<note />
</trans-unit>
<trans-unit id="Remove_async_modifier">
<source>Remove 'async' modifier</source>
<target state="new">Remove 'async' modifier</target>
<note />
</trans-unit>
<trans-unit id="Replace_0_with_1">
<source>Replace '{0}' with '{1}' </source>
<target state="translated">Reemplazar "{0}" por "{1}"</target>
......
......@@ -1790,6 +1790,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
<target state="translated">Les signatures de méthode associées dans les métadonnées ne sont pas mises à jour.</target>
<note />
</trans-unit>
<trans-unit id="Remove_async_modifier">
<source>Remove 'async' modifier</source>
<target state="new">Remove 'async' modifier</target>
<note />
</trans-unit>
<trans-unit id="Replace_0_with_1">
<source>Replace '{0}' with '{1}' </source>
<target state="translated">Remplacer '{0}' par '{1}'</target>
......
......@@ -1790,6 +1790,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
<target state="translated">Le firme del metodo correlate trovate nei metadati non verranno aggiornate.</target>
<note />
</trans-unit>
<trans-unit id="Remove_async_modifier">
<source>Remove 'async' modifier</source>
<target state="new">Remove 'async' modifier</target>
<note />
</trans-unit>
<trans-unit id="Replace_0_with_1">
<source>Replace '{0}' with '{1}' </source>
<target state="translated">Sostituisci '{0}' con '{1}'</target>
......
......@@ -1790,6 +1790,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
<target state="translated">メタデータ内に検出される関連するメソッド シグネチャは更新されません。</target>
<note />
</trans-unit>
<trans-unit id="Remove_async_modifier">
<source>Remove 'async' modifier</source>
<target state="new">Remove 'async' modifier</target>
<note />
</trans-unit>
<trans-unit id="Replace_0_with_1">
<source>Replace '{0}' with '{1}' </source>
<target state="translated">'{0}' を '{1}' に置き換える</target>
......
......@@ -1790,6 +1790,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
<target state="translated">메타데이터에서 찾은 관련 메서드 시그니처가 업데이트되지 않습니다.</target>
<note />
</trans-unit>
<trans-unit id="Remove_async_modifier">
<source>Remove 'async' modifier</source>
<target state="new">Remove 'async' modifier</target>
<note />
</trans-unit>
<trans-unit id="Replace_0_with_1">
<source>Replace '{0}' with '{1}' </source>
<target state="translated">'{0}'을(를) '{1}'(으)로 바꾸기</target>
......
......@@ -1790,6 +1790,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
<target state="translated">Sygnatury powiązanych metod znalezione w metadanych nie zostaną zaktualizowane.</target>
<note />
</trans-unit>
<trans-unit id="Remove_async_modifier">
<source>Remove 'async' modifier</source>
<target state="new">Remove 'async' modifier</target>
<note />
</trans-unit>
<trans-unit id="Replace_0_with_1">
<source>Replace '{0}' with '{1}' </source>
<target state="translated">Zamień element „{0}” na element „{1}”</target>
......
......@@ -1790,6 +1790,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
<target state="translated">As assinaturas de método relacionadas encontradas nos metadados não serão atualizadas.</target>
<note />
</trans-unit>
<trans-unit id="Remove_async_modifier">
<source>Remove 'async' modifier</source>
<target state="new">Remove 'async' modifier</target>
<note />
</trans-unit>
<trans-unit id="Replace_0_with_1">
<source>Replace '{0}' with '{1}' </source>
<target state="translated">Substituir '{0}' por '{1}'</target>
......
......@@ -1790,6 +1790,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
<target state="translated">Связанные сигнатуры методов, найденные в метаданных, не будут обновлены.</target>
<note />
</trans-unit>
<trans-unit id="Remove_async_modifier">
<source>Remove 'async' modifier</source>
<target state="new">Remove 'async' modifier</target>
<note />
</trans-unit>
<trans-unit id="Replace_0_with_1">
<source>Replace '{0}' with '{1}' </source>
<target state="translated">Замените '{0}' на '{1}'</target>
......
......@@ -1790,6 +1790,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
<target state="translated">Meta verilerde bulunan ilgili metot imzaları güncelleştirilmez.</target>
<note />
</trans-unit>
<trans-unit id="Remove_async_modifier">
<source>Remove 'async' modifier</source>
<target state="new">Remove 'async' modifier</target>
<note />
</trans-unit>
<trans-unit id="Replace_0_with_1">
<source>Replace '{0}' with '{1}' </source>
<target state="translated">'{0}' öğesini '{1}' ile değiştir</target>
......
......@@ -1790,6 +1790,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
<target state="translated">不更新在元数据中发现的相关方法签名。</target>
<note />
</trans-unit>
<trans-unit id="Remove_async_modifier">
<source>Remove 'async' modifier</source>
<target state="new">Remove 'async' modifier</target>
<note />
</trans-unit>
<trans-unit id="Replace_0_with_1">
<source>Replace '{0}' with '{1}' </source>
<target state="translated">将 "{0}" 替换为 "{1}"</target>
......
......@@ -1790,6 +1790,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
<target state="translated">將不會更新中繼資料中所找到的相關方法簽章。</target>
<note />
</trans-unit>
<trans-unit id="Remove_async_modifier">
<source>Remove 'async' modifier</source>
<target state="new">Remove 'async' modifier</target>
<note />
</trans-unit>
<trans-unit id="Replace_0_with_1">
<source>Replace '{0}' with '{1}' </source>
<target state="translated">將 ‘{0}’ 取代為 ‘{1}'</target>
......
' 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
......@@ -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
' 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
<ExportCodeFixProvider(LanguageNames.VisualBasic, Name:=PredefinedCodeFixProviderNames.RemoveAsyncModifier), [Shared]>
<ExtensionOrder(After:=PredefinedCodeFixProviderNames.MakeMethodSynchronous)>
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)
<ImportingConstructor>
<SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification:="Used in test code: https://github.com/dotnet/roslyn/issues/42814")>
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
......@@ -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";
......
......@@ -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)
......
......@@ -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]
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册