File: CodeModel\CSharpCodeModelService.CodeModelEventCollector.cs
Web Access
Project: src\src\VisualStudio\CSharp\Impl\Microsoft.VisualStudio.LanguageServices.CSharp_ihyjvpf0_wpftmp.csproj (Microsoft.VisualStudio.LanguageServices.CSharp)
// 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.
 
#nullable disable
 
// Taken from csharp\LanguageAnalysis\Compiler\IDE\LIB\CMEvents.cpp
 
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel;
 
namespace Microsoft.VisualStudio.LanguageServices.CSharp.CodeModel;
 
internal sealed partial class CSharpCodeModelService
{
    protected override AbstractCodeModelEventCollector CreateCodeModelEventCollector()
        => new CodeModelEventCollector(this);
 
    private sealed class CodeModelEventCollector : AbstractCodeModelEventCollector
    {
        public CodeModelEventCollector(AbstractCodeModelService codeModelService)
            : base(codeModelService)
        {
        }
 
        private static IReadOnlyList<MemberDeclarationSyntax> GetValidMembers(SyntaxNode node)
        {
            return CSharpCodeModelService
                .GetChildMemberNodes(node)
                .Where(n => !n.IsKind(SyntaxKind.IncompleteMember))
                .ToArray();
        }
 
        private void CompareCompilationUnits(
            CompilationUnitSyntax oldCompilationUnit,
            CompilationUnitSyntax newCompilationUnit,
            CodeModelEventQueue eventQueue)
        {
            // Note: In the C# legacy code model, events are generated for the top-level
            // namespace that is at the root of every parse tree. In the Roslyn C# code model
            // implementation, we won't bother.
 
            CompareChildren(
                CompareNamespacesOrTypes,
                GetValidMembers(oldCompilationUnit),
                GetValidMembers(newCompilationUnit),
                (SyntaxNode)null,
                CodeModelEventType.Unknown,
                eventQueue);
        }
 
        private bool CompareAttributeLists(
            AttributeListSyntax oldAttributeList,
            AttributeListSyntax newAttributeList,
            SyntaxNode newNodeParent,
            CodeModelEventQueue eventQueue)
        {
            return CompareChildren(
                CompareAttributes,
                oldAttributeList.Attributes.AsReadOnlyList(),
                newAttributeList.Attributes.AsReadOnlyList(),
                newNodeParent,
                CodeModelEventType.Unknown,
                eventQueue);
        }
 
        private bool CompareAttributes(
            AttributeSyntax oldAttribute,
            AttributeSyntax newAttribute,
            SyntaxNode newNodeParent,
            CodeModelEventQueue eventQueue)
        {
            Debug.Assert(oldAttribute != null && newAttribute != null);
 
            var same = true;
 
            if (!CompareNames(oldAttribute.Name, newAttribute.Name))
            {
                EnqueueChangeEvent(newAttribute, newNodeParent, CodeModelEventType.Rename, eventQueue);
                same = false;
            }
 
            // If arguments have changed enqueue a element changed (arguments changed) node
            if (!CompareAttributeArguments(oldAttribute.ArgumentList, newAttribute.ArgumentList))
            {
                EnqueueChangeEvent(newAttribute, newNodeParent, CodeModelEventType.ArgChange, eventQueue);
                same = false;
            }
 
            return same;
        }
 
        private bool CompareAttributeArguments(AttributeArgumentListSyntax oldAttributeArguments, AttributeArgumentListSyntax newAttributeArguments)
        {
            if (oldAttributeArguments == null || newAttributeArguments == null)
            {
                return oldAttributeArguments == newAttributeArguments;
            }
 
            var oldArguments = oldAttributeArguments.Arguments;
            var newArguments = newAttributeArguments.Arguments;
 
            if (oldArguments.Count != newArguments.Count)
            {
                return false;
            }
 
            for (var i = 0; i < oldArguments.Count; i++)
            {
                var oldArgument = oldArguments[i];
                var newArgument = newArguments[i];
 
                if (!StringComparer.Ordinal.Equals(CodeModelService.GetName(oldArgument), CodeModelService.GetName(newArgument)))
                {
                    return false;
                }
 
                if (!CompareExpressions(oldArgument.Expression, newArgument.Expression))
                {
                    return false;
                }
            }
 
            return true;
        }
 
