|
// 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 System.Linq;
using Microsoft.CodeAnalysis;
namespace Analyzer.Utilities.Extensions
{
internal static class IEnumerableOfIMethodSymbolExtensions
{
/// <summary>
/// Excludes <paramref name="methods"/> that have an attribute that precisely matches <paramref name="attributeType"/>.
/// </summary>
/// <param name="methods">List of <see cref="IMethodSymbol"/> to filter.</param>
/// <param name="attributeType">The <see cref="INamedTypeSymbol"/> of the attribute class to search.</param>
/// <returns>A filtered list of methods.</returns>
public static IEnumerable<IMethodSymbol> WhereMethodDoesNotContainAttribute(
this IEnumerable<IMethodSymbol> methods,
INamedTypeSymbol? attributeType)
{
if (attributeType == null)
{
return methods;
}
return methods.Where(m => !m.HasAnyAttribute(attributeType));
}
/// <summary>
/// Returns a list of method symbols from a given list of the method symbols, which has its parameter type as
/// expectedParameterType as its first parameter or the last parameter in addition to matching all the other
/// parameter types of the selectedOverload method symbol
/// </summary>
/// <param name="methods">List of <see cref="IMethodSymbol"/> to scan for possible overloads</param>
/// <param name="selectedOverload"><see cref="IMethodSymbol"/> that is currently picked by the user</param>
/// <param name="expectedParameterType"><see cref="INamedTypeSymbol"/> type of the leading parameter or the trailing parameter</param>
/// <param name="trailingOnly"><see cref="INamedTypeSymbol"/> If the expected parameter should appear at the trailing position of the parameter list of the method overload</param>
public static IEnumerable<IMethodSymbol> GetMethodOverloadsWithDesiredParameterAtLeadingOrTrailing(
this IEnumerable<IMethodSymbol> methods,
IMethodSymbol selectedOverload,
INamedTypeSymbol expectedParameterType,
bool trailingOnly = false)
{
return methods.Where(candidateMethod =>
{
if (!System.Collections.Immutable.ImmutableArrayExtensions.HasExactly(candidateMethod.Parameters, selectedOverload.Parameters.Length + 1))
{
return false;
}
// The expected method overload should either have the expectedParameterType parameter as the first argument or as the last argument
// Assume expectedParameterType is the last parameter so j, which is the index of the parameter
// in candidateMethod to compare against selectedOverload's parameter is set to 0
int j = 0;
if (!trailingOnly && candidateMethod.Parameters.First().Type.Equals(expectedParameterType) && candidateMethod.Parameters[0].RefKind == RefKind.None)
{
// If expectedParameterType is the first parameter then the parameters to compare in candidateMethod against selectedOverload
// is offset by 1
j = 1;
}
else
{
var lastParameter = candidateMethod.Parameters.Last();
if (!lastParameter.Type.Equals(expectedParameterType) || lastParameter.RefKind != RefKind.None)
{
// expectedParameterType is neither the first parameter nor the last parameter
return false;
}
}
for (int i = 0; i < selectedOverload.Parameters.Length; i++, j++)
{
if (!selectedOverload.Parameters[i].Type.Equals(candidateMethod.Parameters[j].Type) ||
selectedOverload.Parameters[i].IsParams != candidateMethod.Parameters[j].IsParams ||
selectedOverload.Parameters[i].RefKind != candidateMethod.Parameters[j].RefKind)
{
return false;
}
}
return true;
});
}
/// <summary>
/// Returns a list of method symbols from a given list of the method symbols, which has its parameter type as
/// expectedParameterType as its last parameter in addition to matching all the other parameter types of the
/// selectedOverload method symbol
/// </summary>
/// <param name="methods">List of <see cref="IMethodSymbol"/> to scan for possible overloads</param>
/// <param name="selectedOverload"><see cref="IMethodSymbol"/> that is currently picked by the user</param>
/// <param name="expectedTrailingParameterType"><see cref="INamedTypeSymbol"/> type of the leading parameter or the trailing parameter</param>
public static IEnumerable<IMethodSymbol> GetMethodOverloadsWithDesiredParameterAtTrailing(
this IEnumerable<IMethodSymbol> methods,
IMethodSymbol selectedOverload,
INamedTypeSymbol expectedTrailingParameterType)
{
return GetMethodOverloadsWithDesiredParameterAtLeadingOrTrailing(methods, selectedOverload, expectedTrailingParameterType, trailingOnly: true);
}
/// <summary>
/// Gets the <see cref="IMethodSymbol"/> in the sequence who's parameters match <paramref name="expectedParameterTypesInOrder"/>.
/// </summary>
/// <param name="members">The sequence of <see cref="IMethodSymbol"/>s to search.</param>
/// <param name="expectedParameterTypesInOrder">The types of the parameters, in order.</param>
/// <returns>
/// The first <see cref="IMethodSymbol"/> in the sequence who's parameters match <paramref name="expectedParameterTypesInOrder"/>, or <langword>null</langword> if
/// no method was found.
/// </returns>
public static IMethodSymbol? GetFirstOrDefaultMemberWithParameterTypes(this IEnumerable<IMethodSymbol>? members, params ITypeSymbol[] expectedParameterTypesInOrder)
{
return members?.FirstOrDefault(member =>
{
if (member.Parameters.Length != expectedParameterTypesInOrder.Length)
return false;
for (int i = 0; i < expectedParameterTypesInOrder.Length; ++i)
{
var parameterType = member.Parameters[i].Type;
if (!expectedParameterTypesInOrder[i].Equals(parameterType))
return false;
}
return true;
});
}
/// <summary>
/// Given a <see cref="IEnumerable{IMethodSymbol}"/>, this method returns the method symbol which
/// matches the expectedParameterTypesInOrder parameter requirement
/// </summary>
/// <param name="members"></param>
/// <param name="expectedParameterTypesInOrder"></param>
/// <returns></returns>
public static IMethodSymbol? GetFirstOrDefaultMemberWithParameterInfos(this IEnumerable<IMethodSymbol>? members, params ParameterInfo[] expectedParameterTypesInOrder)
{
var expectedParameterCount = expectedParameterTypesInOrder.Length;
return members?.FirstOrDefault(member =>
{
if (member.Parameters.Length != expectedParameterCount)
{
return false;
}
for (int i = 0; i < expectedParameterCount; i++)
{
// check IsParams only on the last parameter
if (i == expectedParameterCount - 1 &&
member.Parameters[i].IsParams != expectedParameterTypesInOrder[i].IsParams)
{
return false;
}
var parameterType = member.Parameters[i].Type;
if (expectedParameterTypesInOrder[i].IsArray)
{
var arrayParameterSymbol = parameterType as IArrayTypeSymbol;
if (arrayParameterSymbol?.Rank != expectedParameterTypesInOrder[i].ArrayRank)
{
return false;
}
parameterType = arrayParameterSymbol.ElementType;
}
if (!expectedParameterTypesInOrder[i].ParameterType.Equals(parameterType))
{
return false;
}
}
return true;
});
}
/// <summary>
/// Given an <see cref="IEnumerable{IMethodSymbol}"/>, returns the <see cref="IMethodSymbol"/> whose parameter list
/// matches <paramref name="expectedParameterTypesInOrder"/>.
/// </summary>
/// <param name="members"></param>
/// <param name="expectedParameterTypesInOrder">Expected types of the member's parameters.</param>
/// <returns>
/// The first member in the sequence whose parameters match <paramref name="expectedParameterTypesInOrder"/>,
/// or null if no matches are found.
/// </returns>
public static IMethodSymbol? GetFirstOrDefaultMemberWithParameterTypes(this IEnumerable<IMethodSymbol>? members, IReadOnlyList<ITypeSymbol> expectedParameterTypesInOrder)
{
if (members is null)
return null;
foreach (var member in members)
{
if (Predicate(member))
return member;
}
return null;
bool Predicate(IMethodSymbol member)
{
if (member.Parameters.Length != expectedParameterTypesInOrder.Count)
return false;
for (int index = 0; index < expectedParameterTypesInOrder.Count; index++)
{
if (!member.Parameters[index].Type.Equals(expectedParameterTypesInOrder[index]))
return false;
}
return true;
}
}
}
// Contains the expected properties of a parameter
internal sealed class ParameterInfo
{
public int ArrayRank { get; private set; }
public bool IsArray { get; private set; }
public bool IsParams { get; private set; }
public INamedTypeSymbol ParameterType { get; private set; }
private ParameterInfo(INamedTypeSymbol type, bool isArray, int arrayRank, bool isParams)
{
ParameterType = type;
IsArray = isArray;
ArrayRank = arrayRank;
IsParams = isParams;
}
public static ParameterInfo GetParameterInfo(INamedTypeSymbol type, bool isArray = false, int arrayRank = 0, bool isParams = false)
{
return new ParameterInfo(type, isArray, arrayRank, isParams);
}
}
}
|