Example #1
0
 public static SyntaxToken WithCommentsFrom(
     this SyntaxToken token,
     IEnumerable <SyntaxTrivia> leadingTrivia,
     IEnumerable <SyntaxTrivia> trailingTrivia,
     params SyntaxNodeOrToken[] trailingNodesOrTokens)
 => token
 .WithPrependedLeadingTrivia(leadingTrivia)
 .WithTrailingTrivia((
                         token.TrailingTrivia.Concat(SyntaxNodeOrTokenExtensions.GetTrivia(trailingNodesOrTokens).Concat(trailingTrivia))).FilterComments(addElasticMarker: false));
Example #2
0
        private InvocationExpressionSyntax CreateLinqInvocation(
            ForEachStatementSyntax forEachStatement,
            ExpressionSyntax receiverForInvocation,
            IEnumerable <SyntaxTrivia> leadingCommentsTrivia,
            IEnumerable <SyntaxTrivia> trailingCommentsTrivia,
            ExpressionSyntax selectExpression,
            ref int currentExtendedNodeIndex)
        {
            leadingCommentsTrivia = forEachStatement.ForEachKeyword.GetAllTrivia().Concat(leadingCommentsTrivia);

            // Recursively create linq invocations, possibly updating the receiver (Where invocations), to get the inner expression for
            // the lambda body for the linq invocation to be created for this foreach statement. For example:
            //
            // INPUT:
            //   foreach (var n1 in c1)
            //      foreach (var n2 in c2)
            //          if (n1 > n2)
            //              yield return n1 + n2;
            //
            // OUTPUT:
            //   c1.SelectMany(n1 => c2.Where(n2 => n1 > n2).Select(n2 => n1 + n2))
            //
            var hasForEachChild = false;
            var lambdaBody      = CreateLinqInvocationForExtendedNode(selectExpression, ref currentExtendedNodeIndex, ref receiverForInvocation, ref hasForEachChild);
            var lambda          = SyntaxFactory.SimpleLambdaExpression(
                SyntaxFactory.Parameter(
                    forEachStatement.Identifier.WithPrependedLeadingTrivia(
                        SyntaxNodeOrTokenExtensions.GetTrivia(forEachStatement.Type.GetFirstToken())
                        .FilterComments(addElasticMarker: false))),
                lambdaBody)
                                  .WithCommentsFrom(leadingCommentsTrivia, trailingCommentsTrivia,
                                                    forEachStatement.OpenParenToken, forEachStatement.InKeyword, forEachStatement.CloseParenToken);

            // Create Select or SelectMany linq invocation for this foreach statement. For example:
            //
            // INPUT:
            //   foreach (var n1 in c1)
            //      ...
            //
            // OUTPUT:
            //   c1.Select(n1 => ...
            //      OR
            //   c1.SelectMany(n1 => ...
            //
            var invokedMethodName = !hasForEachChild?nameof(Enumerable.Select) : nameof(Enumerable.SelectMany);

            return(SyntaxFactory.InvocationExpression(
                       SyntaxFactory.MemberAccessExpression(
                           SyntaxKind.SimpleMemberAccessExpression,
                           receiverForInvocation.Parenthesize(),
                           SyntaxFactory.IdentifierName(invokedMethodName)),
                       SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(
                                                      SyntaxFactory.Argument(lambda)))));
        }
Example #3
0
 private static FromClauseSyntax CreateFromClause(
     ForEachStatementSyntax forEachStatement,
     IEnumerable <SyntaxTrivia> extraLeadingTrivia,
     IEnumerable <SyntaxTrivia> extraTrailingTrivia)
 => SyntaxFactory.FromClause(
     fromKeyword: SyntaxFactory.Token(SyntaxKind.FromKeyword)
     .WithCommentsFrom(
         forEachStatement.ForEachKeyword.LeadingTrivia,
         forEachStatement.ForEachKeyword.TrailingTrivia,
         forEachStatement.OpenParenToken)
     .KeepCommentsAndAddElasticMarkers(),
     type: forEachStatement.Type.IsVar ? null : forEachStatement.Type,
     identifier: forEachStatement.Type.IsVar ?
     forEachStatement.Identifier.WithPrependedLeadingTrivia(
         SyntaxNodeOrTokenExtensions.GetTrivia(forEachStatement.Type.GetFirstToken())
         .FilterComments(addElasticMarker: false)) :
     forEachStatement.Identifier,
     inKeyword: forEachStatement.InKeyword.KeepCommentsAndAddElasticMarkers(),
     expression: forEachStatement.Expression)
 .WithCommentsFrom(extraLeadingTrivia, extraTrailingTrivia, forEachStatement.CloseParenToken);
