Skip to content

Commit

Permalink
Merge 632a02a into d0199f3
Browse files Browse the repository at this point in the history
  • Loading branch information
0xced committed Oct 28, 2023
2 parents d0199f3 + 632a02a commit 2d1cb5a
Show file tree
Hide file tree
Showing 20 changed files with 1,227 additions and 494 deletions.
32 changes: 32 additions & 0 deletions Src/FluentAssertions/AssertionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,31 @@ public static MethodInfoSelectorAssertions Should(this MethodInfoSelector method
return new MethodInfoSelectorAssertions(methodSelector.ToArray());
}

/// <summary>
/// Returns a <see cref="ParameterInfoAssertions"/> object that can be used to assert the
/// current <see cref="ParameterInfo"/>.
/// </summary>
/// <seealso cref="TypeAssertions"/>
[Pure]
public static ParameterInfoAssertions Should(this ParameterInfo parameterInfo)
{
return new ParameterInfoAssertions(parameterInfo);
}

/// <summary>
/// Returns a <see cref="ParameterInfoSelectorAssertions"/> object that can be used to assert the properties returned by the
/// current <see cref="ParameterInfoSelector"/>.
/// </summary>
/// <seealso cref="TypeAssertions"/>
/// <exception cref="ArgumentNullException"><paramref name="parameterInfoSelector"/> is <see langword="null"/>.</exception>
[Pure]
public static ParameterInfoSelectorAssertions Should(this ParameterInfoSelector parameterInfoSelector)
{
Guard.ThrowIfArgumentIsNull(parameterInfoSelector);

return new ParameterInfoSelectorAssertions(parameterInfoSelector.ToArray());
}

/// <summary>
/// Returns a <see cref="PropertyInfoAssertions"/> object that can be used to assert the
/// current <see cref="PropertyInfoSelector"/>.
Expand Down Expand Up @@ -997,6 +1022,13 @@ public static void Should(this MethodInfoSelectorAssertions _)
InvalidShouldCall();
}

/// <inheritdoc cref="Should(ExecutionTimeAssertions)" />
[Obsolete("You are asserting the 'AndConstraint' itself. Remove the 'Should()' method directly following 'And'", error: true)]
public static void Should(this ParameterInfoSelectorAssertions _)
{
InvalidShouldCall();
}

/// <inheritdoc cref="Should(ExecutionTimeAssertions)" />
[Obsolete("You are asserting the 'AndConstraint' itself. Remove the 'Should()' method directly following 'And'", error: true)]
public static void Should(this PropertyInfoSelectorAssertions _)
Expand Down
25 changes: 15 additions & 10 deletions Src/FluentAssertions/Common/TypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ internal static class TypeExtensions
private static readonly ConcurrentDictionary<(Type Type, MemberVisibility Visibility), TypeMemberReflector>
TypeMemberReflectorsCache = new();

public static bool IsDecoratedWith<TAttribute>(this Type type)
public static bool IsDecoratedWith<TAttribute>(this ICustomAttributeProvider type)
where TAttribute : Attribute
{
return type.IsDefined(typeof(TAttribute), inherit: false);
Expand All @@ -40,7 +40,7 @@ public static bool IsDecoratedWith<TAttribute>(this MemberInfo type)
return Attribute.IsDefined(type, typeof(TAttribute), inherit: false);
}

public static bool IsDecoratedWithOrInherit<TAttribute>(this Type type)
public static bool IsDecoratedWithOrInherit<TAttribute>(this ICustomAttributeProvider type)
where TAttribute : Attribute
{
return type.IsDefined(typeof(TAttribute), inherit: true);
Expand All @@ -55,7 +55,7 @@ public static bool IsDecoratedWithOrInherit<TAttribute>(this MemberInfo type)
return Attribute.IsDefined(type, typeof(TAttribute), inherit: true);
}

