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(GetDocumentUpdater(context)), context.Diagnostics); } }
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)); } }
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); }
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 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 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 bool IsTaskType(ITypeSymbol returnType, KnownTypes knownTypes) => returnType.OriginalDefinition.Equals(knownTypes._taskType) || returnType.OriginalDefinition.Equals(knownTypes._valueTaskType);
private static bool ShouldOfferFix(ITypeSymbol returnType, KnownTypes knownTypes) => IsTaskType(returnType, knownTypes) || returnType.OriginalDefinition.Equals(knownTypes._taskOfTType) || returnType.OriginalDefinition.Equals(knownTypes._valueTaskOfTTypeOpt);