Skip to content

Commit

Permalink
Merge pull request #6 from NickHarvey2/master
Browse files Browse the repository at this point in the history
Validate whether a class has only nullable fields
  • Loading branch information
BenMorris authored Mar 20, 2019
2 parents 4015440 + a486059 commit e4df8bd
Show file tree
Hide file tree
Showing 15 changed files with 306 additions and 10 deletions.
24 changes: 22 additions & 2 deletions src/NetArchTest.Rules/Conditions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ public ConditionList NotBeSealed()
}

/// <summary>
/// Selects types according that are immutable.
/// Selects types that are immutable.
/// </summary>
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
public ConditionList BeImmutable()
Expand All @@ -345,7 +345,7 @@ public ConditionList BeImmutable()
}

/// <summary>
/// Selects types according that are mutable.
/// Selects types that are mutable.
/// </summary>
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
public ConditionList BeMutable()
Expand All @@ -354,6 +354,26 @@ public ConditionList BeMutable()
return new ConditionList(_types, _should, _sequence);
}

/// <summary>
/// Selects types according to whether they have nullable members.
/// </summary>
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
public ConditionList HaveOnlyNullableMembers()
{
_sequence.AddFunctionCall(FunctionDelegates.HasNullableMembers, true, true);
return new ConditionList(_types, _should, _sequence);
}

/// <summary>
/// Selects types according to whether they have nullable members.
/// </summary>
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
public ConditionList HaveSomeNonNullableMembers()
{
_sequence.AddFunctionCall(FunctionDelegates.HasNullableMembers, true, false);
return new ConditionList(_types, _should, _sequence);
}

/// <summary>
/// Selects types that reside in a particular namespace.
/// </summary>
Expand Down
10 changes: 10 additions & 0 deletions src/NetArchTest.Rules/Extensions/FieldDefinitionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,15 @@ public static bool IsReadonly(this FieldDefinition fieldDefinition)
{
return !fieldDefinition.IsPublic || fieldDefinition.IsInitOnly || fieldDefinition.HasConstant || fieldDefinition.IsCompilerControlled;
}

/// <summary>
/// Tests whether a field is nullable
/// </summary>
/// <param name="fieldDefinition">The field to test.</param>
/// <returns>An indication of whether the field is nullable.</returns>
public static bool IsNullable(this FieldDefinition fieldDefinition)
{
return fieldDefinition.FieldType.IsNullable();
}
}
}
10 changes: 10 additions & 0 deletions src/NetArchTest.Rules/Extensions/PropertyDefinitionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,15 @@ public static bool IsReadonly(this PropertyDefinition propertyDefinition)
{
return propertyDefinition.SetMethod == null || !propertyDefinition.SetMethod.IsPublic;
}

/// <summary>
/// Tests whether a property is nullable
/// </summary>
/// <param name="propertyDefinition">The property to test.</param>
/// <returns>An indication of whether the property is nullable.</returns>
public static bool IsNullable(this PropertyDefinition propertyDefinition)
{
return propertyDefinition.PropertyType.IsNullable();
}
}
}
12 changes: 12 additions & 0 deletions src/NetArchTest.Rules/Extensions/TypeDefinitionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,17 @@ public static bool IsImmutable(this TypeDefinition typeDefinition)
var fieldsAreReadonly = typeDefinition.Fields.All(f => f.IsReadonly());
return propertiesAreReadonly && fieldsAreReadonly;
}

/// <summary>
/// Tests whether a Type has any memebers that are non-nullable value types
/// </summary>
/// <param name="typeDefinition">The class to test.</param>
/// <returns>An indication of whether the type has any memebers that are non-nullable value types</returns>
public static bool HasNullableMembers(this TypeDefinition typeDefinition)
{
var propertiesAreNullable = typeDefinition.Properties.All(p => p.IsNullable());
var fieldsAreNullable = typeDefinition.Fields.All(f => f.IsNullable());
return propertiesAreNullable && fieldsAreNullable;
}
}
}
21 changes: 21 additions & 0 deletions src/NetArchTest.Rules/Extensions/TypeReferenceExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace NetArchTest.Rules.Extensions
{
using System;
using System.Linq;
using System.Collections.Generic;
using Mono.Cecil;

static internal class TypeReferenceExtensions
{

/// <summary>
/// Tests whether a Type is a non-nullable value type
/// </summary>
/// <param name="typeReference">The class to test.</param>
/// <returns>An indication of whether the type has any memebers that are non-nullable value types</returns>
public static bool IsNullable(this TypeReference typeReference)
{
return !typeReference.IsValueType || typeReference.Resolve().ToType() == typeof(System.Nullable<>);
}
}
}
15 changes: 14 additions & 1 deletion src/NetArchTest.Rules/FunctionDelegates.cs
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ internal static class FunctionDelegates
}
};

