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 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)); }
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); }
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 T TransformConditionalAccessToConditionalExpressions <T>( T node, SimpleNameSyntax nameNode, IFunctionReferenceAnalyzationResult funReferenceResult, ITypeTransformationMetadata typeMetadata, ConditionalAccessExpressionSyntax conditionalAccessNode, InvocationExpressionSyntax invokeNode) where T : SyntaxNode { // TODO: we should check the async symbol instead var returnType = funReferenceResult.ReferenceSymbol.ReturnType; var type = returnType.CreateTypeSyntax(); var canSkipCast = !returnType.IsValueType && !returnType.IsNullable(); if (returnType.IsValueType && !returnType.IsNullable()) { type = NullableType(type); } ExpressionSyntax whenNotNullNode = null; if (invokeNode.Parent is MemberAccessExpressionSyntax memberAccessParent) { whenNotNullNode = conditionalAccessNode.WhenNotNull .ReplaceNode(memberAccessParent, MemberBindingExpression(Token(SyntaxKind.DotToken), memberAccessParent.Name)); } else if (invokeNode.Parent is ElementAccessExpressionSyntax elementAccessParent) { whenNotNullNode = conditionalAccessNode.WhenNotNull .ReplaceNode(elementAccessParent, ElementBindingExpression(elementAccessParent.ArgumentList)); } var valueNode = conditionalAccessNode.Expression.WithoutTrivia(); StatementSyntax variableStatement = null; BlockSyntax statementBlock = null; var statementIndex = 0; // We have to save the value in a variable when the expression is an invocation, index accessor or property in // order to prevent double calls // TODO: find a more robust solution if (!(conditionalAccessNode.Expression is SimpleNameSyntax simpleName) || char.IsUpper(simpleName.ToString()[0])) { var statement = (StatementSyntax)conditionalAccessNode.Ancestors().FirstOrDefault(o => o is StatementSyntax); if (statement == null || !(statement.Parent is BlockSyntax block)) { // TODO: convert arrow method/property/function to a normal one // TODO: convert to block if there is no block throw new NotSupportedException( $"Arrow method with null-conditional is not supported. Node: {conditionalAccessNode}"); } var leadingTrivia = statement.GetLeadingTrivia(); var fnName = nameNode.Identifier.ValueText; statementIndex = block.Statements.IndexOf(statement); statementBlock = block; // TODO: handle name collisions var variableName = $"{char.ToLowerInvariant(fnName[0])}{fnName.Substring(1)}{statementIndex}"; variableStatement = LocalDeclarationStatement( VariableDeclaration( IdentifierName(Identifier(leadingTrivia, "var", TriviaList(Space))), SingletonSeparatedList( VariableDeclarator( Identifier(TriviaList(), variableName, TriviaList(Space))) .WithInitializer( EqualsValueClause(valueNode) .WithEqualsToken(Token(TriviaList(), SyntaxKind.EqualsToken, TriviaList(Space))) ) ))) .WithSemicolonToken(Token(TriviaList(), SyntaxKind.SemicolonToken, TriviaList(typeMetadata.EndOfLineTrivia))); valueNode = IdentifierName(variableName); } var invocationAnnotation = Guid.NewGuid().ToString(); var nullNode = LiteralExpression( SyntaxKind.NullLiteralExpression, Token(TriviaList(), SyntaxKind.NullKeyword, TriviaList(Space))); var ifNullCondition = BinaryExpression( SyntaxKind.EqualsExpression, valueNode.WithTrailingTrivia(TriviaList(Space)), nullNode) .WithOperatorToken( Token(TriviaList(), SyntaxKind.EqualsEqualsToken, TriviaList(Space))); ExpressionSyntax wrappedNode = ParenthesizedExpression( ConditionalExpression( ifNullCondition, canSkipCast ? (ExpressionSyntax)nullNode : CastExpression( Token(SyntaxKind.OpenParenToken), type, Token(TriviaList(), SyntaxKind.CloseParenToken, TriviaList(Space)), nullNode), InvocationExpression( MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, valueNode, ((MemberBindingExpressionSyntax)invokeNode.Expression).Name)) .WithAdditionalAnnotations(new SyntaxAnnotation(invocationAnnotation)) .WithArgumentList(invokeNode.ArgumentList.WithoutTrailingTrivia()) ) .WithColonToken(Token(TriviaList(), SyntaxKind.ColonToken, TriviaList(Space))) .WithQuestionToken(Token(TriviaList(), SyntaxKind.QuestionToken, TriviaList(Space))) ); if (whenNotNullNode != null) { wrappedNode = conditionalAccessNode .WithExpression(wrappedNode) .WithWhenNotNull(whenNotNullNode); } wrappedNode = wrappedNode.WithTriviaFrom(conditionalAccessNode); invokeNode = (InvocationExpressionSyntax)wrappedNode.GetAnnotatedNodes(invocationAnnotation).First(); wrappedNode = wrappedNode.ReplaceNode(invokeNode, invokeNode.AddAwait(_configuration.ConfigureAwaitArgument)); if (statementBlock != null) { var newBlock = statementBlock.ReplaceNode(conditionalAccessNode, wrappedNode); newBlock = newBlock.WithStatements(newBlock.Statements.Insert(statementIndex, variableStatement)); return(node.ReplaceNode(statementBlock, newBlock)); } return(node.ReplaceNode(conditionalAccessNode, wrappedNode)); }
public FunctionReferenceTransformationResult(IFunctionReferenceAnalyzationResult analyzationResult) : base(analyzationResult.ReferenceNameNode) { AnalyzationResult = analyzationResult; }
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))))); }