        private bool CompareExpressions(ExpressionSyntax oldExpression, ExpressionSyntax newExpression)
        {
            if (oldExpression == null || newExpression == null)
            {
                return oldExpression == newExpression;
            }
 
            if (oldExpression.Kind() != newExpression.Kind())
            {
                return false;
            }
 
            if (oldExpression is TypeSyntax typeSyntax)
            {
                return CompareTypes(typeSyntax, (TypeSyntax)newExpression);
            }
 
            if (oldExpression is LiteralExpressionSyntax)
            {
                return StringComparer.Ordinal.Equals(oldExpression.ToString(), newExpression.ToString());
            }
 
            if (oldExpression is CastExpressionSyntax oldCast)
            {
                var newCast = (CastExpressionSyntax)newExpression;
 
                return CompareTypes(oldCast.Type, newCast.Type)
                    && CompareExpressions(oldCast.Expression, newCast.Expression);
            }
 
            if (oldExpression is PrefixUnaryExpressionSyntax prefixUnary)
            {
                return CompareExpressions(prefixUnary.Operand, ((PrefixUnaryExpressionSyntax)newExpression).Operand);
            }
 
            if (oldExpression is AwaitExpressionSyntax awaitExpression)
            {
                return CompareExpressions(awaitExpression.Expression, ((AwaitExpressionSyntax)newExpression).Expression);
            }
 
            if (oldExpression is PostfixUnaryExpressionSyntax postfixUnary)
            {
                return CompareExpressions(postfixUnary.Operand, ((PostfixUnaryExpressionSyntax)newExpression).Operand);
            }
 
            if (oldExpression is BinaryExpressionSyntax oldBinaryExpression)
            {
                var newBinaryExpression = (BinaryExpressionSyntax)newExpression;
 
                return CompareExpressions(oldBinaryExpression.Left, newBinaryExpression.Left)
                    && CompareExpressions(oldBinaryExpression.Right, newBinaryExpression.Right);
            }
 
            if (oldExpression is AssignmentExpressionSyntax oldAssignmentExpression)
            {
                var newAssignmentExpression = (AssignmentExpressionSyntax)newExpression;
 
                return CompareExpressions(oldAssignmentExpression.Left, newAssignmentExpression.Left)
                    && CompareExpressions(oldAssignmentExpression.Right, newAssignmentExpression.Right);
            }
 
            if (oldExpression is MemberAccessExpressionSyntax oldMemberAccessExpression)
            {
                var newMemberAccessExpression = (MemberAccessExpressionSyntax)newExpression;
 
                return CompareExpressions(oldMemberAccessExpression.Expression, newMemberAccessExpression.Expression)
                    && CompareExpressions(oldMemberAccessExpression.Name, newMemberAccessExpression.Name);
            }
 
            return true;
        }
 
        private bool CompareParameters(ParameterSyntax oldParameter, ParameterSyntax newParameter, SyntaxNode newNodeParent, CodeModelEventQueue eventQueue)
        {
            Debug.Assert(oldParameter != null && newParameter != null);
 
            var same = true;
 
            if (!StringComparer.Ordinal.Equals(CodeModelService.GetName(oldParameter), CodeModelService.GetName(newParameter)))
            {
                EnqueueChangeEvent(newParameter, newNodeParent, CodeModelEventType.Rename, eventQueue);
                same = false;
            }
 
            // If modifiers or the type have changed enqueue a element changed (unknown change) node
            if (!CompareModifiers(oldParameter, newParameter) ||
                !CompareTypes(oldParameter.Type, newParameter.Type))
            {
                EnqueueChangeEvent(newParameter, newNodeParent, CodeModelEventType.Unknown, eventQueue);
                same = false;
            }
 
            return same;
        }
 
