File: src\Workspaces\VisualBasic\Portable\Formatting\Engine\Trivia\TriviaDataFactory.TriviaRewriter.vb
Web Access
Project: src\src\CodeStyle\VisualBasic\Analyzers\Microsoft.CodeAnalysis.VisualBasic.CodeStyle.vbproj (Microsoft.CodeAnalysis.VisualBasic.CodeStyle)
' 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