' 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.Runtime.CompilerServices
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports Microsoft.CodeAnalysis.VisualBasic.Utilities
Imports System.Threading

Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions.ContextQuery
    Friend Module SyntaxTreeExtensions

        <Extension()>
        Friend Function GetTargetToken(syntaxTree As SyntaxTree, position As Integer, cancellationToken As CancellationToken) As SyntaxToken
            Dim token = syntaxTree.FindTokenOnLeftOfPosition(
                position, cancellationToken,
                includeDirectives:=syntaxTree.IsInPreprocessorDirectiveContext(position, cancellationToken),
                includeDocumentationComments:=True)

            Do While token.Kind <> SyntaxKind.None
                ' If we have a non-word token to our left, we should always stop there
                If Not token.IsWord() AndAlso token.Span.End <= position Then
                    Exit Do
                End If

                ' If this token is to our left, return it
                If Not token.IsKind(SyntaxKind.EmptyToken) AndAlso token.Span.End < position Then
                    Exit Do
                End If

                token = token.GetPreviousToken()
            Loop

            Return token
        End Function

        <Extension()>
        Public Function IsPreProcessorKeywordContext(syntaxTree As SyntaxTree, position As Integer, cancellationToken As CancellationToken) As Boolean
            Return IsPreProcessorKeywordContext(
                syntaxTree, position,
                syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken, includeDirectives:=True),
                cancellationToken)
        End Function

#Disable Warning IDE0060 ' Remove unused parameter - Unused 'SyntaxTree' parameter for consistency with other Context extension methods
        <Extension()>
        Public Function IsPreProcessorKeywordContext(syntaxTree As SyntaxTree, position As Integer, preProcessorTokenOnLeftOfPosition As SyntaxToken, cancellationToken As CancellationToken) As Boolean