        private bool CompareMemberDeclarations(
            MemberDeclarationSyntax oldMember,
            MemberDeclarationSyntax newMember,
            SyntaxNode newNodeParent,
            CodeModelEventQueue eventQueue)
        {
            Debug.Assert(oldMember != null && newMember != null);
 
            // If the kind doesn't match, it has to be a remove/add.
            if (oldMember.Kind() != newMember.Kind())
            {
                EnqueueRemoveEvent(oldMember, newNodeParent, eventQueue);
                EnqueueAddEvent(newMember, newNodeParent, eventQueue);
 
                return false;
            }
 
            if (oldMember is BaseTypeDeclarationSyntax or
                DelegateDeclarationSyntax)
            {
                return CompareTypeDeclarations(oldMember, newMember, newNodeParent, eventQueue);
            }
            else if (oldMember is BaseMethodDeclarationSyntax baseMethod)
            {
                return CompareMethodDeclarations(baseMethod, (BaseMethodDeclarationSyntax)newMember, newNodeParent, eventQueue);
            }
            else if (oldMember is BaseFieldDeclarationSyntax baseField)
            {
                return CompareFieldDeclarations(baseField, (BaseFieldDeclarationSyntax)newMember, newNodeParent, eventQueue);
            }
            else if (oldMember is BasePropertyDeclarationSyntax baseProperty)
            {
                return ComparePropertyDeclarations(baseProperty, (BasePropertyDeclarationSyntax)newMember, newNodeParent, eventQueue);
            }
            else if (oldMember is EnumMemberDeclarationSyntax enumMember)
            {
                return CompareEnumMemberDeclarations(enumMember, (EnumMemberDeclarationSyntax)newMember, newNodeParent, eventQueue);
            }
 
            throw new NotImplementedException();
        }
 
        private bool CompareEnumMemberDeclarations(
            EnumMemberDeclarationSyntax oldEnumMember,
            EnumMemberDeclarationSyntax newEnumMember,
            SyntaxNode newNodeParent,
            CodeModelEventQueue eventQueue)
        {
            Debug.Assert(oldEnumMember != null && newEnumMember != null);
 
            var same = true;
 
            if (!StringComparer.Ordinal.Equals(CodeModelService.GetName(oldEnumMember), CodeModelService.GetName(newEnumMember)))
            {
                EnqueueChangeEvent(newEnumMember, newNodeParent, CodeModelEventType.Rename, eventQueue);
                same = false;
            }
 
            same &= CompareChildren(
                CompareAttributeLists,
                oldEnumMember.AttributeLists.AsReadOnlyList(),
                newEnumMember.AttributeLists.AsReadOnlyList(),
                newEnumMember,
                CodeModelEventType.Unknown,
                eventQueue);
 
            return same;
        }
 
        private bool ComparePropertyDeclarations(
            BasePropertyDeclarationSyntax oldProperty,
            BasePropertyDeclarationSyntax newProperty,
            SyntaxNode newNodeParent,
            CodeModelEventQueue eventQueue)
        {
            Debug.Assert(oldProperty != null && newProperty != null);
 
            var same = true;
 
            if (!StringComparer.Ordinal.Equals(CodeModelService.GetName(oldProperty), CodeModelService.GetName(newProperty)))
            {
                EnqueueChangeEvent(newProperty, newNodeParent, CodeModelEventType.Rename, eventQueue);
                same = false;
            }
 
            // If modifiers have changed enqueue a element changed (unknown change) node
            if (!CompareModifiers(oldProperty, newProperty))
            {
                EnqueueChangeEvent(newProperty, newNodeParent, CodeModelEventType.Unknown, eventQueue);
                same = false;
            }
 
            // If return type had changed enqueue a element changed (typeref changed) node
            if (!CompareTypes(oldProperty.Type, newProperty.Type))
            {
                EnqueueChangeEvent(newProperty, newNodeParent, CodeModelEventType.TypeRefChange, eventQueue);
                same = false;
            }
 
            same &= CompareChildren(
                CompareAttributeLists,
                oldProperty.AttributeLists.AsReadOnlyList(),
                newProperty.AttributeLists.AsReadOnlyList(),
                newProperty,
                CodeModelEventType.Unknown,
                eventQueue);
 
            if (oldProperty is IndexerDeclarationSyntax oldIndexer)
            {
                var newIndexer = (IndexerDeclarationSyntax)newProperty;
                same &= CompareChildren(
                    CompareParameters,
                    oldIndexer.ParameterList.Parameters.AsReadOnlyList(),
                    newIndexer.ParameterList.Parameters.AsReadOnlyList(),
                    newIndexer,
                    CodeModelEventType.SigChange,
                    eventQueue);
            }
 
            return same;
        }
 
