|
// 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;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.LanguageService;
using Microsoft.CodeAnalysis.Operations;
namespace Microsoft.CodeAnalysis.CSharp.UseIndexOrRangeOperator
{
internal static class Helpers
{
/// <summary>
/// Find an `int MyType.Count` or `int MyType.Length` property.
/// </summary>
public static IPropertySymbol? TryGetLengthOrCountProperty(ITypeSymbol namedType)
=> TryGetNoArgInt32Property(namedType, nameof(string.Length)) ??
TryGetNoArgInt32Property(namedType, nameof(ICollection.Count));
/// <summary>
/// Tried to find a public, non-static, int-returning property in the given type with the
/// specified <paramref name="name"/>.
/// </summary>
public static IPropertySymbol? TryGetNoArgInt32Property(ITypeSymbol type, string name)
=> type.GetMembers(name)
.OfType<IPropertySymbol>()
.Where(p => IsPublicInstance(p) &&
p.Type.SpecialType == SpecialType.System_Int32)
.FirstOrDefault();
public static bool IsPublicInstance(ISymbol symbol)
=> !symbol.IsStatic && symbol.DeclaredAccessibility == Accessibility.Public;
/// <summary>
/// Checks if this <paramref name="operation"/> is `expr.Length` where `expr` is equivalent
/// to the <paramref name="instance"/> we were originally invoking an accessor/method off
/// of.
/// </summary>
public static bool IsInstanceLengthCheck(IPropertySymbol lengthLikeProperty, IOperation instance, IOperation operation)
=> operation is IPropertyReferenceOperation propertyRef &&
propertyRef.Instance != null &&
lengthLikeProperty.Equals(propertyRef.Property) &&
CSharpSyntaxFacts.Instance.AreEquivalent(instance.Syntax, propertyRef.Instance.Syntax);
/// <summary>
/// Checks if <paramref name="operation"/> is a binary subtraction operator. If so, it
/// will be returned through <paramref name="subtraction"/>.
/// </summary>
public static bool IsSubtraction(IOperation operation, [NotNullWhen(true)] out IBinaryOperation? subtraction)
{
if (operation is IBinaryOperation binaryOperation &&
binaryOperation.OperatorKind == BinaryOperatorKind.Subtract)
{
subtraction = binaryOperation;
return true;
}
subtraction = null;
return false;
}
/// <summary>
/// Look for methods like "SomeType MyType.Get(int)". Also matches against the 'getter'
/// of an indexer like 'SomeType MyType.this[int]`
/// </summary>
public static bool IsIntIndexingMethod(IMethodSymbol method)
=> method != null &&
(method.MethodKind == MethodKind.PropertyGet || method.MethodKind == MethodKind.Ordinary) &&
IsPublicInstance(method) &&
method.Parameters.Length == 1 &&
// From: https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#decisions-made-during-implementation
//
// When looking for the pattern members, we look for original definitions, not
// constructed members
method.OriginalDefinition.Parameters[0].Type.SpecialType == SpecialType.System_Int32;
/// <summary>
/// Look for methods like "SomeType MyType.Slice(int start, int length)". Note that the
/// names of the parameters are checked to ensure they are appropriate slice-like. These
/// names were picked by examining the patterns in the BCL for slicing members.
/// </summary>
public static bool IsTwoArgumentSliceLikeMethod(IMethodSymbol method)
{
// From: https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#decisions-made-during-implementation
//
// When looking for the pattern members, we look for original definitions, not
// constructed members
return method != null &&
IsPublicInstance(method) &&
method.Parameters.Length == 2 &&
IsSliceFirstParameter(method.OriginalDefinition.Parameters[0]) &&
IsSliceSecondParameter(method.OriginalDefinition.Parameters[1]);
}
/// <summary>
/// Look for methods like "SomeType MyType.Slice(int start)". Note that the
/// name of the parameter is checked to ensure it is appropriate slice-like.
/// This name was picked by examining the patterns in the BCL for slicing members.
/// </summary>
public static bool IsOneArgumentSliceLikeMethod(IMethodSymbol method)
{
// From: https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#decisions-made-during-implementation
//
// When looking for the pattern members, we look for original definitions, not
// constructed members
return method != null &&
IsPublicInstance(method) &&
method.Parameters.Length == 1 &&
IsSliceFirstParameter(method.OriginalDefinition.Parameters[0]);
}
private static bool IsSliceFirstParameter(IParameterSymbol parameter)
=> parameter.Type.SpecialType == SpecialType.System_Int32 &&
(parameter.Name == "start" || parameter.Name == "startIndex");
private static bool IsSliceSecondParameter(IParameterSymbol parameter)
=> parameter.Type.SpecialType == SpecialType.System_Int32 &&
(parameter.Name == "count" || parameter.Name == "length");
/// <summary>
/// Finds a public, non-static indexer in the given type. The indexer has to accept the
/// provided <paramref name="parameterType"/> and must return the provided <paramref
/// name="returnType"/>.
/// </summary>
public static IPropertySymbol? GetIndexer(ITypeSymbol type, ITypeSymbol parameterType, ITypeSymbol returnType)
=> type.GetMembers(WellKnownMemberNames.Indexer)
.OfType<IPropertySymbol>()
.Where(p => p.IsIndexer &&
IsPublicInstance(p) &&
returnType.Equals(p.Type) &&
p.Parameters.Length == 1 &&
p.Parameters[0].Type.Equals(parameterType))
.FirstOrDefault();
/// <summary>
/// Finds a public, non-static overload of <paramref name="method"/> in the containing type.
/// The overload must have the same return type as <paramref name="method"/>. It must only
/// have a single parameter, with the provided <paramref name="parameterType"/>.
/// </summary>
public static IMethodSymbol? GetOverload(IMethodSymbol method, ITypeSymbol parameterType)
=> method.MethodKind != MethodKind.Ordinary
? null
: method.ContainingType.GetMembers(method.Name)
.OfType<IMethodSymbol>()
.Where(m => IsPublicInstance(m) &&
m.Parameters.Length == 1 &&
m.Parameters[0].Type.Equals(parameterType) &&
m.ReturnType.Equals(method.ReturnType))
.FirstOrDefault();
}
}
|