' 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. Imports System.Threading Imports Microsoft.CodeAnalysis.Formatting Imports Microsoft.CodeAnalysis.Shared.Collections Imports Microsoft.CodeAnalysis.Text Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting Friend Class TriviaDataFactory Friend Class TriviaRewriter Inherits VisualBasicSyntaxRewriter Private ReadOnly _node As SyntaxNode Private ReadOnly _spans As TextSpanMutableIntervalTree Private ReadOnly _lastToken As SyntaxToken Private ReadOnly _cancellationToken As CancellationToken Private ReadOnly _trailingTriviaMap As Dictionary(Of SyntaxToken, SyntaxTriviaList) Private ReadOnly _leadingTriviaMap As Dictionary(Of SyntaxToken, SyntaxTriviaList) Public Sub New(node As SyntaxNode, spanToFormat As TextSpanMutableIntervalTree, map As Dictionary(Of ValueTuple(Of SyntaxToken, SyntaxToken), TriviaData), cancellationToken As CancellationToken) Contract.ThrowIfNull(node) Contract.ThrowIfNull(map) _node = node _spans = spanToFormat _lastToken = node.GetLastToken(includeZeroWidth:=True) _cancellationToken = cancellationToken _trailingTriviaMap = New Dictionary(Of SyntaxToken, SyntaxTriviaList)() _leadingTriviaMap = New Dictionary(Of SyntaxToken, SyntaxTriviaList)() PreprocessTriviaListMap(map) End Sub Public Function Transform() As SyntaxNode Return Visit(_node) End Function Private Sub PreprocessTriviaListMap(map As Dictionary(Of ValueTuple(Of SyntaxToken, SyntaxToken), TriviaData)) For Each pair In map _cancellationToken.ThrowIfCancellationRequested() Dim tuple = GetTrailingAndLeadingTrivia(pair) If pair.Key.Item1.Kind <> 0 Then _trailingTriviaMap.Add(pair.Key.Item1, tuple.Item1) End If If pair.Key.Item2.Kind <> 0 Then _leadingTriviaMap.Add(pair.Key.Item2, tuple.Item2) End If Next pair End Sub Private Function GetTrailingAndLeadingTrivia(pair As KeyValuePair(Of ValueTuple(Of SyntaxToken, SyntaxToken), TriviaData)) As (SyntaxTriviaList, SyntaxTriviaList) If pair.Key.Item1.Kind = 0 OrElse _lastToken = pair.Key.Item2 Then Return (SyntaxTriviaList.Empty, GetSyntaxTriviaList(GetTextSpan(pair.Key), pair.Value, _cancellationToken)) End If Dim vbTriviaData = TryCast(pair.Value, TriviaDataWithList) If vbTriviaData IsNot Nothing Then Dim triviaList = vbTriviaData.GetTriviaList(_cancellationToken) Dim index = GetIndexForEndOfLeadingTrivia(triviaList) Return (TriviaHelpers.CreateTriviaListFromTo(triviaList, 0, index), TriviaHelpers.CreateTriviaListFromTo(triviaList, index + 1, triviaList.Count - 1)) End If ' Grab the text change we're making and split it into the trailing trivia for the ' previous token and the leading trivia for the next token. The trivia may contain ' multiple newlines, so we need to first grab the trailing portion (up through the ' first newline), then use the remainder as the leading portion. Dim text = pair.Value.GetTextChanges(GetTextSpan(pair.Key)).Single().NewText Dim trailing = SyntaxFactory.ParseTrailingTrivia(text) Dim leading = SyntaxFactory.ParseLeadingTrivia(text.Substring(trailing.FullSpan.Length)) Return (trailing, leading) End Function Private Function GetTextSpan(pair As ValueTuple(Of SyntaxToken, SyntaxToken)) As TextSpan If pair.Item1.Kind = 0 Then Return TextSpan.FromBounds(_node.FullSpan.Start, pair.Item2.SpanStart) End If If pair.Item2.Kind = 0 Then Return TextSpan.FromBounds(pair.Item1.Span.End, _node.FullSpan.End) End If Return TextSpan.FromBounds(pair.Item1.Span.End, pair.Item2.SpanStart) End Function Private Shared Function GetIndexForEndOfLeadingTrivia(triviaList As SyntaxTriviaList) As Integer For i As Integer = 0 To triviaList.Count - 1 Dim trivia = triviaList(i) If trivia.Kind = SyntaxKind.EndOfLineTrivia Or trivia.Kind = SyntaxKind.ColonTrivia Then Return i End If Next i Return triviaList.Count - 1 End Function Private Shared Function GetSyntaxTriviaList(textSpan As TextSpan, triviaData As TriviaData, cancellationToken As CancellationToken) As SyntaxTriviaList Dim vbTriviaData = TryCast(triviaData, TriviaDataWithList) If vbTriviaData IsNot Nothing Then Return SyntaxFactory.TriviaList(vbTriviaData.GetTriviaList(cancellationToken)) End If ' there is no difference between ParseLeading and ParseTrailing for the given text Dim text = triviaData.GetTextChanges(textSpan).Single().NewText Return SyntaxFactory.ParseLeadingTrivia(text) End Function Public Overrides Function Visit(node As SyntaxNode) As SyntaxNode _cancellationToken.ThrowIfCancellationRequested() If node Is Nothing OrElse Not Me._spans.HasIntervalThatIntersectsWith(node.FullSpan) Then Return node End If Return MyBase.Visit(node) End Function Public Overrides Function VisitToken(token As SyntaxToken) As SyntaxToken _cancellationToken.ThrowIfCancellationRequested() If Not Me._spans.HasIntervalThatIntersectsWith(token.FullSpan) Then Return token End If Dim hasChanges = False ' check whether we have trivia info belongs to this token Dim leadingTrivia = token.LeadingTrivia Dim trailingTrivia = token.TrailingTrivia Dim triviaList As SyntaxTriviaList = Nothing If _trailingTriviaMap.TryGetValue(token, triviaList) Then ' okay, we have this situation ' token|trivia trailingTrivia = triviaList hasChanges = True End If If _leadingTriviaMap.TryGetValue(token, triviaList) Then ' okay, we have this situation ' trivia|token leadingTrivia = triviaList hasChanges = True End If If hasChanges Then Return CreateNewToken(leadingTrivia, token, trailingTrivia) End If ' we have no trivia belongs to this one Return token End Function Private Shared Function CreateNewToken(leadingTrivia As SyntaxTriviaList, token As SyntaxToken, trailingTrivia As SyntaxTriviaList) As SyntaxToken Return token.With(leadingTrivia, trailingTrivia) End Function End Class End Class End Namespace |