public static bool IsDecoratedWith<TAttribute>(this Type type,
public static bool IsDecoratedWith<TAttribute>(this ICustomAttributeProvider type,
Expression<Func<TAttribute, bool>> isMatchingAttributePredicate)
where TAttribute : Attribute
{
Expand All @@ -69,33 +69,38 @@ public static bool IsDecoratedWithOrInherit<TAttribute>(this MemberInfo type)
return GetCustomAttributes(type, isMatchingAttributePredicate).Any();
}

public static bool IsDecoratedWithOrInherit<TAttribute>(this Type type,
public static bool IsDecoratedWithOrInherit<TAttribute>(this ICustomAttributeProvider type,
Expression<Func<TAttribute, bool>> isMatchingAttributePredicate)
where TAttribute : Attribute
{
return GetCustomAttributes(type, isMatchingAttributePredicate, inherit: true).Any();
}

public static IEnumerable<TAttribute> GetMatchingAttributes<TAttribute>(this Type type)
public static IEnumerable<TAttribute> GetMatchingAttributes<TAttribute>(this ICustomAttributeProvider type)
where TAttribute : Attribute
{
return GetCustomAttributes<TAttribute>(type);
}

public static IEnumerable<TAttribute> GetMatchingAttributes<TAttribute>(this Type type,
public static IEnumerable<TAttribute> GetMatchingAttributes<TAttribute>(this ICustomAttributeProvider type,
Expression<Func<TAttribute, bool>> isMatchingAttributePredicate)
where TAttribute : Attribute
{
if (type is MemberInfo memberInfo)
{
return memberInfo.GetMatchingAttributes(isMatchingAttributePredicate);
}

return GetCustomAttributes(type, isMatchingAttributePredicate);
}

public static IEnumerable<TAttribute> GetMatchingOrInheritedAttributes<TAttribute>(this Type type)
public static IEnumerable<TAttribute> GetMatchingOrInheritedAttributes<TAttribute>(this ICustomAttributeProvider type)
where TAttribute : Attribute
{
return GetCustomAttributes<TAttribute>(type, inherit: true);
}

public static IEnumerable<TAttribute> GetMatchingOrInheritedAttributes<TAttribute>(this Type type,
public static IEnumerable<TAttribute> GetMatchingOrInheritedAttributes<TAttribute>(this ICustomAttributeProvider type,
Expression<Func<TAttribute, bool>> isMatchingAttributePredicate)
where TAttribute : Attribute
{
Expand All @@ -119,13 +124,13 @@ public static IEnumerable<TAttribute> GetCustomAttributes<TAttribute>(this Membe
return GetCustomAttributes<TAttribute>(type, inherit).Where(isMatchingAttribute);
}

private static TAttribute[] GetCustomAttributes<TAttribute>(this Type type, bool inherit = false)
private static TAttribute[] GetCustomAttributes<TAttribute>(this ICustomAttributeProvider type, bool inherit = false)
where TAttribute : Attribute
{
return (TAttribute[])type.GetCustomAttributes(typeof(TAttribute), inherit);
}

private static IEnumerable<TAttribute> GetCustomAttributes<TAttribute>(Type type,
private static IEnumerable<TAttribute> GetCustomAttributes<TAttribute>(ICustomAttributeProvider type,
Expression<Func<TAttribute, bool>> isMatchingAttributePredicate, bool inherit = false)
where TAttribute : Attribute
{
Expand Down
6 changes: 4 additions & 2 deletions Src/FluentAssertions/Specialized/AssemblyAssertions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
using System.Reflection;
using FluentAssertions.Common;
using FluentAssertions.Execution;
using FluentAssertions.Primitives;
using FluentAssertions.Types;

namespace FluentAssertions.Reflection;

/// <summary>
/// Contains a number of methods to assert that an <see cref="Assembly"/> is in the expected state.
/// </summary>
public class AssemblyAssertions : ReferenceTypeAssertions<Assembly, AssemblyAssertions>
public class AssemblyAssertions : ReflectionAssertions<Assembly, AssemblyAssertions>
{
/// <summary>
/// Initializes a new instance of the <see cref="AssemblyAssertions" /> class.
Expand Down Expand Up @@ -213,6 +213,8 @@ public AndConstraint<AssemblyAssertions> BeSignedWithPublicKey(string publicKey,
BitConverter.ToString(bytes).Replace("-", string.Empty, StringComparison.Ordinal);
#endif

internal override string SubjectDescription => Subject.FullName;

/// <summary>
/// Returns the type of the subject the assertion applies on.
/// </summary>
Expand Down
22 changes: 22 additions & 0 deletions Src/FluentAssertions/TypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,28 @@ public static MethodInfoSelector Methods(this TypeSelector typeSelector)
return new MethodInfoSelector(typeSelector.ToList());
}

/// <summary>
/// Returns a parameter selector for the current <see cref="MethodInfo"/>.
/// </summary>
/// <exception cref="ArgumentNullException"><paramref name="method"/> is <see langword="null"/>.</exception>
public static ParameterInfoSelector Parameters(this MethodInfo method)
{
Guard.ThrowIfArgumentIsNull(method);

return new ParameterInfoSelector(method);
}

/// <summary>
/// Returns a parameter selector for the current collection of <see cref="MethodInfo"/>.
/// </summary>
/// <exception cref="ArgumentNullException"><paramref name="methods"/> is <see langword="null"/>.</exception>
public static ParameterInfoSelector Parameters(this IEnumerable<MethodInfo> methods)
{
Guard.ThrowIfArgumentIsNull(methods);

return new ParameterInfoSelector(methods);
}

/// <summary>
/// Returns a property selector for the current <see cref="Type"/>.
/// </summary>
Expand Down
135 changes: 2 additions & 133 deletions Src/FluentAssertions/Types/MemberInfoAssertions.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,13 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using FluentAssertions.Common;
using FluentAssertions.Execution;
using FluentAssertions.Primitives;

namespace FluentAssertions.Types;

/// <summary>
/// Contains a number of methods to assert that a <see cref="MemberInfo"/> is in the expected state.
/// </summary>
[DebuggerNonUserCode]
public abstract class MemberInfoAssertions<TSubject, TAssertions> : ReferenceTypeAssertions<TSubject, TAssertions>
public abstract class MemberInfoAssertions<TSubject, TAssertions> : ReflectionAssertions<TSubject, TAssertions>
where TSubject : MemberInfo
where TAssertions : MemberInfoAssertions<TSubject, TAssertions>
{
Expand All @@ -23,131 +16,7 @@ protected MemberInfoAssertions(TSubject subject)
{
}

/// <summary>
/// Asserts that the selected member is decorated with the specified <typeparamref name="TAttribute"/>.
/// </summary>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
/// </param>
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <paramref name="because" />.
/// </param>
public AndWhichConstraint<MemberInfoAssertions<TSubject, TAssertions>, TAttribute> BeDecoratedWith<TAttribute>(
string because = "", params object[] becauseArgs)
where TAttribute : Attribute
{
return BeDecoratedWith<TAttribute>(_ => true, because, becauseArgs);
}

/// <summary>
/// Asserts that the selected member is not decorated with the specified <typeparamref name="TAttribute"/>.
/// </summary>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
/// </param>
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <paramref name="because" />.
/// </param>
public AndConstraint<TAssertions> NotBeDecoratedWith<TAttribute>(
string because = "", params object[] becauseArgs)
where TAttribute : Attribute
{
return NotBeDecoratedWith<TAttribute>(_ => true, because, becauseArgs);
}

/// <summary>
/// Asserts that the selected member is decorated with an attribute of type <typeparamref name="TAttribute"/>
/// that matches the specified <paramref name="isMatchingAttributePredicate"/>.
/// </summary>
/// <param name="isMatchingAttributePredicate">
/// The predicate that the attribute must match.
/// </param>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
/// </param>
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <paramref name="because" />.
/// </param>
/// <exception cref="ArgumentNullException"><paramref name="isMatchingAttributePredicate"/> is <see langword="null"/>.</exception>
public AndWhichConstraint<MemberInfoAssertions<TSubject, TAssertions>, TAttribute> BeDecoratedWith<TAttribute>(
Expression<Func<TAttribute, bool>> isMatchingAttributePredicate,
string because = "", params object[] becauseArgs)
where TAttribute : Attribute
{
Guard.ThrowIfArgumentIsNull(isMatchingAttributePredicate);

bool success = Execute.Assertion
.BecauseOf(because, becauseArgs)
.ForCondition(Subject is not null)
.FailWith(
$"Expected {Identifier} to be decorated with {typeof(TAttribute)}{{reason}}" +
", but {context:member} is <null>.");

IEnumerable<TAttribute> attributes = Array.Empty<TAttribute>();

if (success)
{
attributes = Subject.GetMatchingAttributes(isMatchingAttributePredicate);

Execute.Assertion
.ForCondition(attributes.Any())
.BecauseOf(because, becauseArgs)
.FailWith(
$"Expected {Identifier} {SubjectDescription} to be decorated with {typeof(TAttribute)}{{reason}}" +
", but that attribute was not found.");
}

return new AndWhichConstraint<MemberInfoAssertions<TSubject, TAssertions>, TAttribute>(this, attributes);
}

/// <summary>
/// Asserts that the selected member is not decorated with an attribute of type <typeparamref name="TAttribute"/>
/// that matches the specified <paramref name="isMatchingAttributePredicate"/>.
/// </summary>
/// <param name="isMatchingAttributePredicate">
/// The predicate that the attribute must match.
/// </param>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
/// </param>
/// <param name="becauseArgs">
/// Zero or more objects to format using the placeholders in <paramref name="because" />.
/// </param>
/// <exception cref="ArgumentNullException"><paramref name="isMatchingAttributePredicate"/> is <see langword="null"/>.</exception>
public AndConstraint<TAssertions> NotBeDecoratedWith<TAttribute>(
Expression<Func<TAttribute, bool>> isMatchingAttributePredicate,
string because = "", params object[] becauseArgs)
where TAttribute : Attribute
{
Guard.ThrowIfArgumentIsNull(isMatchingAttributePredicate);

bool success = Execute.Assertion
.BecauseOf(because, becauseArgs)
.ForCondition(Subject is not null)
.FailWith(
$"Expected {Identifier} to not be decorated with {typeof(TAttribute)}{{reason}}" +
", but {context:member} is <null>.");

if (success)
{
IEnumerable<TAttribute> attributes = Subject.GetMatchingAttributes(isMatchingAttributePredicate);

Execute.Assertion
.ForCondition(!attributes.Any())
.BecauseOf(because, becauseArgs)
.FailWith(
$"Expected {Identifier} {SubjectDescription} to not be decorated with {typeof(TAttribute)}{{reason}}" +
", but that attribute was found.");
}

return new AndConstraint<TAssertions>((TAssertions)this);
}

protected override string Identifier => "member";

internal virtual string SubjectDescription => $"{Subject.DeclaringType}.{Subject.Name}";
internal override string SubjectDescription => $"{Subject.DeclaringType}.{Subject.Name}";
}
28 changes: 28 additions & 0 deletions Src/FluentAssertions/Types/ParameterInfoAssertions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.Diagnostics;
using System.Reflection;

namespace FluentAssertions.Types;

/// <summary>
/// Contains a number of methods to assert that a <see cref="ParameterInfo"/> is in the expected state.
/// </summary>
[DebuggerNonUserCode]
public class ParameterInfoAssertions : ReflectionAssertions<ParameterInfo, ParameterInfoAssertions>
{
public ParameterInfoAssertions(ParameterInfo parameterInfo)
: base(parameterInfo)
{
}

internal static string GetDescriptionFor(ParameterInfo parameter)
{
return parameter?.Name ?? string.Empty;
}

internal override string SubjectDescription => Subject.Name;

/// <summary>
/// Returns the type of the subject the assertion applies on.
/// </summary>
protected override string Identifier => "parameter";
}

0 comments on commit 2d1cb5a

Please sign in to comment.