private static bool ParentIsElementAccessOrForEachExpression(InvocationExpressionSyntax invocation) { if (invocation.IsParentKind(SyntaxKind.ElementAccessExpression)) { return(true); } if (invocation.IsParentKind(SyntaxKind.ForEachStatement)) { var forEachStatement = (ForEachStatementSyntax)invocation.Parent; if (invocation.Equals(forEachStatement.Expression)) { return(true); } } return(false); }
private void AnalyzeInvocationExpression(DocumentData documentData, InvocationExpressionSyntax node, InvokeFunctionReferenceData functionReferenceData) { var functionData = functionReferenceData.FunctionData; var methodSymbol = functionReferenceData.ReferenceSymbol; var functionNode = functionData.GetNode(); var functionBodyNode = functionData.GetBodyNode(); var queryExpression = node.Ancestors() .TakeWhile(o => o != functionNode) .OfType <QueryExpressionSyntax>() .FirstOrDefault(); if (queryExpression != null) // Await is not supported in a linq query { functionReferenceData.Ignore = true; Logger.Warn($"Cannot await async method in a query expression:\r\n{queryExpression}\r\n"); return; } var searchOptions = AsyncCounterpartsSearchOptions.Default; if (_configuration.UseCancellationTokenOverload) { searchOptions |= AsyncCounterpartsSearchOptions.HasCancellationToken; } functionReferenceData.ReferenceAsyncSymbols = new HashSet <IMethodSymbol>(GetAsyncCounterparts(methodSymbol.OriginalDefinition, searchOptions)); if (functionReferenceData.ReferenceAsyncSymbols.Any()) { if (functionReferenceData.ReferenceAsyncSymbols.All(o => o.ReturnsVoid || !o.ReturnType.IsTaskType())) { functionReferenceData.AwaitInvocation = false; Logger.Info($"Cannot await method that is either void or do not return a Task:\r\n{methodSymbol}\r\n"); } var nameGroups = functionReferenceData.ReferenceAsyncSymbols.GroupBy(o => o.Name).ToList(); if (nameGroups.Count == 1) { functionReferenceData.AsyncCounterpartName = nameGroups[0].Key; } } else if (!ProjectData.Contains(functionReferenceData.ReferenceSymbol)) { // If we are dealing with an external method and there are no async counterparts for it, we cannot convert it to async functionReferenceData.Ignore = true; Logger.Info($"Method {methodSymbol} can not be async as there is no async counterparts for it"); return; } else if (functionReferenceData.ReferenceFunctionData != null) { functionReferenceData.AsyncCounterpartName = functionReferenceData.ReferenceSymbol.Name + "Async"; } // If the invocation returns a Task then we need to analyze it further to see how the Task is handled if (methodSymbol.ReturnType.IsTaskType()) { var retrunType = (INamedTypeSymbol)methodSymbol.ReturnType; var canBeAwaited = false; var currNode = node.Parent; while (true) { var memberExpression = currNode as MemberAccessExpressionSyntax; if (memberExpression == null) { break; } var memberName = memberExpression.Name.ToString(); if (retrunType.IsGenericType && memberName == "Result") { canBeAwaited = true; break; } if (memberName == "ConfigureAwait") { var invocationNode = currNode.Parent as InvocationExpressionSyntax; if (invocationNode != null) { functionReferenceData.ConfigureAwaitParameter = invocationNode.ArgumentList.Arguments.First().Expression; currNode = invocationNode.Parent; continue; } break; } if (memberName == "GetAwaiter") { var invocationNode = currNode.Parent as InvocationExpressionSyntax; if (invocationNode != null) { currNode = invocationNode.Parent; continue; } break; } if (_taskResultMethods.Contains(memberName)) { var invocationNode = currNode.Parent as InvocationExpressionSyntax; if (invocationNode != null) { canBeAwaited = true; } } break; } if (!canBeAwaited) { functionReferenceData.AwaitInvocation = false; Logger.Info( $"Cannot await invocation of a method that returns a Task without be synchronously awaited:\r\n{methodSymbol}\r\n"); } else { functionReferenceData.SynchronouslyAwaited = true; } } if (node.Parent.IsKind(SyntaxKind.ReturnStatement)) { functionReferenceData.UseAsReturnValue = true; } // Calculate if node is the last statement if (node.Parent.Equals(functionBodyNode) || //eg. bool ExpressionReturn() => SimpleFile.Write(); node.Equals(functionBodyNode) // eg. Func<bool> fn = () => SimpleFile.Write(); ) { functionReferenceData.LastInvocation = true; functionReferenceData.UseAsReturnValue = !methodSymbol.ReturnsVoid; } var bodyBlock = functionBodyNode as BlockSyntax; if (bodyBlock?.Statements.Last() == node.Parent) { functionReferenceData.LastInvocation = true; } // Set CancellationTokenRequired if we detect that one of the async counterparts has a cancellation token as a parameter if (_configuration.UseCancellationTokenOverload && functionReferenceData.ReferenceAsyncSymbols.Any(o => o.Parameters.Length > methodSymbol.Parameters.Length)) { functionReferenceData.CancellationTokenRequired = true; } foreach (var analyzer in _configuration.InvocationExpressionAnalyzers) { analyzer.Analyze(node, functionReferenceData, documentData.SemanticModel); } // Propagate CancellationTokenRequired to the method data only if the invocation can be async if (functionReferenceData.CancellationTokenRequired && functionReferenceData.GetConversion() == ReferenceConversion.ToAsync) { // We need to set CancellationTokenRequired to true for the method that contains this invocation var methodData = functionReferenceData.FunctionData.GetMethodData(); methodData.CancellationTokenRequired = true; } }
private T TransformFunctionReference <T>(T node, IFunctionAnalyzationResult funcResult, FunctionReferenceTransformationResult transfromReference, ITypeTransformationMetadata typeMetadata, INamespaceTransformationMetadata namespaceMetadata) where T : SyntaxNode { var nameNode = node.GetAnnotatedNodes(transfromReference.Annotation).OfType <SimpleNameSyntax>().First(); var funReferenceResult = transfromReference.AnalyzationResult; var bodyFuncReferenceResult = funReferenceResult as IBodyFunctionReferenceAnalyzationResult; var newNameNode = nameNode .WithIdentifier(Identifier(funReferenceResult.AsyncCounterpartName)) .WithTriviaFrom(nameNode); transfromReference.Transformed = newNameNode; var cancellationTokenParamName = funcResult.GetMethodOrAccessor().CancellationTokenRequired ? "cancellationToken" : null; // TODO: remove // If we have a cref change the name to the async counterpart and add/update arguments if (bodyFuncReferenceResult == null) { if (funReferenceResult.IsCref) { var crefNode = (NameMemberCrefSyntax)nameNode.Parent; var paramList = new List <CrefParameterSyntax>(); // If the cref has already the parameters set then use them if (crefNode.Parameters != null) { paramList.AddRange(crefNode.Parameters.Parameters); // If the external async counterpart has a cancellation token, add it if (funReferenceResult.AsyncCounterpartFunction == null && funReferenceResult.ReferenceSymbol.Parameters.Length < funReferenceResult.AsyncCounterpartSymbol.Parameters.Length) { paramList.Add(CrefParameter(IdentifierName(nameof(CancellationToken)))); } } else { // We have to add the parameters to avoid ambiguity var asyncSymbol = funReferenceResult.AsyncCounterpartSymbol; paramList.AddRange(asyncSymbol.Parameters .Select(o => CrefParameter(o.Type .CreateTypeSyntax(true, namespaceMetadata.AnalyzationResult.IsIncluded(o.Type.ContainingNamespace?.ToString()))))); } // If the async counterpart is internal and a token is required add a token parameter if (funReferenceResult.AsyncCounterpartFunction?.GetMethodOrAccessor()?.CancellationTokenRequired == true) { paramList.Add(CrefParameter(IdentifierName(nameof(CancellationToken)))); } node = node.ReplaceNestedNodes( crefNode.Parent as QualifiedCrefSyntax, crefNode, crefNode .ReplaceNode(nameNode, newNameNode) .WithParameters(CrefParameterList(SeparatedList(paramList))), rootNode => UpdateTypeAndRunReferenceTransformers(rootNode, funcResult, funReferenceResult, namespaceMetadata, (type, fullName) => rootNode.WithContainer(type.CreateTypeSyntax(true, fullName).WithTriviaFrom(rootNode.Container))), childNode => RunReferenceTransformers(childNode, funcResult, funReferenceResult, namespaceMetadata) ); } else if (funReferenceResult.IsNameOf) { node = node.ReplaceNestedNodes( nameNode.Parent as MemberAccessExpressionSyntax, nameNode, newNameNode, rootNode => UpdateTypeAndRunReferenceTransformers(rootNode, funcResult, funReferenceResult, namespaceMetadata, (type, fullName) => rootNode.WithExpression(type.CreateTypeSyntax(false, fullName).WithTriviaFrom(rootNode.Expression))), childNode => RunReferenceTransformers(childNode, funcResult, funReferenceResult, namespaceMetadata) ); } return(node); } // If we have a method passed as an argument we need to check if we have to wrap it inside a function if (bodyFuncReferenceResult.AsyncDelegateArgument != null) { if (bodyFuncReferenceResult.WrapInsideFunction) { // TODO: move to analyze step var argumentNode = nameNode.Ancestors().OfType <ArgumentSyntax>().First(); var delReturnType = (INamedTypeSymbol)bodyFuncReferenceResult.AsyncDelegateArgument.ReturnType; var returnType = bodyFuncReferenceResult.AsyncCounterpartSymbol.ReturnType; bool returnTypeMismatch; if (bodyFuncReferenceResult.ReferenceFunction != null) { var refMethod = bodyFuncReferenceResult.ReferenceFunction as IMethodAnalyzationResult; if (refMethod != null && refMethod.PreserveReturnType) { returnTypeMismatch = !delReturnType.Equals(returnType); // TODO Generics } else if (delReturnType.IsGenericType) // Generic Task { returnTypeMismatch = delReturnType.TypeArguments.First().IsAwaitRequired(returnType); } else { returnTypeMismatch = delReturnType.IsAwaitRequired(returnType); } } else { returnTypeMismatch = !delReturnType.Equals(returnType); // TODO Generics } var newArgumentExpression = argumentNode.Expression .ReplaceNestedNodes( nameNode.Parent as MemberAccessExpressionSyntax, nameNode, newNameNode, rootNode => UpdateTypeAndRunReferenceTransformers(rootNode, funcResult, funReferenceResult, namespaceMetadata, (type, fullName) => rootNode.WithExpression(type.CreateTypeSyntax(false, fullName))), childNode => RunReferenceTransformers(childNode, funcResult, funReferenceResult, namespaceMetadata) ) .WrapInsideFunction(bodyFuncReferenceResult.AsyncDelegateArgument, returnTypeMismatch, namespaceMetadata.TaskConflict, invocation => invocation.AddCancellationTokenArgumentIf(cancellationTokenParamName, bodyFuncReferenceResult)); node = node .ReplaceNode(argumentNode.Expression, newArgumentExpression); } else { node = node.ReplaceNestedNodes( nameNode.Parent as MemberAccessExpressionSyntax, nameNode, newNameNode, rootNode => UpdateTypeAndRunReferenceTransformers(rootNode, funcResult, funReferenceResult, namespaceMetadata, (type, fullName) => rootNode.WithExpression(type.CreateTypeSyntax(false, fullName))), childNode => RunReferenceTransformers(childNode, funcResult, funReferenceResult, namespaceMetadata) ); } return(node); } InvocationExpressionSyntax invokeNode = null; var isAccessor = bodyFuncReferenceResult.ReferenceSymbol.IsAccessor(); if (!isAccessor && funReferenceResult.ReferenceNode.IsKind(SyntaxKind.InvocationExpression)) { invokeNode = nameNode.Ancestors().OfType <InvocationExpressionSyntax>().First(); } if (!bodyFuncReferenceResult.AwaitInvocation) { // An arrow method does not have a statement var statement = nameNode.Ancestors().OfType <StatementSyntax>().FirstOrDefault(); var statementInParentFunction = nameNode.Ancestors().TakeWhile(o => !o.Equals(statement)).Any(o => o.IsFunction()); var newNode = (SyntaxNode)statement ?? node; if (invokeNode != null) { newNode = newNode.ReplaceNestedNodes( invokeNode, nameNode, newNameNode, rootNode => UpdateTypeAndRunReferenceTransformers(rootNode .AddCancellationTokenArgumentIf(cancellationTokenParamName, bodyFuncReferenceResult), funcResult, funReferenceResult, namespaceMetadata, (memberNode, type, fullName) => memberNode.WithExpression(type.CreateTypeSyntax(true, fullName).WithTriviaFrom(memberNode.Expression))) ); } else if (isAccessor) { newNode = ConvertAccessor(newNode, nameNode, newNameNode, cancellationTokenParamName, bodyFuncReferenceResult, invNode => UpdateTypeAndRunReferenceTransformers(invNode, funcResult, funReferenceResult, namespaceMetadata, (memberNode, type, fullName) => memberNode.WithExpression(type.CreateTypeSyntax(true, fullName).WithTriviaFrom(memberNode.Expression)))); } else { newNode = newNode.ReplaceNestedNodes( nameNode.Parent as MemberAccessExpressionSyntax, nameNode, newNameNode, rootNode => UpdateTypeAndRunReferenceTransformers(rootNode, funcResult, funReferenceResult, namespaceMetadata, (type, fullName) => rootNode.WithExpression(type.CreateTypeSyntax(false, fullName).WithTriviaFrom(rootNode.Expression))), childNode => RunReferenceTransformers(childNode, funcResult, funReferenceResult, namespaceMetadata) ); } if (statement != null && !statement.IsKind(SyntaxKind.LocalFunctionStatement)) { // Skip adding return statement for arrow functions if (bodyFuncReferenceResult.UseAsReturnValue && !statementInParentFunction) { newNode = ((StatementSyntax)newNode).ToReturnStatement(); } node = node .ReplaceNode(statement, newNode); } else { node = (T)newNode; } } else { // We need to annotate the invocation node because of the AddAwait method as it needs the parent node var invokeAnnotation = Guid.NewGuid().ToString(); if (isAccessor) { node = ConvertAccessor(node, nameNode, newNameNode, cancellationTokenParamName, bodyFuncReferenceResult, invNode => UpdateTypeAndRunReferenceTransformers(invNode, funcResult, funReferenceResult, namespaceMetadata, (memberNode, type, fullName) => memberNode.WithExpression(type.CreateTypeSyntax(true, fullName).WithTriviaFrom(memberNode.Expression))) .WithAdditionalAnnotations(new SyntaxAnnotation(invokeAnnotation)) ); } else { node = node.ReplaceNestedNodes( invokeNode, nameNode, newNameNode, rootNode => UpdateTypeAndRunReferenceTransformers(rootNode .AddCancellationTokenArgumentIf(cancellationTokenParamName, bodyFuncReferenceResult), funcResult, funReferenceResult, namespaceMetadata, (memberNode, type, fullName) => memberNode.WithExpression(type.CreateTypeSyntax(true, fullName).WithTriviaFrom(memberNode.Expression))) .WithAdditionalAnnotations(new SyntaxAnnotation(invokeAnnotation)) ); } invokeNode = node.GetAnnotatedNodes(invokeAnnotation).OfType <InvocationExpressionSyntax>().First(); // Check if the invocation has a ?. var conditionalAccessNode = invokeNode.Ancestors() .TakeWhile(o => !(o is StatementSyntax)) .OfType <ConditionalAccessExpressionSyntax>() .FirstOrDefault(o => o.WhenNotNull.Contains(invokeNode)); if (conditionalAccessNode != null) // ?. syntax { // We have to find out which strategy to use, if we have a non assignable expression, we are force to use if statements // otherwise a ternary condition will be used if (!conditionalAccessNode.Parent.IsKind(SyntaxKind.ExpressionStatement) || !invokeNode.Equals(conditionalAccessNode.WhenNotNull)) { node = TransformConditionalAccessToConditionalExpressions(node, nameNode, funReferenceResult, typeMetadata, conditionalAccessNode, invokeNode); } else { node = TransformConditionalAccessToIfStatements(node, nameNode, typeMetadata, conditionalAccessNode, invokeNode); } } else { node = node.ReplaceNode(invokeNode, invokeNode.AddAwait(_configuration.ConfigureAwaitArgument)); } } return(node); }