#Enable Warning IDE0060 ' Remove unused parameter
            ' cases:
            '  #|
            '  #d|
            '  # |
            '  # d|

            ' note comments are Not allowed between the # And item.
            Dim token = preProcessorTokenOnLeftOfPosition
            token = token.GetPreviousTokenIfTouchingWord(position)

            Return token.HasAncestor(Of DirectiveTriviaSyntax)
        End Function

        <Extension()>
        Public Function IsNamespaceContext(syntaxTree As SyntaxTree, position As Integer, token As SyntaxToken, cancellationToken As CancellationToken, Optional semanticModelOpt As SemanticModel = Nothing) As Boolean
            Return syntaxTree.IsTypeContext(position, token, cancellationToken, semanticModelOpt)
        End Function

        <Extension()>
        Public Function IsNamespaceDeclarationNameContext(syntaxTree As SyntaxTree, position As Integer, cancellationToken As CancellationToken) As Boolean
            If syntaxTree.IsScript() OrElse syntaxTree.IsInNonUserCode(position, cancellationToken) Then
                Return False
            End If

            Dim token = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken) _
                                  .GetPreviousTokenIfTouchingWord(position)

            Dim statement = token.GetAncestor(Of NamespaceStatementSyntax)()

            Return statement IsNot Nothing AndAlso (statement.Name.Span.IntersectsWith(position) OrElse statement.NamespaceKeyword = token)
        End Function

        <Extension()>
        Public Function IsPartialTypeDeclarationNameContext(tree As SyntaxTree, position As Integer, cancellationToken As CancellationToken, ByRef statementSyntax As TypeStatementSyntax) As Boolean
            If tree.IsInNonUserCode(position, cancellationToken) OrElse tree.IsInSkippedText(position, cancellationToken) Then
                Return False
            End If

            Dim token = tree.FindTokenOnLeftOfPosition(position, cancellationToken) _
                            .GetPreviousTokenIfTouchingWord(position)

            Select Case token.Kind()
                Case SyntaxKind.ClassKeyword,
                     SyntaxKind.StructureKeyword,
                     SyntaxKind.InterfaceKeyword,
                     SyntaxKind.ModuleKeyword

                    statementSyntax = token.GetAncestor(Of TypeStatementSyntax)()
                    Return statementSyntax IsNot Nothing AndAlso
                           statementSyntax.DeclarationKeyword = token AndAlso
                           statementSyntax.Modifiers.Any(SyntaxKind.PartialKeyword)
            End Select

            Return False
        End Function

        <Extension()>
        Public Function GetContainingTypeBlock(syntaxTree As SyntaxTree, position As Integer, cancellationToken As CancellationToken) As TypeBlockSyntax
            Dim token = syntaxTree.GetRoot(cancellationToken).FindToken(position)
            Return TryCast(token.GetInnermostDeclarationContext(), TypeBlockSyntax)
        End Function

        ''' <summary>
        ''' The specified position is where we can declare some .NET type, such as classes, structures, etc.
        ''' </summary>
        <Extension()>
        Friend Function IsTypeDeclarationContext(syntaxTree As SyntaxTree, position As Integer, token As SyntaxToken, cancellationToken As CancellationToken) As Boolean
            Return Not syntaxTree.IsInPreprocessorDirectiveContext(position, cancellationToken) AndAlso
                syntaxTree.IsDeclarationContextWithinTypeBlocks(position, token, True, cancellationToken, SyntaxKind.ClassBlock, SyntaxKind.StructureBlock, SyntaxKind.InterfaceBlock, SyntaxKind.NamespaceBlock, SyntaxKind.ModuleBlock, SyntaxKind.CompilationUnit)
        End Function

        <Extension()>
        Friend Function IsDeclarationContextWithinTypeBlocks(
            syntaxTree As SyntaxTree, position As Integer, targetToken As SyntaxToken, allowAfterModifiersOrDim As Boolean, cancellationToken As CancellationToken, ParamArray allowedParentBlocks As SyntaxKind()) As Boolean

            Debug.Assert(targetToken = syntaxTree.GetTargetToken(position, cancellationToken))
            If targetToken.Kind = SyntaxKind.None OrElse targetToken.Parent Is Nothing Then
                ' We're at the root, so we're acceptable if we allow us to be in the root
                Return allowedParentBlocks.Contains(SyntaxKind.CompilationUnit)
            End If

            If syntaxTree.IsInPreprocessorDirectiveContext(position, cancellationToken) Then
                Return False
            End If

            ' If we're within a method/event/property, then always no
            Dim method = targetToken.GetAncestor(Of MethodBlockBaseSyntax)()
            If method IsNot Nothing AndAlso
               (method.EndBlockStatement Is Nothing OrElse
                method.EndBlockStatement.IsMissing OrElse
                method.EndBlockStatement.BlockKeyword <> targetToken) Then

                Return False
            End If

            Dim [event] = targetToken.GetAncestor(Of EventBlockSyntax)()
            If [event] IsNot Nothing AndAlso
               ([event].EndEventStatement Is Nothing OrElse
                [event].EndEventStatement.IsMissing OrElse
                [event].EndEventStatement.BlockKeyword <> targetToken) Then

                Return False
            End If

            Dim afterDimOrModifiers = allowAfterModifiersOrDim AndAlso IsDimOrModifierOrAttributeList(targetToken)

            ' We either must be on a separate line, or else after Dim or modifiers
            If targetToken.FollowsEndOfStatement(position) OrElse afterDimOrModifiers Then
                Return targetToken.GetInnermostDeclarationContext().IsKind(allowedParentBlocks)
            End If

            Return False
        End Function

        Private Function IsDimOrModifierOrAttributeList(token As SyntaxToken) As Boolean
            If token.IsModifier Then
                Return True
            End If

            If token.Kind = SyntaxKind.DimKeyword Then
                Return True
            End If

            If token.HasMatchingText(SyntaxKind.AsyncKeyword) OrElse token.HasMatchingText(SyntaxKind.IteratorKeyword) Then
                Return True
            End If

            ' eg. <Extension> |
            If token.Kind = SyntaxKind.GreaterThanToken AndAlso token.Parent.Kind = SyntaxKind.AttributeList Then
                Return True
            End If

            Return False
        End Function

        ''' <summary>
        ''' The specified position is where a keyword can go like "Sub", "Function", etc. in a classes, structures, and modules
        ''' </summary>
        <Extension()>
        Friend Function IsTypeMemberDeclarationKeywordContext(syntaxTree As SyntaxTree, position As Integer, token As SyntaxToken, cancellationToken As CancellationToken) As Boolean
            Return syntaxTree.IsDeclarationContextWithinTypeBlocks(
                position, token, True, cancellationToken, SyntaxKind.ClassBlock, SyntaxKind.StructureBlock, SyntaxKind.ModuleBlock)
        End Function

        ''' <summary>
        ''' The specified position is where a keyword can go like "Sub", "Function" in an interface
        ''' </summary>
        <Extension()>
        Friend Function IsInterfaceMemberDeclarationKeywordContext(syntaxTree As SyntaxTree, position As Integer, token As SyntaxToken, cancellationToken As CancellationToken) As Boolean
            Return syntaxTree.IsDeclarationContextWithinTypeBlocks(position, token, True, cancellationToken, SyntaxKind.InterfaceBlock)
        End Function

        ''' <summary>
        ''' The specified position is where we can declare some .NET type, such as classes, structures, etc.
        ''' </summary>
        <Extension()>
        Friend Function IsTypeDeclarationKeywordContext(syntaxTree As SyntaxTree, position As Integer, token As SyntaxToken, cancellationToken As CancellationToken) As Boolean
            Return Not syntaxTree.IsInPreprocessorDirectiveContext(position, cancellationToken) AndAlso
                syntaxTree.IsDeclarationContextWithinTypeBlocks(
                    position, token, True, cancellationToken, SyntaxKind.ClassBlock, SyntaxKind.StructureBlock, SyntaxKind.InterfaceBlock, SyntaxKind.NamespaceBlock, SyntaxKind.ModuleBlock, SyntaxKind.CompilationUnit)
        End Function

        <Extension>
        Friend Function IsFieldNameDeclarationContext(syntaxTree As SyntaxTree, position As Integer, targetToken As SyntaxToken, cancellationToken As CancellationToken) As Boolean
            Debug.Assert(targetToken = syntaxTree.GetTargetToken(position, cancellationToken))
            If targetToken.FollowsEndOfStatement(position) Then
                Return False
            End If

            If targetToken.IsKind(SyntaxKind.ConstKeyword,
                                 SyntaxKind.DimKeyword,
                                 SyntaxKind.FriendKeyword,
                                 SyntaxKind.PrivateKeyword,
                                 SyntaxKind.ProtectedKeyword,
                                 SyntaxKind.ReadOnlyKeyword,
                                 SyntaxKind.PublicKeyword,
                                 SyntaxKind.ShadowsKeyword,
                                 SyntaxKind.SharedKeyword,
                                 SyntaxKind.WithEventsKeyword) Then

                Dim typeBlock = targetToken.GetAncestor(Of TypeBlockSyntax)()

                If typeBlock IsNot Nothing AndAlso
                       typeBlock.IsKind(SyntaxKind.ClassBlock,
                                             SyntaxKind.ModuleBlock,
                                             SyntaxKind.StructureBlock) Then

                    Dim modifierFacts = New ModifierCollectionFacts(syntaxTree, position, targetToken, cancellationToken)
                    Return modifierFacts.CouldApplyToOneOf(PossibleDeclarationTypes.Field)
                End If
            End If

            Return False
        End Function

        <Extension()>
        Friend Function IsParameterNameDeclarationContext(syntaxTree As SyntaxTree, position As Integer, cancellationToken As CancellationToken) As Boolean
            Dim targetToken = syntaxTree.GetTargetToken(position, cancellationToken)

            If targetToken.Parent.IsKind(SyntaxKind.ParameterList) AndAlso
                targetToken.IsKind(SyntaxKind.OpenParenToken,
                                  SyntaxKind.CommaToken) Then

                Return True
            End If

            If targetToken.Parent.IsKind(SyntaxKind.Parameter) AndAlso
                targetToken.IsKind(SyntaxKind.ByValKeyword,
                                  SyntaxKind.ByRefKeyword,
                                  SyntaxKind.ParamArrayKeyword,
                                  SyntaxKind.OptionalKeyword) Then

                Return True
            End If

            Return False
        End Function

        <Extension()>
        Friend Function IsLabelContext(syntaxTree As SyntaxTree, position As Integer, targetToken As SyntaxToken, cancellationToken As CancellationToken) As Boolean
            Debug.Assert(targetToken = syntaxTree.GetTargetToken(position, cancellationToken))
            If targetToken.FollowsEndOfStatement(position) Then
                Return False
            End If

            Dim gotoStatement = targetToken.GetAncestor(Of GoToStatementSyntax)()
            If gotoStatement IsNot Nothing Then
                If gotoStatement.GoToKeyword = targetToken Then
                    Return True
                End If

                If gotoStatement.Label.LabelToken = targetToken AndAlso targetToken.IntersectsWith(position) Then
                    Return True
                End If
            End If

            Dim onErrorGotoStatement = targetToken.GetAncestor(Of OnErrorGoToStatementSyntax)()
            If onErrorGotoStatement IsNot Nothing Then
                If onErrorGotoStatement.GoToKeyword = targetToken Then
                    Return True
                End If

                If onErrorGotoStatement.Label.LabelToken = targetToken AndAlso targetToken.IntersectsWith(position) Then
                    Return True
                End If
            End If

            Return False
        End Function

#Disable Warning IDE0060 ' Remove unused parameter - Unused 'SyntaxTree' parameter for consistency with other Context extension methods
        <Extension()>
        Friend Function IsEnumMemberNameContext(syntaxTree As SyntaxTree, context As VisualBasicSyntaxContext) As Boolean
