// 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.Linq; using System.Threading; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Formatting; internal abstract class AbstractFormattingResult : IFormattingResult { protected readonly TreeData TreeInfo; protected readonly TokenStream TokenStream; private readonly CancellableLazy<IList<TextChange>> _lazyChanges; private readonly CancellableLazy<SyntaxNode> _lazyNode; /// <summary> /// span in the tree to format /// </summary> public readonly TextSpan FormattedSpan; internal AbstractFormattingResult( TreeData treeInfo, TokenStream tokenStream, TextSpan formattedSpan) { this.TreeInfo = treeInfo; this.TokenStream = tokenStream; this.FormattedSpan = formattedSpan; _lazyChanges = new CancellableLazy<IList<TextChange>>(CreateTextChanges); _lazyNode = new CancellableLazy<SyntaxNode>(CreateFormattedRoot); } /// <summary> /// rewrite the tree info root node with the trivia information in the map /// </summary> protected abstract SyntaxNode Rewriter(Dictionary<ValueTuple<SyntaxToken, SyntaxToken>, TriviaData> map, CancellationToken cancellationToken); #region IFormattingResult implementation public IList<TextChange> GetTextChanges(CancellationToken cancellationToken) => _lazyChanges.GetValue(cancellationToken); public SyntaxNode GetFormattedRoot(CancellationToken cancellationToken) => _lazyNode.GetValue(cancellationToken); private IList<TextChange> CreateTextChanges(CancellationToken cancellationToken) { using (Logger.LogBlock(FunctionId.Formatting_CreateTextChanges, cancellationToken)) { var data = this.TokenStream.GetTriviaDataWithTokenPair(cancellationToken); var result = new List<TextChange>(); foreach (var f in data) { if (!f.Item2.ContainsChanges) { continue; } cancellationToken.ThrowIfCancellationRequested(); AddTextChanges(result, f.Item1.Item1, f.Item1.Item2, f.Item2); } return result; } } private void AddTextChanges(List<TextChange> list, SyntaxToken token1, SyntaxToken token2, TriviaData data) { var span = TextSpan.FromBounds(token1.RawKind == 0 ? this.TreeInfo.StartPosition : token1.Span.End, token2.RawKind == 0 ? this.TreeInfo.EndPosition : token2.SpanStart); var originalString = this.TreeInfo.GetTextBetween(token1, token2); foreach (var change in data.GetTextChanges(span)) { var oldText = (change.Span == span) ? originalString : originalString.Substring(change.Span.Start - span.Start, change.Span.Length); list.Add(change.SimpleDiff(oldText)); } } private SyntaxNode CreateFormattedRoot(CancellationToken cancellationToken) { using (Logger.LogBlock(FunctionId.Formatting_CreateFormattedRoot, cancellationToken)) { var changes = GetChanges(cancellationToken); // create a map using var pooledObject = SharedPools.Default<Dictionary<ValueTuple<SyntaxToken, SyntaxToken>, TriviaData>>().GetPooledObject(); var map = pooledObject.Object; changes.Do(change => map.Add(change.Item1, change.Item2)); // no changes, return as it is. if (map.Count == 0) { return this.TreeInfo.Root; } return Rewriter(map, cancellationToken); } } internal IEnumerable<ValueTuple<ValueTuple<SyntaxToken, SyntaxToken>, TriviaData>> GetChanges(CancellationToken cancellationToken) => TokenStream.GetTriviaDataWithTokenPair(cancellationToken).Where(d => d.Item2.ContainsChanges); #endregion } |