File: NameSyntaxComparer.cs
Web Access
Project: ..\..\..\src\CodeStyle\CSharp\Analyzers\Microsoft.CodeAnalysis.CSharp.CodeStyle.csproj (Microsoft.CodeAnalysis.CSharp.CodeStyle)
// 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.
 
using System.Collections.Generic;
using Microsoft.CodeAnalysis.CSharp.Syntax;
 
namespace Microsoft.CodeAnalysis.CSharp.Utilities
{
    internal class NameSyntaxComparer : IComparer<NameSyntax?>
    {
        private readonly IComparer<SyntaxToken> _tokenComparer;
        internal readonly TypeSyntaxComparer TypeComparer;
 
        internal NameSyntaxComparer(IComparer<SyntaxToken> tokenComparer)
        {
            _tokenComparer = tokenComparer;
            TypeComparer = new TypeSyntaxComparer(tokenComparer, this);
        }
 
        public static IComparer<NameSyntax?> Create()
            => Create(TokenComparer.NormalInstance);
 
        public static IComparer<NameSyntax?> Create(IComparer<SyntaxToken> tokenComparer)
            => new NameSyntaxComparer(tokenComparer);
 
        public int Compare(NameSyntax? x, NameSyntax? y)
        {
            if (x == y)
                return 0;
 
            return (x, y) switch
            {
                (null, null) => 0,
                (null, _) => -1,
                (_, null) => 1,
                ({ IsMissing: true }, { IsMissing: true }) => 0,
                ({ IsMissing: true }, _) => -1,
                (_, { IsMissing: true }) => 1,
                (IdentifierNameSyntax identifierX, IdentifierNameSyntax identifierY) => _tokenComparer.Compare(identifierX.Identifier, identifierY.Identifier),
                (GenericNameSyntax genericX, GenericNameSyntax genericY) => Compare(genericX, genericY),
                (IdentifierNameSyntax identifierX, GenericNameSyntax genericY) =>
                    _tokenComparer.Compare(identifierX.Identifier, genericY.Identifier) is var diff && diff != 0
                        ? diff
                        : -1, // Goo goes before Goo<T>
                (GenericNameSyntax genericX, IdentifierNameSyntax identifierY) =>
                    _tokenComparer.Compare(genericX.Identifier, identifierY.Identifier) is var diff && diff != 0
                        ? diff
                        : -1, // Goo<T> goes after Goo
                (_, _) => DecomposeCompare(x, y),
            };
 
            int DecomposeCompare(NameSyntax x, NameSyntax y)
            {
                // At this point one or both of the nodes is a dotted name or
                // aliased name.  Break them apart into individual pieces and
                // compare those.
 
                var xNameParts = DecomposeNameParts(x);
                var yNameParts = DecomposeNameParts(y);
 
                for (var i = 0; i < xNameParts.Count && i < yNameParts.Count; i++)
                {
                    var compare = Compare(xNameParts[i], yNameParts[i]);
                    if (compare != 0)
                        return compare;
                }
 
                // they matched up to this point.  The shorter one should come
                // first.
                return xNameParts.Count - yNameParts.Count;
            }
        }
 
        private static IList<SimpleNameSyntax> DecomposeNameParts(NameSyntax name)
        {
            var result = new List<SimpleNameSyntax>();
            DecomposeNameParts(name, result);
            return result;
        }
 
        private static void DecomposeNameParts(NameSyntax name, List<SimpleNameSyntax> result)
        {
            switch (name.Kind())
            {
                case SyntaxKind.QualifiedName:
                    var dottedName = (QualifiedNameSyntax)name;
                    DecomposeNameParts(dottedName.Left, result);
                    DecomposeNameParts(dottedName.Right, result);
                    break;
                case SyntaxKind.AliasQualifiedName:
                    var aliasedName = (AliasQualifiedNameSyntax)name;
                    result.Add(aliasedName.Alias);
                    DecomposeNameParts(aliasedName.Name, result);
                    break;
                case SyntaxKind.IdentifierName:
                    result.Add((IdentifierNameSyntax)name);
                    break;
                case SyntaxKind.GenericName:
                    result.Add((GenericNameSyntax)name);
                    break;
            }
        }
 
        private int Compare(GenericNameSyntax x, GenericNameSyntax y)
        {
            var compare = _tokenComparer.Compare(x.Identifier, y.Identifier);
            if (compare != 0)
                return compare;
 
            // The one with less type params comes first.
            compare = x.Arity - y.Arity;
            if (compare != 0)
                return compare;
 
            // Same name, same parameter count.  Compare each parameter.
            for (var i = 0; i < x.Arity; i++)
            {
                var xArg = x.TypeArgumentList.Arguments[i];
                var yArg = y.TypeArgumentList.Arguments[i];
 
                compare = TypeComparer.Compare(xArg, yArg);
                if (compare != 0)
                    return compare;
            }
 
            return 0;
        }
    }
}