File: src\Workspaces\SharedUtilitiesAndExtensions\Compiler\CSharp\Utilities\FormattingRangeHelper.cs
Web Access
Project: src\src\Workspaces\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Workspaces.csproj (Microsoft.CodeAnalysis.CSharp.Workspaces)
// 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;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Formatting;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.Utilities;
 
/// <summary>
/// this help finding a range of tokens to format based on given ending token
/// </summary>
internal static class FormattingRangeHelper
{
    public static ValueTuple<SyntaxToken, SyntaxToken>? FindAppropriateRange(SyntaxToken endToken, bool useDefaultRange = true)
    {
        Contract.ThrowIfTrue(endToken.Kind() == SyntaxKind.None);
 
        return FixupOpenBrace(FindAppropriateRangeWorker(endToken, useDefaultRange));
    }
 
    private static ValueTuple<SyntaxToken, SyntaxToken>? FixupOpenBrace(ValueTuple<SyntaxToken, SyntaxToken>? tokenRange)
    {
        if (!tokenRange.HasValue)
        {
            return tokenRange;
        }
 
        // with a auto brace completion which will do auto formatting when a user types "{", it is quite common that we will automatically put a space
        // between "{" and "}". but user might blindly type without knowing that " " has automatically inserted for him. and ends up have two spaces.
        // for those cases, whenever we see previous token of the range is "{", we expand the range to include preceding "{"
        var currentToken = tokenRange.Value.Item1;
        var previousToken = currentToken.GetPreviousToken();
 
        while (currentToken.Kind() != SyntaxKind.CloseBraceToken && previousToken.Kind() == SyntaxKind.OpenBraceToken)
        {
            var (_, closeBrace) = previousToken.Parent.GetBracePair();
            if (closeBrace.Kind() == SyntaxKind.None || !AreTwoTokensOnSameLine(previousToken, closeBrace))
            {
                return ValueTuple.Create(currentToken, tokenRange.Value.Item2);
            }
 
            currentToken = previousToken;
            previousToken = currentToken.GetPreviousToken();
        }
 
        return ValueTuple.Create(currentToken, tokenRange.Value.Item2);
    }
 
    private static ValueTuple<SyntaxToken, SyntaxToken>? FindAppropriateRangeWorker(SyntaxToken endToken, bool useDefaultRange)
    {
        // special token that we know how to find proper starting token
        switch (endToken.Kind())
        {
            case SyntaxKind.CloseBraceToken:
                {
                    return FindAppropriateRangeForCloseBrace(endToken);
                }
 
            case SyntaxKind.SemicolonToken:
                {
                    return FindAppropriateRangeForSemicolon(endToken);
                }
 
            case SyntaxKind.ColonToken:
                {
                    return FindAppropriateRangeForColon(endToken);
                }
 
            default:
                {
                    // default case
                    if (!useDefaultRange)
                    {
                        return null;
                    }
 
                    // if given token is skipped token, don't bother to find appropriate
                    // starting point
                    if (endToken.Kind() == SyntaxKind.SkippedTokensTrivia)
                    {
                        return null;
                    }
 
                    var parent = endToken.Parent;
                    if (parent == null)
                    {
                        // if there is no parent setup yet, nothing we can do here.
                        return null;
                    }
 
                    // if we are called due to things in trivia or literals, don't bother
                    // finding a starting token
                    if (parent.Kind() is SyntaxKind.StringLiteralExpression or
                        SyntaxKind.CharacterLiteralExpression)
                    {
                        return null;
                    }
 
                    // format whole node that containing the end token + its previous one
                    // to do indentation
                    return ValueTuple.Create(GetAppropriatePreviousToken(parent.GetFirstToken()), parent.GetLastToken());
                }
        }
    }
 