        private bool CompareVariableDeclarators(
            VariableDeclaratorSyntax oldVariableDeclarator,
            VariableDeclaratorSyntax newVariableDeclarator,
            SyntaxNode newNodeParent,
            CodeModelEventQueue eventQueue)
        {
            Debug.Assert(oldVariableDeclarator != null && newVariableDeclarator != null);
 
            if (!StringComparer.Ordinal.Equals(CodeModelService.GetName(oldVariableDeclarator), CodeModelService.GetName(newVariableDeclarator)))
            {
                EnqueueChangeEvent(newVariableDeclarator, newNodeParent, CodeModelEventType.Rename, eventQueue);
                return false;
            }
 
            return true;
        }
 
        private bool CompareFieldDeclarations(
            BaseFieldDeclarationSyntax oldField,
            BaseFieldDeclarationSyntax newField,
            SyntaxNode newNodeParent,
            CodeModelEventQueue eventQueue)
        {
            Debug.Assert(oldField != null && newField != null);
 
            var same = true;
            same &= CompareChildren(
                CompareVariableDeclarators,
                oldField.Declaration.Variables.AsReadOnlyList(),
                newField.Declaration.Variables.AsReadOnlyList(),
                newNodeParent,
                CodeModelEventType.Unknown,
                eventQueue);
 
            // If modifiers have changed enqueue a element changed (unknown change) node
            if (oldField.Kind() != newField.Kind() ||
                !CompareModifiers(oldField, newField))
            {
                EnqueueChangeEvent(newField, newNodeParent, CodeModelEventType.Unknown, eventQueue);
                same = false;
            }
 
            // If type had changed enqueue a element changed (typeref changed) node
            if (!CompareTypes(oldField.Declaration.Type, newField.Declaration.Type))
            {
                EnqueueChangeEvent(newField, newNodeParent, CodeModelEventType.TypeRefChange, eventQueue);
                same = false;
            }
 
            same &= CompareChildren(
                CompareAttributeLists,
                oldField.AttributeLists.AsReadOnlyList(),
                newField.AttributeLists.AsReadOnlyList(),
                newField,
                CodeModelEventType.Unknown, eventQueue);
 
            return same;
        }
 
        private bool CompareMethodDeclarations(
            BaseMethodDeclarationSyntax oldMethod,
            BaseMethodDeclarationSyntax newMethod,
            SyntaxNode newNodeParent,
            CodeModelEventQueue eventQueue)
        {
            Debug.Assert(oldMethod != null && newMethod != null);
 
            if (!StringComparer.Ordinal.Equals(CodeModelService.GetName(oldMethod), CodeModelService.GetName(newMethod)))
            {
                var change = CompareRenamedDeclarations(
                    CompareParameters,
                    oldMethod.ParameterList.Parameters.AsReadOnlyList(),
                    newMethod.ParameterList.Parameters.AsReadOnlyList(),
                    oldMethod,
                    newMethod,
                    newNodeParent,
                    eventQueue);
 
                if (change == DeclarationChange.NameOnly)
                {
                    EnqueueChangeEvent(newMethod, newNodeParent, CodeModelEventType.Rename, eventQueue);
                }
 
                return false;
            }
            else
            {
                var same = true;
 
                if (!CompareModifiers(oldMethod, newMethod))
                {
                    same = false;
                    EnqueueChangeEvent(newMethod, newNodeParent, CodeModelEventType.Unknown, eventQueue);
                }
 
                if (!CompareTypes(GetReturnType(oldMethod), GetReturnType(newMethod)))
                {
                    same = false;
                    EnqueueChangeEvent(newMethod, newNodeParent, CodeModelEventType.TypeRefChange, eventQueue);
                }
 
                same &= CompareChildren(
                    CompareAttributeLists,
                    oldMethod.AttributeLists.AsReadOnlyList(),
                    newMethod.AttributeLists.AsReadOnlyList(),
                    newMethod,
                    CodeModelEventType.Unknown,
                    eventQueue);
 
                same &= CompareChildren(
                    CompareParameters,
                    oldMethod.ParameterList.Parameters.AsReadOnlyList(),
                    newMethod.ParameterList.Parameters.AsReadOnlyList(),
                    newMethod,
                    CodeModelEventType.SigChange,
                    eventQueue);
 
                return same;
            }
        }
 
