File: CodeCleanup\Providers\NormalizeModifiersOrOperatorsCodeCleanupProvider.vb
Web Access
Project: src\src\Workspaces\VisualBasic\Portable\Microsoft.CodeAnalysis.VisualBasic.Workspaces.vbproj (Microsoft.CodeAnalysis.VisualBasic.Workspaces)
' 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.Collections.Immutable
Imports System.Composition
Imports System.Diagnostics.CodeAnalysis
Imports System.Threading
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.CodeStyle
Imports Microsoft.CodeAnalysis.Formatting
Imports Microsoft.CodeAnalysis.Host
Imports Microsoft.CodeAnalysis.Shared.Collections
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.CodeStyle
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
 
Namespace Microsoft.CodeAnalysis.CodeCleanup.Providers
    <ExportCodeCleanupProvider(PredefinedCodeCleanupProviderNames.NormalizeModifiersOrOperators, LanguageNames.VisualBasic), [Shared]>
    <ExtensionOrder(After:=PredefinedCodeCleanupProviderNames.AddMissingTokens, Before:=PredefinedCodeCleanupProviderNames.Format)>
    Friend Class NormalizeModifiersOrOperatorsCodeCleanupProvider
        Implements ICodeCleanupProvider
 
        <ImportingConstructor>
        <SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification:="https://github.com/dotnet/roslyn/issues/42820")>
        Public Sub New()
        End Sub
 
        Public ReadOnly Property Name As String Implements ICodeCleanupProvider.Name
            Get
                Return PredefinedCodeCleanupProviderNames.NormalizeModifiersOrOperators
            End Get
        End Property
 
        Public Async Function CleanupAsync(document As Document, spans As ImmutableArray(Of TextSpan), options As CodeCleanupOptions, cancellationToken As CancellationToken) As Task(Of Document) Implements ICodeCleanupProvider.CleanupAsync
            Dim root = Await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(False)
            Dim newRoot = Await CleanupAsync(root, spans, options.FormattingOptions, document.Project.Solution.Services, cancellationToken).ConfigureAwait(False)
 
            Return If(root Is newRoot, document, document.WithSyntaxRoot(newRoot))
        End Function
 
        Public Function CleanupAsync(root As SyntaxNode, spans As ImmutableArray(Of TextSpan), options As SyntaxFormattingOptions, services As SolutionServices, cancellationToken As CancellationToken) As Task(Of SyntaxNode) Implements ICodeCleanupProvider.CleanupAsync
            Dim rewriter = New Rewriter(spans, cancellationToken)
            Dim newRoot = rewriter.Visit(root)
 
            Return Task.FromResult(If(root Is newRoot, root, newRoot))
        End Function
 
        Private Class Rewriter
            Inherits VisualBasicSyntaxRewriter
 
            ' list of modifier syntax kinds in order
            ' this order will be used when the rewriter re-order modifiers
            ' PERF: Using UShort instead of SyntaxKind as the element type so that the compiler can use array literal initialization
            Private Shared ReadOnly s_modifierKindsInOrder As SyntaxKind() =
                {SyntaxKind.PartialKeyword, SyntaxKind.DefaultKeyword, SyntaxKind.PrivateKeyword, SyntaxKind.ProtectedKeyword,
                SyntaxKind.PublicKeyword, SyntaxKind.FriendKeyword, SyntaxKind.NotOverridableKeyword, SyntaxKind.OverridableKeyword,
                SyntaxKind.MustOverrideKeyword, SyntaxKind.OverloadsKeyword, SyntaxKind.OverridesKeyword, SyntaxKind.MustInheritKeyword,
                SyntaxKind.NotInheritableKeyword, SyntaxKind.StaticKeyword, SyntaxKind.SharedKeyword, SyntaxKind.ShadowsKeyword,
                SyntaxKind.ReadOnlyKeyword, SyntaxKind.WriteOnlyKeyword, SyntaxKind.DimKeyword, SyntaxKind.ConstKeyword,
                SyntaxKind.WithEventsKeyword, SyntaxKind.WideningKeyword, SyntaxKind.NarrowingKeyword, SyntaxKind.CustomKeyword,
                SyntaxKind.AsyncKeyword, SyntaxKind.IteratorKeyword}
 
            Private Shared ReadOnly s_removeDimKeywordSet As HashSet(Of SyntaxKind) = New HashSet(Of SyntaxKind)(SyntaxFacts.EqualityComparer) From {
                SyntaxKind.PrivateKeyword, SyntaxKind.ProtectedKeyword, SyntaxKind.PublicKeyword, SyntaxKind.FriendKeyword,
                SyntaxKind.SharedKeyword, SyntaxKind.ShadowsKeyword, SyntaxKind.ReadOnlyKeyword}
 
            Private Shared ReadOnly s_normalizeOperatorsSet As Dictionary(Of SyntaxKind, List(Of SyntaxKind)) = New Dictionary(Of SyntaxKind, List(Of SyntaxKind))(SyntaxFacts.EqualityComparer) From {
                    {SyntaxKind.LessThanGreaterThanToken, New List(Of SyntaxKind) From {SyntaxKind.GreaterThanToken, SyntaxKind.LessThanToken}},
                    {SyntaxKind.GreaterThanEqualsToken, New List(Of SyntaxKind) From {SyntaxKind.EqualsToken, SyntaxKind.GreaterThanToken}},
                    {SyntaxKind.LessThanEqualsToken, New List(Of SyntaxKind) From {SyntaxKind.EqualsToken, SyntaxKind.LessThanToken}}
                }
 
            Private ReadOnly _spans As TextSpanMutableIntervalTree
            Private ReadOnly _cancellationToken As CancellationToken
 
            Shared Sub New()
                Debug.Assert(String.Join(",", s_modifierKindsInOrder.Select(AddressOf SyntaxFacts.GetText)) = VisualBasicCodeStyleOptions.PreferredModifierOrder.DefaultValue.Value)
            End Sub
 
            Public Sub New(spans As ImmutableArray(Of TextSpan), cancellationToken As CancellationToken)
                MyBase.New(visitIntoStructuredTrivia:=True)
 
                _spans = New TextSpanMutableIntervalTree(spans)
                _cancellationToken = cancellationToken
            End Sub
 
            Public Overrides Function Visit(node As SyntaxNode) As SyntaxNode
                _cancellationToken.ThrowIfCancellationRequested()
 
                ' if there are no overlapping spans, no need to walk down this node
                If node Is Nothing OrElse
                   Not _spans.HasIntervalThatOverlapsWith(node.FullSpan.Start, node.FullSpan.Length) Then
                    Return node
                End If
 
                ' walk down this path
                Return MyBase.Visit(node)
            End Function
 
            Public Overrides Function VisitModuleStatement(node As ModuleStatementSyntax) As SyntaxNode
                Return NormalizeModifiers(node, MyBase.VisitModuleStatement(node), Function(n) n.Modifiers, Function(n, modifiers) n.WithModifiers(modifiers))
            End Function
 
            Public Overrides Function VisitStructureStatement(node As StructureStatementSyntax) As SyntaxNode
                Return NormalizeModifiers(node, MyBase.VisitStructureStatement(node), Function(n) n.Modifiers, Function(n, modifiers) n.WithModifiers(modifiers))
            End Function
 
            Public Overrides Function VisitInterfaceStatement(node As InterfaceStatementSyntax) As SyntaxNode
                Return NormalizeModifiers(node, MyBase.VisitInterfaceStatement(node), Function(n) n.Modifiers, Function(n, modifiers) n.WithModifiers(modifiers))
            End Function
 
            Public Overrides Function VisitClassStatement(node As ClassStatementSyntax) As SyntaxNode
                Return NormalizeModifiers(node, MyBase.VisitClassStatement(node), Function(n) n.Modifiers, Function(n, modifiers) n.WithModifiers(modifiers))
            End Function
 
            Public Overrides Function VisitEnumStatement(node As EnumStatementSyntax) As SyntaxNode
                Return NormalizeModifiers(node, MyBase.VisitEnumStatement(node), Function(n) n.Modifiers, Function(n, modifiers) n.WithModifiers(modifiers))
            End Function
 
            Public Overrides Function VisitMethodStatement(node As MethodStatementSyntax) As SyntaxNode
                Return NormalizeModifiers(node, MyBase.VisitMethodStatement(node), Function(n) n.Modifiers, Function(n, modifiers) n.WithModifiers(modifiers))
            End Function
 
            Public Overrides Function VisitSubNewStatement(node As SubNewStatementSyntax) As SyntaxNode
                Return NormalizeModifiers(node, MyBase.VisitSubNewStatement(node), Function(n) n.Modifiers, Function(n, modifiers) n.WithModifiers(modifiers))
            End Function
 
            Public Overrides Function VisitDeclareStatement(node As DeclareStatementSyntax) As SyntaxNode
                Return NormalizeModifiers(node, MyBase.VisitDeclareStatement(node), Function(n) n.Modifiers, Function(n, modifiers) n.WithModifiers(modifiers))
            End Function
 
            Public Overrides Function VisitDelegateStatement(node As DelegateStatementSyntax) As SyntaxNode
                Return NormalizeModifiers(node, MyBase.VisitDelegateStatement(node), Function(n) n.Modifiers, Function(n, modifiers) n.WithModifiers(modifiers))
            End Function
 
            Public Overrides Function VisitEventStatement(node As EventStatementSyntax) As SyntaxNode
                Return NormalizeModifiers(node, MyBase.VisitEventStatement(node), Function(n) n.Modifiers, Function(n, modifiers) n.WithModifiers(modifiers))
            End Function
 
            Public Overrides Function VisitPropertyStatement(node As PropertyStatementSyntax) As SyntaxNode
                Return NormalizeModifiers(node, MyBase.VisitPropertyStatement(node), Function(n) n.Modifiers, Function(n, modifiers) n.WithModifiers(modifiers))
            End Function
 
            Public Overrides Function VisitAccessorStatement(node As AccessorStatementSyntax) As SyntaxNode
                Return NormalizeModifiers(node, MyBase.VisitAccessorStatement(node), Function(n) n.Modifiers, Function(n, modifiers) n.WithModifiers(modifiers))
            End Function
 
            Public Overrides Function VisitIncompleteMember(node As IncompleteMemberSyntax) As SyntaxNode
                ' don't do anything
                Return MyBase.VisitIncompleteMember(node)
            End Function
 
            Public Overrides Function VisitFieldDeclaration(node As FieldDeclarationSyntax) As SyntaxNode
                Return NormalizeModifiers(node, MyBase.VisitFieldDeclaration(node), Function(n) n.Modifiers, Function(n, modifiers) n.WithModifiers(modifiers))
            End Function
 
            Public Overrides Function VisitLocalDeclarationStatement(node As LocalDeclarationStatementSyntax) As SyntaxNode
                Return NormalizeModifiers(node, MyBase.VisitLocalDeclarationStatement(node), Function(n) n.Modifiers, Function(n, modifiers) n.WithModifiers(modifiers))
            End Function
 
            Public Overrides Function VisitParameterList(node As ParameterListSyntax) As SyntaxNode
                ' whole node must be under the span. otherwise, we just return
                Dim newNode = MyBase.VisitParameterList(node)
 
                ' bug # 12898
                ' decide not to automatically remove "ByVal"
