|
// 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;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.CodeGeneration;
internal class CSharpDeclarationComparer : IComparer<SyntaxNode>
{
private static readonly Dictionary<SyntaxKind, int> s_kindPrecedenceMap = new(SyntaxFacts.EqualityComparer)
{
{ SyntaxKind.FieldDeclaration, 0 },
{ SyntaxKind.ConstructorDeclaration, 1 },
{ SyntaxKind.DestructorDeclaration, 2 },
{ SyntaxKind.IndexerDeclaration, 3 },
{ SyntaxKind.PropertyDeclaration, 4 },
{ SyntaxKind.EventFieldDeclaration, 5 },
{ SyntaxKind.EventDeclaration, 6 },
{ SyntaxKind.MethodDeclaration, 7 },
{ SyntaxKind.OperatorDeclaration, 8 },
{ SyntaxKind.ConversionOperatorDeclaration, 9 },
{ SyntaxKind.EnumDeclaration, 10 },
{ SyntaxKind.InterfaceDeclaration, 11 },
{ SyntaxKind.StructDeclaration, 12 },
{ SyntaxKind.ClassDeclaration, 13 },
{ SyntaxKind.RecordDeclaration, 14 },
{ SyntaxKind.RecordStructDeclaration, 15 },
{ SyntaxKind.DelegateDeclaration, 16 }
};
private static readonly Dictionary<SyntaxKind, int> s_operatorPrecedenceMap = new(SyntaxFacts.EqualityComparer)
{
{ SyntaxKind.PlusToken, 0 },
{ SyntaxKind.MinusToken, 1 },
{ SyntaxKind.ExclamationToken, 2 },
{ SyntaxKind.TildeToken, 3 },
{ SyntaxKind.PlusPlusToken, 4 },
{ SyntaxKind.MinusMinusToken, 5 },
{ SyntaxKind.AsteriskToken, 6 },
{ SyntaxKind.SlashToken, 7 },
{ SyntaxKind.PercentToken, 8 },
{ SyntaxKind.AmpersandToken, 9 },
{ SyntaxKind.BarToken, 10 },
{ SyntaxKind.CaretToken, 11 },
{ SyntaxKind.LessThanLessThanToken, 12 },
{ SyntaxKind.GreaterThanGreaterThanToken, 13 },
{ SyntaxKind.EqualsEqualsToken, 14 },
{ SyntaxKind.ExclamationEqualsToken, 15 },
{ SyntaxKind.LessThanToken, 16 },
{ SyntaxKind.GreaterThanToken, 17 },
{ SyntaxKind.LessThanEqualsToken, 18 },
{ SyntaxKind.GreaterThanEqualsToken, 19 },
{ SyntaxKind.TrueKeyword, 20 },
{ SyntaxKind.FalseKeyword, 21 },
{ SyntaxKind.GreaterThanGreaterThanGreaterThanToken, 22 },
};
public static readonly CSharpDeclarationComparer WithNamesInstance = new(includeName: true);
public static readonly CSharpDeclarationComparer WithoutNamesInstance = new(includeName: false);
private readonly bool _includeName;
private CSharpDeclarationComparer(bool includeName)
=> _includeName = includeName;
public int Compare(SyntaxNode? x, SyntaxNode? y)
{
if (ReferenceEquals(x, y))
{
return 0;
}
if (x is null)
{
return -1;
}
if (y is null)
{
return 1;
}
if (x.Kind() != y.Kind())
{
if (!s_kindPrecedenceMap.TryGetValue(x.Kind(), out var xPrecedence) ||
!s_kindPrecedenceMap.TryGetValue(y.Kind(), out var yPrecedence))
{
// The containing declaration is malformed and contains a node kind we did not expect.
// Ignore comparisons with those unexpected nodes and sort them to the end of the declaration.
return 1;
}
return xPrecedence < yPrecedence ? -1 : 1;
}
switch (x.Kind())
{
case SyntaxKind.DelegateDeclaration:
return Compare((DelegateDeclarationSyntax)x, (DelegateDeclarationSyntax)y);
case SyntaxKind.FieldDeclaration:
case SyntaxKind.EventFieldDeclaration:
return Compare((BaseFieldDeclarationSyntax)x, (BaseFieldDeclarationSyntax)y);
case SyntaxKind.ConstructorDeclaration:
return Compare((ConstructorDeclarationSyntax)x, (ConstructorDeclarationSyntax)y);
case SyntaxKind.DestructorDeclaration:
// All destructors are equal since there can only be one per named type
return 0;
case SyntaxKind.MethodDeclaration:
return Compare((MethodDeclarationSyntax)x, (MethodDeclarationSyntax)y);
case SyntaxKind.OperatorDeclaration:
return Compare((OperatorDeclarationSyntax)x, (OperatorDeclarationSyntax)y);
case SyntaxKind.EventDeclaration:
return Compare((EventDeclarationSyntax)x, (EventDeclarationSyntax)y);
case SyntaxKind.IndexerDeclaration:
return Compare((IndexerDeclarationSyntax)x, (IndexerDeclarationSyntax)y);
case SyntaxKind.PropertyDeclaration:
return Compare((PropertyDeclarationSyntax)x, (PropertyDeclarationSyntax)y);
case SyntaxKind.EnumDeclaration:
return Compare((EnumDeclarationSyntax)x, (EnumDeclarationSyntax)y);
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.StructDeclaration:
case SyntaxKind.RecordStructDeclaration:
case SyntaxKind.ClassDeclaration:
case SyntaxKind.RecordDeclaration:
return Compare((BaseTypeDeclarationSyntax)x, (BaseTypeDeclarationSyntax)y);
case SyntaxKind.ConversionOperatorDeclaration:
return Compare((ConversionOperatorDeclarationSyntax)x, (ConversionOperatorDeclarationSyntax)y);
case SyntaxKind.IncompleteMember:
// Since these are incomplete members they are considered to be equal
return 0;
case SyntaxKind.GlobalStatement:
// for REPL, don't mess with order, just put new one at the end.
return 1;
default:
throw ExceptionUtilities.UnexpectedValue(x.Kind());
}
}
private int Compare(DelegateDeclarationSyntax x, DelegateDeclarationSyntax y)
{
if (EqualAccessibility(x, x.Modifiers, y, y.Modifiers, out var result))
{
if (_includeName)
{
EqualIdentifierName(x.Identifier, y.Identifier, out result);
}
}
return result;
}
private int Compare(BaseFieldDeclarationSyntax x, BaseFieldDeclarationSyntax y)
{
if (EqualConstness(x.Modifiers, y.Modifiers, out var result) &&
EqualStaticness(x.Modifiers, y.Modifiers, out result) &&
EqualReadOnlyness(x.Modifiers, y.Modifiers, out result) &&
EqualAccessibility(x, x.Modifiers, y, y.Modifiers, out result))
{
if (_includeName)
{
EqualIdentifierName(
x.Declaration.Variables.First().Identifier,
y.Declaration.Variables.First().Identifier,
out result);
}
}
return result;
}
private static int Compare(ConstructorDeclarationSyntax x, ConstructorDeclarationSyntax y)
{
if (EqualStaticness(x.Modifiers, y.Modifiers, out var result) &&
EqualAccessibility(x, x.Modifiers, y, y.Modifiers, out result))
{
EqualParameterCount(x.ParameterList, y.ParameterList, out result);
}
return result;
}
private int Compare(MethodDeclarationSyntax x, MethodDeclarationSyntax y)
{
if (EqualStaticness(x.Modifiers, y.Modifiers, out var result) &&
EqualAccessibility(x, x.Modifiers, y, y.Modifiers, out result))
{
if (!_includeName)
{
EqualIdentifierName(x.Identifier, y.Identifier, out result);
}
}
return result;
}
private static int Compare(ConversionOperatorDeclarationSyntax x, ConversionOperatorDeclarationSyntax y)
{
if (x.ImplicitOrExplicitKeyword.Kind() != y.ImplicitOrExplicitKeyword.Kind())
{
return x.ImplicitOrExplicitKeyword.Kind() == SyntaxKind.ImplicitKeyword ? -1 : 1;
}
EqualParameterCount(x.ParameterList, y.ParameterList, out var result);
return result;
}
private static int Compare(OperatorDeclarationSyntax x, OperatorDeclarationSyntax y)
{
if (EqualOperatorPrecedence(x.OperatorToken, y.OperatorToken, out var result))
{
EqualParameterCount(x.ParameterList, y.ParameterList, out result);
}
return result;
}
private int Compare(EventDeclarationSyntax x, EventDeclarationSyntax y)
{
if (EqualStaticness(x.Modifiers, y.Modifiers, out var result) &&
EqualAccessibility(x, x.Modifiers, y, y.Modifiers, out result))
{
if (_includeName)
{
EqualIdentifierName(x.Identifier, y.Identifier, out result);
}
}
return result;
}
private static int Compare(IndexerDeclarationSyntax x, IndexerDeclarationSyntax y)
{
if (EqualStaticness(x.Modifiers, y.Modifiers, out var result) &&
EqualAccessibility(x, x.Modifiers, y, y.Modifiers, out result))
{
EqualParameterCount(x.ParameterList, y.ParameterList, out result);
}
return result;
}
private int Compare(PropertyDeclarationSyntax x, PropertyDeclarationSyntax y)
{
if (EqualStaticness(x.Modifiers, y.Modifiers, out var result) &&
EqualAccessibility(x, x.Modifiers, y, y.Modifiers, out result))
{
if (_includeName)
{
EqualIdentifierName(x.Identifier, y.Identifier, out result);
}
}
return result;
}
private int Compare(EnumDeclarationSyntax x, EnumDeclarationSyntax y)
{
if (EqualAccessibility(x, x.Modifiers, y, y.Modifiers, out var result))
{
if (_includeName)
{
EqualIdentifierName(x.Identifier, y.Identifier, out result);
}
}
return result;
}
private int Compare(BaseTypeDeclarationSyntax x, BaseTypeDeclarationSyntax y)
{
if (EqualStaticness(x.Modifiers, y.Modifiers, out var result) &&
EqualAccessibility(x, x.Modifiers, y, y.Modifiers, out result))
{
if (_includeName)
{
EqualIdentifierName(x.Identifier, y.Identifier, out result);
}
}
return result;
}
private static bool ContainsToken(SyntaxTokenList list, SyntaxKind kind)
=> list.Contains(token => token.Kind() == kind);
private enum Accessibility
{
Public,
Protected,
ProtectedInternal,
Internal,
PrivateProtected,
Private
}
private static int GetAccessibilityPrecedence(SyntaxTokenList modifiers, SyntaxNode? parent)
{
if (ContainsToken(modifiers, SyntaxKind.PublicKeyword))
{
return (int)Accessibility.Public;
}
else if (ContainsToken(modifiers, SyntaxKind.ProtectedKeyword))
{
if (ContainsToken(modifiers, SyntaxKind.InternalKeyword))
{
return (int)Accessibility.ProtectedInternal;
}
if (ContainsToken(modifiers, SyntaxKind.PrivateKeyword))
{
return (int)Accessibility.PrivateProtected;
}
return (int)Accessibility.Protected;
}
else if (ContainsToken(modifiers, SyntaxKind.InternalKeyword))
{
return (int)Accessibility.Internal;
}
else if (ContainsToken(modifiers, SyntaxKind.PrivateKeyword))
{
return (int)Accessibility.Private;
}
// Determine default accessibility: This declaration is internal if we traverse up
// the syntax tree and don't find a containing named type.
for (var node = parent; node != null; node = node.Parent)
{
if (node.Kind() == SyntaxKind.InterfaceDeclaration)
{
// All interface members are public
return (int)Accessibility.Public;
}
else if (node.Kind() is SyntaxKind.StructDeclaration or SyntaxKind.ClassDeclaration or SyntaxKind.RecordDeclaration or SyntaxKind.RecordStructDeclaration)
{
// Members and nested types default to private
return (int)Accessibility.Private;
}
}
return (int)Accessibility.Internal;
}
private static bool BothHaveModifier(SyntaxTokenList x, SyntaxTokenList y, SyntaxKind modifierKind, out int comparisonResult)
{
var xHasModifier = ContainsToken(x, modifierKind);
var yHasModifier = ContainsToken(y, modifierKind);
if (xHasModifier == yHasModifier)
{
comparisonResult = 0;
return true;
}
comparisonResult = xHasModifier ? -1 : 1;
return false;
}
private static bool EqualStaticness(SyntaxTokenList x, SyntaxTokenList y, out int comparisonResult)
=> BothHaveModifier(x, y, SyntaxKind.StaticKeyword, out comparisonResult);
private static bool EqualConstness(SyntaxTokenList x, SyntaxTokenList y, out int comparisonResult)
=> BothHaveModifier(x, y, SyntaxKind.ConstKeyword, out comparisonResult);
private static bool EqualReadOnlyness(SyntaxTokenList x, SyntaxTokenList y, out int comparisonResult)
=> BothHaveModifier(x, y, SyntaxKind.ReadOnlyKeyword, out comparisonResult);
private static bool EqualAccessibility(SyntaxNode x, SyntaxTokenList xModifiers, SyntaxNode y, SyntaxTokenList yModifiers, out int comparisonResult)
{
var xAccessibility = GetAccessibilityPrecedence(xModifiers, x.Parent ?? y.Parent);
var yAccessibility = GetAccessibilityPrecedence(yModifiers, y.Parent ?? x.Parent);
comparisonResult = xAccessibility - yAccessibility;
return comparisonResult == 0;
}
private static bool EqualIdentifierName(SyntaxToken x, SyntaxToken y, out int comparisonResult)
{
comparisonResult = string.Compare(x.ValueText, y.ValueText, StringComparison.OrdinalIgnoreCase);
return comparisonResult == 0;
}
private static bool EqualOperatorPrecedence(SyntaxToken x, SyntaxToken y, out int comparisonResult)
{
s_operatorPrecedenceMap.TryGetValue(x.Kind(), out var xPrecedence);
s_operatorPrecedenceMap.TryGetValue(y.Kind(), out var yPrecedence);
comparisonResult = xPrecedence - yPrecedence;
return comparisonResult == 0;
}
private static bool EqualParameterCount(BaseParameterListSyntax x, BaseParameterListSyntax y, out int comparisonResult)
{
var xParameterCount = x.Parameters.Count;
var yParameterCount = y.Parameters.Count;
comparisonResult = xParameterCount - yParameterCount;
return comparisonResult == 0;
}
}
|