        private bool CompareNamespaceDeclarations(
            BaseNamespaceDeclarationSyntax oldNamespace,
            BaseNamespaceDeclarationSyntax newNamespace,
            SyntaxNode newNodeParent,
            CodeModelEventQueue eventQueue)
        {
            Debug.Assert(oldNamespace != null && newNamespace != null);
 
            // Check if the namespace nodes are identical w.r.t Name
            if (!CompareNames(oldNamespace.Name, newNamespace.Name))
            {
                var change = CompareRenamedDeclarations(
                    CompareNamespacesOrTypes,
                    GetValidMembers(oldNamespace),
                    GetValidMembers(newNamespace),
                    oldNamespace,
                    newNamespace,
                    newNodeParent,
                    eventQueue);
 
                if (change == DeclarationChange.NameOnly)
                {
                    EnqueueChangeEvent(newNamespace, newNodeParent, CodeModelEventType.Rename, eventQueue);
                }
 
                return false;
            }
 
            return CompareChildren(
                CompareNamespacesOrTypes,
                GetValidMembers(oldNamespace),
                GetValidMembers(newNamespace),
                newNamespace,
                CodeModelEventType.Unknown,
                eventQueue);
        }
 
        private bool CompareTypeDeclarations(
            MemberDeclarationSyntax oldMember,
            MemberDeclarationSyntax newMember,
            SyntaxNode newNodeParent,
            CodeModelEventQueue eventQueue)
        {
            Debug.Assert(oldMember != null && newMember != null);
            Debug.Assert(oldMember is BaseTypeDeclarationSyntax or DelegateDeclarationSyntax);
            Debug.Assert(newMember is BaseTypeDeclarationSyntax or DelegateDeclarationSyntax);
 
            // If the kind doesn't match, it has to be a remove/add.
            if (oldMember.Kind() != newMember.Kind())
            {
                EnqueueRemoveEvent(oldMember, newNodeParent, eventQueue);
                EnqueueAddEvent(newMember, newNodeParent, eventQueue);
 
                return false;
            }
 
            if (oldMember is BaseTypeDeclarationSyntax oldType)
            {
                var newType = (BaseTypeDeclarationSyntax)newMember;
 
                var oldMembers = GetValidMembers(oldType);
                var newMembers = GetValidMembers(newType);
 
                var same = true;
 
                // If the type name is different, it might mean that the whole type has been removed and a new one added.
                // In that case, we shouldn't do any other checks and instead return immediately.
                if (!StringComparer.Ordinal.Equals(oldType.Identifier.ToString(), newType.Identifier.ToString()))
                {
                    var change = CompareRenamedDeclarations(
                        CompareMemberDeclarations,
                        oldMembers,
                        newMembers,
                        oldType,
                        newType,
                        newNodeParent,
                        eventQueue);
 
                    if (change == DeclarationChange.WholeDeclaration)
                    {
                        return false;
                    }
 
                    same = false;
                    EnqueueChangeEvent(newType, newNodeParent, CodeModelEventType.Rename, eventQueue);
                }
 
                if (!CompareModifiers(oldType, newType))
                {
                    same = false;
                    EnqueueChangeEvent(newType, newNodeParent, CodeModelEventType.Unknown, eventQueue);
                }
 
                if (!CompareBaseLists(oldType, newType))
                {
                    same = false;
                    EnqueueChangeEvent(newType, newNodeParent, CodeModelEventType.BaseChange, eventQueue);
                }
 
                same &= CompareChildren(
                    CompareAttributeLists,
                    oldType.AttributeLists.AsReadOnlyList(),
                    newType.AttributeLists.AsReadOnlyList(),
                    newType,
                    CodeModelEventType.Unknown,
                    eventQueue);
 
                same &= CompareChildren(
                    CompareMemberDeclarations,
                    oldMembers,
                    newMembers,
                    newType,
                    CodeModelEventType.Unknown,
                    eventQueue);
 
                return same;
            }
            else if (oldMember is DelegateDeclarationSyntax oldDelegate)
            {
                var newDelegate = (DelegateDeclarationSyntax)newMember;
 
                var same = true;
 
                // If the delegate name is different, it might mean that the whole delegate has been removed and a new one added.
                // In that case, we shouldn't do any other checks and instead return immediately.
                if (!StringComparer.Ordinal.Equals(oldDelegate.Identifier.ToString(), newDelegate.Identifier.ToString()))
                {
                    var change = CompareRenamedDeclarations(
                        CompareParameters,
                        oldDelegate.ParameterList.Parameters.AsReadOnlyList(),
                        newDelegate.ParameterList.Parameters.AsReadOnlyList(),
                        oldDelegate,
                        newDelegate,
                        newNodeParent,
                        eventQueue);
 
                    if (change == DeclarationChange.WholeDeclaration)
                    {
                        return false;
                    }
 
                    same = false;
                    EnqueueChangeEvent(newDelegate, newNodeParent, CodeModelEventType.Rename, eventQueue);
                }
 
                if (!CompareModifiers(oldDelegate, newDelegate))
                {
                    same = false;
                    EnqueueChangeEvent(newDelegate, newNodeParent, CodeModelEventType.Unknown, eventQueue);
                }
 
                if (!CompareTypes(oldDelegate.ReturnType, newDelegate.ReturnType))
                {
                    same = false;
                    EnqueueChangeEvent(newDelegate, newNodeParent, CodeModelEventType.TypeRefChange, eventQueue);
                }
 
                same &= CompareChildren(
                    CompareAttributeLists,
                    oldDelegate.AttributeLists.AsReadOnlyList(),
                    newDelegate.AttributeLists.AsReadOnlyList(),
                    newDelegate,
                    CodeModelEventType.Unknown,
                    eventQueue);
 
                same &= CompareChildren(
                    CompareParameters,
                    oldDelegate.ParameterList.Parameters.AsReadOnlyList(),
                    newDelegate.ParameterList.Parameters.AsReadOnlyList(),
                    newDelegate,
                    CodeModelEventType.SigChange,
                    eventQueue);
 
                return same;
            }
 
            return false;
        }
 