#If False Then
                Dim span = node.Span
                If Not _spans.GetContainingIntervals(span.Start, span.Length).Any() Then
                    Return newNode
                End If
 
                ' remove any existing ByVal keyword
                Dim currentNode = DirectCast(newNode, ParameterListSyntax)
                For i = 0 To node.Parameters.Count - 1
                    currentNode = RemoveByValKeyword(currentNode, i)
                Next
 
                ' no changes
                If newNode Is currentNode Then
                    Return newNode
                End If
 
                ' replace whole parameter list
                _textChanges.Add(node.FullSpan, currentNode.GetFullText())
 
                Return currentNode
#End If
 
                Return newNode
            End Function
 
            Public Overrides Function VisitLambdaHeader(node As LambdaHeaderSyntax) As SyntaxNode
                ' lambda can have async and iterator modifiers but we currently don't support those
                Return node
            End Function
 
            Public Overrides Function VisitOperatorStatement(node As OperatorStatementSyntax) As SyntaxNode
                Dim visitedNode = DirectCast(MyBase.VisitOperatorStatement(node), OperatorStatementSyntax)
 
                Dim span = node.Span
                If Not _spans.HasIntervalThatContains(span.Start, span.Length) Then
                    Return visitedNode
                End If
 
                ' operator sometimes requires a fix up outside of modifiers
                Dim fixedUpNode = OperatorStatementSpecialFixup(visitedNode)
 
                ' now, normalize modifiers
                Dim newNode = NormalizeModifiers(node, fixedUpNode, Function(n) n.Modifiers, Function(n, modifiers) n.WithModifiers(modifiers))
 
                Dim [operator] = NormalizeOperator(
                                    newNode.OperatorToken,
                                    Function(t) t.Kind = SyntaxKind.GreaterThanToken,
                                    Function(t) t.TrailingTrivia,
                                    Function(t) New List(Of SyntaxKind) From {SyntaxKind.LessThanToken},
                                    Function(t, i)
                                        Return t.CopyAnnotationsTo(
                                            SyntaxFactory.Token(
                                                t.LeadingTrivia.Concat(t.TrailingTrivia.Take(i)).ToSyntaxTriviaList(),
                                                SyntaxKind.LessThanGreaterThanToken,
                                                t.TrailingTrivia.Skip(i + 1).ToSyntaxTriviaList()))
                                    End Function)
 
                If [operator].Kind = SyntaxKind.None Then
                    Return newNode
                End If
 
                Return newNode.WithOperatorToken([operator])
            End Function
 
            Public Overrides Function VisitBinaryExpression(node As BinaryExpressionSyntax) As SyntaxNode
                ' normalize binary operators
                Dim binaryOperator = DirectCast(MyBase.VisitBinaryExpression(node), BinaryExpressionSyntax)
 
                ' quick check. operator must be missing
                If Not binaryOperator.OperatorToken.IsMissing Then
                    Return binaryOperator
                End If
 
                Dim span = node.Span
                If Not _spans.HasIntervalThatContains(span.Start, span.Length) Then
                    Return binaryOperator
                End If
 
                ' and the operator must be one of kinds that we are interested in
                Dim [operator] = NormalizeOperator(
                                    binaryOperator.OperatorToken,
                                    Function(t) s_normalizeOperatorsSet.ContainsKey(t.Kind),
                                    Function(t) t.LeadingTrivia,
                                    Function(t) s_normalizeOperatorsSet(t.Kind),
                                    Function(t, i)
                                        Return t.CopyAnnotationsTo(
                                            SyntaxFactory.Token(
                                                t.LeadingTrivia.Take(i).ToSyntaxTriviaList(),
                                                t.Kind,
                                                t.LeadingTrivia.Skip(i + 1).Concat(t.TrailingTrivia).ToSyntaxTriviaList()))
                                    End Function)
 
                If [operator].Kind = SyntaxKind.None Then
                    Return binaryOperator
                End If
 
                Return binaryOperator.WithOperatorToken([operator])
            End Function
 
            Public Overrides Function VisitToken(token As SyntaxToken) As SyntaxToken
                Dim newToken = MyBase.VisitToken(token)
 
                Dim span = token.Span
                If Not _spans.HasIntervalThatContains(span.Start, span.Length) Then
                    Return newToken
                End If
 
                If token.IsMissing OrElse Not (SyntaxFacts.IsOperator(token.Kind) OrElse token.IsKind(SyntaxKind.ColonEqualsToken)) Then
                    Return newToken
                End If
 
                Dim actualText = token.ToString()
                Dim expectedText = SyntaxFacts.GetText(token.Kind)
 
                If String.IsNullOrWhiteSpace(expectedText) OrElse actualText = expectedText Then
                    Return newToken
                End If
 
                Return SyntaxFactory.Token(newToken.LeadingTrivia, newToken.Kind, newToken.TrailingTrivia, expectedText)
            End Function
 
            ''' <summary>
            ''' this will put operator token and modifier tokens in right order
            ''' </summary>
            Private Shared Function OperatorStatementSpecialFixup(node As OperatorStatementSyntax) As OperatorStatementSyntax
                ' first check whether operator is missing
                If Not node.OperatorToken.IsMissing Then
                    Return node
                End If
 
                ' check whether operator has missing stuff in skipped token list
                Dim skippedTokens = node.OperatorToken.TrailingTrivia _
                                                 .Where(Function(t) t.Kind = SyntaxKind.SkippedTokensTrivia) _
                                                 .Select(Function(t) DirectCast(t.GetStructure(), SkippedTokensTriviaSyntax)) _
                                                 .SelectMany(Function(t) t.Tokens)
 
                ' there must be 2 skipped tokens
                If skippedTokens.Count <> 2 Then
                    Return node
                End If
 
                Dim last = skippedTokens.Last()
                If Not SyntaxFacts.IsOperatorStatementOperatorToken(last.Kind) Then
                    Return node
                End If
 
                ' reorder some tokens
                Dim newNode = node.WithModifiers(node.Modifiers.AddRange(skippedTokens.Take(skippedTokens.Count - 1).ToArray())).WithOperatorToken(last)
                If Not ValidOperatorStatement(newNode) Then
                    Return node
                End If
 
                Return newNode
            End Function
 
            ''' <summary>
            ''' check whether given operator statement is valid or not
            ''' </summary>
            Private Shared Function ValidOperatorStatement(node As OperatorStatementSyntax) As Boolean
                Dim parsableStatementText = node.NormalizeWhitespace().ToString()
                Dim parsableCompilationUnit = "Class C" + vbCrLf + parsableStatementText + vbCrLf + "End Operator" + vbCrLf + "End Class"
                Dim parsedNode = SyntaxFactory.ParseCompilationUnit(parsableCompilationUnit)
 
                Return Not parsedNode.ContainsDiagnostics()
            End Function
 
            ''' <summary>
            ''' normalize operator
            ''' </summary>
            Private Shared Function NormalizeOperator(
                [operator] As SyntaxToken,
                checker As Func(Of SyntaxToken, Boolean),
                triviaListGetter As Func(Of SyntaxToken, SyntaxTriviaList),
                tokenKindsGetter As Func(Of SyntaxToken, List(Of SyntaxKind)),
                operatorCreator As Func(Of SyntaxToken, Integer, SyntaxToken)) As SyntaxToken
 
                If Not checker([operator]) Then
                    Return Nothing
                End If
 
                ' now, it should have skipped token trivia in trivia list
                Dim skippedTokenTrivia = triviaListGetter([operator]).FirstOrDefault(Function(t) t.Kind = SyntaxKind.SkippedTokensTrivia)
                If skippedTokenTrivia.Kind = SyntaxKind.None Then
                    Return Nothing
                End If
 
                ' token in the skipped token list must match what we are expecting
                Dim skippedTokensList = DirectCast(skippedTokenTrivia.GetStructure(), SkippedTokensTriviaSyntax)
 
                Dim actual = skippedTokensList.Tokens
                Dim expected = tokenKindsGetter([operator])
                If actual.Count <> expected.Count Then
                    Return Nothing
                End If
 
                Dim i = -1
                For Each token In actual
                    i = i + 1
                    If token.Kind <> expected(i) Then
                        Return Nothing
                    End If
                Next
 
                ' okay, looks like it is what we are expecting. let's fix it up
                ' move everything after skippedTokenTrivia to trailing trivia
                Dim index = -1
                Dim list = triviaListGetter([operator])
                For i = 0 To list.Count - 1
                    If list(i) = skippedTokenTrivia Then
                        index = i
                        Exit For
                    End If
                Next
 
                ' it must exist
                Contract.ThrowIfFalse(index >= 0)
 
                Return operatorCreator([operator], index)
            End Function
 
            ''' <summary>
            ''' reorder modifiers in the list
            ''' </summary>
            Private Shared Function ReorderModifiers(modifiers As SyntaxTokenList) As SyntaxTokenList
                ' quick check - if there is only one or less modifier, return as it is
                If modifiers.Count <= 1 Then
                    Return modifiers
                End If
 
                ' do quick check to see whether modifiers are already in right order
                If AreModifiersInRightOrder(modifiers) Then
                    Return modifiers
                End If
 
                ' re-create the list with trivia from old modifier token list
                Dim currentModifierIndex = 0
                Dim result = New List(Of SyntaxToken)(modifiers.Count)
 
                Dim modifierList = modifiers.ToList()
                For Each k In s_modifierKindsInOrder
                    ' we found all modifiers
                    If currentModifierIndex = modifierList.Count Then
                        Exit For
                    End If
 
                    Dim tokenInRightOrder = modifierList.FirstOrDefault(Function(m) m.Kind = k)
 
                    ' if we didn't find, move on to next one
                    If tokenInRightOrder.Kind = SyntaxKind.None Then
                        Continue For
                    End If
 
                    ' we found a modifier, re-create list in right order with right trivia from right original token
                    Dim originalToken = modifierList(currentModifierIndex)
                    result.Add(tokenInRightOrder.With(originalToken.LeadingTrivia, originalToken.TrailingTrivia))
 
                    currentModifierIndex += 1
                Next
 
                ' Verify that all unique modifiers were added to the result.
                ' The number added to the result count is the duplicate modifier count in the input modifierList.
                Debug.Assert(modifierList.Count = result.Count +
                             modifierList.GroupBy(Function(token) token.Kind).SelectMany(Function(grp) grp.Skip(1)).Count)
                Return SyntaxFactory.TokenList(result)
            End Function
 
            ''' <summary>
            ''' normalize modifier list of the node and record changes if there is any change
            ''' </summary>
            Private Function NormalizeModifiers(Of T As SyntaxNode)(originalNode As T, node As SyntaxNode, modifiersGetter As Func(Of T, SyntaxTokenList), withModifiers As Func(Of T, SyntaxTokenList, T)) As T
                Return NormalizeModifiers(originalNode, DirectCast(node, T), modifiersGetter, withModifiers)
            End Function
 
            ''' <summary>
            ''' normalize modifier list of the node and record changes if there is any change
            ''' </summary>
            Private Function NormalizeModifiers(Of T As SyntaxNode)(originalNode As T, node As T, modifiersGetter As Func(Of T, SyntaxTokenList), withModifiers As Func(Of T, SyntaxTokenList, T)) As T
                Dim modifiers = modifiersGetter(node)
 
                ' if number of modifiers are less than 1, we don't need to do anything
                If modifiers.Count <= 1 Then
                    Return node
                End If
 
                ' whole node must be under span, otherwise, we will just return
                Dim span = originalNode.Span
                If Not _spans.HasIntervalThatContains(span.Start, span.Length) Then
                    Return node
                End If
 
                ' try normalize modifier list
                Dim newNode = withModifiers(node, ReorderModifiers(modifiers))
 
                ' new modifier list
                Dim newModifiers = modifiersGetter(newNode)
 
                ' check whether we need to remove "Dim" keyword or not
                If newModifiers.Any(Function(m) s_removeDimKeywordSet.Contains(m.Kind)) Then
                    newNode = RemoveDimKeyword(newNode, modifiersGetter)
                End If
 
                ' no change
                If newNode Is node Then
                    Return node
                End If
 
                ' add text change
                Dim originalModifiers = modifiersGetter(originalNode)
                Contract.ThrowIfFalse(originalModifiers.Count > 0)
 
                Return newNode
            End Function
 
            ''' <summary>
            ''' remove "Dim" keyword if present
            ''' </summary>
            Private Shared Function RemoveDimKeyword(Of T As SyntaxNode)(node As T, modifiersGetter As Func(Of T, SyntaxTokenList)) As T
                Return RemoveModifierKeyword(node, modifiersGetter, SyntaxKind.DimKeyword)
            End Function
 
            ''' <summary>
            ''' remove a modifier from the given node
            ''' </summary>
            Private Shared Function RemoveModifierKeyword(Of T As SyntaxNode)(node As T, modifiersGetter As Func(Of T, SyntaxTokenList), modifierKind As SyntaxKind) As T
                Dim modifiers = modifiersGetter(node)
 
                ' "Dim" doesn't exist
                Dim modifier = modifiers.FirstOrDefault(Function(m) m.Kind = modifierKind)
                If modifier.Kind = SyntaxKind.None Then
                    Return node
                End If
 
                ' merge trivia belong to the modifier to be deleted
                Dim trivia = modifier.LeadingTrivia.Concat(modifier.TrailingTrivia)
 
                ' we have node which owns tokens around modifiers. just replace tokens in the node in case we need to
                ' touch tokens outside of the modifier list
                Dim previousToken = modifier.GetPreviousToken(includeZeroWidth:=True)
                Dim newPreviousToken = previousToken.WithAppendedTrailingTrivia(trivia)
 
                ' replace previous token and remove "Dim"
                Return node.ReplaceTokens(SpecializedCollections.SingletonEnumerable(modifier).Concat(previousToken),
                                   Function(o, n)
                                       If o = modifier Then
                                           Return Nothing
                                       ElseIf o = previousToken Then
                                           Return newPreviousToken
                                       End If
 
                                       Throw ExceptionUtilities.UnexpectedValue(o)
                                   End Function)
            End Function
 
            ''' <summary>
            ''' check whether given modifiers are in right order (in sync with ModifierKindsInOrder list)
            ''' </summary>
            Private Shared Function AreModifiersInRightOrder(modifiers As SyntaxTokenList) As Boolean
                Dim startIndex = 0
                For Each modifier In modifiers
                    Dim newIndex = s_modifierKindsInOrder.IndexOf(modifier.Kind, startIndex)
                    If newIndex = 0 AndAlso startIndex = 0 Then
                        ' very first search with matching the very first modifier in the modifier orders
                        startIndex = newIndex + 1
                    ElseIf startIndex < newIndex Then
                        ' new one is after the previous one in order
                        startIndex = newIndex + 1
                    Else
                        ' oops, in wrong order
                        Return False
                    End If
                Next
 
                Return True
            End Function
        End Class
    End Class
End Namespace