File: CodeCleanup\Providers\AddMissingTokensCodeCleanupProvider.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.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
 
Namespace Microsoft.CodeAnalysis.CodeCleanup.Providers
    <ExportCodeCleanupProvider(PredefinedCodeCleanupProviderNames.AddMissingTokens, LanguageNames.VisualBasic), [Shared]>
    <ExtensionOrder(After:=PredefinedCodeCleanupProviderNames.CaseCorrection, Before:=PredefinedCodeCleanupProviderNames.Format)>
    Friend Class AddMissingTokensCodeCleanupProvider
        Inherits AbstractTokensCodeCleanupProvider
 
        <ImportingConstructor>
        <SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification:="https://github.com/dotnet/roslyn/issues/42820")>
        Public Sub New()
        End Sub
 
        Public Overrides ReadOnly Property Name As String
            Get
                Return PredefinedCodeCleanupProviderNames.AddMissingTokens
            End Get
        End Property
 
        Protected Overrides Async Function GetRewriterAsync(document As Document, root As SyntaxNode, spans As ImmutableArray(Of TextSpan), cancellationToken As CancellationToken) As Task(Of Rewriter)
            Return Await AddMissingTokensRewriter.CreateAsync(document, spans, cancellationToken).ConfigureAwait(False)
        End Function
 
        Private Class AddMissingTokensRewriter
            Inherits AbstractTokensCodeCleanupProvider.Rewriter
 
            Private ReadOnly _model As SemanticModel
 
            Private Sub New(semanticModel As SemanticModel, spans As ImmutableArray(Of TextSpan), cancellationToken As CancellationToken)
                MyBase.New(spans, cancellationToken)
 
                Me._model = semanticModel
            End Sub
 
            Public Shared Async Function CreateAsync(document As Document, spans As ImmutableArray(Of TextSpan), cancellationToken As CancellationToken) As Task(Of AddMissingTokensRewriter)
                Dim modifiedSpan = spans.Collapse()
                Dim semanticModel = If(document Is Nothing, Nothing,
                    Await document.ReuseExistingSpeculativeModelAsync(modifiedSpan, cancellationToken).ConfigureAwait(False))
 
                Return New AddMissingTokensRewriter(semanticModel, spans, cancellationToken)
            End Function
 
            Public Overrides Function Visit(node As SyntaxNode) As SyntaxNode
                If TypeOf node Is ExpressionSyntax Then
                    Return VisitExpression(DirectCast(node, ExpressionSyntax))
                Else
                    Return MyBase.Visit(node)
                End If
            End Function
 
            Private Function VisitExpression(node As ExpressionSyntax) As SyntaxNode
                If Not ShouldRewrite(node) Then
                    Return node
                End If
 
                Return AddParenthesesTransform(node, MyBase.Visit(node),
                                                 Function()
                                                     ' we only care whole name not part of dotted names
                                                     Dim name As NameSyntax = TryCast(node, NameSyntax)
                                                     If name Is Nothing OrElse TypeOf name.Parent Is NameSyntax Then
                                                         Return False
                                                     End If
 
                                                     Return CheckName(name)
                                                 End Function,
                                                 Function(n) DirectCast(n, InvocationExpressionSyntax).ArgumentList,
                                                 Function(n) SyntaxFactory.InvocationExpression(n, SyntaxFactory.ArgumentList()),
                                                 Function(n) IsMethodSymbol(DirectCast(n, ExpressionSyntax)))
            End Function
 
            Private Function CheckName(name As NameSyntax) As Boolean
                If _underStructuredTrivia OrElse name.IsStructuredTrivia() OrElse name.IsMissing Then
                    Return False
                End If
 
                ' can't/don't try to transform member access to invocation
                If TypeOf name.Parent Is MemberAccessExpressionSyntax OrElse
                   TypeOf name.Parent Is TupleElementSyntax OrElse
                   name.CheckParent(Of AttributeSyntax)(Function(p) p.Name Is name) OrElse
                   name.CheckParent(Of ImplementsClauseSyntax)(Function(p) p.InterfaceMembers.Any(Function(i) i Is name)) OrElse
                   name.CheckParent(Of UnaryExpressionSyntax)(Function(p) p.Kind = SyntaxKind.AddressOfExpression AndAlso p.Operand Is name) OrElse
                   name.CheckParent(Of InvocationExpressionSyntax)(Function(p) p.Expression Is name) OrElse
                   name.CheckParent(Of NamedFieldInitializerSyntax)(Function(p) p.Name Is name) OrElse
                   name.CheckParent(Of ImplementsStatementSyntax)(Function(p) p.Types.Any(Function(t) t Is name)) OrElse
                   name.CheckParent(Of HandlesClauseItemSyntax)(Function(p) p.EventMember Is name) OrElse
                   name.CheckParent(Of ObjectCreationExpressionSyntax)(Function(p) p.Type Is name) OrElse
                   name.CheckParent(Of ArrayCreationExpressionSyntax)(Function(p) p.Type Is name) OrElse
                   name.CheckParent(Of ArrayTypeSyntax)(Function(p) p.ElementType Is name) OrElse
                   name.CheckParent(Of SimpleAsClauseSyntax)(Function(p) p.Type Is name) OrElse
                   name.CheckParent(Of TypeConstraintSyntax)(Function(p) p.Type Is name) OrElse
                   name.CheckParent(Of GetTypeExpressionSyntax)(Function(p) p.Type Is name) OrElse
                   name.CheckParent(Of TypeOfExpressionSyntax)(Function(p) p.Type Is name) OrElse
                   name.CheckParent(Of CastExpressionSyntax)(Function(p) p.Type Is name) OrElse
                   name.CheckParent(Of ForEachStatementSyntax)(Function(p) p.ControlVariable Is name) OrElse
                   name.CheckParent(Of ForStatementSyntax)(Function(p) p.ControlVariable Is name) OrElse
                   name.CheckParent(Of AssignmentStatementSyntax)(Function(p) p.Left Is name) OrElse
                   name.CheckParent(Of TypeArgumentListSyntax)(Function(p) p.Arguments.Any(Function(i) i Is name)) OrElse
                   name.CheckParent(Of SimpleArgumentSyntax)(Function(p) p.IsNamed AndAlso p.NameColonEquals.Name Is name) OrElse
                   name.CheckParent(Of CastExpressionSyntax)(Function(p) p.Type Is name) OrElse
                   name.CheckParent(Of SimpleArgumentSyntax)(Function(p) p.Expression Is name) OrElse
                   name.CheckParent(Of NameOfExpressionSyntax)(Function(p) p.Argument Is name) Then
                    Return False
                End If
 
                Return True
            End Function
 
            Private Function IsMethodSymbol(expression As ExpressionSyntax) As Boolean
                If Me._model Is Nothing Then
                    Return False
                End If
 
                Dim symbols = Me._model.GetSymbolInfo(expression, _cancellationToken).GetAllSymbols()
                Return symbols.Any() AndAlso symbols.All(
                    Function(s) If(TryCast(s, IMethodSymbol)?.MethodKind = MethodKind.Ordinary, False))
            End Function
 
            Private Function IsDelegateType(expression As ExpressionSyntax) As Boolean
                If Me._model Is Nothing Then
                    Return False
                End If
 
                Dim type = Me._model.GetTypeInfo(expression, _cancellationToken).Type
                Return type.IsDelegateType
            End Function
 
            Public Overrides Function VisitInvocationExpression(node As InvocationExpressionSyntax) As SyntaxNode
                Dim newNode = MyBase.VisitInvocationExpression(node)
 
                ' make sure we are not under structured trivia
                If _underStructuredTrivia Then
                    Return newNode
                End If
 
                If Not TypeOf node.Expression Is NameSyntax AndAlso
                   Not TypeOf node.Expression Is ParenthesizedExpressionSyntax AndAlso
                   Not TypeOf node.Expression Is MemberAccessExpressionSyntax Then
                    Return newNode
                End If
 
                Dim semanticChecker As Func(Of InvocationExpressionSyntax, Boolean) =
                    Function(n) IsMethodSymbol(n.Expression) OrElse IsDelegateType(n.Expression)
 
                Return AddParenthesesTransform(
                        node, newNode, Function(n) n.Expression.Span.Length > 0, Function(n) n.ArgumentList, Function(n) n.WithArgumentList(SyntaxFactory.ArgumentList()), semanticChecker)
            End Function
 
            Public Overrides Function VisitRaiseEventStatement(node As RaiseEventStatementSyntax) As SyntaxNode
                Return AddParenthesesTransform(
                    node, MyBase.VisitRaiseEventStatement(node), Function(n) Not n.Name.IsMissing, Function(n) n.ArgumentList, Function(n) n.WithArgumentList(SyntaxFactory.ArgumentList()))
            End Function
 
            Public Overrides Function VisitMethodStatement(node As MethodStatementSyntax) As SyntaxNode
                Dim rewrittenMethod = DirectCast(AddParameterListTransform(node, MyBase.VisitMethodStatement(node), Function(n) Not n.Identifier.IsMissing), MethodStatementSyntax)
                Return AsyncOrIteratorFunctionReturnTypeFixer.RewriteMethodStatement(rewrittenMethod, Me._model, node, Me._cancellationToken)
            End Function
 
            Public Overrides Function VisitSubNewStatement(node As SubNewStatementSyntax) As SyntaxNode
                Return AddParameterListTransform(node, MyBase.VisitSubNewStatement(node), Function(n) Not n.NewKeyword.IsMissing)
            End Function
 
            Public Overrides Function VisitDeclareStatement(node As DeclareStatementSyntax) As SyntaxNode
                Return AddParameterListTransform(node, MyBase.VisitDeclareStatement(node), Function(n) Not n.Identifier.IsMissing)
            End Function
 
            Public Overrides Function VisitDelegateStatement(node As DelegateStatementSyntax) As SyntaxNode
                Return AddParameterListTransform(node, MyBase.VisitDelegateStatement(node), Function(n) Not n.Identifier.IsMissing)
            End Function
 
            Public Overrides Function VisitEventStatement(node As EventStatementSyntax) As SyntaxNode
                If node.AsClause IsNot Nothing Then
                    Return MyBase.VisitEventStatement(node)
                End If
 
                Return AddParameterListTransform(node, MyBase.VisitEventStatement(node), Function(n) Not n.Identifier.IsMissing)
            End Function
 
            Public Overrides Function VisitAccessorStatement(node As AccessorStatementSyntax) As SyntaxNode
                Dim newNode = MyBase.VisitAccessorStatement(node)
                If node.DeclarationKeyword.Kind <> SyntaxKind.AddHandlerKeyword AndAlso
                   node.DeclarationKeyword.Kind <> SyntaxKind.RemoveHandlerKeyword AndAlso
                   node.DeclarationKeyword.Kind <> SyntaxKind.RaiseEventKeyword Then
                    Return newNode
                End If
 
                Return AddParameterListTransform(node, newNode, Function(n) Not n.DeclarationKeyword.IsMissing)
            End Function
 
            Public Overrides Function VisitAttribute(node As AttributeSyntax) As SyntaxNode
                ' we decide not to auto insert parentheses for attribute
                Return MyBase.VisitAttribute(node)
            End Function
 
            Public Overrides Function VisitOperatorStatement(node As OperatorStatementSyntax) As SyntaxNode
                ' don't auto insert parentheses
                ' these methods are okay to be removed. but it is here to show other cases where parse tree node can have parentheses
                Return MyBase.VisitOperatorStatement(node)
            End Function
 
            Public Overrides Function VisitPropertyStatement(node As PropertyStatementSyntax) As SyntaxNode
                ' don't auto insert parentheses
                ' these methods are okay to be removed. but it is here to show other cases where parse tree node can have parentheses
                Return MyBase.VisitPropertyStatement(node)
            End Function
 
            Public Overrides Function VisitLambdaHeader(node As LambdaHeaderSyntax) As SyntaxNode
                Dim rewrittenLambdaHeader = DirectCast(MyBase.VisitLambdaHeader(node), LambdaHeaderSyntax)
                rewrittenLambdaHeader = AsyncOrIteratorFunctionReturnTypeFixer.RewriteLambdaHeader(rewrittenLambdaHeader, Me._model, node, Me._cancellationToken)
                Return AddParameterListTransform(node, rewrittenLambdaHeader, Function(n) True)
            End Function
 
            Private Shared Function TryFixupTrivia(Of T As SyntaxNode)(node As T, previousToken As SyntaxToken, lastToken As SyntaxToken, ByRef newNode As T) As Boolean
                ' initialize to initial value
                newNode = Nothing
 
                ' hold onto the trivia
                Dim prevTrailingTrivia = previousToken.TrailingTrivia
 
                ' if previous token is not part of node and if it has any trivia, don't do anything
                If Not node.DescendantTokens().Any(Function(token) token = previousToken) AndAlso prevTrailingTrivia.Count > 0 Then
                    Return False
                End If
 
                ' remove the trivia from the token
                Dim previousTokenWithoutTrailingTrivia = previousToken.WithTrailingTrivia(SyntaxFactory.ElasticMarker)
 
                ' If previousToken has trailing WhitespaceTrivia, strip off the trailing WhitespaceTrivia from the lastToken.
                Dim lastTrailingTrivia = lastToken.TrailingTrivia
                If prevTrailingTrivia.Any(SyntaxKind.WhitespaceTrivia) Then
                    lastTrailingTrivia = lastTrailingTrivia.WithoutLeadingWhitespaceOrEndOfLine()
                End If
 
                ' get the trivia and attach it to the last token
                Dim lastTokenWithTrailingTrivia = lastToken.WithTrailingTrivia(prevTrailingTrivia.Concat(lastTrailingTrivia))
 
                ' replace tokens
                newNode = node.ReplaceTokens(SpecializedCollections.SingletonEnumerable(previousToken).Concat(lastToken),
                                              Function(o, m)
                                                  If o = previousToken Then
                                                      Return previousTokenWithoutTrailingTrivia
                                                  ElseIf o = lastToken Then
                                                      Return lastTokenWithTrailingTrivia
                                                  End If
 
                                                  Throw ExceptionUtilities.UnexpectedValue(o)
                                              End Function)
 
                Return True
            End Function
 
            Private Function AddParameterListTransform(Of T As MethodBaseSyntax)(node As T, newNode As SyntaxNode, nameChecker As Func(Of T, Boolean)) As T
                Dim transform As Func(Of T, T) = Function(n As T)
                                                     Dim newParamList = SyntaxFactory.ParameterList()
                                                     If n.ParameterList IsNot Nothing Then
                                                         If n.ParameterList.HasLeadingTrivia Then
                                                             newParamList = newParamList.WithLeadingTrivia(n.ParameterList.GetLeadingTrivia)
                                                         End If
 
                                                         If n.ParameterList.HasTrailingTrivia Then
                                                             newParamList = newParamList.WithTrailingTrivia(n.ParameterList.GetTrailingTrivia)
                                                         End If
                                                     End If
 
                                                     Dim nodeWithParams = DirectCast(n.WithParameterList(newParamList), T)
                                                     If n.HasTrailingTrivia AndAlso nodeWithParams.GetLastToken() = nodeWithParams.ParameterList.CloseParenToken Then
                                                         Dim trailing = n.GetTrailingTrivia
                                                         nodeWithParams = DirectCast(n _
                                                             .WithoutTrailingTrivia() _
                                                             .WithParameterList(newParamList) _
                                                             .WithTrailingTrivia(trailing), T)
                                                     End If
 
                                                     Return nodeWithParams
                                                 End Function
 
                Return AddParenthesesTransform(node, newNode, nameChecker, Function(n) n.ParameterList, transform)
            End Function
 
            Private Function AddParenthesesTransform(Of T As SyntaxNode)(
                originalNode As T,
                node As SyntaxNode,
                nameChecker As Func(Of T, Boolean),
                listGetter As Func(Of T, SyntaxNode),
                withTransform As Func(Of T, T),
                Optional semanticPredicate As Func(Of T, Boolean) = Nothing
            ) As T
                Dim newNode = DirectCast(node, T)
                If Not nameChecker(newNode) Then
                    Return newNode
                End If
 
                Dim syntaxPredicate As Func(Of Boolean) = Function()
                                                              Dim list = listGetter(originalNode)
                                                              If list Is Nothing Then
                                                                  Return True
                                                              End If
 
                                                              Dim paramList = TryCast(list, ParameterListSyntax)
                                                              If paramList IsNot Nothing Then
                                                                  Return paramList.Parameters = Nothing AndAlso
                                                                         paramList.OpenParenToken.IsMissing AndAlso
                                                                         paramList.CloseParenToken.IsMissing
                                                              End If
 
                                                              Dim argsList = TryCast(list, ArgumentListSyntax)
                                                              Return argsList IsNot Nothing AndAlso
                                                                     argsList.Arguments = Nothing AndAlso
                                                                     argsList.OpenParenToken.IsMissing AndAlso
                                                                     argsList.CloseParenToken.IsMissing
                                                          End Function
 
                Return AddParenthesesTransform(originalNode, node, syntaxPredicate, listGetter, withTransform, semanticPredicate)
            End Function
 
            Private Function AddParenthesesTransform(Of T As SyntaxNode)(
                originalNode As T,
                node As SyntaxNode,
                syntaxPredicate As Func(Of Boolean),
                listGetter As Func(Of T, SyntaxNode),
                transform As Func(Of T, T),
                Optional semanticPredicate As Func(Of T, Boolean) = Nothing
            ) As T
                Dim span = originalNode.Span
 
                If syntaxPredicate() AndAlso
                   _spans.HasIntervalThatContains(span.Start, span.Length) AndAlso
                   CheckSkippedTriviaForMissingToken(originalNode, SyntaxKind.OpenParenToken, SyntaxKind.CloseParenToken) Then
 
                    Dim transformedNode = transform(DirectCast(node, T))
 
                    ' previous token can be different per different node types. 
                    ' it could be name or close paren of type parameter list and etc. also can be different based on
                    ' what token is omitted
                    ' get one that actually exist and get trailing trivia of that token
                    Dim fixedUpNode As T = Nothing
 
                    Dim list = listGetter(transformedNode)
                    Dim previousToken = list.GetFirstToken(includeZeroWidth:=True).GetPreviousToken(includeZeroWidth:=True)
                    Dim lastToken = list.GetLastToken(includeZeroWidth:=True)
 
                    If Not TryFixupTrivia(transformedNode, previousToken, lastToken, fixedUpNode) Then
                        Return DirectCast(node, T)
                    End If
 
                    ' semanticPredicate is invoked at the last step as it is the most expensive operation which requires building the compilation for semantic validations.
                    If semanticPredicate Is Nothing OrElse semanticPredicate(originalNode) Then
                        Return DirectCast(fixedUpNode, T)
                    End If
                End If
 
                Return DirectCast(node, T)
            End Function
 
            Private Shared Function CheckSkippedTriviaForMissingToken(node As SyntaxNode, ParamArray kinds As SyntaxKind()) As Boolean
                Dim lastToken = node.GetLastToken(includeZeroWidth:=True)
                If lastToken.TrailingTrivia.Count = 0 Then
                    Return True
                End If
 
                Return Not lastToken _
                           .TrailingTrivia _
                           .Where(Function(t) t.Kind = SyntaxKind.SkippedTokensTrivia) _
                           .SelectMany(Function(t) DirectCast(t.GetStructure(), SkippedTokensTriviaSyntax).Tokens) _
                           .Any(Function(t) kinds.Contains(t.Kind))
            End Function
 
            Public Overrides Function VisitIfStatement(node As IfStatementSyntax) As SyntaxNode
                Return AddMissingOrOmittedTokenTransform(node, MyBase.VisitIfStatement(node), Function(n) n.ThenKeyword, SyntaxKind.ThenKeyword)
            End Function
 
            Public Overrides Function VisitIfDirectiveTrivia(node As IfDirectiveTriviaSyntax) As SyntaxNode
                Return AddMissingOrOmittedTokenTransform(node, MyBase.VisitIfDirectiveTrivia(node), Function(n) n.ThenKeyword, SyntaxKind.ThenKeyword)
            End Function
 
            Public Overrides Function VisitElseIfStatement(node As ElseIfStatementSyntax) As SyntaxNode
                Return AddMissingOrOmittedTokenTransform(node, MyBase.VisitElseIfStatement(node), Function(n) n.ThenKeyword, SyntaxKind.ThenKeyword)
            End Function
 
            Public Overrides Function VisitTypeArgumentList(node As TypeArgumentListSyntax) As SyntaxNode
                Return AddMissingOrOmittedTokenTransform(node, MyBase.VisitTypeArgumentList(node), Function(n) n.OfKeyword, SyntaxKind.OfKeyword)
            End Function
 
            Public Overrides Function VisitTypeParameterList(node As TypeParameterListSyntax) As SyntaxNode
                Return AddMissingOrOmittedTokenTransform(node, MyBase.VisitTypeParameterList(node), Function(n) n.OfKeyword, SyntaxKind.OfKeyword)
            End Function
 
            Public Overrides Function VisitContinueStatement(node As ContinueStatementSyntax) As SyntaxNode
                Return AddMissingOrOmittedTokenTransform(node, MyBase.VisitContinueStatement(node), Function(n) n.BlockKeyword, SyntaxKind.DoKeyword, SyntaxKind.ForKeyword, SyntaxKind.WhileKeyword)
            End Function
 
            Public Overrides Function VisitOptionStatement(node As OptionStatementSyntax) As SyntaxNode
                Select Case node.NameKeyword.Kind
                    Case SyntaxKind.ExplicitKeyword,
                        SyntaxKind.InferKeyword,
                        SyntaxKind.StrictKeyword
                        Return AddMissingOrOmittedTokenTransform(node, node, Function(n) n.ValueKeyword, SyntaxKind.OnKeyword, SyntaxKind.OffKeyword)
                    Case Else
                        Return node
                End Select
            End Function
 
            Public Overrides Function VisitSelectStatement(node As SelectStatementSyntax) As SyntaxNode
                Dim newNode = DirectCast(MyBase.VisitSelectStatement(node), SelectStatementSyntax)
                Return If(newNode.CaseKeyword.Kind = SyntaxKind.None,
                           newNode.WithCaseKeyword(SyntaxFactory.Token(SyntaxKind.CaseKeyword)),
                           newNode)
            End Function
 
            Private Function AddMissingOrOmittedTokenTransform(Of T As SyntaxNode)(
                originalNode As T, node As SyntaxNode, tokenGetter As Func(Of T, SyntaxToken), ParamArray kinds As SyntaxKind()) As T
 
                Dim newNode = DirectCast(node, T)
                If Not CheckSkippedTriviaForMissingToken(originalNode, kinds) Then
                    Return newNode
                End If
 
                Dim newToken = tokenGetter(newNode)
                Dim processedToken = ProcessToken(tokenGetter(originalNode), newToken, newNode)
                If processedToken <> newToken Then
                    Dim replacedNode = ReplaceOrSetToken(newNode, newToken, processedToken)
 
                    Dim replacedToken = tokenGetter(replacedNode)
                    Dim previousToken = replacedToken.GetPreviousToken(includeZeroWidth:=True)
 
                    Dim fixedupNode As T = Nothing
                    If Not TryFixupTrivia(replacedNode, previousToken, replacedToken, fixedupNode) Then
                        Return newNode
                    End If
 
                    Return fixedupNode
                End If
 
                Return newNode
            End Function
 
            Private Function ProcessToken(originalToken As SyntaxToken, token As SyntaxToken, parent As SyntaxNode) As SyntaxToken
                ' special case omitted token case
                If IsOmitted(originalToken) Then
                    Return ProcessOmittedToken(originalToken, token, parent)
                End If
 
                Dim span = originalToken.Span
                If Not _spans.HasIntervalThatContains(span.Start, span.Length) Then
                    ' token is outside of the provided span
                    Return token
                End If
 
                ' token is not missing or if missing token is identifier there is not much we can do
                If Not originalToken.IsMissing OrElse
                   originalToken.Kind = SyntaxKind.None OrElse
                   originalToken.Kind = SyntaxKind.IdentifierToken Then
                    Return token
                End If
 
                Return ProcessMissingToken(originalToken, token)
            End Function
 
            Private Shared Function ReplaceOrSetToken(Of T As SyntaxNode)(originalParent As T, tokenToFix As SyntaxToken, replacementToken As SyntaxToken) As T
                If Not IsOmitted(tokenToFix) Then
                    Return originalParent.ReplaceToken(tokenToFix, replacementToken)
                Else
                    Return DirectCast(SetOmittedToken(originalParent, replacementToken), T)
                End If
            End Function
 
            Private Shared Function SetOmittedToken(originalParent As SyntaxNode, newToken As SyntaxToken) As SyntaxNode
                Select Case newToken.Kind
                    Case SyntaxKind.ThenKeyword
                        ' this can be regular If, an If directive, or an ElseIf
                        Dim regularIf = TryCast(originalParent, IfStatementSyntax)
                        If regularIf IsNot Nothing Then
                            Dim previousToken = regularIf.Condition.GetLastToken(includeZeroWidth:=True)
                            Dim nextToken = regularIf.GetLastToken.GetNextToken
 
                            If Not InvalidOmittedToken(previousToken, nextToken) Then
                                Return regularIf.WithThenKeyword(newToken)
                            End If
 
                        Else
                            Dim regularElseIf = TryCast(originalParent, ElseIfStatementSyntax)
                            If regularElseIf IsNot Nothing Then
                                Dim previousToken = regularElseIf.Condition.GetLastToken(includeZeroWidth:=True)
                                Dim nextToken = regularElseIf.GetLastToken.GetNextToken
 
                                If Not InvalidOmittedToken(previousToken, nextToken) Then
                                    Return regularElseIf.WithThenKeyword(newToken)
                                End If
 
                            Else
                                Dim ifDirective = TryCast(originalParent, IfDirectiveTriviaSyntax)
                                If ifDirective IsNot Nothing Then
                                    Dim previousToken = ifDirective.Condition.GetLastToken(includeZeroWidth:=True)
                                    Dim nextToken = ifDirective.GetLastToken.GetNextToken
 
                                    If Not InvalidOmittedToken(previousToken, nextToken) Then
                                        Return ifDirective.WithThenKeyword(newToken)
                                    End If
                                End If
                            End If
                        End If
 
                    Case SyntaxKind.OnKeyword
                        Dim optionStatement = TryCast(originalParent, OptionStatementSyntax)
                        If optionStatement IsNot Nothing Then
                            Return optionStatement.WithValueKeyword(newToken)
                        End If
                End Select
 
                Return originalParent
            End Function
 
            Private Shared Function IsOmitted(token As SyntaxToken) As Boolean
                Return token.Kind = SyntaxKind.None
            End Function
 
            Private Shared Function ProcessOmittedToken(originalToken As SyntaxToken, token As SyntaxToken, parent As SyntaxNode) As SyntaxToken
                ' multiline if statement with missing then keyword case
                If TypeOf parent Is IfStatementSyntax Then
                    Dim ifStatement = DirectCast(parent, IfStatementSyntax)
                    If Exist(ifStatement.Condition) AndAlso ifStatement.ThenKeyword = originalToken Then
                        Return If(parent.GetAncestor(Of MultiLineIfBlockSyntax)() IsNot Nothing, CreateOmittedToken(token, SyntaxKind.ThenKeyword), token)
                    End If
                End If
 
                If TryCast(parent, IfDirectiveTriviaSyntax)?.ThenKeyword = originalToken Then
                    Return CreateOmittedToken(token, SyntaxKind.ThenKeyword)
                ElseIf TryCast(parent, ElseIfStatementSyntax)?.ThenKeyword = originalToken Then
                    Return If(parent.GetAncestor(Of ElseIfBlockSyntax)() IsNot Nothing, CreateOmittedToken(token, SyntaxKind.ThenKeyword), token)
                ElseIf TryCast(parent, OptionStatementSyntax)?.ValueKeyword = originalToken Then
                    Return CreateOmittedToken(token, SyntaxKind.OnKeyword)
                End If
 
                Return token
            End Function
 
            Private Shared Function InvalidOmittedToken(previousToken As SyntaxToken, nextToken As SyntaxToken) As Boolean
                ' if previous token has a problem, don't bother
                If previousToken.IsMissing OrElse previousToken.IsSkipped OrElse previousToken.Kind = 0 Then
                    Return True
                End If
 
                ' if next token has a problem, do little bit more check
                ' if there is no next token, it is okay to insert the missing token
                If nextToken.Kind = 0 Then
                    Return False
                End If
 
                ' if next token is missing or skipped, check whether it has EOL
                If nextToken.IsMissing OrElse nextToken.IsSkipped Then
                    Return Not previousToken.TrailingTrivia.Any(SyntaxKind.EndOfLineTrivia) And
                           Not nextToken.LeadingTrivia.Any(SyntaxKind.EndOfLineTrivia)
                End If
 
                Return False
            End Function
 
            Private Shared Function Exist(node As SyntaxNode) As Boolean
                Return node IsNot Nothing AndAlso node.Span.Length > 0
            End Function
 
            Private Shared Function ProcessMissingToken(originalToken As SyntaxToken, token As SyntaxToken) As SyntaxToken
                ' auto insert missing "Of" keyword in type argument list
                If TryCast(originalToken.Parent, TypeArgumentListSyntax)?.OfKeyword = originalToken Then
                    Return CreateMissingToken(token)
                ElseIf TryCast(originalToken.Parent, TypeParameterListSyntax)?.OfKeyword = originalToken Then
                    Return CreateMissingToken(token)
                ElseIf TryCast(originalToken.Parent, ContinueStatementSyntax)?.BlockKeyword = originalToken Then
                    Return CreateMissingToken(token)
                End If
 
                Return token
            End Function
 
            Private Shared Function CreateMissingToken(token As SyntaxToken) As SyntaxToken
                Return CreateToken(token, token.Kind)
            End Function
 
            Private Shared Function CreateOmittedToken(token As SyntaxToken, kind As SyntaxKind) As SyntaxToken
                Return CreateToken(token, kind)
            End Function
        End Class
    End Class
End Namespace