|
// 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>();
}
}
|