File: Wrapping\BinaryExpression\AbstractBinaryExpressionWrapper.cs
Web Access
Project: src\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Indentation;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Precedence;
using Microsoft.CodeAnalysis.Shared.Extensions;
 
#if DEBUG
using System.Diagnostics;
#endif
 
namespace Microsoft.CodeAnalysis.Wrapping.BinaryExpression;
 
internal abstract partial class AbstractBinaryExpressionWrapper<TBinaryExpressionSyntax> : AbstractSyntaxWrapper
    where TBinaryExpressionSyntax : SyntaxNode
{
    private readonly ISyntaxFacts _syntaxFacts;
    private readonly IPrecedenceService _precedenceService;
 
    protected AbstractBinaryExpressionWrapper(
        IIndentationService indentationService,
        ISyntaxFacts syntaxFacts,
        IPrecedenceService precedenceService) : base(indentationService)
    {
        _syntaxFacts = syntaxFacts;
        _precedenceService = precedenceService;
    }
 
    /// <summary>
    /// Get's the language specific trivia that should be inserted before an operator if the
    /// user wants to wrap the operator to the next line.  For C# this is a simple newline-trivia.
    /// For VB, this will be a line-continuation char (<c>_</c>), followed by a newline.
    /// </summary>
    protected abstract SyntaxTriviaList GetNewLineBeforeOperatorTrivia(SyntaxTriviaList newLine);
 
    public sealed override async Task<ICodeActionComputer?> TryCreateComputerAsync(
        Document document, int position, SyntaxNode node, SyntaxWrappingOptions options, bool containsSyntaxError, CancellationToken cancellationToken)
    {
        if (containsSyntaxError)
            return null;
 
        if (node is not TBinaryExpressionSyntax binaryExpr)
            return null;
 
        var precedence = _precedenceService.GetPrecedenceKind(binaryExpr);
        if (precedence == PrecedenceKind.Other)
            return null;
 
        // Don't process this binary expression if it's in a parent binary expr of the same or
        // lower precedence.  We'll just allow our caller to walk up to that and call back into
        // us to handle.  This way, we're always starting at the topmost binary expr of this
        // precedence.
        //
        // for example, if we have `if (a + b == c + d)` expectation is to wrap on the lower
        // precedence `==` op, not either of the `+` ops
        //
        // Note: we use `<=` when comparing precedence because lower precedence has a higher
        // value.
        if (binaryExpr.Parent is TBinaryExpressionSyntax parentBinary &&
            precedence <= _precedenceService.GetPrecedenceKind(parentBinary))
        {
            return null;
        }
 
        var exprsAndOperators = GetExpressionsAndOperators(precedence, binaryExpr);
#if DEBUG
        Debug.Assert(exprsAndOperators.Length >= 3);
        Debug.Assert(exprsAndOperators.Length % 2 == 1, "Should have odd number of exprs and operators");
        for (var i = 0; i < exprsAndOperators.Length; i++)
        {
            var item = exprsAndOperators[i];
            Debug.Assert(((i % 2) == 0 && item.IsNode) ||
                         ((i % 2) == 1 && item.IsToken));
        }
#endif
 
        var containsUnformattableContent = await ContainsUnformattableContentAsync(
            document, exprsAndOperators, cancellationToken).ConfigureAwait(false);
 
        if (containsUnformattableContent)
            return null;
 
        var sourceText = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false);
        return new BinaryExpressionCodeActionComputer(
            this, document, sourceText, options, binaryExpr, exprsAndOperators);
    }
 
    private ImmutableArray<SyntaxNodeOrToken> GetExpressionsAndOperators(
        PrecedenceKind precedence, TBinaryExpressionSyntax binaryExpr)
    {
        using var _ = ArrayBuilder<SyntaxNodeOrToken>.GetInstance(out var result);
        AddExpressionsAndOperators(precedence, binaryExpr, result);
        return result.ToImmutableAndClear();
    }
 
    private void AddExpressionsAndOperators(
        PrecedenceKind precedence, SyntaxNode expr, ArrayBuilder<SyntaxNodeOrToken> result)
    {
        // In-order traverse which visit the left child -> operator in the binary expression -> right child
        using var pooledStack = SharedPools.Default<Stack<SyntaxNodeOrToken>>().GetPooledObject();
        var stack = pooledStack.Object;
        stack.Push(expr);
 
        while (stack.TryPop(out var currentNodeOrToken))
        {
            if (currentNodeOrToken.IsNode && IsValidBinaryExpression(precedence, currentNodeOrToken.AsNode()))
            {
                _syntaxFacts.GetPartsOfBinaryExpression(currentNodeOrToken.AsNode()!, out var left, out var opToken, out var right);
                // We are visiting the tree In-order, so push the node in a reverse order.
                stack.Push(right);
                stack.Push(opToken);
                stack.Push(left);
            }
            else
            {
                result.Add(currentNodeOrToken);
            }
        }
 
        bool IsValidBinaryExpression(PrecedenceKind precedence, SyntaxNode? node)
            => node is TBinaryExpressionSyntax && precedence == _precedenceService.GetPrecedenceKind(node);
    }
}