File: ExtractMethod\VisualBasicMethodExtractor.TriviaResult.vb
Web Access
Project: src\src\Features\VisualBasic\Portable\Microsoft.CodeAnalysis.VisualBasic.Features.vbproj (Microsoft.CodeAnalysis.VisualBasic.Features)
' 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.ExtractMethod
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
 
Namespace Microsoft.CodeAnalysis.VisualBasic.ExtractMethod
    Partial Friend Class VisualBasicMethodExtractor
        Private Class VisualBasicTriviaResult
            Inherits TriviaResult
 
            Public Shared Async Function ProcessAsync(selectionResult As VisualBasicSelectionResult, cancellationToken As CancellationToken) As Task(Of VisualBasicTriviaResult)
                Dim preservationService = selectionResult.SemanticDocument.Document.Project.Services.GetService(Of ISyntaxTriviaService)()
                Dim root = selectionResult.SemanticDocument.Root
                Dim result = preservationService.SaveTriviaAroundSelection(root, selectionResult.FinalSpan)
 
                Return New VisualBasicTriviaResult(
                    Await selectionResult.SemanticDocument.WithSyntaxRootAsync(result.Root, cancellationToken).ConfigureAwait(False),
                    result)
            End Function
 
            Private Sub New(document As SemanticDocument, result As ITriviaSavedResult)
                MyBase.New(document, result, SyntaxKind.EndOfLineTrivia, SyntaxKind.WhitespaceTrivia)
            End Sub
 
            Protected Overrides Function GetAnnotationResolver(callsite As SyntaxNode, method As SyntaxNode) As AnnotationResolver
                Dim methodDefinition = TryCast(method, MethodBlockBaseSyntax)
                If callsite Is Nothing OrElse methodDefinition Is Nothing Then
                    Return Nothing
                End If
 
                Return Function(node, location, annotation) AnnotationResolver(node, location, annotation, callsite, methodDefinition)
            End Function
 
            Protected Overrides Function GetTriviaResolver(method As SyntaxNode) As TriviaResolver
                Dim methodDefinition = TryCast(method, MethodBlockBaseSyntax)
                If methodDefinition Is Nothing Then
                    Return Nothing
                End If
 
                Return Function(location, tokenPair, triviaMap) TriviaResolver(location, tokenPair, triviaMap, methodDefinition)
            End Function
 
            Private Shared Function AnnotationResolver(
                node As SyntaxNode,
                location As TriviaLocation,
                annotation As SyntaxAnnotation,
                callsite As SyntaxNode,
                method As MethodBlockBaseSyntax) As SyntaxToken
 
                Dim token = node.GetAnnotatedNodesAndTokens(annotation).FirstOrDefault().AsToken()
                If token.Kind <> 0 Then
                    Return token
                End If
 
                Select Case location
                    Case TriviaLocation.BeforeBeginningOfSpan
                        Return callsite.GetFirstToken(includeZeroWidth:=True).GetPreviousToken(includeZeroWidth:=True)
                    Case TriviaLocation.AfterEndOfSpan
                        Return callsite.GetLastToken(includeZeroWidth:=True).GetNextToken(includeZeroWidth:=True)
                    Case TriviaLocation.AfterBeginningOfSpan
                        Return method.BlockStatement.GetLastToken(includeZeroWidth:=True).GetNextToken(includeZeroWidth:=True)
                    Case TriviaLocation.BeforeEndOfSpan
                        Return method.EndBlockStatement.GetFirstToken(includeZeroWidth:=True).GetPreviousToken(includeZeroWidth:=True)
                End Select
 
                throw ExceptionUtilities.UnexpectedValue(location)
            End Function
 
            Private Function TriviaResolver(
                location As TriviaLocation,
                tokenPair As PreviousNextTokenPair,
                triviaMap As Dictionary(Of SyntaxToken, LeadingTrailingTriviaPair),
                method As MethodBlockBaseSyntax) As IEnumerable(Of SyntaxTrivia)
 
                ' Resolve trivia at the edge of the selection. simple case is easy to deal with, but complex cases where
                ' elastic trivia and user trivia are mixed (hybrid case) and we want to preserve some part of user coding style
                ' but not others can be dealt with here.
 
                ' method has no statement in them. so basically two trivia list now pointing to same thing. 
                If tokenPair.PreviousToken = method.BlockStatement.GetLastToken(includeZeroWidth:=True) AndAlso
                   tokenPair.NextToken = method.EndBlockStatement.GetFirstToken(includeZeroWidth:=True) Then
                    Return If(location = TriviaLocation.AfterBeginningOfSpan,
                              SpecializedCollections.SingletonEnumerable(Of SyntaxTrivia)(SyntaxFactory.ElasticMarker),
                              SpecializedCollections.EmptyEnumerable(Of SyntaxTrivia)())
                End If
 
                Dim previousTriviaPair As LeadingTrailingTriviaPair = Nothing
                Dim trailingTrivia = If(triviaMap.TryGetValue(tokenPair.PreviousToken, previousTriviaPair),
                                        previousTriviaPair.TrailingTrivia, SpecializedCollections.EmptyEnumerable(Of SyntaxTrivia)())
 
                Dim nextTriviaPair As LeadingTrailingTriviaPair = Nothing
                Dim leadingTrivia = If(triviaMap.TryGetValue(tokenPair.NextToken, nextTriviaPair),
                                       nextTriviaPair.LeadingTrivia, SpecializedCollections.EmptyEnumerable(Of SyntaxTrivia)())
 
                Dim list = trailingTrivia.Concat(leadingTrivia)
 
                Select Case location
                    Case TriviaLocation.BeforeBeginningOfSpan
                        Return FilterTriviaList(RemoveTrailingElasticTrivia(tokenPair.PreviousToken, list, tokenPair.NextToken))
                    Case TriviaLocation.AfterEndOfSpan
                        Return FilterTriviaList(RemoveLeadingElasticTrivia(RemoveLeadingElasticTrivia(tokenPair.PreviousToken, list, tokenPair.NextToken)))
                    Case TriviaLocation.AfterBeginningOfSpan
                        Return FilterTriviaList(RemoveLeadingElasticTrivia(tokenPair.PreviousToken, list, tokenPair.NextToken))
                    Case TriviaLocation.BeforeEndOfSpan
                        Return FilterTriviaList(RemoveTrailingElasticTrivia(tokenPair.PreviousToken, list, tokenPair.NextToken))
                End Select
 
                throw ExceptionUtilities.UnexpectedValue(location)
            End Function
 
            Private Shared Function RemoveTrailingElasticTrivia(
                token1 As SyntaxToken, list As IEnumerable(Of SyntaxTrivia), token2 As SyntaxToken) As IEnumerable(Of SyntaxTrivia)
 
                ' special case for skipped token trivia
                ' formatter doesn't touch tokens that have skipped tokens in-between. so, we need to take care of such case ourselves
                If list.Any(Function(t) t.RawKind = SyntaxKind.SkippedTokensTrivia) Then
                    Return RemoveElasticAfterColon(
                        token1.TrailingTrivia.Concat(list).Concat(ReplaceElasticToEndOfLine(token2.LeadingTrivia)))
                End If
 
                If token1.IsLastTokenOfStatement() Then
                    Return RemoveElasticAfterColon(token1.TrailingTrivia.Concat(list).Concat(token2.LeadingTrivia))
                End If
 
                Return token1.TrailingTrivia.Concat(list)
            End Function
 
            Private Shared Function RemoveLeadingElasticTrivia(
                token1 As SyntaxToken, list As IEnumerable(Of SyntaxTrivia), token2 As SyntaxToken) As IEnumerable(Of SyntaxTrivia)
 
                If token1.IsLastTokenOfStatement() Then
                    If SingleLineStatement(token1) Then
                        Return list.Concat(token2.LeadingTrivia)
                    End If
 
                    Return RemoveElasticAfterColon(token1.TrailingTrivia.Concat(list).Concat(token2.LeadingTrivia))
                End If
 
                Return list.Concat(token2.LeadingTrivia)
            End Function
 
            Private Shared Function RemoveLeadingElasticTrivia(list As IEnumerable(Of SyntaxTrivia)) As IEnumerable(Of SyntaxTrivia)
                ' remove leading elastic trivia if it is followed by noisy trivia
                Dim trivia = list.FirstOrDefault()
                If Not trivia.IsElastic() Then
                    Return list
                End If
 
                For Each trivia In list.Skip(1)
                    If trivia.Kind = SyntaxKind.EndOfLineTrivia OrElse trivia.IsElastic() Then
                        Return list
                    ElseIf trivia.Kind <> SyntaxKind.EndOfLineTrivia And trivia.Kind <> SyntaxKind.WhitespaceTrivia Then
                        Return list.Skip(1)
                    End If
                Next
 
                Return list
            End Function
 
            Private Shared Function ReplaceElasticToEndOfLine(list As IEnumerable(Of SyntaxTrivia)) As IEnumerable(Of SyntaxTrivia)
                Return list.Select(Function(t) If(t.IsElastic, SyntaxFactory.CarriageReturnLineFeed, t))
            End Function
 
            Private Shared Function SingleLineStatement(token As SyntaxToken) As Boolean
                ' check whether given token is the last token of a single line statement
                Dim singleLineIf = token.Parent.GetAncestor(Of SingleLineIfStatementSyntax)()
                If singleLineIf IsNot Nothing Then
                    Return True
                End If
 
                Dim singleLineLambda = token.Parent.GetAncestor(Of SingleLineLambdaExpressionSyntax)()
                If singleLineLambda IsNot Nothing Then
                    Return True
                End If
 
                Return False
            End Function
 
            Private Shared Function RemoveElasticAfterColon(list As IEnumerable(Of SyntaxTrivia)) As IEnumerable(Of SyntaxTrivia)
                ' make sure we don't have elastic trivia after colon trivia
                Dim colon = False
                Dim result = New List(Of SyntaxTrivia)()
 
                For Each trivia In list
                    If trivia.RawKind = SyntaxKind.ColonTrivia Then
                        colon = True
                    End If
 
                    If colon AndAlso trivia.IsElastic() Then
                        Continue For
                    End If
 
                    result.Add(trivia)
                Next
 
                Return result
            End Function
        End Class
    End Class
End Namespace