Example #4
0
        protected override ForEachInfo <ForEachStatementSyntax, StatementSyntax> CreateForEachInfo(
            ForEachStatementSyntax forEachStatement,
            SemanticModel semanticModel,
            bool convertLocalDeclarations)
        {
            var identifiersBuilder = ArrayBuilder <SyntaxToken> .GetInstance();

            identifiersBuilder.Add(forEachStatement.Identifier);
            var convertingNodesBuilder = ArrayBuilder <ExtendedSyntaxNode> .GetInstance();

            IEnumerable <StatementSyntax> statementsCannotBeConverted = null;
            var trailingTokensBuilder = ArrayBuilder <SyntaxToken> .GetInstance();

            var currentLeadingTokens = ArrayBuilder <SyntaxToken> .GetInstance();

            var current = forEachStatement.Statement;

            // Traverse descentants of the forEachStatement.
            // If a statement traversed can be converted into a query clause,
            //  a. Add it to convertingNodesBuilder.
            //  b. set the current to its nested statement and proceed.
            // Otherwise, set statementsCannotBeConverted and stop processing.
            while (statementsCannotBeConverted == null)
            {
                switch (current.Kind())
                {
                case SyntaxKind.Block:
                    var block = (BlockSyntax)current;
                    // Keep comment trivia from braces to attach them to the qeury created.
                    currentLeadingTokens.Add(block.OpenBraceToken);
                    trailingTokensBuilder.Add(block.CloseBraceToken);
                    var array = block.Statements.ToArray();
                    if (array.Length > 0)
                    {
                        // All except the last one can be local declaration statements like
                        // {
                        //   var a = 0;
                        //   var b = 0;
                        //   if (x != y) <- this is the last one in the block.
                        // We can support it to be a complex foreach or if or whatever. So, set the current to it.
                        //   ...
                        // }
                        for (var i = 0; i < array.Length - 1; i++)
                        {
                            var statement = array[i];
                            if (!(statement is LocalDeclarationStatementSyntax localDeclarationStatement &&
                                  TryProcessLocalDeclarationStatement(localDeclarationStatement)))
                            {
                                // If this one is a local function declaration or has an empty initializer, stop processing.
                                statementsCannotBeConverted = array.Skip(i).ToArray();
                                break;
                            }
                        }

                        // Process the last statement separately.
                        current = array.Last();
                    }
                    else
                    {
                        // Silly case: the block is empty. Stop processing.
                        statementsCannotBeConverted = Enumerable.Empty <StatementSyntax>();
                    }

                    break;

                case SyntaxKind.ForEachStatement:
                    // foreach can always be converted to a from clause.
                    var currentForEachStatement = (ForEachStatementSyntax)current;
                    identifiersBuilder.Add(currentForEachStatement.Identifier);
                    convertingNodesBuilder.Add(new ExtendedSyntaxNode(currentForEachStatement, currentLeadingTokens.ToImmutableAndFree(), Enumerable.Empty <SyntaxToken>()));
                    currentLeadingTokens = ArrayBuilder <SyntaxToken> .GetInstance();

                    // Proceed the loop with the nested statement.
                    current = currentForEachStatement.Statement;
                    break;

                case SyntaxKind.IfStatement:
                    // Prepare conversion of 'if (condition)' into where clauses.
                    // Do not support if-else statements in the conversion.
                    var ifStatement = (IfStatementSyntax)current;
                    if (ifStatement.Else == null)
                    {
                        convertingNodesBuilder.Add(new ExtendedSyntaxNode(
                                                       ifStatement, currentLeadingTokens.ToImmutableAndFree(), Enumerable.Empty <SyntaxToken>()));
                        currentLeadingTokens = ArrayBuilder <SyntaxToken> .GetInstance();

                        // Proceed the loop with the nested statement.
                        current = ifStatement.Statement;
                        break;
                    }
                    else
                    {
                        statementsCannotBeConverted = new[] { current };
                        break;
                    }

                case SyntaxKind.LocalDeclarationStatement:
                    // This is a situation with "var a = something;" is the innermost statements inside the loop.
                    var localDeclaration = (LocalDeclarationStatementSyntax)current;
                    if (TryProcessLocalDeclarationStatement(localDeclaration))
                    {
                        statementsCannotBeConverted = Enumerable.Empty <StatementSyntax>();
                    }
                    else
                    {
                        // As above, if there is an empty initializer, stop processing.
                        statementsCannotBeConverted = new[] { current };
                    }

                    break;

                case SyntaxKind.EmptyStatement:
                    // The innermost statement is an empty statement, stop processing
                    // Example:
                    // foreach(...)
                    // {
                    //    ;<- empty statement
                    // }
                    statementsCannotBeConverted = Enumerable.Empty <StatementSyntax>();
                    break;

                default:
                    // If no specific case found, stop processing.
                    statementsCannotBeConverted = new[] { current };
                    break;
                }
            }

            // Trailing tokens are collected in the reverse order: from external block down to internal ones. Reverse them.
            trailingTokensBuilder.ReverseContents();

            return(new ForEachInfo <ForEachStatementSyntax, StatementSyntax>(
                       forEachStatement,
                       semanticModel,
                       convertingNodesBuilder.ToImmutableAndFree(),
                       identifiersBuilder.ToImmutableAndFree(),
                       statementsCannotBeConverted.ToImmutableArray(),
                       currentLeadingTokens.ToImmutableAndFree(),
                       trailingTokensBuilder.ToImmutableAndFree()));

            // Try to prepare variable declarations to be converted into separate let clauses.
            bool TryProcessLocalDeclarationStatement(LocalDeclarationStatementSyntax localDeclarationStatement)
            {
                if (!convertLocalDeclarations)
                {
                    return(false);
                }

                // Do not support declarations without initialization.
                // int a = 0, b, c = 0;
                if (localDeclarationStatement.Declaration.Variables.All(variable => variable.Initializer != null))
                {
                    var localDeclarationLeadingTrivia = new IEnumerable <SyntaxTrivia>[] {
                        currentLeadingTokens.ToImmutableAndFree().GetTrivia(),
                        localDeclarationStatement.Declaration.Type.GetLeadingTrivia(),
                        localDeclarationStatement.Declaration.Type.GetTrailingTrivia()
                    }.Flatten();
                    currentLeadingTokens = ArrayBuilder <SyntaxToken> .GetInstance();

                    var localDeclarationTrailingTrivia = SyntaxNodeOrTokenExtensions.GetTrivia(localDeclarationStatement.SemicolonToken);
                    var separators = localDeclarationStatement.Declaration.Variables.GetSeparators().ToArray();
                    for (var i = 0; i < localDeclarationStatement.Declaration.Variables.Count; i++)
                    {
                        var variable = localDeclarationStatement.Declaration.Variables[i];
                        convertingNodesBuilder.Add(new ExtendedSyntaxNode(
                                                       variable,
                                                       i == 0 ? localDeclarationLeadingTrivia : separators[i - 1].TrailingTrivia,
                                                       i == localDeclarationStatement.Declaration.Variables.Count - 1
                                ? (IEnumerable <SyntaxTrivia>)localDeclarationTrailingTrivia
                                : separators[i].LeadingTrivia));
                        identifiersBuilder.Add(variable.Identifier);
                    }

                    return(true);
                }

                return(false);
            }
        }
