Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 42 additions & 24 deletions Source/Testably.Architecture.Rules/Extensions/TypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,45 @@ public static bool HasMethodWithAttribute<TAttribute>(
a => predicate(a, method), inherit));
}

/// <summary>
/// Determines whether the current <see cref="Type" /> implements the <paramref name="interfaceType" />.
/// </summary>
/// <param name="type">The <see cref="Type" />.</param>
/// <param name="interfaceType">The interface <see cref="Type" />.</param>
/// <param name="forceDirect">
/// If set to <see langword="false" /> (default value), the <paramref name="interfaceType" />
/// can be anywhere in the inheritance tree, otherwise if set to <see langword="true" /> requires the
/// <paramref name="interfaceType" /> to be directly implemented in the <paramref name="type"/>.
/// </param>
public static bool Implements(
this Type type,
Type interfaceType,
bool forceDirect = false)
{
if (!interfaceType.IsInterface)
{
return false;
}

Type[] interfaces = type.GetInterfaces();
if (forceDirect && type.BaseType != null)
{
interfaces = interfaces
.Except(type.BaseType.GetInterfaces())
.ToArray();
}

return interfaces
.Any(childInterface =>
{
Type currentInterface = childInterface.IsGenericType
? childInterface.GetGenericTypeDefinition()
: childInterface;

return currentInterface == interfaceType;
});
}

