File: Classification\ClassificationHelpers.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.Threading
Imports Microsoft.CodeAnalysis.Classification
Imports Microsoft.CodeAnalysis.Collections
Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports Microsoft.CodeAnalysis.VisualBasic.Utilities
 
Namespace Microsoft.CodeAnalysis.VisualBasic.Classification
    Friend Module ClassificationHelpers
        ''' <summary>
        ''' Return the classification type associated with this token.
        ''' </summary>
        ''' <param name="token">The token to be classified.</param>
        ''' <returns>The classification type for the token</returns>
        ''' <remarks></remarks>
        Public Function GetClassification(token As SyntaxToken) As String
            If IsControlKeyword(token) Then
                Return ClassificationTypeNames.ControlKeyword
            ElseIf SyntaxFacts.IsKeywordKind(token.Kind) Then
                Return ClassificationTypeNames.Keyword
            ElseIf IsStringToken(token) Then
                Return ClassificationTypeNames.StringLiteral
            ElseIf SyntaxFacts.IsPunctuation(token.Kind) Then
                Return ClassifyPunctuation(token)
            ElseIf token.Kind = SyntaxKind.IdentifierToken Then
                Return GetSyntacticClassificationForIdentifier(token)
            ElseIf token.IsNumericLiteral() Then
                Return ClassificationTypeNames.NumericLiteral
            ElseIf token.Kind = SyntaxKind.XmlNameToken Then
                Return ClassificationTypeNames.XmlLiteralName
            ElseIf token.Kind = SyntaxKind.XmlTextLiteralToken Then
                Select Case token.Parent.Kind
                    Case SyntaxKind.XmlString
                        Return ClassificationTypeNames.XmlLiteralAttributeValue
                    Case SyntaxKind.XmlProcessingInstruction
                        Return ClassificationTypeNames.XmlLiteralProcessingInstruction
                    Case SyntaxKind.XmlComment
                        Return ClassificationTypeNames.XmlLiteralComment
                    Case SyntaxKind.XmlCDataSection
                        Return ClassificationTypeNames.XmlLiteralCDataSection
                    Case Else
                        Return ClassificationTypeNames.XmlLiteralText
                End Select
            ElseIf token.Kind = SyntaxKind.XmlEntityLiteralToken Then
                Return ClassificationTypeNames.XmlLiteralEntityReference
            ElseIf token.IsKind(SyntaxKind.None, SyntaxKind.BadToken) Then
                Return Nothing
            Else
                Throw ExceptionUtilities.UnexpectedValue(token.Kind())
            End If
        End Function
 
        Private Function IsControlKeyword(token As SyntaxToken) As Boolean
            If token.Parent Is Nothing Then
                Return False
            End If
 
            ' For Exit Statments classify everything as a control keyword
            If token.Parent.IsKind(
                SyntaxKind.ExitFunctionStatement,
                SyntaxKind.ExitOperatorStatement,
                SyntaxKind.ExitPropertyStatement,
                SyntaxKind.ExitSubStatement) Then
                Return True
            End If
 
            ' Control keywords are used in other contexts so check that it is
            ' being used in a supported context.
            Return IsControlKeywordKind(token.Kind) AndAlso
                IsControlStatementKind(token.Parent.Kind)
        End Function
 
        ''' <summary>
        ''' Determine if the kind represents a control keyword
        ''' </summary>
        Private Function IsControlKeywordKind(kind As SyntaxKind) As Boolean
            Select Case kind
                Case _
                SyntaxKind.CaseKeyword,
                SyntaxKind.CatchKeyword,
                SyntaxKind.ContinueKeyword,
                SyntaxKind.DoKeyword,
                SyntaxKind.EachKeyword,
                SyntaxKind.ElseKeyword,
                SyntaxKind.ElseIfKeyword,
                SyntaxKind.EndKeyword,
                SyntaxKind.ExitKeyword,
                SyntaxKind.FinallyKeyword,
                SyntaxKind.ForKeyword,
                SyntaxKind.GoToKeyword,
                SyntaxKind.IfKeyword,
                SyntaxKind.InKeyword,
                SyntaxKind.LoopKeyword,
                SyntaxKind.NextKeyword,
                SyntaxKind.ResumeKeyword,
                SyntaxKind.ReturnKeyword,
                SyntaxKind.SelectKeyword,
                SyntaxKind.ThenKeyword,
                SyntaxKind.TryKeyword,
                SyntaxKind.WhileKeyword,
                SyntaxKind.WendKeyword,
                SyntaxKind.UntilKeyword,
                SyntaxKind.EndIfKeyword,
                SyntaxKind.GosubKeyword,
                SyntaxKind.YieldKeyword,
                SyntaxKind.ThrowKeyword,
                SyntaxKind.ToKeyword
                    Return True
                Case Else
                    Return False
            End Select
        End Function
 
        ''' <summary>
        ''' Determine if the kind represents a control statement
        ''' </summary>
        Private Function IsControlStatementKind(kind As SyntaxKind) As Boolean
            Select Case kind
                Case _
                SyntaxKind.CallStatement,
                SyntaxKind.CaseElseStatement,
                SyntaxKind.CaseStatement,
                SyntaxKind.CatchStatement,
                SyntaxKind.ContinueDoStatement,
                SyntaxKind.ContinueForStatement,
                SyntaxKind.ContinueWhileStatement,
                SyntaxKind.DoUntilStatement,
                SyntaxKind.DoWhileStatement,
                SyntaxKind.ElseIfStatement,
                SyntaxKind.ElseStatement,
                SyntaxKind.EndIfStatement,
                SyntaxKind.EndSelectStatement,
                SyntaxKind.EndTryStatement,
                SyntaxKind.EndWhileStatement,
                SyntaxKind.ExitDoStatement,
                SyntaxKind.ExitForStatement,
                SyntaxKind.ExitSelectStatement,
                SyntaxKind.ExitTryStatement,
                SyntaxKind.ExitWhileStatement,
                SyntaxKind.FinallyStatement,
                SyntaxKind.ForEachStatement,
                SyntaxKind.ForStatement,
                SyntaxKind.GoToStatement,
                SyntaxKind.IfStatement,
                SyntaxKind.LoopUntilStatement,
                SyntaxKind.LoopWhileStatement,
                SyntaxKind.NextStatement,
                SyntaxKind.ResumeLabelStatement,
                SyntaxKind.ResumeNextStatement,
                SyntaxKind.ReturnStatement,
                SyntaxKind.SelectStatement,
                SyntaxKind.SimpleDoStatement,
                SyntaxKind.SimpleLoopStatement,
                SyntaxKind.SingleLineIfStatement,
                SyntaxKind.ThrowStatement,
                SyntaxKind.TryStatement,
                SyntaxKind.UntilClause,
                SyntaxKind.WhileClause,
                SyntaxKind.WhileStatement,
                SyntaxKind.YieldStatement,
                SyntaxKind.TernaryConditionalExpression
                    Return True
                Case Else
                    Return False
            End Select
        End Function
 
        Private Function ClassifyPunctuation(token As SyntaxToken) As String
            If AllOperators.Contains(token.Kind) Then
                ' special cases...
                Select Case token.Kind
                    Case SyntaxKind.LessThanToken, SyntaxKind.GreaterThanToken
                        If TypeOf token.Parent Is AttributeListSyntax Then
                            Return ClassificationTypeNames.Punctuation
                        End If
                End Select
 
                Return ClassificationTypeNames.Operator
            Else
                Return ClassificationTypeNames.Punctuation
            End If
        End Function
 
        Public Function GetSyntacticClassificationForIdentifier(identifier As SyntaxToken) As String
            'Note: parent might be Nothing, if we are classifying raw tokens.
            Dim parent = identifier.Parent
 
            Dim classification As String = Nothing
 
            If TypeOf parent Is IdentifierNameSyntax AndAlso IsNamespaceName(DirectCast(parent, IdentifierNameSyntax)) Then
                Return ClassificationTypeNames.NamespaceName
            ElseIf TypeOf parent Is TypeStatementSyntax AndAlso DirectCast(parent, TypeStatementSyntax).Identifier = identifier Then
                Return ClassifyTypeDeclarationIdentifier(identifier)
            ElseIf TypeOf parent Is EnumStatementSyntax AndAlso DirectCast(parent, EnumStatementSyntax).Identifier = identifier Then
                Return ClassificationTypeNames.EnumName
            ElseIf TypeOf parent Is DelegateStatementSyntax AndAlso DirectCast(parent, DelegateStatementSyntax).Identifier = identifier AndAlso
                  (parent.Kind = SyntaxKind.DelegateSubStatement OrElse parent.Kind = SyntaxKind.DelegateFunctionStatement) Then
 
                Return ClassificationTypeNames.DelegateName
            ElseIf TypeOf parent Is TypeParameterSyntax AndAlso DirectCast(parent, TypeParameterSyntax).Identifier = identifier Then
                Return ClassificationTypeNames.TypeParameterName
            ElseIf TypeOf parent Is MethodStatementSyntax AndAlso DirectCast(parent, MethodStatementSyntax).Identifier = identifier Then
                Return ClassificationTypeNames.MethodName
            ElseIf TypeOf parent Is DeclareStatementSyntax AndAlso DirectCast(parent, DeclareStatementSyntax).Identifier = identifier Then
                Return ClassificationTypeNames.MethodName
            ElseIf TypeOf parent Is PropertyStatementSyntax AndAlso DirectCast(parent, PropertyStatementSyntax).Identifier = identifier Then
                Return ClassificationTypeNames.PropertyName
            ElseIf TypeOf parent Is EventStatementSyntax AndAlso DirectCast(parent, EventStatementSyntax).Identifier = identifier Then
                Return ClassificationTypeNames.EventName
            ElseIf TypeOf parent Is EnumMemberDeclarationSyntax AndAlso DirectCast(parent, EnumMemberDeclarationSyntax).Identifier = identifier Then
                Return ClassificationTypeNames.EnumMemberName
            ElseIf TypeOf parent Is LabelStatementSyntax AndAlso DirectCast(parent, LabelStatementSyntax).LabelToken = identifier Then
                Return ClassificationTypeNames.LabelName
            ElseIf TypeOf parent?.Parent Is CatchStatementSyntax AndAlso DirectCast(parent.Parent, CatchStatementSyntax).IdentifierName.Identifier = identifier Then
                Return ClassificationTypeNames.LocalName
            ElseIf TryClassifyModifiedIdentifer(parent, identifier, classification) Then
                Return classification
            ElseIf (identifier.ToString() = "IsTrue" OrElse identifier.ToString() = "IsFalse") AndAlso
                TypeOf parent Is OperatorStatementSyntax AndAlso DirectCast(parent, OperatorStatementSyntax).OperatorToken = identifier Then
 
                Return ClassificationTypeNames.Keyword
            End If
 
            Return ClassificationTypeNames.Identifier
        End Function
 
        Private Function IsNamespaceName(identifierSyntax As IdentifierNameSyntax) As Boolean
            Dim parent = identifierSyntax.Parent
 
            While TypeOf parent Is QualifiedNameSyntax
                parent = parent.Parent
            End While
 
            Return TypeOf parent Is NamespaceStatementSyntax
        End Function
 
        Public Function IsStaticallyDeclared(identifier As SyntaxToken) As Boolean
            'Note: parent might be Nothing, if we are classifying raw tokens.
            Dim parent = identifier.Parent
 
            If parent.IsKind(SyntaxKind.EnumMemberDeclaration) Then
                ' EnumMembers are not classified as static since there is no
                ' instance equivalent of the concept and they have their own
                ' classification type.
                Return False
            ElseIf parent.IsKind(SyntaxKind.ModifiedIdentifier) Then
                parent = parent.Parent?.Parent
 
                ' We are specifically looking for field declarations or constants.
                If Not parent.IsKind(SyntaxKind.FieldDeclaration) Then
                    Return False
                End If
 
                If parent.GetModifiers().Any(SyntaxKind.ConstKeyword) Then
                    Return True
                End If
            End If
 
            Return parent.GetModifiers().Any(SyntaxKind.SharedKeyword)
        End Function
 
        Private Function IsStringToken(token As SyntaxToken) As Boolean
            If token.IsKind(SyntaxKind.StringLiteralToken, SyntaxKind.CharacterLiteralToken, SyntaxKind.InterpolatedStringTextToken) Then
                Return True
            End If
 
            Return token.IsKind(SyntaxKind.DollarSignDoubleQuoteToken, SyntaxKind.DoubleQuoteToken) AndAlso
                   token.Parent.IsKind(SyntaxKind.InterpolatedStringExpression)
        End Function
 
        Private Function TryClassifyModifiedIdentifer(node As SyntaxNode, identifier As SyntaxToken, ByRef classification As String) As Boolean
            classification = Nothing
 
            If TypeOf node IsNot ModifiedIdentifierSyntax OrElse DirectCast(node, ModifiedIdentifierSyntax).Identifier <> identifier Then
                Return False
            End If
 
            If TypeOf node.Parent Is ParameterSyntax Then
                classification = ClassificationTypeNames.ParameterName
                Return True
            End If
 
            If TypeOf node.Parent IsNot VariableDeclaratorSyntax Then
                Return False
            End If
 
            If TypeOf node.Parent.Parent Is LocalDeclarationStatementSyntax Then
                Dim localDeclaration = DirectCast(node.Parent.Parent, LocalDeclarationStatementSyntax)
                classification = If(localDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword), ClassificationTypeNames.ConstantName, ClassificationTypeNames.LocalName)
                Return True
            End If
 
            If TypeOf node.Parent.Parent Is FieldDeclarationSyntax Then
                Dim localDeclaration = DirectCast(node.Parent.Parent, FieldDeclarationSyntax)
                classification = If(localDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword), ClassificationTypeNames.ConstantName, ClassificationTypeNames.FieldName)
                Return True
            End If
 
            Return False
        End Function
 
        Private Function ClassifyTypeDeclarationIdentifier(identifier As SyntaxToken) As String
            Select Case identifier.Parent.Kind
                Case SyntaxKind.ClassStatement
                    Return ClassificationTypeNames.ClassName
                Case SyntaxKind.ModuleStatement
                    Return ClassificationTypeNames.ModuleName
                Case SyntaxKind.InterfaceStatement
                    Return ClassificationTypeNames.InterfaceName
                Case SyntaxKind.StructureStatement
                    Return ClassificationTypeNames.StructName
                Case Else
                    Throw ExceptionUtilities.UnexpectedValue(identifier.Parent.Kind)
            End Select
        End Function
 
        Friend Sub AddLexicalClassifications(text As SourceText, textSpan As TextSpan, result As SegmentedList(Of ClassifiedSpan), cancellationToken As CancellationToken)
            Dim text2 = text.ToString(textSpan)
            Dim tokens = SyntaxFactory.ParseTokens(text2, initialTokenPosition:=textSpan.Start)
            Worker.CollectClassifiedSpans(tokens, textSpan, result, cancellationToken)
        End Sub
 
#Disable Warning IDE0060 ' Remove unused parameter - TODO: Do we need to do the same work here that we do in C#?
        Friend Function AdjustStaleClassification(text As SourceText, classifiedSpan As ClassifiedSpan) As ClassifiedSpan
#Enable Warning IDE0060 ' Remove unused parameter
            Return classifiedSpan
        End Function
    End Module
End Namespace