| File: Wrapping\BinaryExpression\AbstractBinaryExpressionWrapper.cs | Web Access |
| Project: src\roslyn\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); } }