File: Symbols\Source\QuickAttributeChecker.vb
Web Access
Project: src\src\Compilers\VisualBasic\Portable\Microsoft.CodeAnalysis.VisualBasic.vbproj (Microsoft.CodeAnalysis.VisualBasic)
' 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.Generic
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
 
Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols
    ''' <summary>
    ''' The QuickAttributeChecker applies a simple fast heuristic for determining probable
    ''' attributes without binding attribute types, just by looking at the final syntax of an 
    ''' attribute usage. It is accessed via the QuickAttributeChecker property on Binder.
    ''' </summary>
    ''' <remarks>
    ''' It works by maintaining a dictionary of all possible simple names that might map to a particular
    ''' attribute.
    ''' </remarks>
    Friend Class QuickAttributeChecker
        ' Dictionary mapping a name to quick attribute(s)
        Private ReadOnly _nameToAttributeMap As Dictionary(Of String, QuickAttributes)
 
        ' If true, can no longer add new names.
        Private _sealed As Boolean
 
        Public Sub New()
            _nameToAttributeMap = New Dictionary(Of String, QuickAttributes)(IdentifierComparison.Comparer)
        End Sub
 
        Public Sub New(other As QuickAttributeChecker)
            _nameToAttributeMap = New Dictionary(Of String, QuickAttributes)(other._nameToAttributeMap, IdentifierComparison.Comparer)
        End Sub
 
        ''' <summary>
        ''' Add a mapping from name to some attributes.
        ''' </summary>
        Public Sub AddName(name As String, newAttributes As QuickAttributes)
            Debug.Assert(Not _sealed)
 
            Dim current = QuickAttributes.None
            _nameToAttributeMap.TryGetValue(name, current)
 
            _nameToAttributeMap(name) = newAttributes Or current
 
            ' We allow "Goo" to bind to "GooAttribute".
            If name.EndsWith("Attribute", StringComparison.OrdinalIgnoreCase) Then
                _nameToAttributeMap(name.Substring(0, name.Length - "Attribute".Length)) = newAttributes Or current
            End If
        End Sub
 
        ''' <summary>
        ''' Process an alias clause and any imported mappings from it.
        ''' E.g., If you have an alias Ex=Blah.Extension, add any mapping for Extension to those for Ex.
        ''' Note that although, in VB, an alias cannot reference another alias, this code doesn't not attempt
        ''' to distinguish between aliases and regular names, as that would add complexity to the data structure
        ''' and would be unlikely to matter. This entire class is probabilistic anyone and is only used for quick
        ''' checks.
        ''' </summary>
        Public Sub AddAlias(aliasSyntax As SimpleImportsClauseSyntax)
            Debug.Assert(Not _sealed)
            Debug.Assert(aliasSyntax.Alias IsNot Nothing)
 
            Dim finalName = GetFinalName(aliasSyntax.Name)
 
            If finalName IsNot Nothing Then
                Dim current As QuickAttributes = QuickAttributes.None
                If _nameToAttributeMap.TryGetValue(finalName, current) Then
                    AddName(aliasSyntax.Alias.Identifier.ValueText, current)
                End If
            End If
        End Sub
 
        Public Sub Seal()
            _sealed = True
        End Sub
 
        ''' <summary>
        ''' Check attribute lists quickly to see what attributes might be referenced.
        ''' </summary>
        Public Function CheckAttributes(attributeLists As SyntaxList(Of AttributeListSyntax)) As QuickAttributes
            Debug.Assert(_sealed)
 
            Dim quickAttrs As QuickAttributes = QuickAttributes.None
 
            If attributeLists.Count > 0 Then
                For Each attrList In attributeLists
                    For Each attr In attrList.Attributes
                        quickAttrs = quickAttrs Or CheckAttribute(attr)
                    Next
                Next
            End If
 
            Return quickAttrs
        End Function
 
        Public Function CheckAttribute(attr As AttributeSyntax) As QuickAttributes
            Dim attrTypeSyntax = attr.Name
            Dim finalName = GetFinalName(attrTypeSyntax)
            If finalName IsNot Nothing Then
                Dim quickAttributes As QuickAttributes
                If _nameToAttributeMap.TryGetValue(finalName, quickAttributes) Then
                    Return quickAttributes
                End If
            End If
 
            Return QuickAttributes.None
        End Function
 
        ' Return the last name in a TypeSyntax, or Nothing if there isn't one.
        Public Shared Function GetFinalName(typeSyntax As TypeSyntax) As String
            Dim node As VisualBasicSyntaxNode = typeSyntax
            Do
                Select Case node.Kind
                    Case SyntaxKind.IdentifierName
                        Return DirectCast(node, IdentifierNameSyntax).Identifier.ValueText
                    Case SyntaxKind.QualifiedName
                        node = DirectCast(node, QualifiedNameSyntax).Right
                    Case Else
                        Return Nothing
                End Select
            Loop
        End Function
    End Class
 
    ''' <summary>
    ''' Indicate which attributes might be present. Could be extended to other attributes 
    ''' if desired.
    ''' </summary>
    <Flags>
    Friend Enum QuickAttributes As Byte
        None = 0
        Extension = 1 << 0
        Obsolete = 1 << 1
        MyGroupCollection = 1 << 2
        TypeIdentifier = 1 << 3
        Last = TypeIdentifier
    End Enum
 
    Friend Class QuickAttributeHelpers
        ''' <summary>
        ''' Returns the <see cref="QuickAttributes"/> that corresponds to the particular type 
        ''' <paramref name="name"/> passed in.  If <paramref name="inAttribute"/> Is <see langword="true"/>
        ''' then the name will be checked both as-Is as well as with the 'Attribute' suffix.
        ''' </summary>
        Public Shared Function GetQuickAttributes(name As String, inAttribute As Boolean) As QuickAttributes
            ' Update this code if we add New quick attributes.
            Debug.Assert(QuickAttributes.Last = QuickAttributes.TypeIdentifier)
 
            Dim result = QuickAttributes.None
 
            If Matches(name, inAttribute, AttributeDescription.CaseInsensitiveExtensionAttribute) Then
                result = result Or QuickAttributes.Extension
            ElseIf Matches(name, inAttribute, AttributeDescription.ObsoleteAttribute) Then
                result = result Or QuickAttributes.Obsolete
            ElseIf Matches(name, inAttribute, AttributeDescription.DeprecatedAttribute) Then
                result = result Or QuickAttributes.Obsolete
            ElseIf Matches(name, inAttribute, AttributeDescription.ExperimentalAttribute) Then
                result = result Or QuickAttributes.Obsolete
            ElseIf Matches(name, inAttribute, AttributeDescription.MyGroupCollectionAttribute) Then
                result = result Or QuickAttributes.TypeIdentifier
            ElseIf Matches(name, inAttribute, AttributeDescription.TypeIdentifierAttribute) Then
                result = result Or QuickAttributes.MyGroupCollection
            End If
 
            Return result
        End Function
 
        Private Shared Function Matches(name As String, inAttribute As Boolean, description As AttributeDescription) As Boolean
            Debug.Assert(description.Name.EndsWith(NameOf(System.Attribute)))
 
            If IdentifierComparison.Comparer.Equals(name, description.Name) Then
                Return True
            End If
 
            ' In an attribute context the name might be referenced as the full name (Like 'TypeForwardedToAttribute')
            ' Or the short name (Like 'TypeForwardedTo').
            If inAttribute AndAlso
               (name.Length + NameOf(Attribute).Length) = description.Name.Length AndAlso
               description.Name.StartsWith(name, StringComparison.OrdinalIgnoreCase) Then
 
                Return True
            End If
 
            Return False
        End Function
    End Class
End Namespace