Example #5
0
        protected override bool TryBuildSpecificConverter(
            ForEachInfo <ForEachStatementSyntax, StatementSyntax> forEachInfo,
            SemanticModel semanticModel,
            StatementSyntax statementCannotBeConverted,
            CancellationToken cancellationToken,
            out IConverter <ForEachStatementSyntax, StatementSyntax> converter)
        {
            switch (statementCannotBeConverted.Kind())
            {
            case SyntaxKind.ExpressionStatement:
                var expresisonStatement = (ExpressionStatementSyntax)statementCannotBeConverted;
                var expression          = expresisonStatement.Expression;
                switch (expression.Kind())
                {
                case SyntaxKind.PostIncrementExpression:
                    // Input:
                    // foreach (var x in a)
                    // {
                    //     ...
                    //     c++;
                    // }
                    // Output:
                    // (from x in a ... select x).Count();
                    // Here we put SyntaxFactory.IdentifierName(forEachStatement.Identifier) ('x' in the example)
                    // into the select clause.
                    var postfixUnaryExpression = (PostfixUnaryExpressionSyntax)expression;
                    var operand = postfixUnaryExpression.Operand;
                    converter = new ToCountConverter(
                        forEachInfo,
                        selectExpression: SyntaxFactory.IdentifierName(forEachInfo.ForEachStatement.Identifier),
                        modifyingExpression: operand,
                        trivia: SyntaxNodeOrTokenExtensions.GetTrivia(
                            operand, postfixUnaryExpression.OperatorToken, expresisonStatement.SemicolonToken));
                    return(true);

                case SyntaxKind.InvocationExpression:
                    var invocationExpression = (InvocationExpressionSyntax)expression;
                    // Check that there is 'list.Add(item)'.
                    if (invocationExpression.Expression is MemberAccessExpressionSyntax memberAccessExpression &&
                        semanticModel.GetSymbolInfo(memberAccessExpression, cancellationToken).Symbol is IMethodSymbol methodSymbol &&
                        TypeSymbolOptIsList(methodSymbol.ContainingType, semanticModel) &&
                        methodSymbol.Name == nameof(IList.Add) &&
                        methodSymbol.Parameters.Length == 1 &&
                        invocationExpression.ArgumentList.Arguments.Count == 1)
                    {
                        // Input:
                        // foreach (var x in a)
                        // {
                        //     ...
                        //     list.Add(...);
                        // }
                        // Output:
                        // (from x in a ... select x).ToList();
                        var selectExpression = invocationExpression.ArgumentList.Arguments.Single().Expression;
                        converter = new ToToListConverter(
                            forEachInfo,
                            selectExpression,
                            modifyingExpression: memberAccessExpression.Expression,
                            trivia: SyntaxNodeOrTokenExtensions.GetTrivia(
                                memberAccessExpression,
                                invocationExpression.ArgumentList.OpenParenToken,
                                invocationExpression.ArgumentList.CloseParenToken,
                                expresisonStatement.SemicolonToken));
                        return(true);
                    }

                    break;
                }

                break;

            case SyntaxKind.YieldReturnStatement:
                var memberDeclarationSymbol = semanticModel.GetEnclosingSymbol(
                    forEachInfo.ForEachStatement.SpanStart, cancellationToken);

                // Using Single() is valid even for partial methods.
                var memberDeclarationSyntax = memberDeclarationSymbol.DeclaringSyntaxReferences.Single().GetSyntax();

                var yieldStatementsCount = memberDeclarationSyntax.DescendantNodes().OfType <YieldStatementSyntax>()
                                           // Exclude yield statements from nested local functions.
                                           .Where(statement => semanticModel.GetEnclosingSymbol(
                                                      statement.SpanStart, cancellationToken) == memberDeclarationSymbol).Count();

                if (forEachInfo.ForEachStatement.IsParentKind(SyntaxKind.Block) &&
                    forEachInfo.ForEachStatement.Parent.Parent == memberDeclarationSyntax)
                {
                    // Check that
                    // a. There are either just a single 'yield return' or 'yield return' with 'yield break' just after.
                    // b. Those foreach and 'yield break' (if exists) are last statements in the method (do not count local function declaration statements).
                    var statementsOnBlockWithForEach = ((BlockSyntax)forEachInfo.ForEachStatement.Parent).Statements
                                                       .Where(statement => statement.Kind() != SyntaxKind.LocalFunctionStatement).ToArray();
                    var lastNonLocalFunctionStatement = statementsOnBlockWithForEach.Last();
                    if (yieldStatementsCount == 1 && lastNonLocalFunctionStatement == forEachInfo.ForEachStatement)
                    {
                        converter = new YieldReturnConverter(
                            forEachInfo,
                            (YieldStatementSyntax)statementCannotBeConverted,
                            yieldBreakStatement: null);
                        return(true);
                    }

                    // foreach()
                    // {
                    //    yield return ...;
                    // }
                    // yield break;
                    // end of member
                    if (yieldStatementsCount == 2 &&
                        lastNonLocalFunctionStatement.Kind() == SyntaxKind.YieldBreakStatement &&
                        !lastNonLocalFunctionStatement.ContainsDirectives &&
                        statementsOnBlockWithForEach[statementsOnBlockWithForEach.Length - 2] == forEachInfo.ForEachStatement)
                    {
                        // This removes the yield break.
                        converter = new YieldReturnConverter(
                            forEachInfo,
                            (YieldStatementSyntax)statementCannotBeConverted,
                            yieldBreakStatement: (YieldStatementSyntax)lastNonLocalFunctionStatement);
                        return(true);
                    }
                }

                break;
            }

            converter = default;
            return(false);
        }