        private bool CompareNamespacesOrTypes(
            MemberDeclarationSyntax oldNamespaceOrType,
            MemberDeclarationSyntax newNamespaceOrType,
            SyntaxNode newNodeParent,
            CodeModelEventQueue eventQueue)
        {
            // If the kind doesn't match, it has to be a remove/add.
            if (oldNamespaceOrType.Kind() != newNamespaceOrType.Kind())
            {
                EnqueueRemoveEvent(oldNamespaceOrType, newNodeParent, eventQueue);
                EnqueueAddEvent(newNamespaceOrType, newNodeParent, eventQueue);
 
                return false;
            }
 
            if (oldNamespaceOrType is BaseTypeDeclarationSyntax or
                DelegateDeclarationSyntax)
            {
                return CompareTypeDeclarations(oldNamespaceOrType, newNamespaceOrType, newNodeParent, eventQueue);
            }
            else if (oldNamespaceOrType is BaseNamespaceDeclarationSyntax namespaceDecl)
            {
                return CompareNamespaceDeclarations(namespaceDecl, (BaseNamespaceDeclarationSyntax)newNamespaceOrType, newNodeParent, eventQueue);
            }
 
            return false;
        }
 
        private bool CompareBaseLists(BaseTypeDeclarationSyntax oldType, BaseTypeDeclarationSyntax newType)
        {
            if (oldType.BaseList == null && newType.BaseList == null)
            {
                return true;
            }
 
            if (oldType.BaseList != null && newType.BaseList != null)
            {
                var oldTypes = oldType.BaseList.Types;
                var newTypes = newType.BaseList.Types;
 
                if (oldTypes.Count != newTypes.Count)
                {
                    return false;
                }
 
                for (var i = 0; i < oldTypes.Count; i++)
                {
                    if (!CompareTypes(oldTypes[i].Type, newTypes[i].Type))
                    {
                        return false;
                    }
                }
 
                return true;
            }
 
            // In this case, one of the base lists is null.
            return false;
        }
 
        private static bool CompareModifiers(MemberDeclarationSyntax oldMember, MemberDeclarationSyntax newMember)
            => oldMember.GetModifierFlags() == newMember.GetModifierFlags();
 
        private static bool CompareModifiers(ParameterSyntax oldParameter, ParameterSyntax newParameter)
            => oldParameter.GetParameterFlags() == newParameter.GetParameterFlags();
 