/// <summary> Function for finding sealed classes. </summary>
/// <summary> Function for finding immutable classes. </summary>
internal static FunctionDelegate<bool> BeImmutable = delegate (IEnumerable<TypeDefinition> input, bool dummmy, bool condition)
{
if (condition)
Expand All @@ -234,6 +234,19 @@ internal static class FunctionDelegates
}
};

/// <summary> Function for finding nullable classes. </summary>
internal static FunctionDelegate<bool> HasNullableMembers = delegate (IEnumerable<TypeDefinition> input, bool dummmy, bool condition)
{
if (condition)
{
return input.Where(c => c.HasNullableMembers());
}
else
{
return input.Where(c => !c.HasNullableMembers());
}
};

/// <summary> Function for finding types in a particular namespace. </summary>
internal static FunctionDelegate<string> ResideInNamespace = delegate (IEnumerable<TypeDefinition> input, string name, bool condition)
{
Expand Down
65 changes: 60 additions & 5 deletions src/NetArchTest.Rules/NetArchTest.Rules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 22 additions & 2 deletions src/NetArchTest.Rules/Predicates.cs
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ public PredicateList AreNotSealed()
}

/// <summary>
/// Selects types according that are immutable.
/// Selects types that are immutable.
/// </summary>
/// <returns>An updated set of predicates that can be applied to a list of types.</returns>
public PredicateList AreImmutable()
Expand All @@ -346,7 +346,7 @@ public PredicateList AreImmutable()
}

/// <summary>
/// Selects types according that are mutable.
/// Selects types that are mutable.
/// </summary>
/// <returns>An updated set of predicates that can be applied to a list of types.</returns>
public PredicateList AreMutable()
Expand All @@ -355,6 +355,26 @@ public PredicateList AreMutable()
return new PredicateList(_types, _sequence);
}

/// <summary>
/// Selects types that have only nullable members.
/// </summary>
/// <returns>An updated set of predicates that can be applied to a list of types.</returns>
public PredicateList HaveOnlyNullableMembers()
{
_sequence.AddFunctionCall(FunctionDelegates.HasNullableMembers, true, true);
return new PredicateList(_types, _sequence);
}

/// <summary>
/// Selects types that have some non-nullable members.
/// </summary>
/// <returns>An updated set of predicates that can be applied to a list of types.</returns>
public PredicateList HaveSomeNonNullableMembers()
{
_sequence.AddFunctionCall(FunctionDelegates.HasNullableMembers, true, false);
return new PredicateList(_types, _sequence);
}

/// <summary>
/// Selects types that reside in a particular namespace.
/// </summary>
Expand Down
34 changes: 34 additions & 0 deletions test/NetArchTest.Rules.UnitTests/ConditionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,40 @@ public void AreMutable_MatchesFound_ClassSelected()
Assert.True(result.IsSuccessful);
}

[Fact(DisplayName = "Types can be selected for having only nullable memebers.")]
public void AreNullable_MatchesFound_ClassSelected()
{
var result = Types
.InAssembly(Assembly.GetAssembly(typeof(ClassA1)))
.That()
.ResideInNamespace("NetArchTest.TestStructure.Nullable")
.And()
.AreNotNested() // ignore nested helper types
.And()
.DoNotHaveNameStartingWith("NonNullableClass")
.Should()
.HaveOnlyNullableMembers().GetResult();

Assert.True(result.IsSuccessful);
}

[Fact(DisplayName = "Types can be selected for having non-nullable memebers.")]
public void AreNonNullable_MatchesFound_ClassSelected()
{
var result = Types
.InAssembly(Assembly.GetAssembly(typeof(ClassA1)))
.That()
.ResideInNamespace("NetArchTest.TestStructure.Nullable")
.And()
.AreNotNested() // ignore nested helper types
.And()
.DoNotHaveNameStartingWith("NullableClass")
.Should()
.HaveSomeNonNullableMembers().GetResult();

Assert.True(result.IsSuccessful);
}

[Fact(DisplayName = "Types can be selected if they reside in a namespace.")]
public void ResideInNamespace_MatchesFound_ClassSelected()
{
Expand Down
Loading

0 comments on commit e4df8bd

Please sign in to comment.