|
// 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.Collections.Generic;
using System.Text;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Formatting.Rules;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Shared.Utilities;
internal static class CommonFormattingHelpers
{
public static readonly Comparison<SuppressOperation> SuppressOperationComparer = (o1, o2) =>
{
return o1.TextSpan.Start - o2.TextSpan.Start;
};
public static readonly Comparison<IndentBlockOperation> IndentBlockOperationComparer = (o1, o2) =>
{
// smaller one goes left
var d = o1.TextSpan.Start - o2.TextSpan.Start;
if (d != 0)
return d;
// bigger one goes left
d = o2.TextSpan.End - o1.TextSpan.End;
if (d != 0)
return d;
if (o1.IsRelativeIndentation && o2.IsRelativeIndentation)
{
// if they're at the same location, but indenting separate amounts, have the bigger delta go first.
d = o2.IndentationDeltaOrPosition - o1.IndentationDeltaOrPosition;
if (d != 0)
return d;
}
return 0;
};
public static IEnumerable<(SyntaxToken, SyntaxToken)> ConvertToTokenPairs(this SyntaxNode root, IReadOnlyList<TextSpan> spans)
{
Contract.ThrowIfNull(root);
Contract.ThrowIfFalse(spans.Count > 0);
if (spans.Count == 1)
{
// special case, if there is only one span, return right away
yield return root.ConvertToTokenPair(spans[0]);
yield break;
}
var previousOne = root.ConvertToTokenPair(spans[0]);
// iterate through each spans and make sure each one doesn't overlap each other
for (var i = 1; i < spans.Count; i++)
{
var currentOne = root.ConvertToTokenPair(spans[i]);
if (currentOne.Item1.SpanStart <= previousOne.Item2.Span.End)
{
// oops, looks like two spans are overlapping each other. merge them
previousOne = ValueTuple.Create(previousOne.Item1, previousOne.Item2.Span.End < currentOne.Item2.Span.End ? currentOne.Item2 : previousOne.Item2);
continue;
}
// okay, looks like things are in good shape
yield return previousOne;
// move to next one
previousOne = currentOne;
}
// give out the last one
yield return previousOne;
}
public static ValueTuple<SyntaxToken, SyntaxToken> ConvertToTokenPair(this SyntaxNode root, TextSpan textSpan)
{
Contract.ThrowIfNull(root);
Contract.ThrowIfTrue(textSpan.IsEmpty);
var startToken = root.FindToken(textSpan.Start);
// empty token, get previous non-zero length token
if (startToken.IsMissing)
{
// if there is no previous token, startToken will be set to SyntaxKind.None
startToken = startToken.GetPreviousToken();
}
// span is on leading trivia
if (textSpan.Start < startToken.SpanStart)
{
// if there is no previous token, startToken will be set to SyntaxKind.None
startToken = startToken.GetPreviousToken();
}
// adjust position where we try to search end token
var endToken = (root.FullSpan.End <= textSpan.End) ?
root.GetLastToken(includeZeroWidth: true) : root.FindToken(textSpan.End);
// empty token, get next token
if (endToken.IsMissing)
{
endToken = endToken.GetNextToken();
}
// span is on trailing trivia
if (endToken.Span.End < textSpan.End)
{
endToken = endToken.GetNextToken();
}
// make sure tokens are not SyntaxKind.None
startToken = (startToken.RawKind != 0) ? startToken : root.GetFirstToken(includeZeroWidth: true);
endToken = (endToken.RawKind != 0) ? endToken : root.GetLastToken(includeZeroWidth: true);
// token is in right order
Contract.ThrowIfFalse(startToken.Equals(endToken) || startToken.Span.End <= endToken.SpanStart);
return ValueTuple.Create(startToken, endToken);
}
public static bool IsInvalidTokenRange(this SyntaxNode root, SyntaxToken startToken, SyntaxToken endToken)
{
// given token must be token exist excluding EndOfFile token.
if (startToken.RawKind == 0 || endToken.RawKind == 0)
{
return true;
}
if (startToken.Equals(endToken))
{
return false;
}
// regular case.
// start token can't be end of file token and start token must be before end token if it's not the same token.
return root.FullSpan.End == startToken.SpanStart || startToken.FullSpan.End > endToken.FullSpan.Start;
}
public static int GetTokenColumn(this SyntaxTree tree, SyntaxToken token, int tabSize)
{
Contract.ThrowIfNull(tree);
Contract.ThrowIfTrue(token.RawKind == 0);
var startPosition = token.SpanStart;
var line = tree.GetText().Lines.GetLineFromPosition(startPosition);
return line.GetColumnFromLineOffset(startPosition - line.Start, tabSize);
}
public static string GetText(this SourceText text, SyntaxToken token1, SyntaxToken token2)
=> (token1.RawKind == 0) ? text.ToString(TextSpan.FromBounds(0, token2.SpanStart)) : text.ToString(TextSpan.FromBounds(token1.Span.End, token2.SpanStart));
public static string GetTextBetween(SyntaxToken token1, SyntaxToken token2)
{
var builder = new StringBuilder();
AppendTextBetween(token1, token2, builder);
return builder.ToString();
}
public static void AppendTextBetween(SyntaxToken token1, SyntaxToken token2, StringBuilder builder)
{
Contract.ThrowIfTrue(token1.RawKind == 0 && token2.RawKind == 0);
Contract.ThrowIfTrue(token1.Equals(token2));
if (token1.RawKind == 0)
{
AppendLeadingTriviaText(token2, builder);
return;
}
if (token2.RawKind == 0)
{
AppendTrailingTriviaText(token1, builder);
return;
}
if (token1.FullSpan.End == token2.FullSpan.Start)
{
AppendTextBetweenTwoAdjacentTokens(token1, token2, builder);
return;
}
AppendTrailingTriviaText(token1, builder);
for (var token = token1.GetNextToken(includeZeroWidth: true); token.FullSpan.End <= token2.FullSpan.Start; token = token.GetNextToken(includeZeroWidth: true))
{
builder.Append(token.ToFullString());
}
AppendPartialLeadingTriviaText(token2, builder, token1.TrailingTrivia.FullSpan.End);
}
private static void AppendTextBetweenTwoAdjacentTokens(SyntaxToken token1, SyntaxToken token2, StringBuilder builder)
{
AppendTrailingTriviaText(token1, builder);
AppendLeadingTriviaText(token2, builder);
}
private static void AppendLeadingTriviaText(SyntaxToken token, StringBuilder builder)
{
if (!token.HasLeadingTrivia)
{
return;
}
foreach (var trivia in token.LeadingTrivia)
{
builder.Append(trivia.ToFullString());
}
}
/// <summary>
/// If the token1 is expected to be part of the leading trivia of the token2 then the trivia
/// before the token1FullSpanEnd, which the fullspan end of the token1 should be ignored
/// </summary>
private static void AppendPartialLeadingTriviaText(SyntaxToken token, StringBuilder builder, int token1FullSpanEnd)
{
if (!token.HasLeadingTrivia)
{
return;
}
foreach (var trivia in token.LeadingTrivia)
{
if (trivia.FullSpan.End <= token1FullSpanEnd)
{
continue;
}
builder.Append(trivia.ToFullString());
}
}
private static void AppendTrailingTriviaText(SyntaxToken token, StringBuilder builder)
{
if (!token.HasTrailingTrivia)
{
return;
}
foreach (var trivia in token.TrailingTrivia)
{
builder.Append(trivia.ToFullString());
}
}
/// <summary>
/// this will create a span that includes its trailing trivia of its previous token and leading trivia of its next token
/// for example, for code such as "class A { int ...", if given tokens are "A" and "{", this will return span [] of "class[ A { ]int ..."
/// which included trailing trivia of "class" which is previous token of "A", and leading trivia of "int" which is next token of "{"
/// </summary>
public static TextSpan GetSpanIncludingTrailingAndLeadingTriviaOfAdjacentTokens(SyntaxToken startToken, SyntaxToken endToken)
{
// most of cases we can just ask previous and next token to create the span, but in some corner cases such as omitted token case,
// those navigation function doesn't work, so we have to explore the tree ourselves to create correct span
var startPosition = GetStartPositionOfSpan(startToken);
var endPosition = GetEndPositionOfSpan(endToken);
return TextSpan.FromBounds(startPosition, endPosition);
}
private static int GetEndPositionOfSpan(SyntaxToken token)
{
var nextToken = token.GetNextToken();
if (nextToken.RawKind != 0)
{
return nextToken.SpanStart;
}
var backwardPosition = token.FullSpan.End;
var parentNode = GetParentThatContainsGivenSpan(token.Parent, backwardPosition, forward: false);
if (parentNode == null)
{
// reached the end of tree
return token.FullSpan.End;
}
Contract.ThrowIfFalse(backwardPosition < parentNode.FullSpan.End);
nextToken = parentNode.FindToken(backwardPosition + 1);
Contract.ThrowIfTrue(nextToken.RawKind == 0);
return nextToken.SpanStart;
}
public static int GetStartPositionOfSpan(SyntaxToken token)
{
var previousToken = token.GetPreviousToken();
if (previousToken.RawKind != 0)
{
return previousToken.Span.End;
}
// first token in the tree
var forwardPosition = token.FullSpan.Start;
if (forwardPosition <= 0)
{
return 0;
}
var parentNode = GetParentThatContainsGivenSpan(token.Parent, forwardPosition, forward: true);
Contract.ThrowIfNull(parentNode);
Contract.ThrowIfFalse(parentNode.FullSpan.Start < forwardPosition);
previousToken = parentNode.FindToken(forwardPosition + 1);
Contract.ThrowIfTrue(previousToken.RawKind == 0);
return previousToken.Span.End;
}
private static SyntaxNode? GetParentThatContainsGivenSpan(SyntaxNode? node, int position, bool forward)
{
while (node != null)
{
var fullSpan = node.FullSpan;
if (forward)
{
if (fullSpan.Start < position)
{
return node;
}
}
else
{
if (position > fullSpan.End)
{
return node;
}
}
node = node.Parent;
}
return null;
}
public static bool HasAnyWhitespaceElasticTrivia(SyntaxToken previousToken, SyntaxToken currentToken)
{
if (!previousToken.ContainsAnnotations && !currentToken.ContainsAnnotations)
return false;
if (!previousToken.HasTrailingTrivia && !currentToken.HasLeadingTrivia)
return false;
return previousToken.TrailingTrivia.HasAnyWhitespaceElasticTrivia() || currentToken.LeadingTrivia.HasAnyWhitespaceElasticTrivia();
}
public static TextSpan GetFormattingSpan(SyntaxNode root, TextSpan span)
{
Contract.ThrowIfNull(root);
var startToken = root.FindToken(span.Start).GetPreviousToken();
var endToken = root.FindTokenFromEnd(span.End).GetNextToken();
var startPosition = startToken.SpanStart;
var endPosition = endToken.RawKind == 0 ? root.Span.End : endToken.Span.End;
return TextSpan.FromBounds(startPosition, endPosition);
}
}
|