#Enable Warning IDE0060 ' Remove unused parameter
            Dim token = context.TargetToken

            ' Check to see if we're inside an enum block
            Dim enumBlock = token.GetAncestor(Of EnumBlockSyntax)()
            If enumBlock IsNot Nothing Then
                Return context.FollowsEndOfStatement OrElse token.IsChildToken(Of EnumMemberDeclarationSyntax)(Function(emds) emds.Identifier)
            End If

            Return False
        End Function

        <Extension()>
        Public Function IsDelegateCreationContext(syntaxTree As SyntaxTree, position As Integer, targetToken As SyntaxToken, semanticModel As SemanticModel, cancellationToken As CancellationToken) As Boolean
            Debug.Assert(targetToken = syntaxTree.GetTargetToken(position, cancellationToken))

            If targetToken.FollowsEndOfStatement(position) Then
                Return False
            End If

            If targetToken.Parent.IsKind(SyntaxKind.ArgumentList) AndAlso
               TypeOf targetToken.Parent.Parent Is NewExpressionSyntax Then

                Dim symbolInfo = semanticModel.GetSymbolInfo(DirectCast(targetToken.Parent.Parent, NewExpressionSyntax).Type(), cancellationToken)
                Dim objectCreationType = TryCast(symbolInfo.Symbol, ITypeSymbol)
                If objectCreationType IsNot Nothing AndAlso
                   objectCreationType.TypeKind = TypeKind.Delegate Then

                    Return True
                End If
            End If

            Return False
        End Function

        <Extension()>
        Friend Function IsExpressionContext(
            syntaxTree As SyntaxTree, position As Integer, cancellationToken As CancellationToken, Optional semanticModelOpt As SemanticModel = Nothing) As Boolean
            Dim targetToken = syntaxTree.GetTargetToken(position, cancellationToken)
            Return IsExpressionContext(syntaxTree, position, targetToken, cancellationToken, semanticModelOpt)
        End Function

        <Extension()>
        Friend Function IsExpressionContext(
            syntaxTree As SyntaxTree, position As Integer, targetToken As SyntaxToken, cancellationToken As CancellationToken, Optional semanticModelOpt As SemanticModel = Nothing) As Boolean

            Debug.Assert(targetToken = syntaxTree.GetTargetToken(position, cancellationToken))

            ' Tuple elements are in expression context if the tuple is in expression context
            PositionOutsideTupleIfApplicable(syntaxTree, position, targetToken, cancellationToken)

            If targetToken.FollowsEndOfStatement(position) OrElse targetToken.Kind = SyntaxKind.None Then
                Return False
            End If

            Dim directive As DirectiveTriviaSyntax = Nothing
            If IsInPreprocessorDirectiveContext(syntaxTree, position, cancellationToken, directive) AndAlso
               directive.IsKind(SyntaxKind.IfDirectiveTrivia, SyntaxKind.ElseIfDirectiveTrivia) Then
                Return False
            End If

            If semanticModelOpt IsNot Nothing Then
                If syntaxTree.IsDelegateCreationContext(position, targetToken, semanticModelOpt, cancellationToken) Then
                    Return False
                End If
            End If

            ' Easy ones first
            If targetToken.IsChildToken(Of AddRemoveHandlerStatementSyntax)(Function(handlerStatement) handlerStatement.AddHandlerOrRemoveHandlerKeyword) OrElse
               targetToken.IsChildToken(Of AddRemoveHandlerStatementSyntax)(Function(handlerStatement) handlerStatement.CommaToken) OrElse
               targetToken.IsChildToken(Of ArgumentListSyntax)(Function(argumentList) argumentList.OpenParenToken) OrElse
               targetToken.IsChildToken(Of AssignmentStatementSyntax)(Function(assignmentStatement) assignmentStatement.OperatorToken) OrElse
               targetToken.IsChildToken(Of AwaitExpressionSyntax)(Function(awaitExpression) awaitExpression.AwaitKeyword) OrElse
               targetToken.IsChildToken(Of BinaryExpressionSyntax)(Function(binaryExpression) binaryExpression.OperatorToken) OrElse
               targetToken.IsChildToken(Of BinaryConditionalExpressionSyntax)(Function(binaryExpression) binaryExpression.OpenParenToken) OrElse
               targetToken.IsChildToken(Of BinaryConditionalExpressionSyntax)(Function(binaryExpression) binaryExpression.CommaToken) OrElse
               targetToken.IsChildToken(Of CallStatementSyntax)(Function(callStatementSyntax) callStatementSyntax.CallKeyword) OrElse
               targetToken.IsChildToken(Of CatchFilterClauseSyntax)(Function(catchFilterClauseSyntax) catchFilterClauseSyntax.WhenKeyword) OrElse
               targetToken.IsChildToken(Of CaseStatementSyntax)(Function(caseStatement) caseStatement.CaseKeyword) OrElse
               targetToken.IsChildToken(Of ConditionalAccessExpressionSyntax)(Function(conditionalAccessExpressionSyntax) conditionalAccessExpressionSyntax.QuestionMarkToken) OrElse
               targetToken.IsChildSeparatorToken(Of CaseStatementSyntax, CaseClauseSyntax)(Function(caseStatement) caseStatement.Cases) OrElse
               targetToken.IsChildToken(Of RangeCaseClauseSyntax)(Function(rangeCaseClause) rangeCaseClause.ToKeyword) OrElse
               targetToken.IsChildToken(Of RelationalCaseClauseSyntax)(Function(relationalCaseClause) relationalCaseClause.OperatorToken) OrElse
               targetToken.IsChildToken(Of CastExpressionSyntax)(Function(castExpression) castExpression.OpenParenToken) OrElse
               targetToken.IsChildToken(Of CollectionInitializerSyntax)(Function(collectionInitializer) collectionInitializer.OpenBraceToken) OrElse
               targetToken.IsChildToken(Of CollectionRangeVariableSyntax)(Function(collectionRange) collectionRange.InKeyword) OrElse
               targetToken.IsChildToken(Of EraseStatementSyntax)(Function(eraseStatement) eraseStatement.EraseKeyword) OrElse
               targetToken.IsChildSeparatorToken(Of EraseStatementSyntax, ExpressionSyntax)(Function(eraseStatement) eraseStatement.Expressions) OrElse
               targetToken.IsChildToken(Of ErrorStatementSyntax)(Function(errorStatement) errorStatement.ErrorKeyword) OrElse
               targetToken.IsChildToken(Of ForStatementSyntax)(Function(forStatement) forStatement.EqualsToken) OrElse
               targetToken.IsChildToken(Of ForStatementSyntax)(Function(forStatement) forStatement.ToKeyword) OrElse
               targetToken.IsChildToken(Of ForStepClauseSyntax)(Function(forStepClause) forStepClause.StepKeyword) OrElse
               targetToken.IsChildToken(Of ForEachStatementSyntax)(Function(forEachStatement) forEachStatement.InKeyword) OrElse
               targetToken.IsChildToken(Of FunctionAggregationSyntax)(Function(functionAggregation) functionAggregation.OpenParenToken) OrElse
               targetToken.IsChildToken(Of GetTypeExpressionSyntax)(Function(getTypeExpression) getTypeExpression.OpenParenToken) OrElse
               targetToken.IsChildToken(Of GroupByClauseSyntax)(Function(groupBy) groupBy.GroupKeyword) OrElse
               targetToken.IsChildToken(Of GroupByClauseSyntax)(Function(groupBy) groupBy.ByKeyword) OrElse
               targetToken.IsChildToken(Of IfStatementSyntax)(Function(ifStatement) ifStatement.IfKeyword) OrElse
               targetToken.IsChildToken(Of ElseIfStatementSyntax)(Function(elseIfStatement) elseIfStatement.ElseIfKeyword) OrElse
               targetToken.IsChildToken(Of InferredFieldInitializerSyntax)(Function(inferredField) inferredField.KeyKeyword) OrElse
               targetToken.IsChildToken(Of InterpolationSyntax)(Function(interpolation) interpolation.OpenBraceToken) OrElse
               targetToken.IsChildToken(Of EqualsValueSyntax)(Function(initializer) initializer.EqualsToken) OrElse
               targetToken.IsChildToken(Of JoinClauseSyntax)(Function(joinQuery) joinQuery.OnKeyword) OrElse
               targetToken.IsChildSeparatorToken(Of JoinClauseSyntax, JoinConditionSyntax)(Function(joinQuery) joinQuery.JoinConditions) OrElse
               targetToken.IsChildToken(Of JoinConditionSyntax)(Function(joinCondition) joinCondition.EqualsKeyword) OrElse
               targetToken.IsChildToken(Of SimpleArgumentSyntax)(Function(argument) If(argument.IsNamed, argument.NameColonEquals.ColonEqualsToken, Nothing)) OrElse
               targetToken.IsChildToken(Of NamedFieldInitializerSyntax)(Function(namedField) namedField.EqualsToken) OrElse
               targetToken.IsChildToken(Of OrderByClauseSyntax)(Function(orderByClause) orderByClause.ByKeyword) OrElse
               targetToken.IsChildSeparatorToken(Of OrderByClauseSyntax, OrderingSyntax)(Function(orderByClause) orderByClause.Orderings) OrElse
               targetToken.IsChildToken(Of ParenthesizedExpressionSyntax)(Function(parenthesizedExpression) parenthesizedExpression.OpenParenToken) OrElse
               targetToken.IsChildToken(Of PartitionClauseSyntax)(Function(partitionClause) partitionClause.SkipOrTakeKeyword) OrElse
               targetToken.IsChildToken(Of PartitionWhileClauseSyntax)(Function(partitionWhileClause) partitionWhileClause.WhileKeyword) OrElse
               targetToken.IsChildToken(Of PredefinedCastExpressionSyntax)(Function(buildInCast) buildInCast.OpenParenToken) OrElse
               targetToken.IsChildToken(Of RangeArgumentSyntax)(Function(rangeArgument) rangeArgument.ToKeyword) OrElse
               targetToken.IsChildToken(Of ReDimStatementSyntax)(Function(redimStatement) redimStatement.ReDimKeyword) OrElse
               targetToken.IsChildToken(Of ReDimStatementSyntax)(Function(redimStatement) redimStatement.PreserveKeyword) OrElse
               targetToken.IsChildToken(Of ReturnStatementSyntax)(Function(returnStatement) returnStatement.ReturnKeyword) OrElse
               targetToken.IsChildToken(Of SelectClauseSyntax)(Function(selectClause) selectClause.SelectKeyword) OrElse
               targetToken.IsChildSeparatorToken(Of SelectClauseSyntax, ExpressionRangeVariableSyntax)(Function(selectClause) selectClause.Variables) OrElse
               targetToken.IsChildToken(Of SelectStatementSyntax)(Function(selectStatement) selectStatement.SelectKeyword) OrElse
               targetToken.IsChildToken(Of SelectStatementSyntax)(Function(selectStatement) selectStatement.CaseKeyword) OrElse
               targetToken.IsChildToken(Of SyncLockStatementSyntax)(Function(syncLockStatement) syncLockStatement.SyncLockKeyword) OrElse
               targetToken.IsChildToken(Of TernaryConditionalExpressionSyntax)(Function(ternaryConditional) ternaryConditional.OpenParenToken) OrElse
               targetToken.IsChildToken(Of TernaryConditionalExpressionSyntax)(Function(ternaryConditional) ternaryConditional.FirstCommaToken) OrElse
               targetToken.IsChildToken(Of TernaryConditionalExpressionSyntax)(Function(ternaryConditional) ternaryConditional.SecondCommaToken) OrElse
               targetToken.IsChildToken(Of ThrowStatementSyntax)(Function(throwStatement) throwStatement.ThrowKeyword) OrElse
               targetToken.IsChildToken(Of TypeOfExpressionSyntax)(Function(typeOfIsExpression) typeOfIsExpression.TypeOfKeyword) OrElse
               targetToken.IsChildToken(Of UnaryExpressionSyntax)(Function(unaryExpression) unaryExpression.OperatorToken) OrElse
               targetToken.IsChildToken(Of UsingStatementSyntax)(Function(usingStatementSyntax) usingStatementSyntax.UsingKeyword) OrElse
               targetToken.IsChildToken(Of VariableNameEqualsSyntax)(Function(variableNameEquals) variableNameEquals.EqualsToken) OrElse
               targetToken.IsChildToken(Of WhereClauseSyntax)(Function(whereClause) whereClause.WhereKeyword) OrElse
               targetToken.IsChildToken(Of WhileStatementSyntax)(Function(whileStatement) whileStatement.WhileKeyword) OrElse
               targetToken.IsChildToken(Of WhileOrUntilClauseSyntax)(Function(whileUntilClause) whileUntilClause.WhileOrUntilKeyword) OrElse
               targetToken.IsChildToken(Of WithStatementSyntax)(Function(withStatement) withStatement.WithKeyword) OrElse
               targetToken.IsChildToken(Of XmlEmbeddedExpressionSyntax)(Function(xmlEmbeddedExpression) xmlEmbeddedExpression.LessThanPercentEqualsToken) OrElse
               targetToken.IsChildToken(Of YieldStatementSyntax)(Function(yieldStatement) yieldStatement.YieldKeyword) Then
                Return True
            End If

            ' The close paren of the parameter list of a single-line lambda?
            If targetToken.Kind = SyntaxKind.CloseParenToken AndAlso
               targetToken.Parent.IsKind(SyntaxKind.ParameterList) AndAlso
               TypeOf targetToken.Parent.Parent Is LambdaHeaderSyntax Then
                Return True
            End If

            ' A comma in a method call or collection initializer?
            If targetToken.Kind = SyntaxKind.CommaToken AndAlso
               targetToken.Parent.IsKind(SyntaxKind.ArgumentList, SyntaxKind.CollectionInitializer, SyntaxKind.EraseStatement) Then

                Return True
            End If

            Return False
        End Function

        <Extension()>
        Public Function IsAttributeNameContext(syntaxTree As SyntaxTree, position As Integer, targetToken As SyntaxToken, cancellationToken As CancellationToken) As Boolean
            Debug.Assert(targetToken = syntaxTree.GetTargetToken(position, cancellationToken))
            Debug.Assert(Not (targetToken.IntersectsWith(position) AndAlso IsWord(targetToken)))

            If targetToken.IsChildToken(Function(a As AttributeTargetSyntax) a.ColonToken) OrElse
               targetToken.IsChildToken(Function(a As AttributeListSyntax) a.LessThanToken) OrElse
               targetToken.IsChildSeparatorToken(Function(a As AttributeListSyntax) a.Attributes) Then
                Return True
            End If

            If targetToken.IsKind(SyntaxKind.DotToken) AndAlso
               targetToken.Parent.IsKind(SyntaxKind.QualifiedName) AndAlso
               targetToken.Parent.Parent.IsKind(SyntaxKind.Attribute) Then
                Return True
            End If

            Return False
        End Function

        <Extension()>
        Public Function IsTypeContext(syntaxTree As SyntaxTree, position As Integer, token As SyntaxToken, cancellationToken As CancellationToken, Optional semanticModelOpt As SemanticModel = Nothing) As Boolean
            ' first do quick exit check
            If syntaxTree.IsInPreprocessorDirectiveContext(position, cancellationToken) OrElse
               syntaxTree.IsInInactiveRegion(position, cancellationToken) OrElse
               syntaxTree.IsEntirelyWithinComment(position, cancellationToken) OrElse
               syntaxTree.IsEntirelyWithinStringOrCharOrNumericLiteral(position, cancellationToken) Then

                Return False
            End If

            Debug.Assert(token = syntaxTree.GetTargetToken(position, cancellationToken))

            ' Tuple elements are in type context if the tuple is in type context
            PositionOutsideTupleIfApplicable(syntaxTree, position, token, cancellationToken)

            ' Types may start anywhere a full expression may be given
            If syntaxTree.IsExpressionContext(position, token, cancellationToken, semanticModelOpt) Then
                Return True
            End If

            ' Types may also start a statement
            If syntaxTree.IsSingleLineStatementContext(position, token, cancellationToken) Then
                Return True
            End If

            If syntaxTree.IsAttributeNameContext(position, token, cancellationToken) Then
                Return True
            End If

            ' Simple cases first
            If token.IsChildToken(Of ImportAliasClauseSyntax)(Function(importAliasClause) importAliasClause.EqualsToken) OrElse
               token.IsChildToken(Of ArrayCreationExpressionSyntax)(Function(arrayCreation) arrayCreation.NewKeyword) OrElse
               token.IsChildToken(Of AsNewClauseSyntax)(Function(asNewClause) asNewClause.NewExpression.NewKeyword) OrElse
               token.IsChildToken(Of InheritsStatementSyntax)(Function(node) node.InheritsKeyword) OrElse
               token.IsChildSeparatorToken(Of InheritsStatementSyntax, TypeSyntax)(Function(baseDeclaration) baseDeclaration.Types) OrElse
               token.IsChildToken(Of ImplementsStatementSyntax)(Function(node) node.ImplementsKeyword) OrElse
               token.IsChildSeparatorToken(Of ImplementsStatementSyntax, TypeSyntax)(Function(baseDeclaration) baseDeclaration.Types) OrElse
               token.IsChildToken(Of CastExpressionSyntax)(Function(castExpression) castExpression.CommaToken) OrElse
               token.IsChildToken(Of ImplementsClauseSyntax)(Function(implementsClause) implementsClause.ImplementsKeyword) OrElse
               token.IsChildSeparatorToken(Of ImplementsClauseSyntax, QualifiedNameSyntax)(Function(implementsClause) implementsClause.InterfaceMembers) OrElse
               token.IsChildToken(Of ImportsStatementSyntax)(Function(importsStatement) importsStatement.ImportsKeyword) OrElse
               token.IsChildSeparatorToken(Of ImportsStatementSyntax, ImportsClauseSyntax)(Function(importsStatement) importsStatement.ImportsClauses) OrElse
               token.IsChildToken(Of ObjectCreationExpressionSyntax)(Function(objectCreation) objectCreation.NewKeyword) OrElse
               token.IsChildToken(Of TypeArgumentListSyntax)(Function(typeArgumentList) typeArgumentList.OfKeyword) OrElse
               token.IsChildSeparatorToken(Of TypeArgumentListSyntax, TypeSyntax)(Function(typeArgumentList) typeArgumentList.Arguments) OrElse
               token.IsChildToken(Of TypeOfExpressionSyntax)(Function(typeOfIs) typeOfIs.OperatorToken) OrElse
               token.IsChildToken(Of TypeParameterSingleConstraintClauseSyntax)(Function(constraint) constraint.AsKeyword) OrElse
               token.IsChildToken(Of TypeParameterMultipleConstraintClauseSyntax)(Function(constraint) constraint.OpenBraceToken) OrElse
               token.IsChildSeparatorToken(Of TypeParameterMultipleConstraintClauseSyntax, ConstraintSyntax)(Function(constraint) constraint.Constraints) Then
                Return True
            End If

            Dim parent = token.Parent
            If parent Is Nothing Then
                Return False
            End If

            ' If we're in an Enum's underlying type, we never recommend...
            If parent.IsChildNode(Of EnumStatementSyntax)(Function(enumDeclaration) enumDeclaration.UnderlyingType) Then
                Return False
            End If

            ' ...otherwise any other SimpleAsClause is good
            Return token.IsChildToken(Of SimpleAsClauseSyntax)(Function(asClause) asClause.AsKeyword)
        End Function

        Private Sub PositionOutsideTupleIfApplicable(syntaxTree As SyntaxTree, ByRef position As Integer,
                                                     ByRef token As SyntaxToken, cancellationToken As CancellationToken)

            While syntaxTree.IsPossibleTupleContext(token, position)
                Dim possibleTuple = token.Parent
                position = possibleTuple.FullSpan.Start
                token = syntaxTree.GetTargetToken(position, cancellationToken)
            End While
        End Sub

        <Extension()>
        Public Function IsNameOfContext(syntaxTree As SyntaxTree, position As Integer, Optional cancellationToken As CancellationToken = Nothing) As Boolean
            ' first do quick exit check
            If syntaxTree.IsInPreprocessorDirectiveContext(position, cancellationToken) OrElse
               syntaxTree.IsInInactiveRegion(position, cancellationToken) OrElse
               syntaxTree.IsEntirelyWithinComment(position, cancellationToken) OrElse
               syntaxTree.IsEntirelyWithinStringOrCharOrNumericLiteral(position, cancellationToken) Then

                Return False
            End If

            Return syntaxTree _
                .GetTargetToken(position, cancellationToken) _
                .IsChildToken(Of NameOfExpressionSyntax)(Function(nameOfExpression) nameOfExpression.OpenParenToken)
        End Function

        <Extension()>
        Friend Function IsSingleLineStatementContext(syntaxTree As SyntaxTree, position As Integer, cancellationToken As CancellationToken) As Boolean
            Dim targetToken = syntaxTree.GetTargetToken(position, cancellationToken)
            Return IsSingleLineStatementContext(syntaxTree, position, targetToken, cancellationToken)
        End Function

        ''' <summary>
        ''' The specified position is where I could start a statement in a place where exactly one
        ''' statement could exist.
        ''' </summary>
        <Extension()>
        Friend Function IsSingleLineStatementContext(syntaxTree As SyntaxTree, position As Integer, targetToken As SyntaxToken, cancellationToken As CancellationToken) As Boolean
            ' We can always put single-statement constructs anywhere we can do a multi-statement
            ' construct
            If syntaxTree.IsMultiLineStatementStartContext(position, targetToken, cancellationToken) Then
                Return True
            End If

            Debug.Assert(targetToken = syntaxTree.GetTargetToken(position, cancellationToken))
            If targetToken.FollowsEndOfStatement(position) Then
                Return False
            End If

            ' We might be after a single-line statement lambda
            Dim statementLambdaHeader = targetToken.GetAncestor(Of LambdaHeaderSyntax)()
            If statementLambdaHeader IsNot Nothing AndAlso statementLambdaHeader.Parent.IsKind(SyntaxKind.SingleLineSubLambdaExpression,
                                                                                                    SyntaxKind.MultiLineSubLambdaExpression) Then
                Return statementLambdaHeader.ParameterList Is Nothing AndAlso targetToken = statementLambdaHeader.DeclarationKeyword OrElse
                       statementLambdaHeader.ParameterList IsNot Nothing AndAlso targetToken = statementLambdaHeader.ParameterList.CloseParenToken
            End If

            Return False
        End Function

        ' PERF: Use UShort instead of SyntaxKind so the compiler can use array literal initialization.
        Private ReadOnly s_multilineStatementBlockStartKinds As SyntaxKind() = DirectCast(New UShort() {
            SyntaxKind.MultiLineFunctionLambdaExpression,
            SyntaxKind.MultiLineSubLambdaExpression,
            SyntaxKind.SubBlock,
            SyntaxKind.FunctionBlock,
            SyntaxKind.GetAccessorBlock,
            SyntaxKind.SetAccessorBlock,
            SyntaxKind.AddHandlerAccessorBlock,
            SyntaxKind.RemoveHandlerAccessorBlock,
            SyntaxKind.RaiseEventAccessorBlock,
            SyntaxKind.ConstructorBlock,
            SyntaxKind.OperatorBlock
        }, SyntaxKind())

        ''' <summary>
        ''' The specified position is where I could start a statement in a place where one or more
        ''' statements could exist.
        ''' </summary>
        <Extension()>
        Friend Function IsMultiLineStatementStartContext(syntaxTree As SyntaxTree, position As Integer, targetToken As SyntaxToken, cancellationToken As CancellationToken) As Boolean
            If syntaxTree.IsInPreprocessorDirectiveContext(position, cancellationToken) OrElse
               syntaxTree.IsInSkippedText(position, cancellationToken) Then
                Return False
            End If

            ' There is one interesting exception to the code below: if it's the first statement inside
            ' a select case, then we can never have an executable statement
            If syntaxTree.IsStartOfSelectCaseBlock(position, targetToken, cancellationToken) Then
                Return False
            End If

            Debug.Assert(targetToken = syntaxTree.GetTargetToken(position, cancellationToken))
            If targetToken.Kind = SyntaxKind.None Then
                Return False
            End If

            ' We might be after the Then or Else of a single-line if statement
            Dim singleLineIf = targetToken.GetAncestor(Of SingleLineIfStatementSyntax)()
            If singleLineIf IsNot Nothing AndAlso
              (targetToken.IsChildToken(Of SingleLineIfStatementSyntax)(Function(n) n.ThenKeyword) OrElse
               targetToken.IsChildToken(Of SingleLineElseClauseSyntax)(Function(n) n.ElseKeyword)) Then

                Return True
            End If

            If Not targetToken.FollowsEndOfStatement(position) Then
                Return False
            End If

            Return syntaxTree.IsInStatementBlockOfKind(position,
                                                       targetToken,
                                                       cancellationToken,
                                                       s_multilineStatementBlockStartKinds)
        End Function

        <Extension()>
        Friend Function IsStartOfSelectCaseBlock(syntaxTree As SyntaxTree, position As Integer, token As SyntaxToken, cancellationToken As CancellationToken) As Boolean
            Return syntaxTree.IsAfterStatementOfKind(position, token, cancellationToken, SyntaxKind.SelectStatement)
        End Function

        ''' <summary>
        ''' The specified position is immediately following a statement of one of the given kinds.
        ''' </summary>
        <Extension()>
        Friend Function IsAfterStatementOfKind(syntaxTree As SyntaxTree, position As Integer, targetToken As SyntaxToken, cancellationToken As CancellationToken, ParamArray kinds As SyntaxKind()) As Boolean
            Debug.Assert(targetToken = syntaxTree.GetTargetToken(position, cancellationToken))
            If targetToken.Kind = SyntaxKind.None OrElse targetToken.Parent Is Nothing Then
                Return False
            End If

            If Not targetToken.FollowsEndOfStatement(position) Then
                Return False
            End If

            Return targetToken.GetAncestor(Of StatementSyntax).IsKind(kinds)
        End Function

        <Extension()>
        Friend Function IsInStatementBlockOfKind(syntaxTree As SyntaxTree, position As Integer, targetToken As SyntaxToken, cancellationToken As CancellationToken, ParamArray kinds As SyntaxKind()) As Boolean
            Debug.Assert(targetToken = syntaxTree.GetTargetToken(position, cancellationToken))
            Dim ancestor = targetToken.Parent

            Do While ancestor IsNot Nothing
                If TypeOf ancestor Is EndBlockStatementSyntax Then
                    ' If we're within the End Block, skip the block itself
                    ancestor = ancestor.Parent.Parent

                    If ancestor Is Nothing Then
                        Return False
                    End If
                End If

                If ancestor.IsKind(kinds) Then
                    Return True
                End If

                If TypeOf ancestor Is LambdaExpressionSyntax Then
                    If Not (targetToken.FollowsEndOfStatement(position) AndAlso targetToken = ancestor.GetLastToken()) Then
                        ' We should not look past lambdas
                        Return False
                    End If
                End If

                ancestor = ancestor.Parent
            Loop

            Return False
        End Function

        <Extension()>
        Public Function IsQueryIntoClauseContext(syntaxTree As SyntaxTree, position As Integer, targetToken As SyntaxToken, cancellationToken As CancellationToken) As Boolean
            Debug.Assert(targetToken = syntaxTree.GetTargetToken(position, cancellationToken))
            If targetToken.Kind = SyntaxKind.None Then
                Return False
            End If

            If targetToken.Parent.FirstAncestorOrSelf(Of AggregateClauseSyntax)() Is Nothing AndAlso
               targetToken.Parent.FirstAncestorOrSelf(Of GroupByClauseSyntax)() Is Nothing AndAlso
               targetToken.Parent.FirstAncestorOrSelf(Of GroupJoinClauseSyntax)() Is Nothing Then
                Return False
            End If

            If targetToken.IsChildToken(Of AggregateClauseSyntax)(Function(a) a.IntoKeyword) OrElse
               targetToken.IsChildSeparatorToken(Of AggregateClauseSyntax, AggregationRangeVariableSyntax)(Function(a) a.AggregationVariables) OrElse
               targetToken.IsChildToken(Of GroupByClauseSyntax)(Function(g) g.IntoKeyword) OrElse
               targetToken.IsChildSeparatorToken(Of GroupByClauseSyntax, AggregationRangeVariableSyntax)(Function(g) g.AggregationVariables) OrElse
               targetToken.IsChildToken(Of GroupJoinClauseSyntax)(Function(g) g.IntoKeyword) OrElse
               targetToken.IsChildSeparatorToken(Of GroupJoinClauseSyntax, AggregationRangeVariableSyntax)(Function(g) g.AggregationVariables) Then
                Return True
            End If

            If targetToken.Kind = SyntaxKind.EqualsToken Then
                Dim aggregationRangeVariable = targetToken.GetAncestor(Of AggregationRangeVariableSyntax)()
                If aggregationRangeVariable IsNot Nothing AndAlso aggregationRangeVariable.NameEquals IsNot Nothing Then
                    If aggregationRangeVariable.NameEquals.EqualsToken = targetToken Then
                        Return True
                    End If
                End If
            End If

            Return False
        End Function

        <Extension()>
        Public Function IsRaiseEventContext(syntaxTree As SyntaxTree, position As Integer, targetToken As SyntaxToken, cancellationToken As CancellationToken) As Boolean
            Debug.Assert(targetToken = syntaxTree.GetTargetToken(position, cancellationToken))
            Return Not targetToken.FollowsEndOfStatement(position) AndAlso targetToken.Kind = SyntaxKind.RaiseEventKeyword
        End Function

        <Extension()>
        Public Function IsObjectCreationTypeContext(syntaxTree As SyntaxTree, position As Integer, cancellationToken As CancellationToken) As Boolean
            Dim targetToken = syntaxTree.GetTargetToken(position, cancellationToken)
            Return IsObjectCreationTypeContext(syntaxTree, position, targetToken, cancellationToken)
        End Function

        <Extension()>
        Public Function IsObjectCreationTypeContext(syntaxTree As SyntaxTree, position As Integer, targetToken As SyntaxToken, cancellationToken As CancellationToken) As Boolean
            Debug.Assert(targetToken = syntaxTree.GetTargetToken(position, cancellationToken))

            If Not targetToken.FollowsEndOfStatement(position) AndAlso targetToken.Kind = SyntaxKind.NewKeyword Then
                Return syntaxTree.IsTypeContext(position, targetToken, cancellationToken) OrElse
                       syntaxTree.IsMultiLineStatementStartContext(position, targetToken, cancellationToken) OrElse
                       syntaxTree.IsSingleLineStatementContext(position, targetToken, cancellationToken)
            End If

            Return False
        End Function

        <Extension>
        Friend Function IsEnumTypeMemberAccessContext(syntaxTree As SyntaxTree, position As Integer, targetToken As SyntaxToken, semanticModel As SemanticModel, cancellationToken As CancellationToken) As Boolean
            Debug.Assert(targetToken = syntaxTree.GetTargetToken(position, cancellationToken))

            If Not targetToken.IsKind(SyntaxKind.DotToken) OrElse
               Not targetToken.Parent.IsKind(SyntaxKind.SimpleMemberAccessExpression) Then

                Return False
            End If

            Dim memberAccess = DirectCast(targetToken.Parent, MemberAccessExpressionSyntax)

            Dim leftExpression = memberAccess.GetExpressionOfMemberAccessExpression()
            If leftExpression Is Nothing Then
                Return False
            End If

            Dim leftHandBinding = semanticModel.GetSymbolInfo(leftExpression, cancellationToken)
            Dim symbol = leftHandBinding.GetBestOrAllSymbols().FirstOrDefault()

            If symbol Is Nothing Then
                Return False
            End If

            Select Case symbol.Kind
                Case SymbolKind.NamedType
                    Return DirectCast(symbol, INamedTypeSymbol).TypeKind = TypeKind.Enum
                Case SymbolKind.Alias
                    Dim target = DirectCast(symbol, IAliasSymbol).Target
                    Return target.IsType AndAlso DirectCast(target, ITypeSymbol).TypeKind = TypeKind.Enum
            End Select

            Return False
        End Function

        <Extension()>
        Friend Function IsFollowingCompleteExpression(Of TParent As SyntaxNode)(
            syntaxTree As SyntaxTree,
            position As Integer,
            targetToken As SyntaxToken,
            childGetter As Func(Of TParent, ExpressionSyntax),
            cancellationToken As CancellationToken,
            Optional allowImplicitLineContinuation As Boolean = True
        ) As Boolean

            Debug.Assert(targetToken = syntaxTree.GetTargetToken(position, cancellationToken))

            ' Check if our position begins a new statement
            If targetToken.MustBeginNewStatement(position) OrElse
                (targetToken.FollowsEndOfStatement(position) AndAlso Not allowImplicitLineContinuation) Then

                Return False
            End If

            For Each parent In targetToken.GetAncestors(Of TParent)()
                Dim expression = childGetter(parent)

                If expression Is Nothing Then
                    Continue For
                End If

                Dim terminatingToken = GetExpressionTerminatingToken(expression)
                If terminatingToken.Kind <> SyntaxKind.None AndAlso
                   Not terminatingToken.IsMissing AndAlso
                   terminatingToken = targetToken Then

                    Return True
                End If
            Next

            Return False
        End Function

        ''' <summary>
        ''' Given a syntax node, this returns the token that is the "end" token that ends this
        ''' expression.
        ''' </summary>
        ''' <param name="expression">The expression to get the last token of.</param>
        ''' <returns>The last token, or SyntaxKind.None if the last token is missing.</returns>
        Friend Function GetExpressionTerminatingToken(expression As SyntaxNode) As SyntaxToken
            Dim parenthesizedExpression = TryCast(expression, ParenthesizedExpressionSyntax)
            If parenthesizedExpression IsNot Nothing Then
                Return parenthesizedExpression.CloseParenToken
            End If

            Dim literalExpression = TryCast(expression, LiteralExpressionSyntax)
            If literalExpression IsNot Nothing Then
                Return literalExpression.Token
            End If

            Dim binaryExpression = TryCast(expression, BinaryExpressionSyntax)
            If binaryExpression IsNot Nothing Then
                Return GetExpressionTerminatingToken(binaryExpression.Right)
            End If

            Dim invocationExpression = TryCast(expression, InvocationExpressionSyntax)
            If invocationExpression IsNot Nothing AndAlso invocationExpression.ArgumentList IsNot Nothing Then
                Return invocationExpression.ArgumentList.CloseParenToken
            End If

            Dim memberAccessExpression = TryCast(expression, MemberAccessExpressionSyntax)
            If memberAccessExpression IsNot Nothing Then
                Return memberAccessExpression.Name.Identifier
            End If

            Dim functionAggregationExpression = TryCast(expression, FunctionAggregationSyntax)
            If functionAggregationExpression IsNot Nothing Then
                If functionAggregationExpression.OpenParenToken.Kind <> SyntaxKind.None Then
                    Return functionAggregationExpression.CloseParenToken
                Else
                    Return functionAggregationExpression.FunctionName
                End If
            End If

            Dim identifierName = TryCast(expression, IdentifierNameSyntax)
            If identifierName IsNot Nothing Then
                Return identifierName.Identifier
            End If

            Dim predefinedType = TryCast(expression, PredefinedTypeSyntax)
            If predefinedType IsNot Nothing Then
                Return predefinedType.Keyword
            End If

            Dim collectionInitializer = TryCast(expression, CollectionInitializerSyntax)
            If collectionInitializer IsNot Nothing Then
                Return collectionInitializer.CloseBraceToken
            End If

            Dim objectCreation = TryCast(expression, ObjectCreationExpressionSyntax)
            If objectCreation IsNot Nothing Then
                If objectCreation.ArgumentList IsNot Nothing Then
                    Return objectCreation.ArgumentList.CloseParenToken
                ElseIf objectCreation.Type.IsKind(SyntaxKind.QualifiedName) Then
                    Return DirectCast(objectCreation.Type, QualifiedNameSyntax).Right.GetLastToken()
                Else
                    Return objectCreation.Type.GetLastToken()
                End If
            End If

            Dim arrayCreation = TryCast(expression, ArrayCreationExpressionSyntax)
            If arrayCreation IsNot Nothing Then
                Return arrayCreation.Initializer.CloseBraceToken
            End If

            Dim unaryExpression = TryCast(expression, UnaryExpressionSyntax)
            If unaryExpression IsNot Nothing Then
                Return GetExpressionTerminatingToken(unaryExpression.Operand)
            End If

            Dim queryExpression = TryCast(expression, QueryExpressionSyntax)
            If queryExpression IsNot Nothing Then
                Return GetQueryClauseTerminatingToken(queryExpression.Clauses.Last())
            End If

            Dim singleLineLambda = TryCast(expression, SingleLineLambdaExpressionSyntax)
            If singleLineLambda IsNot Nothing AndAlso singleLineLambda.Kind = SyntaxKind.SingleLineFunctionLambdaExpression Then
                Dim bodyExpression = TryCast(singleLineLambda.Body, ExpressionSyntax)
                If bodyExpression IsNot Nothing Then
                    Return GetExpressionTerminatingToken(bodyExpression)
                End If
            End If

            Dim multiLineLambda = TryCast(expression, MultiLineLambdaExpressionSyntax)
            If multiLineLambda IsNot Nothing Then
                If multiLineLambda.EndSubOrFunctionStatement IsNot Nothing Then
                    Return multiLineLambda.EndSubOrFunctionStatement.BlockKeyword
                End If
            End If

            Dim anonymousObjectCreation = TryCast(expression, AnonymousObjectCreationExpressionSyntax)
            If anonymousObjectCreation IsNot Nothing Then
                If anonymousObjectCreation.Initializer IsNot Nothing Then
                    Return anonymousObjectCreation.Initializer.CloseBraceToken
                End If
            End If

            ' A SyntaxTokenStruct with Kind = None
            Return Nothing
        End Function

        Private Function GetQueryClauseTerminatingToken(queryClause As QueryClauseSyntax) As SyntaxToken
            Dim fromClause = TryCast(queryClause, FromClauseSyntax)
            If fromClause IsNot Nothing Then
                Return GetExpressionTerminatingToken(fromClause.Variables.LastCollectionExpression())
            End If

            Dim whereClause = TryCast(queryClause, WhereClauseSyntax)
            If whereClause IsNot Nothing Then
                Return GetExpressionTerminatingToken(whereClause.Condition)
            End If

            Dim letClause = TryCast(queryClause, LetClauseSyntax)
            If letClause IsNot Nothing Then
                Return GetExpressionTerminatingToken(letClause.Variables.LastRangeExpression())
            End If

            Dim orderByClause = TryCast(queryClause, OrderByClauseSyntax)
            If orderByClause IsNot Nothing Then
                Dim lastOrdering = orderByClause.Orderings.Last()

                If lastOrdering.AscendingOrDescendingKeyword.Kind = SyntaxKind.None Then
                    Return GetExpressionTerminatingToken(lastOrdering.Expression)
                Else
                    Return lastOrdering.AscendingOrDescendingKeyword
                End If
            End If

            Dim partitionWhileClause = TryCast(queryClause, PartitionWhileClauseSyntax)
            If partitionWhileClause IsNot Nothing Then
                Return GetExpressionTerminatingToken(partitionWhileClause.Condition)
            End If

            Dim partitionClause = TryCast(queryClause, PartitionClauseSyntax)
            If partitionClause IsNot Nothing Then
                Return GetExpressionTerminatingToken(partitionClause.Count)
            End If

            Dim aggregateClause = TryCast(queryClause, AggregateClauseSyntax)
            If aggregateClause IsNot Nothing Then
                If aggregateClause.AdditionalQueryOperators.Any() Then
                    Return GetQueryClauseTerminatingToken(aggregateClause.AdditionalQueryOperators.Last())
                Else
                    Return GetExpressionTerminatingToken(aggregateClause.Variables.LastCollectionExpression())
                End If
            End If

            Dim groupJoinClause = TryCast(queryClause, GroupJoinClauseSyntax)
            If groupJoinClause IsNot Nothing Then
                Return GetExpressionTerminatingToken(groupJoinClause.AggregationVariables.LastAggregation())
            End If

            Dim joinClause = TryCast(queryClause, SimpleJoinClauseSyntax)
            If joinClause IsNot Nothing Then
                Dim lastJoinCondition = joinClause.JoinConditions.LastOrDefault()

                If lastJoinCondition IsNot Nothing Then
                    Return GetExpressionTerminatingToken(lastJoinCondition.Right)
                Else
                    Return Nothing
                End If
            End If

            Dim groupByClause = TryCast(queryClause, GroupByClauseSyntax)
            If groupByClause IsNot Nothing Then
                Return GetExpressionTerminatingToken(groupByClause.AggregationVariables.LastAggregation())
            End If

            Dim selectClause = TryCast(queryClause, SelectClauseSyntax)
            If selectClause IsNot Nothing Then
                Return GetExpressionTerminatingToken(selectClause.Variables.LastRangeExpression())
            End If

            Dim distinctClause = TryCast(queryClause, DistinctClauseSyntax)
            If distinctClause IsNot Nothing Then
                Return distinctClause.DistinctKeyword
            End If

            Throw ExceptionUtilities.Unreachable
        End Function

        <Extension()>
        Friend Function LastCollectionExpression(collection As SeparatedSyntaxList(Of CollectionRangeVariableSyntax)) As ExpressionSyntax
            Dim lastCollectionRange = collection.LastOrDefault()

            If lastCollectionRange IsNot Nothing Then
                Return lastCollectionRange.Expression
            Else
                Return Nothing
            End If
        End Function

        <Extension()>
        Friend Function LastRangeExpression(collection As SeparatedSyntaxList(Of ExpressionRangeVariableSyntax)) As ExpressionSyntax
            Dim lastCollectionRange = collection.LastOrDefault()

            If lastCollectionRange IsNot Nothing Then
                Return lastCollectionRange.Expression
            Else
                Return Nothing
            End If
        End Function

        <Extension()>
        Friend Function LastAggregation(collection As SeparatedSyntaxList(Of AggregationRangeVariableSyntax)) As AggregationSyntax
            Dim lastCollectionRange = collection.LastOrDefault()

            If lastCollectionRange IsNot Nothing Then
                Return lastCollectionRange.Aggregation
            Else
                Return Nothing
            End If
        End Function

        ' Tuple literals aren't recognized by the parser until there is a comma
        ' So a parenthesized expression is a possible tuple context too
#Disable Warning IDE0060 ' Remove unused parameter - Unused 'SyntaxTree' parameter for consistency with other Context extension methods
        <Extension>
        Friend Function IsPossibleTupleContext(syntaxTree As SyntaxTree,
                                               tokenOnLeftOfPosition As SyntaxToken,
                                               position As Integer) As Boolean
#Enable Warning IDE0060 ' Remove unused parameter

            tokenOnLeftOfPosition = tokenOnLeftOfPosition.GetPreviousTokenIfTouchingWord(position)

            If tokenOnLeftOfPosition.IsKind(SyntaxKind.OpenParenToken) Then
                Return tokenOnLeftOfPosition.Parent.IsKind(SyntaxKind.ParenthesizedExpression,
                                                           SyntaxKind.TupleExpression, SyntaxKind.TupleType)
            End If

            Return tokenOnLeftOfPosition.IsKind(SyntaxKind.CommaToken) AndAlso
                tokenOnLeftOfPosition.Parent.IsKind(SyntaxKind.TupleExpression, SyntaxKind.TupleType)
        End Function

        <Extension()>
        Public Function IsInPreprocessorExpressionContext(syntaxTree As SyntaxTree, position As Integer, cancellationToken As CancellationToken) As Boolean
            Dim directive As DirectiveTriviaSyntax = Nothing
            If Not IsInPreprocessorDirectiveContext(syntaxTree, position, cancellationToken, directive) Then
                Return False
            End If

            If TypeOf directive IsNot IfDirectiveTriviaSyntax Then
                Return False
            End If

            Dim targetToken = syntaxTree.GetTargetToken(position, cancellationToken)

            ' #if $$
            '
            ' #elseif $$
            '
            ' #if not $$
            '
            ' #if ( $$
            '
            ' #if x or $$
            ' #if x orelse $$
            '
            ' #if x and $$
            ' #if x andalso $$

            Select Case targetToken.Kind()
                Case SyntaxKind.IfKeyword,
                     SyntaxKind.ElseIfKeyword,
                     SyntaxKind.NotKeyword,
                     SyntaxKind.OpenParenToken,
                     SyntaxKind.OrKeyword,
                     SyntaxKind.OrElseKeyword,
                     SyntaxKind.AndKeyword,
                     SyntaxKind.AndAlsoKeyword
                    Return True
            End Select

            Return False
        End Function
    End Module
End Namespace