Example #6
0
 public static SyntaxTrivia[] GetTrivia(this IEnumerable <SyntaxToken> tokens)
 => tokens.SelectMany(token => SyntaxNodeOrTokenExtensions.GetTrivia(token)).ToArray();
Example #7
0
 public static IEnumerable <SyntaxNodeOrToken> DepthFirstTraversal(this SyntaxNode node)
 {
     return(SyntaxNodeOrTokenExtensions.DepthFirstTraversal(node));
 }
Example #8
0
        private ExpressionSyntax CreateLinqInvocationOrSimpleExpression(
            ForEachStatementSyntax forEachStatement,
            ExpressionSyntax receiverForInvocation,
            IEnumerable <SyntaxTrivia> leadingCommentsTrivia,
            IEnumerable <SyntaxTrivia> trailingCommentsTrivia,
            ExpressionSyntax selectExpression,
            ref int currentExtendedNodeIndex)
        {
            leadingCommentsTrivia = forEachStatement.ForEachKeyword.GetAllTrivia().Concat(leadingCommentsTrivia);

            // Recursively create linq invocations, possibly updating the receiver (Where invocations), to get the inner expression for
            // the lambda body for the linq invocation to be created for this foreach statement. For example:
            //
            // INPUT:
            //   foreach (var n1 in c1)
            //      foreach (var n2 in c2)
            //          if (n1 > n2)
            //              yield return n1 + n2;
            //
            // OUTPUT:
            //   c1.SelectMany(n1 => c2.Where(n2 => n1 > n2).Select(n2 => n1 + n2))
            //
            var hasForEachChild = false;
            var lambdaBody      = CreateLinqInvocationForExtendedNode(selectExpression, ref currentExtendedNodeIndex, ref receiverForInvocation, ref hasForEachChild);
            var lambda          = SyntaxFactory.SimpleLambdaExpression(
                SyntaxFactory.Parameter(
                    forEachStatement.Identifier.WithPrependedLeadingTrivia(
                        SyntaxNodeOrTokenExtensions.GetTrivia(forEachStatement.Type.GetFirstToken())
                        .FilterComments(addElasticMarker: false))),
                lambdaBody)
                                  .WithCommentsFrom(leadingCommentsTrivia, trailingCommentsTrivia,
                                                    forEachStatement.OpenParenToken, forEachStatement.InKeyword, forEachStatement.CloseParenToken);

            // Create Select or SelectMany linq invocation for this foreach statement. For example:
            //
            // INPUT:
            //   foreach (var n1 in c1)
            //      ...
            //
            // OUTPUT:
            //   c1.Select(n1 => ...
            //      OR
            //   c1.SelectMany(n1 => ...
            //

            var invokedMethodName = !hasForEachChild?nameof(Enumerable.Select) : nameof(Enumerable.SelectMany);

            // Avoid `.Select(x => x)`
            if (invokedMethodName == nameof(Enumerable.Select) &&
                lambdaBody is IdentifierNameSyntax identifier &&
                identifier.Identifier.ValueText == forEachStatement.Identifier.ValueText)
            {
                // Because we're dropping the lambda, any comments associated with it need to be preserved.

                var droppedTrivia = new List <SyntaxTrivia>();
                foreach (var token in lambda.DescendantTokens())
                {
                    droppedTrivia.AddRange(token.GetAllTrivia().Where(t => !t.IsWhitespace()));
                }

                return(receiverForInvocation.WithAppendedTrailingTrivia(droppedTrivia));
            }

            return(SyntaxFactory.InvocationExpression(
                       SyntaxFactory.MemberAccessExpression(
                           SyntaxKind.SimpleMemberAccessExpression,
                           receiverForInvocation.Parenthesize(),
                           SyntaxFactory.IdentifierName(invokedMethodName)),
                       SyntaxFactory.ArgumentList(SyntaxFactory.SingletonSeparatedList(
                                                      SyntaxFactory.Argument(lambda)))));
        }