        private bool CompareNames(NameSyntax oldName, NameSyntax newName)
        {
            if (oldName.Kind() != newName.Kind())
            {
                return false;
            }
 
            switch (oldName.Kind())
            {
                case SyntaxKind.IdentifierName:
                    var oldIdentifierName = (IdentifierNameSyntax)oldName;
                    var newIdentifierName = (IdentifierNameSyntax)newName;
 
                    return StringComparer.Ordinal.Equals(oldIdentifierName.Identifier.ToString(), newIdentifierName.Identifier.ToString());
 
                case SyntaxKind.QualifiedName:
                    var oldQualifiedName = (QualifiedNameSyntax)oldName;
                    var newQualifiedName = (QualifiedNameSyntax)newName;
 
                    return CompareNames(oldQualifiedName.Left, newQualifiedName.Left)
                        && CompareNames(oldQualifiedName.Right, oldQualifiedName.Right);
 
                case SyntaxKind.GenericName:
                    var oldGenericName = (GenericNameSyntax)oldName;
                    var newGenericName = (GenericNameSyntax)newName;
 
                    if (!StringComparer.Ordinal.Equals(oldGenericName.Identifier.ToString(), newGenericName.Identifier.ToString()))
                    {
                        return false;
                    }
 
                    if (oldGenericName.Arity != newGenericName.Arity)
                    {
                        return false;
                    }
 
                    for (var i = 0; i < oldGenericName.Arity; i++)
                    {
                        if (!CompareTypes(oldGenericName.TypeArgumentList.Arguments[i], newGenericName.TypeArgumentList.Arguments[i]))
                        {
                            return false;
                        }
                    }
 
                    return true;
 
                case SyntaxKind.AliasQualifiedName:
                    var oldAliasQualifiedName = (AliasQualifiedNameSyntax)oldName;
                    var newAliasQualifiedName = (AliasQualifiedNameSyntax)newName;
 
                    return CompareNames(oldAliasQualifiedName.Alias, newAliasQualifiedName.Alias)
                        && CompareNames(oldAliasQualifiedName.Name, newAliasQualifiedName.Name);
            }
 
            Debug.Fail("Unknown kind: " + oldName.Kind());
            return false;
        }
 
        private bool CompareTypes(TypeSyntax oldType, TypeSyntax newType)
        {
            // Type nodes can be NULL for ctor/dtor/operators ...
            if (oldType == null || newType == null)
            {
                return oldType == newType;
            }
 
            if (oldType.Kind() != newType.Kind())
            {
                return false;
            }
 
            switch (oldType.Kind())
            {
                case SyntaxKind.PredefinedType:
                    var oldPredefinedType = (PredefinedTypeSyntax)oldType;
                    var newPredefinedType = (PredefinedTypeSyntax)newType;
 
                    return oldPredefinedType.Keyword.RawKind == newPredefinedType.Keyword.RawKind;
 
                case SyntaxKind.ArrayType:
                    var oldArrayType = (ArrayTypeSyntax)oldType;
                    var newArrayType = (ArrayTypeSyntax)newType;
 
                    return oldArrayType.RankSpecifiers.Count == newArrayType.RankSpecifiers.Count
                        && CompareTypes(oldArrayType.ElementType, newArrayType.ElementType);
 
                case SyntaxKind.PointerType:
                    var oldPointerType = (PointerTypeSyntax)oldType;
                    var newPointerType = (PointerTypeSyntax)newType;
 
                    return CompareTypes(oldPointerType.ElementType, newPointerType.ElementType);
 
                case SyntaxKind.NullableType:
                    var oldNullableType = (NullableTypeSyntax)oldType;
                    var newNullableType = (NullableTypeSyntax)newType;
 
                    return CompareTypes(oldNullableType.ElementType, newNullableType.ElementType);
 
                case SyntaxKind.IdentifierName:
                case SyntaxKind.QualifiedName:
                case SyntaxKind.AliasQualifiedName:
                case SyntaxKind.GenericName:
                    var oldName = (NameSyntax)oldType;
                    var newName = (NameSyntax)newType;
 
                    return CompareNames(oldName, newName);
            }
 
            Debug.Fail("Unknown kind: " + oldType.Kind());
            return false;
        }
 
        private static TypeSyntax GetReturnType(BaseMethodDeclarationSyntax method)
        {
            if (method is MethodDeclarationSyntax methodDecl)
            {
                return methodDecl.ReturnType;
            }
            else if (method is OperatorDeclarationSyntax operatorDecl)
            {
                return operatorDecl.ReturnType;
            }
 
            // TODO(DustinCa): What about conversion operators? How does the legacy code base handle those?
 
            return null;
        }
 
        protected override void CollectCore(SyntaxNode oldRoot, SyntaxNode newRoot, CodeModelEventQueue eventQueue)
            => CompareCompilationUnits((CompilationUnitSyntax)oldRoot, (CompilationUnitSyntax)newRoot, eventQueue);
 
