File: Syntax\SyntaxEquivalence.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 Green = Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax
 
Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax
 
    Friend Module SyntaxEquivalence
        Friend Function AreEquivalent(before As SyntaxTree, after As SyntaxTree, ignoreChildNode As Func(Of SyntaxKind, Boolean), topLevel As Boolean) As Boolean
            If before Is after Then
                Return True
            End If
 
            If before Is Nothing OrElse after Is Nothing Then
                Return False
            End If
 
            Return AreEquivalent(before.GetRoot(), after.GetRoot(), ignoreChildNode, topLevel)
        End Function
 
        Public Function AreEquivalent(before As SyntaxNode, after As SyntaxNode, ignoreChildNode As Func(Of SyntaxKind, Boolean), topLevel As Boolean) As Boolean
            Debug.Assert(Not topLevel OrElse ignoreChildNode Is Nothing)
 
            If before Is Nothing OrElse after Is Nothing Then
                Return before Is after
            End If
 
            Return AreEquivalentRecursive(before.Green, after.Green, parentKind:=Nothing, ignoreChildNode:=ignoreChildNode, topLevel:=topLevel)
        End Function
 
        Public Function AreEquivalent(before As SyntaxTokenList, after As SyntaxTokenList) As Boolean
            Return AreEquivalentRecursive(before.Node, after.Node, parentKind:=Nothing, ignoreChildNode:=Nothing, topLevel:=False)
        End Function
 
        Public Function AreEquivalent(before As SyntaxToken, after As SyntaxToken) As Boolean
            Return before.RawKind = after.RawKind AndAlso (before.Node Is Nothing OrElse AreTokensEquivalent(before.Node, after.Node))
        End Function
 
        Private Function AreTokensEquivalent(before As GreenNode, after As GreenNode) As Boolean
            Debug.Assert(before.RawKind = after.RawKind)
 
            If before.IsMissing <> after.IsMissing Then
                Return False
            End If
 
            Select Case CType(before.RawKind, SyntaxKind)
                Case SyntaxKind.IdentifierToken,
                     SyntaxKind.CharacterLiteralToken,
                     SyntaxKind.DateLiteralToken,
                     SyntaxKind.DecimalLiteralToken,
                     SyntaxKind.FloatingLiteralToken,
                     SyntaxKind.IntegerLiteralToken,
                     SyntaxKind.InterpolatedStringTextToken,
                     SyntaxKind.StringLiteralToken,
                     SyntaxKind.XmlTextLiteralToken,
                     SyntaxKind.XmlNameToken
                    Return String.Equals(DirectCast(before, Green.SyntaxToken).Text,
                                         DirectCast(after, Green.SyntaxToken).Text,
                                         StringComparison.Ordinal)
            End Select
 
            Return True
        End Function
 
        Private Function AreEquivalentRecursive(before As GreenNode, after As GreenNode, parentKind As SyntaxKind, ignoreChildNode As Func(Of SyntaxKind, Boolean), topLevel As Boolean) As Boolean
            If before Is after Then
                Return True
            End If
 
            If before Is Nothing OrElse after Is Nothing Then
                Return False
            End If
 
            If before.RawKind <> after.RawKind Then
                Return False
            End If
 
            If before.IsToken Then
                Debug.Assert(after.IsToken)
                Return AreTokensEquivalent(before, after)
            End If
 
            Dim kind = CType(before.RawKind, SyntaxKind)
 
            If Not AreModifiersEquivalent(before, after, kind) Then
                Return False
            End If
 
            If topLevel Then
                Select Case kind
                    Case SyntaxKind.SubBlock,
                         SyntaxKind.FunctionBlock,
                         SyntaxKind.ConstructorBlock,
                         SyntaxKind.OperatorBlock,
                         SyntaxKind.GetAccessorBlock,
                         SyntaxKind.SetAccessorBlock,
                         SyntaxKind.AddHandlerAccessorBlock,
                         SyntaxKind.RemoveHandlerAccessorBlock,
                         SyntaxKind.RaiseEventAccessorBlock
                        ' Once we get down to the block level we need to only compare the header
                        Return AreEquivalentRecursive(DirectCast(before, Green.MethodBlockBaseSyntax).Begin,
                                                      DirectCast(after, Green.MethodBlockBaseSyntax).Begin,
                                                      kind,
                                                      ignoreChildNode:=Nothing,
                                                      topLevel:=True)
 
                    Case SyntaxKind.FieldDeclaration
                        ' If we're only checking top level equivalence, then we don't have to go down into
                        ' the initializer for a field. However, we can't put that optimization for all
                        ' fields. For example, fields that are 'const' do need their initializers checked as
                        ' changing them can affect binding results.
                        Dim fieldBefore = DirectCast(before, Green.FieldDeclarationSyntax)
                        Dim fieldAfter = DirectCast(after, Green.FieldDeclarationSyntax)
                        Dim isConstBefore = fieldBefore.Modifiers.Any(SyntaxKind.ConstKeyword)
                        Dim isConstAfter = fieldAfter.Modifiers.Any(SyntaxKind.ConstKeyword)
                        If Not isConstBefore AndAlso Not isConstAfter Then
                            ignoreChildNode = Function(childKind) childKind = SyntaxKind.EqualsValue OrElse childKind = SyntaxKind.AsNewClause
                        End If
 
                    Case SyntaxKind.EqualsValue
                        ' Don't recurse into an auto-property initializer.
                        If parentKind = SyntaxKind.PropertyStatement Then
                            Return True
                        End If
                End Select
            End If
 
            If ignoreChildNode IsNot Nothing Then
                Dim e1 = DirectCast(before, Green.VisualBasicSyntaxNode).ChildNodesAndTokens().GetEnumerator()
                Dim e2 = DirectCast(after, Green.VisualBasicSyntaxNode).ChildNodesAndTokens().GetEnumerator()
 
                While True
                    Dim child1 As GreenNode = Nothing
                    Dim child2 As GreenNode = Nothing
 
                    ' skip ignored children
                    While e1.MoveNext()
                        Dim c = e1.Current
                        If c IsNot Nothing AndAlso (c.IsToken OrElse Not ignoreChildNode(CType(c.RawKind, SyntaxKind))) Then
                            child1 = c
                            Exit While
                        End If
                    End While
 
                    While e2.MoveNext()
                        Dim c = e2.Current
 
                        If c IsNot Nothing AndAlso (c.IsToken OrElse Not ignoreChildNode(CType(c.RawKind, SyntaxKind))) Then
                            child2 = c
                            Exit While
                        End If
                    End While
 
                    If child1 Is Nothing OrElse child2 Is Nothing Then
                        ' false if some children remained
                        Return child1 Is child2
                    End If
 
                    If Not AreEquivalentRecursive(child1, child2, kind, ignoreChildNode, topLevel) Then
                        Return False
                    End If
                End While
 
                Throw ExceptionUtilities.Unreachable
            Else
                ' simple comparison - not ignoring children
                Dim slotCount1 = before.SlotCount
 
                If slotCount1 <> after.SlotCount Then
                    Return False
                End If
 
                For i = 0 To slotCount1 - 1
                    Dim child1 = before.GetSlot(i)
                    Dim child2 = after.GetSlot(i)
                    If Not AreEquivalentRecursive(child1, child2, kind, ignoreChildNode, topLevel) Then
                        Return False
                    End If
                Next
 
                Return True
            End If
        End Function
 
        Private Function AreModifiersEquivalent(before As GreenNode, after As GreenNode, kind As SyntaxKind) As Boolean
            Select Case kind
                Case SyntaxKind.SubBlock,
                     SyntaxKind.FunctionBlock
                    Dim beforeModifiers = DirectCast(before, Green.MethodBlockBaseSyntax).Begin.Modifiers
                    Dim afterModifiers = DirectCast(after, Green.MethodBlockBaseSyntax).Begin.Modifiers
 
                    If beforeModifiers.Count <> afterModifiers.Count Then
                        Return False
                    End If
 
                    For i = 0 To beforeModifiers.Count - 1
                        If Not beforeModifiers.Any(afterModifiers(i).Kind) Then
                            Return False
                        End If
                    Next
            End Select
 
            Return True
        End Function
 
    End Module
End Namespace