|
' 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.LanguageService
Imports Microsoft.CodeAnalysis.VisualBasic.LanguageService
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions
Partial Friend Module SyntaxTokenExtensions
<Extension()>
Public Function IsKind(token As SyntaxToken, kind1 As SyntaxKind, kind2 As SyntaxKind) As Boolean
Return token.Kind = kind1 OrElse
token.Kind = kind2
End Function
<Extension()>
Public Function IsKind(token As SyntaxToken, ParamArray kinds As SyntaxKind()) As Boolean
Return kinds.Contains(token.Kind)
End Function
<Extension()>
Public Function IsKindOrHasMatchingText(token As SyntaxToken, kind As SyntaxKind) As Boolean
Return token.Kind = kind OrElse
token.HasMatchingText(kind)
End Function
<Extension()>
Public Function HasMatchingText(token As SyntaxToken, kind As SyntaxKind) As Boolean
Return String.Equals(token.ToString(), SyntaxFacts.GetText(kind), StringComparison.OrdinalIgnoreCase)
End Function
<Extension()>
Public Function IsCharacterLiteral(token As SyntaxToken) As Boolean
Return token.Kind = SyntaxKind.CharacterLiteralToken
End Function
<Extension()>
Public Function IsNumericLiteral(token As SyntaxToken) As Boolean
Return _
token.Kind = SyntaxKind.DateLiteralToken OrElse
token.Kind = SyntaxKind.DecimalLiteralToken OrElse
token.Kind = SyntaxKind.FloatingLiteralToken OrElse
token.Kind = SyntaxKind.IntegerLiteralToken
End Function
<Extension()>
Public Function IsNewOnRightSideOfDotOrBang(token As SyntaxToken) As Boolean
Dim expression = TryCast(token.Parent, ExpressionSyntax)
Return If(expression IsNot Nothing,
expression.IsNewOnRightSideOfDotOrBang(),
False)
End Function
<Extension()>
Public Function IsSkipped(token As SyntaxToken) As Boolean
Return TypeOf token.Parent Is SkippedTokensTriviaSyntax
End Function
<Extension()>
Public Function FirstAncestorOrSelf(token As SyntaxToken, predicate As Func(Of SyntaxNode, Boolean)) As SyntaxNode
Return token.Parent.FirstAncestorOrSelf(predicate)
End Function
<Extension()>
Public Function HasAncestor(Of T As SyntaxNode)(token As SyntaxToken) As Boolean
Return token.GetAncestor(Of T)() IsNot Nothing
End Function
''' <summary>
''' Returns true if is a given token is a child token of a certain type of parent node.
''' </summary>
''' <typeparam name="TParent">The type of the parent node.</typeparam>
''' <param name="token">The token that we are testing.</param>
''' <param name="childGetter">A function that, when given the parent node, returns the child token we are interested in.</param>
<Extension()>
Public Function IsChildToken(Of TParent As SyntaxNode)(token As SyntaxToken, childGetter As Func(Of TParent, SyntaxToken)) As Boolean
Dim ancestor = token.GetAncestor(Of TParent)()
If ancestor Is Nothing Then
Return False
End If
Dim ancestorToken = childGetter(ancestor)
Return token = ancestorToken
End Function
''' <summary>
''' Returns true if is a given token is a separator token in a given parent list.
''' </summary>
''' <typeparam name="TParent">The type of the parent node containing the separated list.</typeparam>
''' <param name="token">The token that we are testing.</param>
''' <param name="childGetter">A function that, when given the parent node, returns the separated list.</param>
<Extension()>
Public Function IsChildSeparatorToken(Of TParent As SyntaxNode, TChild As SyntaxNode)(token As SyntaxToken, childGetter As Func(Of TParent, SeparatedSyntaxList(Of TChild))) As Boolean
Dim ancestor = token.GetAncestor(Of TParent)()
If ancestor Is Nothing Then
Return False
End If
Dim separatedList = childGetter(ancestor)
For i = 0 To separatedList.SeparatorCount - 1
If separatedList.GetSeparator(i) = token Then
Return True
End If
Next
Return False
End Function
<Extension>
Public Function IsDescendantOf(token As SyntaxToken, node As SyntaxNode) As Boolean
Return token.Parent IsNot Nothing AndAlso
token.Parent.AncestorsAndSelf().Any(Function(n) n Is node)
End Function
<Extension()>
Friend Function GetInnermostDeclarationContext(node As SyntaxToken) As SyntaxNode
Dim ancestors = node.GetAncestors(Of SyntaxNode)
' In error cases where the declaration is not complete, the parser attaches the incomplete token to the
' trailing trivia of preceding block. In such cases, skip through the siblings and search upwards to find a candidate ancestor.
If TypeOf ancestors.FirstOrDefault() Is EndBlockStatementSyntax Then
' If the first ancestor is an EndBlock, the second is the matching OpenBlock, if one exists
Dim openBlock = ancestors.ElementAtOrDefault(1)
Dim closeTypeBlock = DirectCast(ancestors.First(), EndBlockStatementSyntax)
If openBlock Is Nothing Then
' case: No matching open block
' End Class
' C|
ancestors = ancestors.Skip(1)
ElseIf TypeOf openBlock Is TypeBlockSyntax Then
ancestors = FilterAncestors(ancestors, DirectCast(openBlock, TypeBlockSyntax).EndBlockStatement, closeTypeBlock)
ElseIf TypeOf openBlock Is NamespaceBlockSyntax Then
ancestors = FilterAncestors(ancestors, DirectCast(openBlock, NamespaceBlockSyntax).EndNamespaceStatement, closeTypeBlock)
ElseIf TypeOf openBlock Is EnumBlockSyntax Then
ancestors = FilterAncestors(ancestors, DirectCast(openBlock, EnumBlockSyntax).EndEnumStatement, closeTypeBlock)
End If
End If
Return ancestors.FirstOrDefault(
Function(ancestor) ancestor.IsKind(SyntaxKind.ClassBlock,
SyntaxKind.StructureBlock,
SyntaxKind.EnumBlock,
SyntaxKind.InterfaceBlock,
SyntaxKind.NamespaceBlock,
SyntaxKind.ModuleBlock,
SyntaxKind.CompilationUnit))
End Function
Private Function FilterAncestors(ancestors As IEnumerable(Of SyntaxNode),
parentEndBlock As EndBlockStatementSyntax,
precedingEndBlock As EndBlockStatementSyntax) As IEnumerable(Of SyntaxNode)
If parentEndBlock.Equals(precedingEndBlock) Then
' case: the preceding end block has a matching open block and the declaration context for 'C' is 'N1'
' Namespace N1
' Class C1
'
' End Class
' C|
' End Namespace
Return ancestors.Skip(2)
Else
' case: mismatched end block and the declaration context for 'C' is 'N1'
' Namespace N1
' End Class
' C|
' End Namespace
Return ancestors.Skip(1)
End If
End Function
<Extension()>
Public Function GetContainingMember(token As SyntaxToken) As DeclarationStatementSyntax
Return token.GetAncestors(Of DeclarationStatementSyntax) _
.FirstOrDefault(Function(a)
Return a.IsMemberDeclaration() OrElse
(a.IsMemberBlock() AndAlso a.GetMemberBlockBegin().IsMemberDeclaration())
End Function)
End Function
<Extension()>
Public Function GetContainingMemberBlockBegin(token As SyntaxToken) As StatementSyntax
Return token.GetContainingMember().GetMemberBlockBegin()
End Function
''' <summary>
''' Determines whether the given SyntaxToken is the first token on a line
''' </summary>
<Extension()>
Public Function IsFirstTokenOnLine(token As SyntaxToken) As Boolean
Dim previousToken = token.GetPreviousToken(includeSkipped:=True, includeDirectives:=True, includeDocumentationComments:=True)
If previousToken.Kind = SyntaxKind.None Then
Return True
End If
Dim text = token.SyntaxTree.GetText()
Dim tokenLine = text.Lines.IndexOf(token.SpanStart)
Dim previousTokenLine = text.Lines.IndexOf(previousToken.SpanStart)
Return tokenLine > previousTokenLine
End Function
<Extension()>
Public Function SpansPreprocessorDirective(tokens As IEnumerable(Of SyntaxToken)) As Boolean
Return VisualBasicSyntaxFacts.Instance.SpansPreprocessorDirective(tokens)
End Function
<Extension()>
Public Function GetPreviousTokenIfTouchingWord(token As SyntaxToken, position As Integer) As SyntaxToken
Return If(token.IntersectsWith(position) AndAlso IsWord(token),
token.GetPreviousToken(includeSkipped:=True),
token)
End Function
<Extension>
Public Function IsWord(token As SyntaxToken) As Boolean
Return VisualBasicSyntaxFacts.Instance.IsWord(token)
End Function
<Extension()>
Public Function IntersectsWith(token As SyntaxToken, position As Integer) As Boolean
Return token.Span.IntersectsWith(position)
End Function
<Extension()>
Public Function GetNextNonZeroWidthTokenOrEndOfFile(token As SyntaxToken) As SyntaxToken
Dim nextToken = token.GetNextToken()
Return If(nextToken.Kind = SyntaxKind.None, token.GetAncestor(Of CompilationUnitSyntax)().EndOfFileToken, nextToken)
End Function
<Extension>
Public Function IsValidAttributeTarget(token As SyntaxToken) As Boolean
Return token.Kind() = SyntaxKind.AssemblyKeyword OrElse
token.Kind() = SyntaxKind.ModuleKeyword
End Function
End Module
End Namespace
|