// 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.Threading; using Microsoft.CodeAnalysis.Shared.Utilities; namespace Microsoft.CodeAnalysis.Formatting; internal abstract partial class AbstractTriviaDataFactory { protected abstract class AbstractComplexTrivia : TriviaDataWithList { public TreeData TreeInfo { get; } public string OriginalString { get; } private readonly bool _treatAsElastic; public AbstractComplexTrivia(LineFormattingOptions options, TreeData treeInfo, SyntaxToken token1, SyntaxToken token2) : base(options) { Contract.ThrowIfNull(treeInfo); Token1 = token1; Token2 = token2; _treatAsElastic = CommonFormattingHelpers.HasAnyWhitespaceElasticTrivia(token1, token2); this.TreeInfo = treeInfo; this.OriginalString = this.TreeInfo.GetTextBetween(token1, token2); ExtractLineAndSpace(this.OriginalString, out var lineBreaks, out var spaces); this.LineBreaks = lineBreaks; this.Spaces = spaces; } protected abstract void ExtractLineAndSpace(string text, out int lines, out int spaces); protected abstract TriviaData CreateComplexTrivia(int line, int space); protected abstract TriviaData CreateComplexTrivia(int line, int space, int indentation); protected abstract TriviaDataWithList Format(FormattingContext context, ChainedFormattingRules formattingRules, int lines, int spaces, CancellationToken cancellationToken); protected abstract bool ContainsSkippedTokensOrText(TriviaList list); public SyntaxToken Token1 { get; } public SyntaxToken Token2 { get; } public override bool TreatAsElastic => _treatAsElastic; public override bool IsWhitespaceOnlyTrivia => false; public override bool ContainsChanges => false; public override TriviaData WithSpace(int space, FormattingContext context, ChainedFormattingRules formattingRules) { // two tokens are on a single line, we don't allow changing spaces between two // tokens that contain noisy characters between them. if (!this.SecondTokenIsFirstTokenOnLine) { return this; } // okay, two tokens are on different lines, we are basically asked to remove line breaks between them // and make them to be on a single line. well, that is not allowed when there are noisy chars between them if (this.SecondTokenIsFirstTokenOnLine) { return this; } throw ExceptionUtilities.Unreachable(); } public override TriviaData WithLine( int line, int indentation, FormattingContext context, ChainedFormattingRules formattingRules, CancellationToken cancellationToken) { Contract.ThrowIfFalse(line > 0); // if we have elastic trivia, always let it be modified if (this.TreatAsElastic) { return CreateComplexTrivia(line, indentation); } // two tokens are on a single line, it is always allowed to put those two tokens on a different lines if (!this.SecondTokenIsFirstTokenOnLine) { return CreateComplexTrivia(line, indentation); } // okay, two tokens are on different lines, now we need to see whether we can add more lines or not if (this.SecondTokenIsFirstTokenOnLine) { // we are asked to add more lines. sure, no problem if (this.LineBreaks < line) { return CreateComplexTrivia(line, indentation); } // we already has same number of lines, but it is asking changing indentation if (this.LineBreaks == line) { return WithIndentation(indentation, context, formattingRules, cancellationToken); } // sorry, we can't reduce lines if it contains noisy chars if (this.LineBreaks > line) { return this; } } throw ExceptionUtilities.Unreachable(); } public override TriviaData WithIndentation( int indentation, FormattingContext context, ChainedFormattingRules formattingRules, CancellationToken cancellationToken) { // if tokens are not in different line, there is nothing we can do here if (!this.SecondTokenIsFirstTokenOnLine) { return this; } // well, we are already in a desired format, nothing to do. return as it is. if (this.Spaces == indentation) { return this; } // do expansive check // we need to actually format here to find out indentation var list = new TriviaList(Token1.TrailingTrivia, Token2.LeadingTrivia); Contract.ThrowIfFalse(list.Count > 0); if (ContainsSkippedTokensOrText(list)) { // we can't format return this; } // okay, we need to do expansive calculation to find out actual space between two tokens var trivia = Format(context, formattingRules, this.LineBreaks, indentation, cancellationToken); var triviaString = CreateString(trivia, cancellationToken); ExtractLineAndSpace(triviaString, out var lineBreaks, out var spaces); return CreateComplexTrivia(lineBreaks, spaces, indentation); } private static string CreateString(TriviaDataWithList triviaData, CancellationToken cancellationToken) { // create string from given trivia data var sb = StringBuilderPool.Allocate(); foreach (var trivia in triviaData.GetTriviaList(cancellationToken)) { sb.Append(trivia.ToFullString()); } return StringBuilderPool.ReturnAndFree(sb); } } } |