    private static ValueTuple<SyntaxToken, SyntaxToken>? FindAppropriateRangeForSemicolon(SyntaxToken endToken)
    {
        var parent = endToken.Parent;
        if (parent == null || parent.Kind() == SyntaxKind.SkippedTokensTrivia)
        {
            return null;
        }
 
        if (parent is UsingDirectiveSyntax or
            DelegateDeclarationSyntax or
            FieldDeclarationSyntax or
            EventFieldDeclarationSyntax or
            MethodDeclarationSyntax or
            PropertyDeclarationSyntax or
            ConstructorDeclarationSyntax or
            DestructorDeclarationSyntax or
            OperatorDeclarationSyntax or
            ConversionOperatorDeclarationSyntax)
        {
            return ValueTuple.Create(GetAppropriatePreviousToken(parent.GetFirstToken(), canTokenBeFirstInABlock: true), parent.GetLastToken());
        }
 
        if (parent is AccessorDeclarationSyntax)
        {
            // if both accessors are on the same line, format the accessor list
            // { get; set; }
            if (GetEnclosingMember(endToken) is PropertyDeclarationSyntax propertyDeclaration &&
                AreTwoTokensOnSameLine(propertyDeclaration.AccessorList!.OpenBraceToken, propertyDeclaration.AccessorList.CloseBraceToken))
            {
                return ValueTuple.Create(propertyDeclaration.AccessorList.OpenBraceToken, propertyDeclaration.AccessorList.CloseBraceToken);
            }
 
            // otherwise, just format the accessor
            return ValueTuple.Create(GetAppropriatePreviousToken(parent.GetFirstToken(), canTokenBeFirstInABlock: true), parent.GetLastToken());
        }
 
        if (parent is StatementSyntax && !endToken.IsSemicolonInForStatement())
        {
            var container = GetTopContainingNode(parent);
            if (container == null)
            {
                return ValueTuple.Create(GetAppropriatePreviousToken(parent.GetFirstToken()), parent.GetLastToken());
            }
 
            if (IsSpecialContainingNode(container))
            {
                return ValueTuple.Create(GetAppropriatePreviousToken(container.GetFirstToken()), container.GetLastToken());
            }
 
            return ValueTuple.Create(GetAppropriatePreviousToken(parent.GetFirstToken(), canTokenBeFirstInABlock: true), parent.GetLastToken());
        }
 
        // don't do anything
        return null;
    }
 
    private static ValueTuple<SyntaxToken, SyntaxToken>? FindAppropriateRangeForCloseBrace(SyntaxToken endToken)
    {
        // don't do anything if there is no proper parent
        var parent = endToken.Parent;
        if (parent == null || parent.Kind() == SyntaxKind.SkippedTokensTrivia)
        {
            return null;
        }
 
        // cases such as namespace, type, enum, method almost any top level elements
        if (parent is MemberDeclarationSyntax or SwitchStatementSyntax or SwitchExpressionSyntax)
        {
            return ValueTuple.Create(GetAppropriatePreviousToken(parent.GetFirstToken()), parent.GetLastToken());
        }
 
        // property decl body or initializer
        if (parent is AccessorListSyntax)
        {
            // include property decl
            var containerOfList = parent.Parent;
            if (containerOfList == null)
            {
                return ValueTuple.Create(GetAppropriatePreviousToken(parent.GetFirstToken()), parent.GetLastToken());
            }
 
            return ValueTuple.Create(containerOfList.GetFirstToken(), containerOfList.GetLastToken());
        }
 
        if (parent is AnonymousObjectCreationExpressionSyntax)
        {
            return ValueTuple.Create(parent.GetFirstToken(), parent.GetLastToken());
        }
 
        if (parent is InitializerExpressionSyntax)
        {
            var parentOfParent = parent.Parent;
            if (parentOfParent == null)
            {
                return ValueTuple.Create(GetAppropriatePreviousToken(parent.GetFirstToken()), parent.GetLastToken());
            }
 
            // double initializer case such as
            // { { }
            if (parentOfParent is InitializerExpressionSyntax)
            {
                // if parent block has a missing brace, and current block is on same line, then
                // don't try to indent inner block.
                var firstTokenOfInnerBlock = parent.GetFirstToken();
                var lastTokenOfInnerBlock = parent.GetLastToken();
 
                var twoTokensOnSameLine = AreTwoTokensOnSameLine(firstTokenOfInnerBlock, lastTokenOfInnerBlock);
                if (twoTokensOnSameLine)
                {
                    return ValueTuple.Create(firstTokenOfInnerBlock, lastTokenOfInnerBlock);
                }
            }
 
            // include owner of the initializer node such as creation node
            return ValueTuple.Create(parentOfParent.GetFirstToken(), parentOfParent.GetLastToken());
        }
 
        if (parent is BlockSyntax)
        {
            var containerOfBlock = GetTopContainingNode(parent);
            if (containerOfBlock == null)
            {
                return ValueTuple.Create(GetAppropriatePreviousToken(parent.GetFirstToken()), parent.GetLastToken());
            }
 
            // things like method, constructor, etc and special cases
            if (containerOfBlock is MemberDeclarationSyntax ||
                IsSpecialContainingNode(containerOfBlock))
            {
                return ValueTuple.Create(GetAppropriatePreviousToken(containerOfBlock.GetFirstToken()), containerOfBlock.GetLastToken());
            }
 
            // double block case on single line case
            // { { }
            if (containerOfBlock is BlockSyntax)
            {
                // if parent block has a missing brace, and current block is on same line, then
                // don't try to indent inner block.
                var firstTokenOfInnerBlock = parent.GetFirstToken();
                var lastTokenOfInnerBlock = parent.GetLastToken();
 
                var twoTokensOnSameLine = AreTwoTokensOnSameLine(firstTokenOfInnerBlock, lastTokenOfInnerBlock);
                if (twoTokensOnSameLine)
                {
                    return ValueTuple.Create(firstTokenOfInnerBlock, lastTokenOfInnerBlock);
                }
            }
 
            // okay, for block, indent regardless whether it is first one on the line
            return ValueTuple.Create(GetPreviousTokenIfNotFirstTokenInTree(parent.GetFirstToken()), parent.GetLastToken());
        }
 
        // don't do anything
        return null;
    }
 
