in src/Microsoft.VisualStudio.Threading.Analyzers.CodeFixes/VSTHRD103UseAsyncOptionCodeFix.cs [112:205]
protected override async Task<Solution> GetChangedSolutionAsync(CancellationToken cancellationToken)
{
Document? document = this.document;
SyntaxNode? root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
// Find the synchronously blocking call member,
// and bookmark it so we can find it again after some mutations have taken place.
var syncAccessBookmark = new SyntaxAnnotation();
SimpleNameSyntax syncMethodName = (SimpleNameSyntax)root.FindNode(this.diagnostic.Location.SourceSpan);
if (syncMethodName is null)
{
MemberAccessExpressionSyntax? syncMemberAccess = root.FindNode(this.diagnostic.Location.SourceSpan).FirstAncestorOrSelf<MemberAccessExpressionSyntax>();
syncMethodName = syncMemberAccess.Name;
}
// When we give the Document a modified SyntaxRoot, yet another is created. So we first assign it to the Document,
// then we query for the SyntaxRoot from the Document.
document = document.WithSyntaxRoot(
root.ReplaceNode(syncMethodName, syncMethodName.WithAdditionalAnnotations(syncAccessBookmark)));
root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
syncMethodName = (SimpleNameSyntax)root.GetAnnotatedNodes(syncAccessBookmark).Single();
// We'll need the semantic model later. But because we've annotated a node, that changes the SyntaxRoot
// and that renders the default semantic model broken (even though we've already updated the document's SyntaxRoot?!).
// So after acquiring the semantic model, update it with the new method body.
SemanticModel? semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
AnonymousFunctionExpressionSyntax? originalAnonymousMethodContainerIfApplicable = syncMethodName.FirstAncestorOrSelf<AnonymousFunctionExpressionSyntax>();
MethodDeclarationSyntax? originalMethodDeclaration = syncMethodName.FirstAncestorOrSelf<MethodDeclarationSyntax>();
ISymbol? enclosingSymbol = semanticModel.GetEnclosingSymbol(this.diagnostic.Location.SourceSpan.Start, cancellationToken);
var hasReturnValue = ((enclosingSymbol as IMethodSymbol)?.ReturnType as INamedTypeSymbol)?.IsGenericType ?? false;
// Ensure that the method or anonymous delegate is using the async keyword.
MethodDeclarationSyntax updatedMethod;
if (originalAnonymousMethodContainerIfApplicable is object)
{
updatedMethod = originalMethodDeclaration.ReplaceNode(
originalAnonymousMethodContainerIfApplicable,
originalAnonymousMethodContainerIfApplicable.MakeMethodAsync(hasReturnValue, semanticModel, cancellationToken));
}
else
{
(document, updatedMethod) = await originalMethodDeclaration.MakeMethodAsync(document, cancellationToken).ConfigureAwait(false);
semanticModel = null; // out-dated
}
if (updatedMethod != originalMethodDeclaration)
{
// Re-discover our synchronously blocking member.
syncMethodName = (SimpleNameSyntax)updatedMethod.GetAnnotatedNodes(syncAccessBookmark).Single();
}
ExpressionSyntax? syncExpression = GetSynchronousExpression(syncMethodName);
ExpressionSyntax awaitExpression;
if (!string.IsNullOrEmpty(this.AlternativeAsyncMethod))
{
// Replace the member being called and await the invocation expression.
// While doing so, move leading trivia to the surrounding await expression.
SimpleNameSyntax? asyncMethodName = syncMethodName.WithIdentifier(SyntaxFactory.Identifier(this.diagnostic.Properties[VSTHRD103UseAsyncOptionAnalyzer.AsyncMethodKeyName]));
awaitExpression = SyntaxFactory.AwaitExpression(
syncExpression.ReplaceNode(syncMethodName, asyncMethodName).WithoutLeadingTrivia())
.WithLeadingTrivia(syncExpression.GetLeadingTrivia());
}
else
{
// Remove the member being accessed that causes a synchronous block and simply await the object.
MemberAccessExpressionSyntax? syncMemberAccess = syncMethodName.FirstAncestorOrSelf<MemberAccessExpressionSyntax>();
ExpressionSyntax? syncMemberStrippedExpression = syncMemberAccess.Expression;
// Special case a common pattern of calling task.GetAwaiter().GetResult() and remove both method calls.
var expressionMethodCall = (syncMemberStrippedExpression as InvocationExpressionSyntax)?.Expression as MemberAccessExpressionSyntax;
if (expressionMethodCall?.Name.Identifier.Text == nameof(Task.GetAwaiter))
{
syncMemberStrippedExpression = expressionMethodCall.Expression;
}
awaitExpression = SyntaxFactory.AwaitExpression(syncMemberStrippedExpression.WithoutLeadingTrivia())
.WithLeadingTrivia(syncMemberStrippedExpression.GetLeadingTrivia());
}
if (!(syncExpression.Parent is ExpressionStatementSyntax))
{
awaitExpression = SyntaxFactory.ParenthesizedExpression(awaitExpression)
.WithAdditionalAnnotations(Simplifier.Annotation);
}
updatedMethod = updatedMethod
.ReplaceNode(syncExpression, awaitExpression);
SyntaxNode? newRoot = root.ReplaceNode(originalMethodDeclaration, updatedMethod);
Document? newDocument = document.WithSyntaxRoot(newRoot);
return newDocument.Project.Solution;
}