/// <summary>
/// Determines whether the current <see cref="Type" /> inherits from the <paramref name="parentType" />.
/// </summary>
Expand All @@ -78,8 +117,7 @@ public static bool HasMethodWithAttribute<TAttribute>(
/// can be anywhere in the inheritance tree, otherwise if set to <see langword="true" /> requires the
/// <paramref name="parentType" /> to be the direct parent.
/// </param>
/// <returns></returns>
public static bool Inherits(
public static bool InheritsFrom(
this Type type,
Type parentType,
bool forceDirect = false)
Expand Down Expand Up @@ -111,7 +149,7 @@ public static bool Inherits(
return false;
}

if (currentType.ImplementsInterface(parentType, forceDirect))
if (currentType.Implements(parentType, forceDirect))
{
return true;
}
Expand All @@ -137,25 +175,5 @@ public static bool Inherits(
public static bool IsStatic(this Type type)
=> type.IsAbstract && type.IsSealed && !type.IsInterface &&
!type.GetConstructors().Any(m => m.IsPublic);

internal static bool ImplementsInterface(this Type type, Type interfaceType, bool forceDirect)
{
Type[] interfaces = type.GetInterfaces();
if (forceDirect && type.BaseType != null)
{
interfaces = interfaces
.Except(type.BaseType.GetInterfaces())
.ToArray();
}

return interfaces
.Any(childInterface =>
{
Type currentInterface = childInterface.IsGenericType
? childInterface.GetGenericTypeDefinition()
: childInterface;

return currentInterface == interfaceType;
});
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System;

namespace Testably.Architecture.Rules;

public static partial class FilterOnTypeExtensions
{
/// <summary>
/// Filter for types that implement the interface <typeparamref name="TInterface" />.
/// </summary>
public static ITypeFilterResult WhichImplement<TInterface>(this ITypeFilter @this,
bool forceDirect = false)
{
return @this.WhichImplement(typeof(TInterface), forceDirect);
}

/// <summary>
/// Filter for types that implement the interface <paramref name="interfaceType" />.
/// </summary>
public static ITypeFilterResult WhichImplement(this ITypeFilter @this,
Type interfaceType,
bool forceDirect = false)
{
return @this.Which(type => type.Implements(interfaceType, forceDirect));
}

/// <summary>
/// Filter for types that don't implement the interface <typeparamref name="TInterface" />.
/// </summary>
public static ITypeFilterResult WhichDoNotImplement<TInterface>(this ITypeFilter @this,
bool forceDirect = false)
{
return @this.WhichDoNotImplement(typeof(TInterface), forceDirect);
}

/// <summary>
/// Filter for types that don't implement the interface <paramref name="interfaceType" />.
/// </summary>
public static ITypeFilterResult WhichDoNotImplement(this ITypeFilter @this,
Type interfaceType,
bool forceDirect = false)
{
return @this.Which(type => !type.Implements(interfaceType, forceDirect));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public static ITypeFilterResult WhichInheritFrom<TBase>(this ITypeFilter @this,
public static ITypeFilterResult WhichInheritFrom(this ITypeFilter @this, Type baseType,
bool forceDirect = false)
{
return @this.Which(type => type.Inherits(baseType, forceDirect));
return @this.Which(type => type.InheritsFrom(baseType, forceDirect));
}

/// <summary>
Expand All @@ -37,6 +37,6 @@ public static ITypeFilterResult WhichDoNotInheritFrom<TBase>(this ITypeFilter @t
public static ITypeFilterResult WhichDoNotInheritFrom(this ITypeFilter @this, Type baseType,
bool forceDirect = false)
{
return @this.Which(type => !type.Inherits(baseType, forceDirect));
return @this.Which(type => !type.InheritsFrom(baseType, forceDirect));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System;

namespace Testably.Architecture.Rules;

public static partial class RequirementOnTypeExtensions
{
/// <summary>
/// Expect the types to implement the <typeparamref name="TInterface" />.
/// </summary>
/// <typeparamref name="TInterface">The type of the interface which should be implemented.</typeparamref>
/// <param name="this">The <see cref="IRequirement{Type}" />.</param>
/// <param name="forceDirect">
/// If set to <see langword="false" /> (default value), the <typeparamref name="TInterface" />
/// can be anywhere in the inheritance tree, otherwise if set to <see langword="true" /> requires the
/// <typeparamref name="TInterface" /> to be directly implemented.
/// </param>
public static IRequirementResult<Type> ShouldImplement<TInterface>(
this IRequirement<Type> @this,
bool forceDirect = false)
=> @this.ShouldImplement(typeof(TInterface), forceDirect);

/// <summary>
/// Expect the types to implement the <paramref name="interfaceType" />.
/// </summary>
/// <param name="this">The <see cref="IRequirement{Type}" />.</param>
/// <param name="interfaceType">The interface which should be implemented.</param>
/// <param name="forceDirect">
/// If set to <see langword="false" /> (default value), the <paramref name="interfaceType" />
/// can be anywhere in the inheritance tree, otherwise if set to <see langword="true" /> requires the
/// <paramref name="interfaceType" /> to be directly implemented.
/// </param>
public static IRequirementResult<Type> ShouldImplement(
this IRequirement<Type> @this,
Type interfaceType,
bool forceDirect = false)
=> @this.ShouldSatisfy(Requirement.ForType(
type => type.Implements(interfaceType, forceDirect),
type => new TypeTestError(type,
$"Type '{type.Name}' should{(forceDirect ? " directly" : "")} implement '{interfaceType.Name}'.")));

/// <summary>
/// Expect the types to not implement the <typeparamref name="TInterface" />.
/// </summary>
/// <typeparamref name="TInterface">The type of the interface which should be implemented.</typeparamref>
/// <param name="this">The <see cref="IRequirement{Type}" />.</param>
/// <param name="forceDirect">
/// If set to <see langword="false" /> (default value), the <typeparamref name="TInterface" />
/// can be anywhere in the inheritance tree, otherwise if set to <see langword="true" /> requires the
/// <typeparamref name="TInterface" /> to be directly implemented.
/// </param>
public static IRequirementResult<Type> ShouldNotImplement<TInterface>(
this IRequirement<Type> @this,
bool forceDirect = false)
=> @this.ShouldNotImplement(typeof(TInterface), forceDirect);

/// <summary>
/// Expect the types to not implement the <paramref name="interfaceType" />.
/// </summary>
/// <param name="this">The <see cref="IRequirement{Type}" />.</param>
/// <param name="interfaceType">The interface which should be implemented.</param>
/// <param name="forceDirect">
/// If set to <see langword="false" /> (default value), the <paramref name="interfaceType" />
/// can be anywhere in the inheritance tree, otherwise if set to <see langword="true" /> requires the
/// <paramref name="interfaceType" /> to be directly implemented.
/// </param>
public static IRequirementResult<Type> ShouldNotImplement(
this IRequirement<Type> @this,
Type interfaceType,
bool forceDirect = false)
=> @this.ShouldSatisfy(Requirement.ForType(
type => !type.Implements(interfaceType, forceDirect),
type => new TypeTestError(type,
$"Type '{type.Name}' should not{(forceDirect ? " directly" : "")} implement '{interfaceType.Name}'.")));
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public static IRequirementResult<Type> ShouldInheritFrom(
Type baseType,
bool forceDirect = false)
=> @this.ShouldSatisfy(Requirement.ForType(
type => type.Inherits(baseType, forceDirect),
type => type.InheritsFrom(baseType, forceDirect),
type => new TypeTestError(type,
$"Type '{type.Name}' should{(forceDirect ? " directly" : "")} inherit from '{baseType.Name}'.")));

Expand Down Expand Up @@ -66,7 +66,7 @@ public static IRequirementResult<Type> ShouldNotInheritFrom(
Type baseType,
bool forceDirect = false)
=> @this.ShouldSatisfy(Requirement.ForType(
type => !type.Inherits(baseType, forceDirect),
type => !type.InheritsFrom(baseType, forceDirect),
type => new TypeTestError(type,
$"Type '{type.Name}' should not{(forceDirect ? " directly" : "")} inherit from '{baseType.Name}'.")));
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,11 @@ public void HasMethodWithAttribute_WithPredicate_ShouldReturnPredicateResult()
[Theory]
[InlineData(false)]
[InlineData(true)]
public void ImplementsInterface_Object_ShouldReturnFalse(bool forceDirect)
public void Implements_Object_ShouldReturnFalse(bool forceDirect)
{
Type sut = typeof(object);

bool result = sut.ImplementsInterface(typeof(IFooInterface), forceDirect);
bool result = sut.Implements(typeof(IFooInterface), forceDirect);

result.Should().BeFalse();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
using FluentAssertions;
using Testably.Architecture.Rules.Tests.TestHelpers;
using Xunit;

namespace Testably.Architecture.Rules.Tests.Filters;

public sealed partial class FilterOnTypeExtensionsTests
{
public sealed class ImplementTests
{
[Theory]
[InlineData(false)]
[InlineData(true)]
public void WhichImplement_WithClass_ShouldReturnFalse(bool forceDirect)
{
ITestResult result = Expect.That.Types
.WhichImplement<FooImplementor1>(forceDirect)
.ShouldAlwaysFail()
.AllowEmpty()
.Check.InAllLoadedAssemblies();

result.ShouldNotBeViolated();
}

[Fact]
public void
WhichImplement_WithInterface_WithForceDirect_ShouldReturnTypesDirectlyImplementingTheInterface()
{
ITestResult result = Expect.That.Types
.WhichImplement<IFooInterface>(true)
.ShouldAlwaysFail()
.Check.InAllLoadedAssemblies();

result.Errors.Length.Should().Be(2);
result.Errors.Should()
.Contain(e => e.ToString().Contains(nameof(FooImplementor1)));
result.Errors.Should()
.NotContain(e => e.ToString().Contains(nameof(FooImplementor2)));
result.Errors.Should()
.Contain(e => e.ToString().Contains(typeof(FooGenericImplementor1<>).Name));
result.Errors.Should()
.NotContain(e => e.ToString().Contains(typeof(FooGenericImplementor2<>).Name));
}

[Fact]
public void
WhichImplement_WithInterface_WithoutForceDirect_ShouldReturnAllTypesImplementingInterface()
{
ITestResult result = Expect.That.Types
.WhichImplement<IFooInterface>()
.ShouldAlwaysFail()
.Check.InAllLoadedAssemblies();

result.Errors.Length.Should().Be(4);
result.Errors.Should()
.Contain(e => e.ToString().Contains(nameof(FooImplementor1)));
result.Errors.Should()
.Contain(e => e.ToString().Contains(nameof(FooImplementor2)));
result.Errors.Should()
.Contain(e => e.ToString().Contains(typeof(FooGenericImplementor1<>).Name));
result.Errors.Should()
.Contain(e => e.ToString().Contains(typeof(FooGenericImplementor2<>).Name));
}

[Fact]
public void
WhichImplement_WithGenericInterface_WithForceDirect_ShouldReturnTypesDirectlyImplementingTheInterface()
{
ITestResult result = Expect.That.Types
.WhichImplement(typeof(IGenericFooInterface<>), true)
.ShouldAlwaysFail()
.Check.InAllLoadedAssemblies();

result.Errors.Length.Should().Be(1);
result.Errors.Should()
.Contain(e => e.ToString().Contains(typeof(FooGenericImplementor1<>).Name));
result.Errors.Should()
.NotContain(e => e.ToString().Contains(typeof(FooGenericImplementor2<>).Name));
}

[Fact]
public void
WhichImplement_WithGenericInterface_WithoutForceDirect_ShouldReturnAllTypesImplementingInterface()
{
ITestResult result = Expect.That.Types
.WhichImplement(typeof(IGenericFooInterface<>))
.ShouldAlwaysFail()
.Check.InAllLoadedAssemblies();

result.Errors.Length.Should().Be(2);
result.Errors.Should()
.Contain(e => e.ToString().Contains(typeof(FooGenericImplementor1<>).Name));
result.Errors.Should()
.Contain(e => e.ToString().Contains(typeof(FooGenericImplementor2<>).Name));
}

private class FooGenericImplementor1<T> : IGenericFooInterface<T>,
IFooInterface
{
}

private class FooGenericImplementor2<T> : FooGenericImplementor1<T>
{
}

private class FooImplementor1 : IFooInterface
{
}

private class FooImplementor2 : FooImplementor1
{
}

private interface IFooInterface
{
}

// ReSharper disable once UnusedTypeParameter
private interface IGenericFooInterface<T>
{
}
}
}
Loading