private SyntaxNode Update(SyntaxNode functionNode, IFunctionAnalyzationResult methodResult, INamespaceTransformationMetadata namespaceMetadata) { if (functionNode is LocalFunctionStatementSyntax localFunction) { return(Update(localFunction, methodResult, namespaceMetadata)); } return(functionNode); }
private LocalFunctionStatementSyntax Update(LocalFunctionStatementSyntax functionNode, IFunctionAnalyzationResult analyzeResult, INamespaceTransformationMetadata namespaceMetadata) { functionNode = functionNode.WithIdentifier(Identifier(analyzeResult.AsyncCounterpartName)); if (!analyzeResult.PreserveReturnType && analyzeResult.Symbol.MethodKind != MethodKind.PropertySet) { functionNode = functionNode.ReturnAsTask(namespaceMetadata.TaskConflict); } return(functionNode); }
private T RunReferenceTransformers <T>(T node, IFunctionAnalyzationResult funcResult, IFunctionReferenceAnalyzationResult funcReferenceResult, INamespaceTransformationMetadata namespaceMetadata) where T : SyntaxNode { foreach (var transformer in _configuration.FunctionReferenceTransformers) { node = (T)transformer.TransformFunctionReference(node, funcResult, funcReferenceResult, namespaceMetadata) ?? node; } return(node); }
public SyntaxNode TransformFunctionReference(SyntaxNode node, IFunctionAnalyzationResult funcResult, IFunctionReferenceAnalyzationResult funcReferenceResult, INamespaceTransformationMetadata namespaceMetadata) { if (funcReferenceResult is IBodyFunctionReferenceAnalyzationResult bodyFunctionReference && (bodyFunctionReference.PassCancellationToken || funcResult.GetMethodOrAccessor().AddCancellationTokenGuards)) { // Mark the invocation node in order to add the OperationCanceledException catch block only if there is at least one async invocation // with a cancellation token passed as an argument or there will be a cancellation token guard added return(node.WithAdditionalAnnotations(new SyntaxAnnotation(Annotations.AsyncCallWithTokenOrGuard))); } return(null); }
private T UpdateTypeAndRunReferenceTransformers <T>(T node, IFunctionAnalyzationResult funcResult, IFunctionReferenceAnalyzationResult funcReferenceResult, INamespaceTransformationMetadata namespaceMetadata, Func <INamedTypeSymbol, bool, T> updateTypeFunc) where T : SyntaxNode { // If the async counterpart is from another type e.g. Thread.Sleep -> Task.Delay, we need to change also the type if (!funcReferenceResult.AsyncCounterpartSymbol.IsExtensionMethod && !funcReferenceResult.AsyncCounterpartSymbol.OriginalDefinition.ContainingType.Equals( funcReferenceResult.ReferenceSymbol.OriginalDefinition.ContainingType)) { var type = funcReferenceResult.AsyncCounterpartSymbol.ContainingType; node = updateTypeFunc(type, namespaceMetadata.AnalyzationResult.IsIncluded(type.ContainingNamespace?.ToString())); } return(RunReferenceTransformers(node, funcResult, funcReferenceResult, namespaceMetadata)); }
private StatementSyntax GetSyncGuard(IFunctionAnalyzationResult methodResult, string parameterName, SyntaxTrivia leadingWhitespace, SyntaxTrivia endOfLine, SyntaxTrivia indent) { return(IfStatement( MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, IdentifierName(parameterName), IdentifierName("IsCancellationRequested")), Block( SingletonList <StatementSyntax>( ReturnStatement( InvocationExpression( MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, IdentifierName("Task"), GenericName(Identifier("FromCanceled")) .WithTypeArgumentList( TypeArgumentList( SingletonSeparatedList( methodResult.Symbol.ReturnsVoid ? PredefinedType(Token(SyntaxKind.ObjectKeyword)) : methodResult.GetNode().GetReturnType().WithoutTrivia()) )))) .WithArgumentList( ArgumentList( SingletonSeparatedList( Argument(IdentifierName(parameterName)))))) .WithReturnKeyword( Token(TriviaList(Whitespace(leadingWhitespace.ToFullString() + indent.ToFullString())), SyntaxKind.ReturnKeyword, TriviaList(Space))) .WithSemicolonToken( Token(TriviaList(), SyntaxKind.SemicolonToken, TriviaList(endOfLine))))) .WithOpenBraceToken( Token(TriviaList(leadingWhitespace), SyntaxKind.OpenBraceToken, TriviaList(endOfLine))) .WithCloseBraceToken( Token(TriviaList(leadingWhitespace), SyntaxKind.CloseBraceToken, TriviaList(endOfLine)))) .WithIfKeyword( Token(TriviaList(leadingWhitespace), SyntaxKind.IfKeyword, TriviaList(Space))) .WithCloseParenToken( Token(TriviaList(), SyntaxKind.CloseParenToken, TriviaList(endOfLine)))); }
public FunctionTransformationResult(IFunctionAnalyzationResult result) : base(result.GetNode()) { AnalyzationResult = result; }
public RootFunctionTransformationResult(IFunctionAnalyzationResult result) : base(result) { }
public SyntaxNode TransformFunctionReference(SyntaxNode node, IFunctionAnalyzationResult funcResult, IFunctionReferenceAnalyzationResult funcReferenceResult, INamespaceTransformationMetadata namespaceMetadata) { if (!funcReferenceResult.AsyncCounterpartSymbol.Equals(_whenAllMethod)) { return(node); } if (!(node is InvocationExpressionSyntax invokeNode) || !(funcReferenceResult is IBodyFunctionReferenceAnalyzationResult bodyReference)) { return(node); // Cref } // Here are some examples of expected nodes // Task.WhenAll(Results, ReadAsync) // Task.WhenAll(1, 100, ReadAsync) // Task.WhenAll(Enumerable.Empty<string>(), ReadAsync) // Task.WhenAll(GetStringList(), i => // { // return SimpleFile.ReadAsync(); // }) // For Parallel.ForEach, we need to combine the two arguments into one, using the Select Linq extension e.g. // Task.WhenAll(Results.Select(i => ReadAsync(i)) // For Parallel.For, we need to move the first two parameters into Enumerable.Range and then apply the same logic as for // Parallel.ForEach var actionParam = bodyReference.ReferenceSymbol.Parameters.Last(); var actionType = actionParam.Type as INamedTypeSymbol; var actionMethod = actionType?.DelegateInvokeMethod; if (actionMethod == null) { throw new InvalidOperationException( $"Unable to transform Parallel.{bodyReference.ReferenceSymbol.Name} to Task.WaitAll. " + $"The second Parallel.{bodyReference.ReferenceSymbol.Name} argument is not a delegate, but is {actionParam.Type}"); } namespaceMetadata.AddUsing("System.Linq"); var newExpression = invokeNode.ArgumentList.Arguments.Last().Expression; if (!(newExpression is AnonymousFunctionExpressionSyntax)) { var delArgument = bodyReference.DelegateArguments.Last(); var cancellationTokenParamName = funcResult.GetMethodOrAccessor().CancellationTokenRequired ? "cancellationToken" : null; // TODO: find a way to not have this duplicated and fix naming colision newExpression = newExpression.WrapInsideFunction(actionMethod, false, namespaceMetadata.TaskConflict, invoke => invoke.AddCancellationTokenArgumentIf(cancellationTokenParamName, delArgument.BodyFunctionReference)); } ExpressionSyntax enumerableExpression; if (bodyReference.ReferenceSymbol.Equals(_forMethod)) { // Construct an Enumerable.Range(1, 10 - 1), where 1 and 10 are the first two arguments of Parallel.For method var startArg = invokeNode.ArgumentList.Arguments.First(); enumerableExpression = InvocationExpression( MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, IdentifierName("Enumerable").WithLeadingTrivia(startArg.GetLeadingTrivia()), Token(SyntaxKind.DotToken), IdentifierName("Range")), ArgumentList( SeparatedList <ArgumentSyntax>( new SyntaxNodeOrToken[] { startArg.WithoutTrivia(), Token(TriviaList(), SyntaxKind.CommaToken, TriviaList(Space)), Argument( BinaryExpression(SyntaxKind.SubtractExpression, invokeNode.ArgumentList.Arguments.Skip(1).First().Expression.WithoutTrivia().WithTrailingTrivia(Space), Token(TriviaList(), SyntaxKind.MinusToken, TriviaList(Space)), startArg.WithoutTrivia().Expression)) } ) ) ); } else { enumerableExpression = invokeNode.ArgumentList.Arguments.First().Expression; // For ForEach take the first parmeter e.g. Enumerable.Range(1, 10) } var memberAccess = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, enumerableExpression, Token(SyntaxKind.DotToken), IdentifierName("Select")); var argument = InvocationExpression(memberAccess) .WithArgumentList( ArgumentList(SingletonSeparatedList(Argument(newExpression.WithoutTrivia()))) .WithCloseParenToken(Token(TriviaList(), SyntaxKind.CloseParenToken, newExpression.GetTrailingTrivia())) ); return(invokeNode.WithArgumentList( invokeNode.ArgumentList.WithArguments(SingletonSeparatedList(Argument(argument))))); }
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 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)) { if (bodyFuncReferenceResult.UseAsReturnValue) { 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(); var conditionalAccessNode = invokeNode.Ancestors() .TakeWhile(o => !(o is StatementSyntax)) .OfType <ConditionalAccessExpressionSyntax>() .FirstOrDefault(); if (conditionalAccessNode != null) // ?. syntax { var statement = (StatementSyntax)invokeNode.Ancestors().FirstOrDefault(o => o is StatementSyntax); var block = statement?.Parent as BlockSyntax; if (statement == null || block == null) { // TODO: convert arrow method/property/function to a normal one // TODO: convert to block if there is no block node = node.ReplaceNode(conditionalAccessNode, conditionalAccessNode.AddAwait(_configuration.ConfigureAwaitArgument)); } else { var fnName = nameNode.Identifier.ValueText; // TODO: handle name collisions var variableName = $"{char.ToLowerInvariant(fnName[0])}{fnName.Substring(1)}Task"; var leadingTrivia = statement.GetLeadingTrivia(); var newConditionalAccessNode = ConditionalAccessExpression( conditionalAccessNode.Expression, invokeNode) .WithTriviaFrom(conditionalAccessNode); var localVar = LocalDeclarationStatement( VariableDeclaration( IdentifierName(Identifier(leadingTrivia, "var", TriviaList(Space))), SingletonSeparatedList( VariableDeclarator( Identifier(TriviaList(), variableName, TriviaList(Space))) .WithInitializer( EqualsValueClause(newConditionalAccessNode.WithoutTrivia()) .WithEqualsToken(Token(TriviaList(), SyntaxKind.EqualsToken, TriviaList(Space))) ) ))) .WithSemicolonToken(Token(TriviaList(), SyntaxKind.SemicolonToken, TriviaList(typeMetadata.EndOfLineTrivia))); var index = block.Statements.IndexOf(statement); var lastReturnNode = block.DescendantNodes() .Where(o => o.SpanStart >= statement.SpanStart) .OfType <ReturnStatementSyntax>() .LastOrDefault(); var variableAnnotation = Guid.NewGuid().ToString(); var newBlock = block.ReplaceNode(conditionalAccessNode, conditionalAccessNode.WhenNotNull.ReplaceNode(invokeNode, IdentifierName(variableName) .WithAdditionalAnnotations(new SyntaxAnnotation(variableAnnotation)) .WithLeadingTrivia(conditionalAccessNode.GetLeadingTrivia()) .WithTrailingTrivia(conditionalAccessNode.GetTrailingTrivia()) )); var variable = newBlock.GetAnnotatedNodes(variableAnnotation).OfType <IdentifierNameSyntax>().First(); newBlock = newBlock.ReplaceNode(variable, variable.AddAwait(_configuration.ConfigureAwaitArgument)); var ifBlock = Block() .WithOpenBraceToken( Token(TriviaList(leadingTrivia), SyntaxKind.OpenBraceToken, TriviaList(typeMetadata.EndOfLineTrivia))) .WithCloseBraceToken( Token(TriviaList(leadingTrivia), SyntaxKind.CloseBraceToken, TriviaList(typeMetadata.EndOfLineTrivia))) .WithStatements(new SyntaxList <StatementSyntax>() .AddRange(newBlock.AppendIndent(typeMetadata.IndentTrivia.ToFullString()).Statements.Skip(index))); var ifStatement = IfStatement( BinaryExpression( SyntaxKind.NotEqualsExpression, IdentifierName(Identifier(TriviaList(), variableName, TriviaList(Space))), LiteralExpression(SyntaxKind.NullLiteralExpression)) .WithOperatorToken( Token(TriviaList(), SyntaxKind.ExclamationEqualsToken, TriviaList(Space))), ifBlock ) .WithIfKeyword( Token(TriviaList(leadingTrivia), SyntaxKind.IfKeyword, TriviaList(Space))) .WithCloseParenToken( Token(TriviaList(), SyntaxKind.CloseParenToken, TriviaList(typeMetadata.EndOfLineTrivia))); var statements = new SyntaxList <StatementSyntax>() .AddRange(newBlock.Statements.Take(index)) .Add(localVar) .Add(ifStatement); if (lastReturnNode?.Expression != null) { // Check if the variable is defined otherwise return default return type value if (lastReturnNode.Expression is IdentifierNameSyntax idNode && statements.OfType <VariableDeclaratorSyntax>().All(o => o.Identifier.ToString() != idNode.Identifier.ValueText)) { lastReturnNode = lastReturnNode.WithExpression(DefaultExpression(funcResult.GetNode().GetReturnType().WithoutTrivia())); } statements = statements.Add(lastReturnNode); } node = node.ReplaceNode(block, newBlock.WithStatements(statements)); } } else { node = node.ReplaceNode(invokeNode, invokeNode.AddAwait(_configuration.ConfigureAwaitArgument)); } } return(node); }
private BlockSyntax AddGuards(IFunctionTransformationResult transformResult, BlockSyntax methodBody, IFunctionAnalyzationResult methodResult, string cancellationTokenParamName) { if (!_configuration.Guards || methodBody == null || methodResult.Faulted) { return(methodBody); } // Avoid duplicate guards if the first statement also needs a guard var afterGuardIndex = methodBody.Statements.Count > methodResult.Preconditions.Count ? (int?)methodResult.Preconditions.Count : null; int?afterGuardStatementSpan = null; if (afterGuardIndex.HasValue) { // We have to update the methodBody node in order to have correct spans var afterGuardStatement = methodBody.Statements[afterGuardIndex.Value]; methodBody = methodBody.ReplaceNode(afterGuardStatement, afterGuardStatement.WithAdditionalAnnotations(new SyntaxAnnotation("AfterGuardStatement"))); afterGuardStatementSpan = methodBody.Statements[afterGuardIndex.Value].SpanStart; } // We need to get all statements that have at least one async invocation without a cancellation token argument, to prepend an extra guard var statements = new Dictionary <int, string>(); foreach (var functionReference in transformResult.TransformedFunctionReferences) { if (!(functionReference.AnalyzationResult is IBodyFunctionReferenceAnalyzationResult bodyFunctionReference)) { continue; } if (bodyFunctionReference.GetConversion() != ReferenceConversion.ToAsync || bodyFunctionReference.PassCancellationToken) { continue; } var statement = methodBody .GetAnnotatedNodes(functionReference.Annotation) .First().Ancestors().OfType <StatementSyntax>().First(); if (statements.ContainsKey(statement.SpanStart) || (afterGuardStatementSpan.HasValue && afterGuardStatementSpan.Value == statement.SpanStart)) { continue; } var annotation = Guid.NewGuid().ToString(); methodBody = methodBody .ReplaceNode(statement, statement.WithAdditionalAnnotations(new SyntaxAnnotation(annotation))); statements.Add(statement.SpanStart, annotation); } var startGuard = methodResult.OmitAsync ? GetSyncGuard(methodResult, cancellationTokenParamName, transformResult.BodyLeadingWhitespaceTrivia, transformResult.EndOfLineTrivia, transformResult.IndentTrivia) : GetAsyncGuard(cancellationTokenParamName, transformResult.BodyLeadingWhitespaceTrivia, transformResult.EndOfLineTrivia); methodBody = methodBody.WithStatements(methodBody.Statements.Insert(methodResult.Preconditions.Count, startGuard)); // For each statement we need to find the index where is located in the block. // TODO: Add support when the parent is not a block syntax foreach (var pair in statements) { var statement = methodBody.GetAnnotatedNodes(pair.Value).OfType <StatementSyntax>().First(); var parentBlock = statement.Parent as BlockSyntax; if (parentBlock == null) { continue; // Currently not supported } var index = parentBlock.Statements.IndexOf(statement); var newParentBlock = parentBlock .WithStatements(parentBlock.Statements .Insert(index, GetAsyncGuard(cancellationTokenParamName, statement.GetLeadingWhitespace(), transformResult.EndOfLineTrivia))); methodBody = methodBody .ReplaceNode(parentBlock, newParentBlock); } return(methodBody); }
private static IEnumerable <IFunctionAnalyzationResult> GetSelfAndDescendantsFunctionsRecursively(IFunctionAnalyzationResult fucData, Func <IFunctionAnalyzationResult, bool> predicate = null) { if (predicate?.Invoke(fucData) == false) { yield break; } yield return(fucData); foreach (var subFuncData in fucData.ChildFunctions) { if (predicate?.Invoke(subFuncData) == false) { continue; } foreach (var td in GetSelfAndDescendantsFunctionsRecursively(subFuncData, predicate)) { yield return(td); } } }
public static IEnumerable <IFunctionAnalyzationResult> GetSelfAndDescendantsFunctions(this IFunctionAnalyzationResult funcResult, Func <IFunctionAnalyzationResult, bool> predicate = null) { return(GetSelfAndDescendantsFunctionsRecursively(funcResult, predicate)); }
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); }
private InvocationExpressionSyntax UpdateTypeAndRunReferenceTransformers(InvocationExpressionSyntax node, IFunctionAnalyzationResult funcResult, IFunctionReferenceAnalyzationResult funcReferenceResult, INamespaceTransformationMetadata namespaceMetadata, Func <MemberAccessExpressionSyntax, INamedTypeSymbol, bool, MemberAccessExpressionSyntax> updateTypeFunc) { // If the async counterpart is from another type e.g. Thread.Sleep -> Task.Delay, we need to change also the type if (!funcReferenceResult.AsyncCounterpartSymbol.IsExtensionMethod && !funcReferenceResult.AsyncCounterpartSymbol.OriginalDefinition.ContainingType.Equals( funcReferenceResult.ReferenceSymbol.OriginalDefinition.ContainingType) && node.Expression is MemberAccessExpressionSyntax memberAccess) { var type = funcReferenceResult.AsyncCounterpartSymbol.ContainingType; node = node.WithExpression(updateTypeFunc(memberAccess, type, namespaceMetadata.AnalyzationResult.IsIncluded(type.ContainingNamespace?.ToString()))); } return(RunReferenceTransformers(node, funcResult, funcReferenceResult, namespaceMetadata)); }
public ReturnTaskFunctionRewriter(IFunctionTransformationResult transformResult, INamespaceTransformationMetadata namespaceMetadata) { _transformResult = transformResult; _methodResult = transformResult.AnalyzationResult; _namespaceMetadata = namespaceMetadata; }
private RootFunctionTransformationResult TransformFunction(IFunctionAnalyzationResult rootFuncResult, ITransformationTrivia parentTransformTrivia, ITypeTransformationMetadata typeMetadata, INamespaceTransformationMetadata namespaceMetadata) { var rootFuncNode = rootFuncResult.GetNode(); var startRootFuncSpan = rootFuncNode.SpanStart; var rootTransformResult = new RootFunctionTransformationResult(rootFuncResult) { }; // We do this here because we want that the root node has span start equal to 0 rootFuncNode = rootFuncNode.WithAdditionalAnnotations(new SyntaxAnnotation(rootTransformResult.Annotation)); startRootFuncSpan -= rootFuncNode.SpanStart; // Before any modification we need to annotate nodes that will be transformed in order to find them later on. foreach (var funcResult in rootFuncResult.GetSelfAndDescendantsFunctions()) { var origFuncNode = funcResult.GetNode(); var funcSpanStart = origFuncNode.SpanStart - startRootFuncSpan; var funcSpanLength = origFuncNode.Span.Length; var funcNode = rootFuncNode.DescendantNodesAndSelf() .Where(o => !o.IsKind(SyntaxKind.Argument)) // An argument can have the same span as the function .First(o => o.SpanStart == funcSpanStart && o.Span.Length == funcSpanLength); FunctionTransformationResult transformResult; if (funcNode == rootFuncNode) { transformResult = rootTransformResult; transformResult.IndentTrivia = parentTransformTrivia.IndentTrivia; transformResult.EndOfLineTrivia = parentTransformTrivia.EndOfLineTrivia; transformResult.LeadingWhitespaceTrivia = Whitespace(parentTransformTrivia.LeadingWhitespaceTrivia.ToFullString() + parentTransformTrivia.IndentTrivia.ToFullString()); transformResult.BodyLeadingWhitespaceTrivia = Whitespace(transformResult.LeadingWhitespaceTrivia.ToFullString() + parentTransformTrivia.IndentTrivia.ToFullString()); } else { transformResult = new FunctionTransformationResult(funcResult); rootFuncNode = rootFuncNode.ReplaceNode(funcNode, funcNode.WithAdditionalAnnotations(new SyntaxAnnotation(transformResult.Annotation))); rootTransformResult.DescendantTransformedFunctions.Add(transformResult); // TODO: calculate trivias } if (funcResult.Conversion == MethodConversion.Ignore) { continue; // Ignored functions shall be only annotated } foreach (var typeReference in funcResult.TypeReferences.Where(o => o.TypeAnalyzationResult.Conversion == TypeConversion.NewType)) { var reference = typeReference.ReferenceLocation; var refSpanStart = reference.Location.SourceSpan.Start - startRootFuncSpan; var refSpanLength = reference.Location.SourceSpan.Length; var nameNode = rootFuncNode.GetSimpleName(refSpanStart, refSpanLength, typeReference.IsCref); var transformedNode = new TransformationResult(nameNode) { Transformed = nameNode.WithIdentifier(Identifier(nameNode.Identifier.ValueText + "Async").WithTriviaFrom(nameNode.Identifier)) }; transformResult.TransformedNodes.Add(transformedNode); rootFuncNode = rootFuncNode.ReplaceNode(nameNode, nameNode.WithAdditionalAnnotations(new SyntaxAnnotation(transformedNode.Annotation))); } // TODO: unify with method in order to avoid duplicate code foreach (var referenceResult in funcResult.FunctionReferences.Where(o => o.GetConversion() == ReferenceConversion.ToAsync)) { var transfromReference = new FunctionReferenceTransformationResult(referenceResult); var reference = referenceResult.ReferenceLocation; var startSpan = reference.Location.SourceSpan.Start - startRootFuncSpan; var nameNode = rootFuncNode.GetSimpleName(startSpan, reference.Location.SourceSpan.Length); rootFuncNode = rootFuncNode.ReplaceNode(nameNode, nameNode.WithAdditionalAnnotations(new SyntaxAnnotation(transfromReference.Annotation))); transformResult.TransformedFunctionReferences.Add(transfromReference); var bodyRef = referenceResult as IBodyFunctionReferenceAnalyzationResult; if (!funcResult.OmitAsync || bodyRef == null) { continue; } // We need to annotate the reference node (InvocationExpression, IdentifierName) in order to know if we need to wrap the node in a Task.FromResult var refNode = referenceResult.ReferenceNode; if (bodyRef.UseAsReturnValue || refNode.IsReturned()) { startSpan = refNode.SpanStart - startRootFuncSpan; var referenceNode = rootFuncNode.DescendantNodes().First(o => o.SpanStart == startSpan && o.Span.Length == refNode.Span.Length); rootFuncNode = rootFuncNode.ReplaceNode(referenceNode, referenceNode.WithAdditionalAnnotations(new SyntaxAnnotation(Annotations.TaskReturned))); } } } foreach (var transformResult in rootTransformResult.GetSelfAndDescendantTransformedFunctions().OrderByDescending(o => o.OriginalStartSpan)) { // We have to order by OriginalStartSpan in order to have consistent formatting when adding awaits foreach (var transfromReference in transformResult.TransformedFunctionReferences.OrderByDescending(o => o.OriginalStartSpan)) { rootFuncNode = TransformFunctionReference(rootFuncNode, rootFuncResult, transfromReference, typeMetadata, namespaceMetadata); } // Replace all rewritten nodes foreach (var rewNode in transformResult.TransformedNodes) { var node = rootFuncNode.GetAnnotatedNodes(rewNode.Annotation).First(); if (rewNode.Transformed == null) { rootFuncNode = rootFuncNode.RemoveNode(node, SyntaxRemoveOptions.KeepUnbalancedDirectives); } else { rootFuncNode = rootFuncNode.ReplaceNode(node, rewNode.Transformed); } } var funcNode = rootFuncNode.GetAnnotatedNodes(transformResult.Annotation).First(); var newFuncNode = funcNode; transformResult.Transformed = newFuncNode; newFuncNode = RunFunctionTransformers(transformResult, typeMetadata, namespaceMetadata); rootFuncNode = rootFuncNode.ReplaceNode(funcNode, newFuncNode); } rootTransformResult.Transformed = rootFuncNode; return(rootTransformResult); }