File: Classification\SyntaxClassification\NameSyntaxClassifier.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.Threading
Imports Microsoft.CodeAnalysis.Classification
Imports Microsoft.CodeAnalysis.Classification.Classifiers
Imports Microsoft.CodeAnalysis.Collections
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Extensions.ContextQuery
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
 
Namespace Microsoft.CodeAnalysis.VisualBasic.Classification.Classifiers
    Friend Class NameSyntaxClassifier
        Inherits AbstractNameSyntaxClassifier
 
        Public Overrides ReadOnly Property SyntaxNodeTypes As ImmutableArray(Of Type) = ImmutableArray.Create(
            GetType(CrefOperatorReferenceSyntax),
            GetType(GenericNameSyntax),
            GetType(GlobalNameSyntax),
            GetType(IdentifierNameSyntax),
            GetType(LabelSyntax),
            GetType(MethodStatementSyntax),
            GetType(ModifiedIdentifierSyntax),
            GetType(QualifiedCrefOperatorReferenceSyntax),
            GetType(QualifiedNameSyntax),
            GetType(SimpleNameSyntax))
 
        Public Overrides Sub AddClassifications(
                syntax As SyntaxNode,
                textSpan As TextSpan,
                semanticModel As SemanticModel,
                options As ClassificationOptions,
                result As SegmentedList(Of ClassifiedSpan),
                cancellationToken As CancellationToken)
 
            Dim nameSyntax = TryCast(syntax, NameSyntax)
            If nameSyntax IsNot Nothing Then
                ClassifyNameSyntax(nameSyntax, semanticModel, result, cancellationToken)
                Return
            End If
 
            Dim modifiedIdentifier = TryCast(syntax, ModifiedIdentifierSyntax)
            If modifiedIdentifier IsNot Nothing Then
                ClassifyModifiedIdentifier(modifiedIdentifier, result)
                Return
            End If
 
            Dim methodStatement = TryCast(syntax, MethodStatementSyntax)
            If methodStatement IsNot Nothing Then
                ClassifyMethodStatement(methodStatement, semanticModel, result)
                Return
            End If
 
            Dim labelSyntax = TryCast(syntax, LabelSyntax)
            If labelSyntax IsNot Nothing Then
                ClassifyLabelSyntax(labelSyntax, result)
                Return
            End If
        End Sub
 
        Protected Overrides Function IsParentAnAttribute(node As SyntaxNode) As Boolean
            Return node.IsParentKind(SyntaxKind.Attribute)
        End Function
 
        Private Sub ClassifyNameSyntax(
                node As NameSyntax,
                semanticModel As SemanticModel,
                result As SegmentedList(Of ClassifiedSpan),
                cancellationToken As CancellationToken)
 
            Dim classifiedSpan As ClassifiedSpan
 
            Dim symbolInfo = semanticModel.GetSymbolInfo(node, cancellationToken)
            Dim symbol = TryGetSymbol(node, symbolInfo)
 
            If symbol Is Nothing Then
                If TryClassifyIdentifier(node, semanticModel, cancellationToken, classifiedSpan) Then
                    result.Add(classifiedSpan)
                End If
 
                Return
            End If
 
            If TryClassifyMyNamespace(node, symbol, semanticModel, classifiedSpan) Then
                result.Add(classifiedSpan)
            ElseIf TryClassifySymbol(node, symbol, classifiedSpan) Then
                result.Add(classifiedSpan)
 
                ' Additionally classify static symbols
                TryClassifyStaticSymbol(symbol, classifiedSpan.TextSpan, result)
            End If
        End Sub
 
        Private Shared Function TryClassifySymbol(
            node As NameSyntax,
            symbol As ISymbol,
            ByRef classifiedSpan As ClassifiedSpan) As Boolean
 
            Select Case symbol.Kind
                Case SymbolKind.Namespace
                    ' Do not classify the Global namespace. It is already syntactically classified as a keyword.
                    ' Also, we ignore QualifiedNameSyntax nodes since we want to classify individual name parts.
                    If Not node.IsKind(SyntaxKind.GlobalName) AndAlso TypeOf node Is IdentifierNameSyntax Then
                        classifiedSpan = New ClassifiedSpan(GetNameToken(node).Span, ClassificationTypeNames.NamespaceName)
                        Return True
                    End If
                Case SymbolKind.Method
                    Dim classification = GetClassificationForMethod(node, DirectCast(symbol, IMethodSymbol))
                    If classification IsNot Nothing Then
                        classifiedSpan = New ClassifiedSpan(GetNameToken(node).Span, classification)
                        Return True
                    End If
                Case SymbolKind.Event
                    classifiedSpan = New ClassifiedSpan(GetNameToken(node).Span, ClassificationTypeNames.EventName)
                    Return True
                Case SymbolKind.Property
                    classifiedSpan = New ClassifiedSpan(GetNameToken(node).Span, ClassificationTypeNames.PropertyName)
                    Return True
                Case SymbolKind.Field
                    Dim classification = GetClassificationForField(DirectCast(symbol, IFieldSymbol))
                    If classification IsNot Nothing Then
                        classifiedSpan = New ClassifiedSpan(GetNameToken(node).Span, classification)
                        Return True
                    End If
                Case SymbolKind.Parameter
                    classifiedSpan = New ClassifiedSpan(GetNameToken(node).Span, ClassificationTypeNames.ParameterName)
                    Return True
                Case SymbolKind.Local
                    Dim classification = GetClassificationForLocal(DirectCast(symbol, ILocalSymbol))
                    If classification IsNot Nothing Then
                        classifiedSpan = New ClassifiedSpan(GetNameToken(node).Span, classification)
                        Return True
                    End If
            End Select
 
            Dim type = TryCast(symbol, ITypeSymbol)
            If type IsNot Nothing Then
                Dim classification = GetClassificationForType(type)
                If classification IsNot Nothing Then
                    Dim token = GetNameToken(node)
                    classifiedSpan = New ClassifiedSpan(token.Span, classification)
                    Return True
                End If
            End If
 
            Return False
        End Function
 
        Private Shared Function TryClassifyMyNamespace(
            node As NameSyntax,
            symbol As ISymbol,
            semanticModel As SemanticModel,
            ByRef classifiedSpan As ClassifiedSpan) As Boolean
 
            If symbol.IsMyNamespace(semanticModel.Compilation) Then
                classifiedSpan = New ClassifiedSpan(GetNameToken(node).Span, ClassificationTypeNames.Keyword)
                Return True
            End If
 
            Return False
        End Function
 
        Private Shared Function TryClassifyIdentifier(
            node As NameSyntax,
            semanticModel As SemanticModel,
            cancellationToken As CancellationToken,
            ByRef classifiedSpan As ClassifiedSpan) As Boolean
 
            ' Okay, it doesn't bind to anything.
            Dim identifierName = TryCast(node, IdentifierNameSyntax)
            If identifierName IsNot Nothing Then
                Dim token = identifierName.Identifier
 
                If token.HasMatchingText(SyntaxKind.FromKeyword) AndAlso
                    semanticModel.SyntaxTree.IsExpressionContext(token.SpanStart, cancellationToken, semanticModel) Then
 
                    ' Optimistically classify "From" as a keyword in expression contexts
                    classifiedSpan = New ClassifiedSpan(token.Span, ClassificationTypeNames.Keyword)
                    Return True
                ElseIf token.HasMatchingText(SyntaxKind.AsyncKeyword) OrElse
                    token.HasMatchingText(SyntaxKind.IteratorKeyword) Then
 
                    ' Optimistically classify "Async" or "Iterator" as a keyword in expression contexts
                    If semanticModel.SyntaxTree.IsExpressionContext(token.SpanStart, cancellationToken, semanticModel) Then
                        classifiedSpan = New ClassifiedSpan(token.Span, ClassificationTypeNames.Keyword)
                        Return True
                    End If
                End If
            End If
 
            Return False
        End Function
 
        Private Shared Function GetClassificationForField(fieldSymbol As IFieldSymbol) As String
            If fieldSymbol.IsConst Then
                Return If(fieldSymbol.ContainingType.IsEnumType(), ClassificationTypeNames.EnumMemberName, ClassificationTypeNames.ConstantName)
            End If
 
            Return ClassificationTypeNames.FieldName
        End Function
 
        Private Shared Function GetClassificationForLocal(localSymbol As ILocalSymbol) As String
            Return If(localSymbol.IsConst,
                      ClassificationTypeNames.ConstantName,
                      ClassificationTypeNames.LocalName)
        End Function
 
        Private Shared Function GetClassificationForMethod(node As NameSyntax, methodSymbol As IMethodSymbol) As String
            Select Case methodSymbol.MethodKind
                Case MethodKind.Constructor
                    ' If node is member access or qualified name with explicit New on the right side, we should classify New as a keyword.
                    If node.IsNewOnRightSideOfDotOrBang() Then
                        Return ClassificationTypeNames.Keyword
                    Else
                        ' We bound to a constructor, but we weren't something like the 'New' in 'X.New'.
                        ' This can happen when we're actually just binding the full node 'X.New'.  In this
                        ' case, don't return anything for this full node.  We'll end up hitting the 
                        ' 'New' node as the worker walks down, and we'll classify it then.
                        Return Nothing
                    End If
 
                Case MethodKind.BuiltinOperator,
                     MethodKind.UserDefinedOperator
                    ' Operators are already classified syntactically.
                    Return Nothing
            End Select
 
            Return If(methodSymbol.IsReducedExtension(),
                      ClassificationTypeNames.ExtensionMethodName,
                      ClassificationTypeNames.MethodName)
        End Function
 
        Private Shared Sub ClassifyModifiedIdentifier(
                modifiedIdentifier As ModifiedIdentifierSyntax,
                result As SegmentedList(Of ClassifiedSpan))
 
            If modifiedIdentifier.ArrayBounds IsNot Nothing OrElse
               modifiedIdentifier.ArrayRankSpecifiers.Count > 0 OrElse
               modifiedIdentifier.Nullable.Kind <> SyntaxKind.None Then
 
                Return
            End If
 
            If modifiedIdentifier.IsParentKind(SyntaxKind.VariableDeclarator) AndAlso
               modifiedIdentifier.Parent.IsParentKind(SyntaxKind.FieldDeclaration) Then
 
                If DirectCast(modifiedIdentifier.Parent, VariableDeclaratorSyntax).AsClause Is Nothing AndAlso
                   DirectCast(modifiedIdentifier.Parent, VariableDeclaratorSyntax).Initializer Is Nothing Then
 
                    Dim token = modifiedIdentifier.Identifier
                    If token.HasMatchingText(SyntaxKind.AsyncKeyword) OrElse
                       token.HasMatchingText(SyntaxKind.IteratorKeyword) Then
 
                        ' Optimistically classify "Async" or "Iterator" as a keyword
                        result.Add(New ClassifiedSpan(token.Span, ClassificationTypeNames.Keyword))
                        Return
                    End If
                End If
            End If
        End Sub
 
        Private Shared Function GetNameToken(node As NameSyntax) As SyntaxToken
            Select Case node.Kind
                Case SyntaxKind.IdentifierName
                    Return DirectCast(node, IdentifierNameSyntax).Identifier
                Case SyntaxKind.GenericName
                    Return DirectCast(node, GenericNameSyntax).Identifier
                Case SyntaxKind.QualifiedName
                    Return DirectCast(node, QualifiedNameSyntax).Right.Identifier
                Case Else
                    Throw New NotSupportedException()
            End Select
        End Function
 
        Private Shared Sub ClassifyMethodStatement(methodStatement As MethodStatementSyntax, semanticModel As SemanticModel, result As SegmentedList(Of ClassifiedSpan))
            ' Ensure that extension method declarations are classified properly.
            ' Note that the method statement name is likely already classified as a method name
            ' by the syntactic classifier. However, there isn't away to determine whether a VB
            ' method declaration is an extension method syntactically.
            Dim methodSymbol = semanticModel.GetDeclaredSymbol(methodStatement)
            If methodSymbol IsNot Nothing AndAlso methodSymbol.IsExtensionMethod Then
                result.Add(New ClassifiedSpan(methodStatement.Identifier.Span, ClassificationTypeNames.ExtensionMethodName))
            End If
        End Sub
 
        Private Shared Sub ClassifyLabelSyntax(
            node As LabelSyntax,
            result As SegmentedList(Of ClassifiedSpan))
 
            result.Add(New ClassifiedSpan(node.LabelToken.Span, ClassificationTypeNames.LabelName))
        End Sub
    End Class
End Namespace