File: src\Workspaces\VisualBasic\Portable\Formatting\Engine\Trivia\TriviaDataFactory.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
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.CodeAnalysis.Formatting
 
Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting
    ''' <summary>
    ''' trivia factory.
    ''' 
    ''' it will cache some commonly used trivia to reduce memory footprint and heap allocation
    ''' </summary>
    Partial Friend Class TriviaDataFactory
        Inherits AbstractTriviaDataFactory
 
        Private Const s_lineContinuationCacheSize = 80
 
        Private ReadOnly _lineContinuations(s_lineContinuationCacheSize) As LineContinuationTrivia
 
        Public Sub New(treeInfo As TreeData, options As LineFormattingOptions)
            MyBase.New(treeInfo, options)
        End Sub
 
        Public Overrides Function CreateLeadingTrivia(token As SyntaxToken) As TriviaData
            ' no trivia
            If Not token.HasLeadingTrivia Then
                Debug.Assert(String.IsNullOrWhiteSpace(Me.TreeInfo.GetTextBetween(Nothing, token)))
                Return GetSpaceTriviaData(space:=0)
            End If
 
            Dim result = Analyzer.Leading(token)
            Dim info = GetWhitespaceOnlyTriviaInfo(Nothing, token, result)
            If info IsNot Nothing Then
                Debug.Assert(String.IsNullOrWhiteSpace(Me.TreeInfo.GetTextBetween(Nothing, token)))
                Return info
            End If
 
            Return New ComplexTrivia(Me.Options, Me.TreeInfo, Nothing, token)
        End Function
 
        Public Overrides Function CreateTrailingTrivia(token As SyntaxToken) As TriviaData
            ' no trivia
            If Not token.HasTrailingTrivia Then
                Debug.Assert(String.IsNullOrWhiteSpace(Me.TreeInfo.GetTextBetween(token, Nothing)))
                Return GetSpaceTriviaData(space:=0)
            End If
 
            Dim result = Analyzer.Trailing(token)
            Dim info = GetWhitespaceOnlyTriviaInfo(token, Nothing, result)
            If info IsNot Nothing Then
                Debug.Assert(String.IsNullOrWhiteSpace(Me.TreeInfo.GetTextBetween(token, Nothing)))
                Return info
            End If
 
            Return New ComplexTrivia(Me.Options, Me.TreeInfo, token, Nothing)
        End Function
 
        Public Overrides Function Create(token1 As SyntaxToken, token2 As SyntaxToken) As TriviaData
            ' no trivia in between
            If (Not token1.HasTrailingTrivia) AndAlso (Not token2.HasLeadingTrivia) Then
                Debug.Assert(String.IsNullOrWhiteSpace(Me.TreeInfo.GetTextBetween(token1, token2)))
                Return GetSpaceTriviaData(space:=0)
            End If
 
            Dim result = Analyzer.Between(token1, token2)
            If ContainsOnlyLineContinuation(result) Then
                Dim lineContinuationTriviaData = GetLineContinuationTriviaInfo(token1, token2, result)
 
                If lineContinuationTriviaData IsNot Nothing Then
                    Return lineContinuationTriviaData
                End If
 
                Return New ComplexTrivia(Me.Options, Me.TreeInfo, token1, token2)
            End If
 
            Dim triviaData = GetWhitespaceOnlyTriviaInfo(token1, token2, result)
            If triviaData IsNot Nothing Then
                Debug.Assert(String.IsNullOrWhiteSpace(Me.TreeInfo.GetTextBetween(token1, token2)))
                Return triviaData
            End If
 
            Return New ComplexTrivia(Me.Options, Me.TreeInfo, token1, token2)
        End Function
 
        Private Function GetLineContinuationTriviaInfo(token1 As SyntaxToken, token2 As SyntaxToken, result As Analyzer.AnalysisResult) As TriviaData
            If result.LineBreaks <> 1 OrElse
               result.TreatAsElastic OrElse
               Not result.HasOnlyOneSpaceBeforeLineContinuation OrElse
               Not result.HasTrailingSpace Then
                Return Nothing
            End If
 
            ' check whether we can cache trivia info for current indentation
            Dim lineCountAndIndentation = GetLineBreaksAndIndentation(result)
 
            Dim useTriviaAsItIs As Boolean = lineCountAndIndentation.Item1
            Dim lineBreaks = lineCountAndIndentation.Item2
            Dim indentation = lineCountAndIndentation.Item3
 
            Contract.ThrowIfFalse(lineBreaks = 1)
 
            Dim canUseCache = useTriviaAsItIs AndAlso
                              indentation < s_lineContinuationCacheSize
 
            If Not canUseCache Then
                Return Nothing
            End If
 
            EnsureLineContinuationTriviaInfo(indentation, Me.TreeInfo.GetTextBetween(token1, token2))
            Return Me._lineContinuations(indentation)
        End Function
 
        Private Sub EnsureLineContinuationTriviaInfo(indentation As Integer, originalString As String)
            Contract.ThrowIfFalse(indentation >= 0 AndAlso indentation < s_lineContinuationCacheSize)
            Debug.Assert(originalString.Substring(0, 4) = " _" & vbCrLf)
 
            ' set up caches
            If Me._lineContinuations(indentation) Is Nothing Then
                Dim triviaInfo = New LineContinuationTrivia(Me.Options, originalString, indentation)
                Interlocked.CompareExchange(Me._lineContinuations(indentation), triviaInfo, Nothing)
            End If
        End Sub
 
        Private Shared Function ContainsOnlyLineContinuation(result As Analyzer.AnalysisResult) As Boolean
            Return result.HasLineContinuation AndAlso
                   Not result.HasComments AndAlso
                   Not result.HasColonTrivia AndAlso
                   Not result.HasPreprocessor AndAlso
                   Not result.HasSkippedOrDisabledText AndAlso
                   Not result.HasUnknownWhitespace AndAlso
                   Not result.HasConflictMarker
        End Function
 
        Private Shared Function ContainsOnlyWhitespace(result As Analyzer.AnalysisResult) As Boolean
            Return Not result.HasComments AndAlso
                   Not result.HasColonTrivia AndAlso
                   Not result.HasPreprocessor AndAlso
                   Not result.HasSkippedOrDisabledText AndAlso
                   Not result.HasLineContinuation AndAlso
                   Not result.HasConflictMarker
        End Function
 
        Private Function GetWhitespaceOnlyTriviaInfo(token1 As SyntaxToken, token2 As SyntaxToken, result As Analyzer.AnalysisResult) As TriviaData
            If Not ContainsOnlyWhitespace(result) Then
                Return Nothing
            End If
 
            ' only whitespace in between
            Dim space As Integer = GetSpaceOnSingleLine(result)
            Contract.ThrowIfFalse(space >= -1)
 
            If space >= 0 Then
                Return GetSpaceTriviaData(space, result.TreatAsElastic)
            End If
 
            ' tab is used in a place where it is not an indentation
            If result.LineBreaks = 0 AndAlso result.Tab > 0 Then
                ' calculate actual space size from tab
                Dim spaces = CalculateSpaces(token1, token2)
                Return New ModifiedWhitespace(Me.Options, result.LineBreaks, indentation:=spaces, elastic:=result.TreatAsElastic)
            End If
 
            ' check whether we can cache trivia info for current indentation
            Dim lineCountAndIndentation = GetLineBreaksAndIndentation(result)
 
            Dim useTriviaAsItIs As Boolean = lineCountAndIndentation.Item1
            Return GetWhitespaceTriviaData(lineCountAndIndentation.Item2, lineCountAndIndentation.Item3, useTriviaAsItIs, result.TreatAsElastic)
        End Function
 
        Private Function CalculateSpaces(token1 As SyntaxToken, token2 As SyntaxToken) As Integer
            Dim initialColumn = If(token1.Kind = 0, 0, Me.TreeInfo.GetOriginalColumn(Me.Options.TabSize, token1) + token1.Width)
            Dim textSnippet = Me.TreeInfo.GetTextBetween(token1, token2)
 
            Return textSnippet.ConvertTabToSpace(Me.Options.TabSize, initialColumn, textSnippet.Length)
        End Function
 
        Private Function GetLineBreaksAndIndentation(result As Analyzer.AnalysisResult) As ValueTuple(Of Boolean, Integer, Integer)
            Debug.Assert(result.Tab >= 0)
            Debug.Assert(result.LineBreaks >= 0)
 
            Dim indentation = result.Tab * Me.Options.TabSize + result.Space
            If result.HasTrailingSpace OrElse result.HasUnknownWhitespace Then
                Return ValueTuple.Create(False, result.LineBreaks, indentation)
            End If
 
            If Not Me.Options.UseTabs Then
                If result.Tab > 0 Then
                    Return ValueTuple.Create(False, result.LineBreaks, indentation)
                End If
 
                Return ValueTuple.Create(True, result.LineBreaks, indentation)
            End If
 
            Debug.Assert(Me.Options.UseTabs)
 
            ' tab can only appear before space to be a valid tab for indentation
            If result.HasTabAfterSpace Then
                Return ValueTuple.Create(False, result.LineBreaks, indentation)
            End If
 
            If result.Space >= Me.Options.TabSize Then
                Return ValueTuple.Create(False, result.LineBreaks, indentation)
            End If
 
            Debug.Assert((indentation \ Options.TabSize) = result.Tab)
            Debug.Assert((indentation Mod Options.TabSize) = result.Space)
 
            Return ValueTuple.Create(True, result.LineBreaks, indentation)
        End Function
 
        Private Shared Function GetSpaceOnSingleLine(result As Analyzer.AnalysisResult) As Integer
            If result.HasTrailingSpace OrElse result.HasUnknownWhitespace OrElse result.LineBreaks > 0 OrElse result.Tab > 0 Then
                Return -1
            End If
 
            Return result.Space
        End Function
    End Class
End Namespace