|
// 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.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.Formatting;
internal partial class TriviaDataFactory
{
private class Analyzer
{
public static AnalysisResult Leading(SyntaxToken token)
{
var result = default(AnalysisResult);
Analyze(token.LeadingTrivia, ref result);
return result;
}
public static AnalysisResult Trailing(SyntaxToken token)
{
var result = default(AnalysisResult);
Analyze(token.TrailingTrivia, ref result);
return result;
}
public static AnalysisResult Between(SyntaxToken token1, SyntaxToken token2)
{
if (!token1.HasTrailingTrivia && !token2.HasLeadingTrivia)
{
return default;
}
var result = default(AnalysisResult);
if (token1.IsMissing && token1.FullWidth() == 0)
{
// Consider the following case:
//
// return // <- note the missing semicolon
// }
//
// in this case, the compiler will insert a missing semicolon token at the
// start of the line containing the close curly. This is problematic as it
// means that if we're looking at the token-pair for the semicolon and close-
// curly, then we'll think there is no newline here. Because we think there
// is no newline, we won't attempt to indent in a manner that preserves tabs
// (if the user has 'use tabs for indent' enabled).
//
// Here we detect if our previous token is an empty missing token. If so,
// we look back to the previous non-missing token to see if it ends with a
// newline. If so, we keep track of that so we'll appropriately indent later
// on.
// Keep walking backward until we hit a token whose *full width* is greater than
// 0. See if this token has an end of line trivia at the end of it. Note:
// we need to "includeZeroWidth" tokens because we can have zero width tokens
// that still have a full width that is non-zero. i.e. a missing token that
// still has trailing trivia on it.
for (var currentToken = token1; !currentToken.IsKind(SyntaxKind.None);)
{
var previousToken = currentToken.GetPreviousToken(includeSkipped: false, includeZeroWidth: true);
if (previousToken.FullWidth() == 0)
{
currentToken = previousToken;
continue;
}
// Finally hit the first previous token with non-zero full width.
if (previousToken.TrailingTrivia is [.., (kind: SyntaxKind.EndOfLineTrivia)])
result.LineBreaks = 1;
break;
}
}
else
{
Analyze(token1.TrailingTrivia, ref result);
}
Analyze(token2.LeadingTrivia, ref result);
return result;
}
private static void Analyze(SyntaxTriviaList list, ref AnalysisResult result)
{
if (list.Count == 0)
{
return;
}
foreach (var trivia in list)
{
if (trivia.Kind() == SyntaxKind.WhitespaceTrivia)
{
AnalyzeWhitespacesInTrivia(trivia, ref result);
}
else if (trivia.Kind() == SyntaxKind.EndOfLineTrivia)
{
AnalyzeLineBreak(trivia, ref result);
}
else if (trivia.IsRegularOrDocComment())
{
result.HasComments = true;
}
else if (trivia.Kind() == SyntaxKind.SkippedTokensTrivia)
{
result.HasSkippedTokens = true;
}
else if (trivia.Kind() is SyntaxKind.DisabledTextTrivia or
SyntaxKind.PreprocessingMessageTrivia)
{
result.HasSkippedOrDisabledText = true;
}
else if (trivia.Kind() == SyntaxKind.ConflictMarkerTrivia)
{
result.HasConflictMarker = true;
}
else
{
Contract.ThrowIfFalse(SyntaxFacts.IsPreprocessorDirective(trivia.Kind()));
result.HasPreprocessor = true;
}
}
}
private static void AnalyzeLineBreak(SyntaxTrivia trivia, ref AnalysisResult result)
{
// if there was any space before line break, then we have trailing spaces
if (result.Space > 0 || result.Tab > 0)
{
result.HasTrailingSpace = true;
}
// reset space and tab information
result.LineBreaks++;
result.HasTabAfterSpace = false;
result.Space = 0;
result.Tab = 0;
result.TreatAsElastic |= trivia.IsElastic();
}
private static void AnalyzeWhitespacesInTrivia(SyntaxTrivia trivia, ref AnalysisResult result)
{
// trivia already has text. getting text should be noop
Debug.Assert(trivia.Kind() == SyntaxKind.WhitespaceTrivia);
Debug.Assert(trivia.Width() == trivia.FullWidth());
var space = 0;
var tab = 0;
var unknownWhitespace = 0;
var text = trivia.ToString();
for (var i = 0; i < trivia.Width(); i++)
{
if (text[i] == ' ')
{
space++;
}
else if (text[i] == '\t')
{
if (result.Space > 0)
{
result.HasTabAfterSpace = true;
}
tab++;
}
else
{
unknownWhitespace++;
}
}
// set result
result.Space += space;
result.Tab += tab;
result.HasUnknownWhitespace |= unknownWhitespace > 0;
result.TreatAsElastic |= trivia.IsElastic();
}
internal struct AnalysisResult
{
internal int LineBreaks { get; set; }
internal int Space { get; set; }
internal int Tab { get; set; }
internal bool HasTabAfterSpace { get; set; }
internal bool HasUnknownWhitespace { get; set; }
internal bool HasTrailingSpace { get; set; }
internal bool HasSkippedTokens { get; set; }
internal bool HasSkippedOrDisabledText { get; set; }
internal bool HasConflictMarker { get; set; }
internal bool HasComments { get; set; }
internal bool HasPreprocessor { get; set; }
internal bool TreatAsElastic { get; set; }
}
}
}
|