File: src\Workspaces\SharedUtilitiesAndExtensions\Workspace\VisualBasic\CodeGeneration\VisualBasicDeclarationComparer.vb
Web Access
Project: src\src\CodeStyle\VisualBasic\CodeFixes\Microsoft.CodeAnalysis.VisualBasic.CodeStyle.Fixes.vbproj (Microsoft.CodeAnalysis.VisualBasic.CodeStyle.Fixes)
' 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 Microsoft.CodeAnalysis.VisualBasic.Syntax
 
Namespace Microsoft.CodeAnalysis.VisualBasic.CodeGeneration
    Friend Class VisualBasicDeclarationComparer
        Implements IComparer(Of SyntaxNode)
 
        Private Shared ReadOnly s_kindPrecedenceMap As Dictionary(Of SyntaxKind, Integer) = New Dictionary(Of SyntaxKind, Integer)(SyntaxFacts.EqualityComparer) From
            {
                {SyntaxKind.FieldDeclaration, 0},
                {SyntaxKind.ConstructorBlock, 1},
                {SyntaxKind.SubNewStatement, 1},
                {SyntaxKind.PropertyBlock, 2},
                {SyntaxKind.PropertyStatement, 2},
                {SyntaxKind.EventBlock, 3},
                {SyntaxKind.EventStatement, 3},
                {SyntaxKind.SubBlock, 4},
                {SyntaxKind.SubStatement, 4},
                {SyntaxKind.FunctionBlock, 5},
                {SyntaxKind.FunctionStatement, 5},
                {SyntaxKind.OperatorBlock, 6},
                {SyntaxKind.OperatorStatement, 6},
                {SyntaxKind.EnumBlock, 7},
                {SyntaxKind.InterfaceBlock, 8},
                {SyntaxKind.StructureBlock, 9},
                {SyntaxKind.ClassBlock, 10},
                {SyntaxKind.ModuleBlock, 11},
                {SyntaxKind.DelegateSubStatement, 12},
                {SyntaxKind.DelegateFunctionStatement, 12}
            }
 
        Private Shared ReadOnly s_operatorPrecedenceMap As Dictionary(Of SyntaxKind, Integer) = New Dictionary(Of SyntaxKind, Integer)(SyntaxFacts.EqualityComparer) From
            {
                {SyntaxKind.PlusToken, 0},
                {SyntaxKind.MinusToken, 1},
                {SyntaxKind.AsteriskToken, 3},
                {SyntaxKind.SlashToken, 4},
                {SyntaxKind.BackslashToken, 5},
                {SyntaxKind.CaretToken, 6},
                {SyntaxKind.AmpersandToken, 7},
                {SyntaxKind.NotKeyword, 8},
                {SyntaxKind.LikeKeyword, 9},
                {SyntaxKind.ModKeyword, 10},
                {SyntaxKind.AndKeyword, 11},
                {SyntaxKind.OrKeyword, 12},
                {SyntaxKind.XorKeyword, 13},
                {SyntaxKind.LessThanLessThanToken, 14},
                {SyntaxKind.GreaterThanGreaterThanToken, 15},
                {SyntaxKind.EqualsToken, 16},
                {SyntaxKind.LessThanGreaterThanToken, 17},
                {SyntaxKind.GreaterThanToken, 18},
                {SyntaxKind.LessThanToken, 19},
                {SyntaxKind.GreaterThanEqualsToken, 20},
                {SyntaxKind.LessThanEqualsToken, 21},
                {SyntaxKind.IsTrueKeyword, 22},
                {SyntaxKind.IsFalseKeyword, 23},
                {SyntaxKind.CTypeKeyword, 24}
            }
 
        Public Shared ReadOnly WithNamesInstance As New VisualBasicDeclarationComparer(includeName:=True)
        Public Shared ReadOnly WithoutNamesInstance As New VisualBasicDeclarationComparer(includeName:=False)
 
        Private ReadOnly _includeName As Boolean
 
        Private Sub New(includeName As Boolean)
            _includeName = includeName
        End Sub
 
        Public Function Compare(x As SyntaxNode, y As SyntaxNode) As Integer Implements IComparer(Of SyntaxNode).Compare
            Dim xPrecedence As Integer
            Dim yPrecedence As Integer
            If Not s_kindPrecedenceMap.TryGetValue(x.Kind(), xPrecedence) OrElse
               Not s_kindPrecedenceMap.TryGetValue(y.Kind(), yPrecedence) Then
                ' The containing definition is malformed and contains a node kind we didn't expect.
                ' Ignore comparisons with those unexpected nodes and sort them to the end of the declaration.
                Return 1
            End If
 
            If xPrecedence <> yPrecedence Then
                Return If(xPrecedence < yPrecedence, -1, 1)
            End If
 
            x = ConvertBlockToStatement(x)
            y = ConvertBlockToStatement(y)
 
            Select Case x.Kind
                Case SyntaxKind.DelegateSubStatement,
                     SyntaxKind.DelegateFunctionStatement
                    Return Compare(DirectCast(x, DelegateStatementSyntax), DirectCast(y, DelegateStatementSyntax))
 
                Case SyntaxKind.FieldDeclaration
                    Return Compare(DirectCast(x, FieldDeclarationSyntax), DirectCast(y, FieldDeclarationSyntax))
 
                Case SyntaxKind.SubNewStatement
                    Return Compare(DirectCast(x, SubNewStatementSyntax), DirectCast(y, SubNewStatementSyntax))
 
                Case SyntaxKind.SubStatement,
                     SyntaxKind.FunctionStatement
                    Return Compare(DirectCast(x, MethodStatementSyntax), DirectCast(y, MethodStatementSyntax))
 
                Case SyntaxKind.EventStatement
                    Return Compare(DirectCast(x, EventStatementSyntax), DirectCast(y, EventStatementSyntax))
 
                Case SyntaxKind.PropertyStatement
                    Return Compare(DirectCast(x, PropertyStatementSyntax), DirectCast(y, PropertyStatementSyntax))
 
                Case SyntaxKind.OperatorStatement
                    Return Compare(DirectCast(x, OperatorStatementSyntax), DirectCast(y, OperatorStatementSyntax))
 
                Case SyntaxKind.EnumStatement
                    Return Compare(DirectCast(x, EnumStatementSyntax), DirectCast(y, EnumStatementSyntax))
 
                Case SyntaxKind.InterfaceStatement,
                     SyntaxKind.StructureStatement,
                     SyntaxKind.ClassStatement,
                     SyntaxKind.ModuleStatement
                    Return Compare(DirectCast(x, TypeStatementSyntax), DirectCast(y, TypeStatementSyntax))
            End Select
 
            throw ExceptionUtilities.UnexpectedValue(x.Kind)
        End Function
 
        Private Shared Function ConvertBlockToStatement(node As SyntaxNode) As SyntaxNode
            Select Case node.Kind
                Case SyntaxKind.PropertyBlock
                    Return DirectCast(node, PropertyBlockSyntax).PropertyStatement
 
                Case SyntaxKind.EventBlock
                    Return DirectCast(node, EventBlockSyntax).EventStatement
 
                Case SyntaxKind.ConstructorBlock,
                     SyntaxKind.SubBlock,
                     SyntaxKind.FunctionBlock,
                     SyntaxKind.OperatorBlock
                    Return DirectCast(node, MethodBlockBaseSyntax).BlockStatement
 
                Case SyntaxKind.EnumBlock
                    Return DirectCast(node, EnumBlockSyntax).EnumStatement
 
                Case SyntaxKind.InterfaceBlock
                    Return DirectCast(node, InterfaceBlockSyntax).BlockStatement
 
                Case SyntaxKind.StructureBlock
                    Return DirectCast(node, StructureBlockSyntax).BlockStatement
 
                Case SyntaxKind.ClassBlock
                    Return DirectCast(node, ClassBlockSyntax).BlockStatement
 
                Case SyntaxKind.ModuleBlock
                    Return DirectCast(node, ModuleBlockSyntax).BlockStatement
            End Select
 
            Return node
        End Function
 
        Private Function Compare(x As DelegateStatementSyntax, y As DelegateStatementSyntax) As Integer
            Dim result = 0
            If EqualAccessibility(x, x.Modifiers, y, y.Modifiers, result) Then
 
                If _includeName Then
                    EqualIdentifierName(x.Identifier, y.Identifier, result)
                End If
            End If
 
            Return result
        End Function
 
        Private Function Compare(x As FieldDeclarationSyntax, y As FieldDeclarationSyntax) As Integer
            Dim result = 0
            If EqualConstness(x.Modifiers, y.Modifiers, result) AndAlso
               EqualSharedness(x.Modifiers, y.Modifiers, result) AndAlso
               EqualReadOnlyNess(x.Modifiers, y.Modifiers, result) AndAlso
               EqualAccessibility(x, x.Modifiers, y, y.Modifiers, result) Then
 
                If _includeName Then
                    EqualIdentifierName(
                    x.Declarators.FirstOrDefault().Names.FirstOrDefault().Identifier,
                    y.Declarators.FirstOrDefault().Names.FirstOrDefault().Identifier,
                    result)
                End If
            End If
 
            Return result
        End Function
 
        Private Shared Function Compare(x As SubNewStatementSyntax, y As SubNewStatementSyntax) As Integer
            Dim result = 0
            If EqualSharedness(x.Modifiers, y.Modifiers, result) AndAlso
               EqualAccessibility(x, x.Modifiers, y, y.Modifiers, result) Then
 
                EqualParameterLists(x.ParameterList, y.ParameterList, result)
            End If
 
            Return result
        End Function
 
        Private Function Compare(x As MethodStatementSyntax, y As MethodStatementSyntax) As Integer
            Dim result = 0
            If EqualSharedness(x.Modifiers, y.Modifiers, result) AndAlso
               EqualAccessibility(x, x.Modifiers, y, y.Modifiers, result) Then
 
                If _includeName Then
                    EqualIdentifierName(x.Identifier, y.Identifier, result)
                End If
            End If
 
            Return result
        End Function
 
        Private Function Compare(x As EventStatementSyntax, y As EventStatementSyntax) As Integer
            Dim result = 0
            If EqualSharedness(x.Modifiers, y.Modifiers, result) AndAlso
               EqualAccessibility(x, x.Modifiers, y, y.Modifiers, result) Then
 
                If _includeName Then
                    EqualIdentifierName(x.Identifier, y.Identifier, result)
                End If
            End If
 
            Return result
        End Function
 
        Private Function Compare(x As PropertyStatementSyntax, y As PropertyStatementSyntax) As Integer
            Dim result = 0
            If EqualSharedness(x.Modifiers, y.Modifiers, result) AndAlso
               EqualAccessibility(x, x.Modifiers, y, y.Modifiers, result) Then
 
                If _includeName Then
                    EqualIdentifierName(x.Identifier, y.Identifier, result)
                End If
            End If
 
            Return result
        End Function
 
        Private Shared Function Compare(x As OperatorStatementSyntax, y As OperatorStatementSyntax) As Integer
            Dim result = 0
            If EqualOperatorPrecedence(x.OperatorToken, y.OperatorToken, result) Then
                EqualParameterLists(x.ParameterList, y.ParameterList, result)
            End If
 
            Return result
        End Function
 
        Private Function Compare(x As EnumStatementSyntax, y As EnumStatementSyntax) As Integer
            Dim result = 0
            If EqualAccessibility(x, x.Modifiers, y, y.Modifiers, result) Then
                If _includeName Then
                    EqualIdentifierName(x.Identifier, y.Identifier, result)
                End If
            End If
 
            Return result
        End Function
 
        Private Function Compare(x As TypeStatementSyntax, y As TypeStatementSyntax) As Integer
            Dim result = 0
            If EqualSharedness(x.Modifiers, y.Modifiers, result) AndAlso
               EqualAccessibility(x, x.Modifiers, y, y.Modifiers, result) Then
 
                If _includeName Then
                    EqualIdentifierName(x.Identifier, y.Identifier, result)
                End If
            End If
 
            Return result
        End Function
 
        Private Shared Function NeitherNull(x As Object, y As Object, ByRef comparisonResult As Integer) As Boolean
            If x Is Nothing AndAlso y Is Nothing Then
                comparisonResult = 0
                Return False
            ElseIf x Is Nothing Then
                comparisonResult = -1
                Return False
            ElseIf y Is Nothing Then
                comparisonResult = 1
                Return False
            Else
                comparisonResult = 0
                Return True
            End If
        End Function
 
        Private Shared Function ContainsToken(list As IEnumerable(Of SyntaxToken), kind As SyntaxKind) As Boolean
            Return list.Contains(Function(token As SyntaxToken)
                                     Return token.Kind = kind
                                 End Function)
        End Function
 
        Private Enum Accessibility
            [Public]
            [Protected]
            [ProtectedFriend]
            [Friend]
            [Private]
        End Enum
 
        Private Shared Function GetAccessibilityPrecedence(declaration As SyntaxNode, modifiers As IEnumerable(Of SyntaxToken)) As Integer
            If ContainsToken(modifiers, SyntaxKind.PublicKeyword) Then
                Return Accessibility.Public
            ElseIf ContainsToken(modifiers, SyntaxKind.ProtectedKeyword) Then
                If ContainsToken(modifiers, SyntaxKind.FriendKeyword) Then
                    Return Accessibility.ProtectedFriend
                End If
 
                Return Accessibility.Protected
            ElseIf ContainsToken(modifiers, SyntaxKind.FriendKeyword) Then
                Return Accessibility.Friend
            ElseIf ContainsToken(modifiers, SyntaxKind.PrivateKeyword) Then
                Return Accessibility.Private
            End If
 
            ' Determine default accessibility
            Select Case declaration.Kind
                Case SyntaxKind.InterfaceStatement,
                     SyntaxKind.ModuleStatement,
                     SyntaxKind.ClassStatement,
                     SyntaxKind.StructureStatement
 
                    ' Convert type definition statements to their corresponding blocks so we can traverse up the tree
                    declaration = declaration.Parent
            End Select
 
            Dim node = declaration.Parent
            While node IsNot Nothing
                Select Case node.Kind
                    Case SyntaxKind.InterfaceBlock
                        ' Interface members are all Public
                        Return Accessibility.Public
 
                    Case SyntaxKind.ModuleBlock,
                         SyntaxKind.ClassBlock
                        ' Standard module and class members default to Public unless they are variable declarations
                        Return If(declaration.Kind = SyntaxKind.FieldDeclaration, Accessibility.Private, Accessibility.Public)
 
                    Case SyntaxKind.StructureBlock
                        ' Structure member declarations always default to Public
                        Return Accessibility.Public
                End Select
 
                node = node.Parent
            End While
 
            ' Namespace members default to Friend
            Return Accessibility.Friend
        End Function
 
        Private Shared Function BothHaveModifier(x As SyntaxTokenList, y As SyntaxTokenList, modifierKind As SyntaxKind, ByRef comparisonResult As Integer) As Boolean
            Dim xHasModifier = ContainsToken(x, modifierKind)
            Dim yHasModifier = ContainsToken(y, modifierKind)
 
            If xHasModifier = yHasModifier Then
                comparisonResult = 0
                Return True
            End If
 
            comparisonResult = If(xHasModifier, -1, 1)
            Return False
        End Function
 
        Private Shared Function EqualSharedness(x As SyntaxTokenList, y As SyntaxTokenList, ByRef comparisonResult As Integer) As Boolean
            Return BothHaveModifier(x, y, SyntaxKind.SharedKeyword, comparisonResult)
        End Function
 
        Private Shared Function EqualReadOnlyness(x As SyntaxTokenList, y As SyntaxTokenList, ByRef comparisonResult As Integer) As Boolean
            Return BothHaveModifier(x, y, SyntaxKind.ReadOnlyKeyword, comparisonResult)
        End Function
 
        Private Shared Function EqualConstness(x As SyntaxTokenList, y As SyntaxTokenList, ByRef comparisonResult As Integer) As Boolean
            Return BothHaveModifier(x, y, SyntaxKind.ConstKeyword, comparisonResult)
        End Function
 
        Private Shared Function EqualAccessibility(x As SyntaxNode, xModifiers As SyntaxTokenList, y As SyntaxNode, yModifiers As SyntaxTokenList, ByRef comparisonResult As Integer) As Boolean
            Dim xAccessibility = GetAccessibilityPrecedence(x, xModifiers)
            Dim yAccessibility = GetAccessibilityPrecedence(y, yModifiers)
 
            If xAccessibility = yAccessibility Then
                comparisonResult = 0
                Return True
            End If
 
            comparisonResult = If(xAccessibility < yAccessibility, -1, 1)
            Return False
        End Function
 
        Private Shared Function EqualIdentifierName(x As SyntaxToken, y As SyntaxToken, ByRef comparisonResult As Integer) As Boolean
            comparisonResult = CaseInsensitiveComparison.Compare(x.ValueText, y.ValueText)
            Return comparisonResult = 0
        End Function
 
        Private Shared Function EqualOperatorPrecedence(x As SyntaxToken, y As SyntaxToken, ByRef comparisonResult As Integer) As Boolean
            Dim xPrecedence = 0
            Dim yPrecedence = 0
 
            s_operatorPrecedenceMap.TryGetValue(x.Kind, xPrecedence)
            s_operatorPrecedenceMap.TryGetValue(y.Kind, yPrecedence)
 
            comparisonResult = If(xPrecedence = yPrecedence, 0, If(xPrecedence < yPrecedence, -1, 1))
            Return comparisonResult = 0
        End Function
 
        Private Shared Function EqualParameterLists(x As ParameterListSyntax, y As ParameterListSyntax, ByRef comparisonResult As Integer) As Boolean
            Dim result = 0
            If NeitherNull(x, y, result) Then
                Dim xParameterCount = x.Parameters.Count
                Dim yParameterCount = y.Parameters.Count
 
                comparisonResult = If(xParameterCount = yParameterCount, 0, If(x.Parameters.Count < y.Parameters.Count, -1, 1))
            End If
 
            Return comparisonResult = 0
        End Function
    End Class
End Namespace