src/Common/ISymbolExtensions.cs
using System.Runtime.CompilerServices;
namespace Moq.Analyzers.Common;
internal static class ISymbolExtensions
{
/// <summary>
/// Determines whether the symbol is an instance of the specified symbol.
/// </summary>
/// <typeparam name="TSymbol">The type of the <see cref="ISymbol"/> to compare.</typeparam>
/// <param name="symbol">The symbol to compare.</param>
/// <param name="other">The symbol to compare to.</param>
/// <param name="symbolEqualityComparer">The <see cref="SymbolEqualityComparer"/> to use for equality.</param>
/// <returns>
/// <see langword="true"/> if <paramref name="symbol"/> is an instance of <paramref name="other"/>, either as a direct match,
/// or as a specialaization; otherwise, <see langword="false"/>.
/// </returns>
/// <example>
/// <c>MyType.MyMethod<int>()</c> is an instance of <c>MyType.MyMethod<T>()</c>.
/// </example>
/// <example>
/// <c>MyType<int>()</c> is an instance of <c>MyType<T>()</c>.
/// </example>
public static bool IsInstanceOf<TSymbol>(this ISymbol? symbol, TSymbol? other, SymbolEqualityComparer? symbolEqualityComparer = null)
where TSymbol : class, ISymbol
{
symbolEqualityComparer ??= SymbolEqualityComparer.Default;
if (symbol is IMethodSymbol methodSymbol)
{
return symbolEqualityComparer.Equals(methodSymbol.OriginalDefinition, other);
}
if (symbol is INamedTypeSymbol namedTypeSymbol)
{
if (namedTypeSymbol.IsGenericType)
{
namedTypeSymbol = namedTypeSymbol.ConstructedFrom;
}
return symbolEqualityComparer.Equals(namedTypeSymbol, other);
}
return symbolEqualityComparer.Equals(symbol, other);
}
/// <inheritdoc cref="IsInstanceOf{TSymbol}(ISymbol, TSymbol, SymbolEqualityComparer?)"/>
/// <param name="symbol">The symbol to compare.</param>
/// <param name="others">
/// The symbols to compare to. Returns <see langword="true"/> if <paramref name="symbol"/> matches any of others.
/// </param>
/// <param name="symbolEqualityComparer">The <see cref="SymbolEqualityComparer"/> to use for equality.</param>
public static bool IsInstanceOf<TSymbol>(this ISymbol symbol, ImmutableArray<TSymbol> others, SymbolEqualityComparer? symbolEqualityComparer = null)
where TSymbol : class, ISymbol
{
symbolEqualityComparer ??= SymbolEqualityComparer.Default;
return others.Any(other => symbol.IsInstanceOf(other, symbolEqualityComparer));
}
public static bool IsConstructor(this ISymbol symbol)
{
return symbol.DeclaredAccessibility != Accessibility.Private
&& symbol is IMethodSymbol { MethodKind: MethodKind.Constructor } and { IsStatic: false };
}
public static bool IsMethodReturnTypeTask(this ISymbol methodSymbol)
{
string type = methodSymbol.ToDisplayString();
return string.Equals(type, "System.Threading.Tasks.Task", StringComparison.Ordinal)
|| string.Equals(type, "System.Threading.ValueTask", StringComparison.Ordinal)
|| type.StartsWith("System.Threading.Tasks.Task<", StringComparison.Ordinal)
|| (type.StartsWith("System.Threading.Tasks.ValueTask<", StringComparison.Ordinal)
&& type.EndsWith(".Result", StringComparison.Ordinal));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsOverridable(this ISymbol symbol)
{
return !symbol.IsSealed && (symbol.IsVirtual || symbol.IsAbstract || symbol.IsOverride);
}
public static SymbolVisibility GetResultantVisibility(this ISymbol symbol)
{
// Start by assuming it's visible.
SymbolVisibility visibility = SymbolVisibility.Public;
switch (symbol.Kind)
{
case SymbolKind.Alias:
// Aliases are uber private. They're only visible in the same file that they
// were declared in.
return SymbolVisibility.Private;
case SymbolKind.Parameter:
// Parameters are only as visible as their containing symbol
return GetResultantVisibility(symbol.ContainingSymbol);
case SymbolKind.TypeParameter:
// Type Parameters are private.
return SymbolVisibility.Private;
}
while (symbol != null && symbol.Kind != SymbolKind.Namespace)
{
switch (symbol.DeclaredAccessibility)
{
// If we see anything private, then the symbol is private.
case Accessibility.NotApplicable:
case Accessibility.Private:
return SymbolVisibility.Private;
// If we see anything internal, then knock it down from public to
// internal.
case Accessibility.Internal:
case Accessibility.ProtectedAndInternal:
visibility = SymbolVisibility.Internal;
break;
// For anything else (Public, Protected, ProtectedOrInternal), the
// symbol stays at the level we've gotten so far.
}
symbol = symbol.ContainingSymbol;
}
return visibility;
}
}