    private static ValueTuple<SyntaxToken, SyntaxToken>? FindAppropriateRangeForColon(SyntaxToken endToken)
    {
        // don't do anything if there is no proper parent
        var parent = endToken.Parent;
        if (parent == null || parent.Kind() == SyntaxKind.SkippedTokensTrivia)
            return null;
 
        // cases such as namespace, type, enum, method almost any top level elements
        if (IsColonInSwitchLabel(endToken))
            return (GetPreviousTokenIfNotFirstTokenInTree(parent.GetFirstToken()), parent.GetLastToken());
 
        if (endToken.Kind() == SyntaxKind.ColonToken && parent is LabeledStatementSyntax)
            return (GetPreviousTokenIfNotFirstTokenInTree(parent.GetFirstToken()), parent.GetLastToken());
 
        return null;
    }
 
    private static SyntaxToken GetPreviousTokenIfNotFirstTokenInTree(SyntaxToken token)
    {
        var previousToken = token.GetPreviousToken();
        return previousToken.Kind() == SyntaxKind.None ? token : previousToken;
    }
 
    public static bool AreTwoTokensOnSameLine(SyntaxToken token1, SyntaxToken token2)
    {
        if (token1 == token2)
        {
            return true;
        }
 
        var tree = token1.SyntaxTree;
        if (tree != null && tree.TryGetText(out var text))
        {
            return text.AreOnSameLine(token1, token2);
        }
 
        return !CommonFormattingHelpers.GetTextBetween(token1, token2).ContainsLineBreak();
    }
 
    private static SyntaxToken GetAppropriatePreviousToken(SyntaxToken startToken, bool canTokenBeFirstInABlock = false)
    {
        var previousToken = startToken.GetPreviousToken();
        if (previousToken.Kind() == SyntaxKind.None)
        {
            // no previous token, return as it is
            return startToken;
        }
 
        if (AreTwoTokensOnSameLine(previousToken, startToken))
        {
            // The previous token can be '{' of a block and type declaration
            // { int s = 0;
            if (canTokenBeFirstInABlock)
            {
                if (IsOpenBraceTokenOfABlockOrTypeOrNamespace(previousToken))
                {
                    return previousToken;
                }
            }
 
            // there is another token on same line.
            return startToken;
        }
 
        // start token is the first token on line
 
        // now check a special case where previous token belongs to a label.
        if (previousToken.IsLastTokenInLabelStatement())
        {
            RoslynDebug.AssertNotNull(previousToken.Parent?.Parent);
            var labelNode = previousToken.Parent.Parent;
            return GetAppropriatePreviousToken(labelNode.GetFirstToken());
        }
 
        return previousToken;
    }
 