        protected override void EnqueueAddEvent(SyntaxNode node, SyntaxNode parent, CodeModelEventQueue eventQueue)
        {
            if (eventQueue == null)
            {
                return;
            }
 
            if (node is IncompleteMemberSyntax)
            {
                return;
            }
 
            if (node is BaseFieldDeclarationSyntax baseField)
            {
                foreach (var variableDeclarator in baseField.Declaration.Variables)
                {
                    eventQueue.EnqueueAddEvent(variableDeclarator, parent);
                }
            }
            else if (node is AttributeListSyntax attributeList)
            {
                foreach (var attribute in attributeList.Attributes)
                {
                    AddEventToEventQueueForAttributes(attribute, parent, eventQueue.EnqueueAddEvent);
                }
            }
            else if (node is AttributeSyntax attribute)
            {
                AddEventToEventQueueForAttributes(attribute, parent, eventQueue.EnqueueAddEvent);
            }
            else
            {
                eventQueue.EnqueueAddEvent(node, parent);
            }
        }
 
        protected override void EnqueueChangeEvent(SyntaxNode node, SyntaxNode parent, CodeModelEventType eventType, CodeModelEventQueue eventQueue)
        {
            if (eventQueue == null)
            {
                return;
            }
 
            if (node is IncompleteMemberSyntax)
            {
                return;
            }
 
            if (node is BaseFieldDeclarationSyntax baseField)
            {
                foreach (var variableDeclarator in baseField.Declaration.Variables)
                {
                    eventQueue.EnqueueChangeEvent(variableDeclarator, parent, eventType);
                }
            }
            else if (node is AttributeListSyntax attributeList)
            {
                foreach (var attribute in attributeList.Attributes)
                {
                    ChangeEventQueueForAttributes(attribute, parent, eventType, eventQueue);
                }
            }
            else if (node is AttributeSyntax attribute)
            {
                ChangeEventQueueForAttributes(attribute, parent, eventType, eventQueue);
            }
            else
            {
                eventQueue.EnqueueChangeEvent(node, parent, eventType);
            }
        }
 
        private static void ChangeEventQueueForAttributes(AttributeSyntax attribute, SyntaxNode parent, CodeModelEventType eventType, CodeModelEventQueue eventQueue)
        {
            if (parent is BaseFieldDeclarationSyntax baseField)
            {
                foreach (var variableDeclarator in baseField.Declaration.Variables)
                {
                    eventQueue.EnqueueChangeEvent(attribute, variableDeclarator, eventType);
                }
            }
            else
            {
                eventQueue.EnqueueChangeEvent(attribute, parent, eventType);
            }
        }
 
        protected override void EnqueueRemoveEvent(SyntaxNode node, SyntaxNode parent, CodeModelEventQueue eventQueue)
        {
            if (eventQueue == null)
            {
                return;
            }
 
            if (node is IncompleteMemberSyntax)
            {
                return;
            }
 
            if (node is BaseFieldDeclarationSyntax baseField)
            {
                foreach (var variableDeclarator in baseField.Declaration.Variables)
                {
                    eventQueue.EnqueueRemoveEvent(variableDeclarator, parent);
                }
            }
            else if (node is AttributeListSyntax attributeList)
            {
                foreach (var attribute in attributeList.Attributes)
                {
                    AddEventToEventQueueForAttributes(attribute, parent, eventQueue.EnqueueRemoveEvent);
                }
            }
            else if (node is AttributeSyntax attribute)
            {
                AddEventToEventQueueForAttributes(attribute, parent, eventQueue.EnqueueRemoveEvent);
            }
            else
            {
                eventQueue.EnqueueRemoveEvent(node, parent);
            }
        }
 
        private static void AddEventToEventQueueForAttributes(AttributeSyntax attribute, SyntaxNode parent, Action<SyntaxNode, SyntaxNode> enqueueAddOrRemoveEvent)
        {
            if (parent is BaseFieldDeclarationSyntax baseField)
            {
                foreach (var variableDeclarator in baseField.Declaration.Variables)
                {
                    enqueueAddOrRemoveEvent(attribute, variableDeclarator);
                }
            }
            else
            {
                enqueueAddOrRemoveEvent(attribute, parent);
            }
        }
    }
}