    private static bool IsOpenBraceTokenOfABlockOrTypeOrNamespace(SyntaxToken previousToken)
    {
        return previousToken.IsKind(SyntaxKind.OpenBraceToken) &&
                                (previousToken.Parent.IsKind(SyntaxKind.Block) ||
                                 previousToken.Parent is TypeDeclarationSyntax ||
                                 previousToken.Parent is NamespaceDeclarationSyntax);
    }
 
    private static bool IsSpecialContainingNode(SyntaxNode node)
    {
        return
            node.Kind() is SyntaxKind.IfStatement or
            SyntaxKind.ElseClause or
            SyntaxKind.WhileStatement or
            SyntaxKind.ForStatement or
            SyntaxKind.ForEachStatement or
            SyntaxKind.ForEachVariableStatement or
            SyntaxKind.UsingStatement or
            SyntaxKind.DoStatement or
            SyntaxKind.TryStatement or
            SyntaxKind.CatchClause or
            SyntaxKind.FinallyClause or
            SyntaxKind.LabeledStatement or
            SyntaxKind.LockStatement or
            SyntaxKind.FixedStatement or
            SyntaxKind.UncheckedStatement or
            SyntaxKind.CheckedStatement or
            SyntaxKind.GetAccessorDeclaration or
            SyntaxKind.SetAccessorDeclaration or
            SyntaxKind.InitAccessorDeclaration or
            SyntaxKind.AddAccessorDeclaration or
            SyntaxKind.RemoveAccessorDeclaration;
    }
 
    private static SyntaxNode? GetTopContainingNode([DisallowNull] SyntaxNode? node)
    {
        RoslynDebug.AssertNotNull(node.Parent);
 
        node = node.Parent;
        if (!IsSpecialContainingNode(node))
        {
            return node;
        }
 
        var lastSpecialContainingNode = node;
        node = node.Parent;
 
        while (node != null)
        {
            if (!IsSpecialContainingNode(node))
            {
                return lastSpecialContainingNode;
            }
 
            lastSpecialContainingNode = node;
            node = node.Parent;
        }
 
        return null;
    }
 
    public static bool IsColonInSwitchLabel(SyntaxToken token)
    {
        return token.Kind() == SyntaxKind.ColonToken &&
            token.Parent is SwitchLabelSyntax switchLabel &&
            switchLabel.ColonToken == token;
    }
 
    public static bool InBetweenTwoMembers(SyntaxToken previousToken, SyntaxToken currentToken)
    {
        if (previousToken.Kind() is not SyntaxKind.SemicolonToken and not SyntaxKind.CloseBraceToken)
        {
            return false;
        }
 
        if (currentToken.Kind() == SyntaxKind.CloseBraceToken)
        {
            return false;
        }
 
        var previousMember = GetEnclosingMember(previousToken);
        var nextMember = GetEnclosingMember(currentToken);
 
        return previousMember != null
            && nextMember != null
            && previousMember != nextMember;
    }
 
    public static MemberDeclarationSyntax? GetEnclosingMember(SyntaxToken token)
    {
        RoslynDebug.AssertNotNull(token.Parent);
 
        if (token.Kind() == SyntaxKind.CloseBraceToken)
        {
            if (token.Parent.Kind() is SyntaxKind.Block or
                SyntaxKind.AccessorList)
            {
                return token.Parent.Parent as MemberDeclarationSyntax;
            }
        }
 
        return token.Parent.FirstAncestorOrSelf<MemberDeclarationSyntax>();
    }
}