Skip to content

Commit bb769ed

Browse files
committed
BenMorris/NetArchTest#100 - differentiate two kinds of immutability : AreImmutable and AreImmutableExternally
1 parent 277503a commit bb769ed

38 files changed

Lines changed: 409 additions & 265 deletions

src/NetArchTest.Rules/Condition.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,6 @@ public ConditionList NotImplementInterface<T>()
194194
}
195195

196196

197-
198197
/// <summary>
199198
/// Selects types that meet a custom rule.
200199
/// </summary>

src/NetArchTest.Rules/Condition_AccessModifiers.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,4 +144,4 @@ public ConditionList NotBePublic()
144144
return CreateConditionList();
145145
}
146146
}
147-
}
147+
}

src/NetArchTest.Rules/Condition_Names.cs

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using System;
2-
using NetArchTest.Functions;
1+
using NetArchTest.Functions;
32

43
namespace NetArchTest.Rules
54
{
@@ -12,7 +11,7 @@ public sealed partial class Condition
1211
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
1312
public ConditionList HaveName(string name)
1413
{
15-
AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveName(context, inputTypes, name, true));
14+
AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveName(context, inputTypes, new[] { name }, true));
1615
return CreateConditionList();
1716
}
1817

@@ -23,7 +22,7 @@ public ConditionList HaveName(string name)
2322
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
2423
public ConditionList NotHaveName(string name)
2524
{
26-
AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveName(context, inputTypes, name, false));
25+
AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveName(context, inputTypes, new[] { name }, false));
2726
return CreateConditionList();
2827
}
2928

@@ -56,7 +55,7 @@ public ConditionList NotHaveNameMatching(string pattern)
5655
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
5756
public ConditionList HaveNameStartingWith(string start)
5857
{
59-
AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveNameStartingWith(context, inputTypes, start, true));
58+
AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveNameStartingWith(context, inputTypes, new[] { start }, true));
6059
return CreateConditionList();
6160
}
6261

@@ -67,7 +66,7 @@ public ConditionList HaveNameStartingWith(string start)
6766
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
6867
public ConditionList NotHaveNameStartingWith(string start)
6968
{
70-
AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveNameStartingWith(context, inputTypes, start, false));
69+
AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveNameStartingWith(context, inputTypes, new[] { start }, false));
7170
return CreateConditionList();
7271
}
7372

@@ -78,7 +77,7 @@ public ConditionList NotHaveNameStartingWith(string start)
7877
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
7978
public ConditionList HaveNameEndingWith(string end)
8079
{
81-
AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveNameEndingWith(context, inputTypes, end, true));
80+
AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveNameEndingWith(context, inputTypes, new[] { end }, true));
8281
return CreateConditionList();
8382
}
8483

@@ -89,19 +88,10 @@ public ConditionList HaveNameEndingWith(string end)
8988
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
9089
public ConditionList NotHaveNameEndingWith(string end)
9190
{
92-
AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveNameEndingWith(context, inputTypes, end, false));
91+
AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveNameEndingWith(context, inputTypes, new[] { end }, false));
9392
return CreateConditionList();
9493
}
95-
96-
/// <summary>
97-
/// Selects types whose names do not end with the specified text.
98-
/// </summary>
99-
/// <param name="end">The text to match against.</param>
100-
/// <param name="comparer">The string comparer.</param>
101-
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
102-
103-
104-
94+
10595

10696
/// <summary>
10797
/// Selects types that reside in a particular namespace.

src/NetArchTest.Rules/Condition_Special.cs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ namespace NetArchTest.Rules
44
{
55
public sealed partial class Condition
66
{
7-
87
/// <summary>
9-
/// Selects types that are immutable.
8+
/// Selects types that are immutable. (shallow immutability). It is stronger constraint than BeImmutableExternally()
109
/// </summary>
1110
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
1211
public ConditionList BeImmutable()
@@ -25,13 +24,23 @@ public ConditionList BeMutable()
2524
return CreateConditionList();
2625
}
2726

27+
/// <summary>
28+
/// Selects types that are immutable. (shallow external only immutability). It is waker constraint than BeImmutable()
29+
/// </summary>
30+
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
31+
public ConditionList BeImmutableExternally()
32+
{
33+
AddFunctionCall(x => FunctionDelegates.BeImmutableExternally(x, true));
34+
return CreateConditionList();
35+
}
36+
2837
/// <summary>
2938
/// Selects types according to whether they have nullable members.
3039
/// </summary>
3140
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
3241
public ConditionList OnlyHaveNullableMembers()
3342
{
34-
AddFunctionCall(x => FunctionDelegates.HasNullableMembers(x, true));
43+
AddFunctionCall(x => FunctionDelegates.OnlyHaveNullableMembers(x, true));
3544
return CreateConditionList();
3645
}
3746

@@ -41,8 +50,18 @@ public ConditionList OnlyHaveNullableMembers()
4150
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
4251
public ConditionList HaveSomeNonNullableMembers()
4352
{
44-
AddFunctionCall(x => FunctionDelegates.HasNullableMembers(x, false));
53+
AddFunctionCall(x => FunctionDelegates.OnlyHaveNullableMembers(x, false));
54+
return CreateConditionList();
55+
}
56+
57+
/// <summary>
58+
/// Selects types according to whether they have only non-nullable members.
59+
/// </summary>
60+
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
61+
public ConditionList OnlyHaveNonNullableMembers()
62+
{
63+
AddFunctionCall(x => FunctionDelegates.OnlyHaveNonNullableMembers(x, true));
4564
return CreateConditionList();
4665
}
4766
}
48-
}
67+
}

src/NetArchTest.Rules/Condition_Types.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,4 @@ public ConditionList NotBeStructures()
104104
return CreateConditionList();
105105
}
106106
}
107-
}
107+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
namespace Mono.Cecil
2+
{
3+
internal static class EventDefinitionExtensions
4+
{
5+
public static bool IsReadonly(this EventDefinition propertyDefinition)
6+
{
7+
if (propertyDefinition.AddMethod == null)
8+
{
9+
return true;
10+
}
11+
12+
return false;
13+
}
14+
public static bool IsReadonlyExternally(this EventDefinition propertyDefinition)
15+
{
16+
if (propertyDefinition.AddMethod?.IsPublic == false)
17+
{
18+
return true;
19+
}
20+
return propertyDefinition.IsReadonly();
21+
}
22+
}
23+
}

src/NetArchTest.Rules/Extensions/Mono.Cecil/FieldDefinitionExtensions.cs

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
11
namespace Mono.Cecil
22
{
33
static internal class FieldDefinitionExtensions
4-
{
5-
/// <summary>
6-
/// Tests whether a field is readonly
7-
/// </summary>
8-
/// <param name="fieldDefinition">The field to test.</param>
9-
/// <returns>An indication of whether the field is readonly.</returns>
4+
{
105
public static bool IsReadonly(this FieldDefinition fieldDefinition)
116
{
127
return fieldDefinition.IsInitOnly || fieldDefinition.HasConstant || fieldDefinition.IsCompilerControlled;
138
}
14-
15-
/// <summary>
16-
/// Tests whether a field is nullable
17-
/// </summary>
18-
/// <param name="fieldDefinition">The field to test.</param>
19-
/// <returns>An indication of whether the field is nullable.</returns>
9+
public static bool IsReadonlyExternally(this FieldDefinition fieldDefinition)
10+
{
11+
if (!fieldDefinition.IsPublic)
12+
{
13+
return true;
14+
}
15+
return fieldDefinition.IsReadonly();
16+
}
17+
18+
2019
public static bool IsNullable(this FieldDefinition fieldDefinition)
2120
{
2221
return fieldDefinition.FieldType.IsNullable();

src/NetArchTest.Rules/Extensions/Mono.Cecil/PropertyDefinitionExtensions.cs

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
namespace Mono.Cecil
22
{
3-
43
static internal class PropertyDefinitionExtensions
5-
{
6-
/// <summary>
7-
/// Tests whether a property is readonly
8-
/// </summary>
9-
/// <param name="propertyDefinition">The property to test.</param>
10-
/// <returns>An indication of whether the property is readonly.</returns>
4+
{
115
public static bool IsReadonly(this PropertyDefinition propertyDefinition)
126
{
137
if (propertyDefinition.SetMethod == null)
@@ -24,17 +18,21 @@ public static bool IsReadonly(this PropertyDefinition propertyDefinition)
2418

2519
return false;
2620
}
21+
public static bool IsReadonlyExternally(this PropertyDefinition propertyDefinition)
22+
{
23+
if (propertyDefinition.SetMethod?.IsPublic == false)
24+
{
25+
return true;
26+
}
27+
return propertyDefinition.IsReadonly();
28+
}
2729

2830
public static bool IsInitOnly(this PropertyDefinition propertyDefinition)
2931
{
3032
return propertyDefinition.SetMethod?.ReturnType.FullName == "System.Void modreq(System.Runtime.CompilerServices.IsExternalInit)";
3133
}
3234

33-
/// <summary>
34-
/// Tests whether a property is nullable
35-
/// </summary>
36-
/// <param name="propertyDefinition">The property to test.</param>
37-
/// <returns>An indication of whether the property is nullable.</returns>
35+
3836
public static bool IsNullable(this PropertyDefinition propertyDefinition)
3937
{
4038
return propertyDefinition.PropertyType.IsNullable();

src/NetArchTest.Rules/Extensions/Mono.Cecil/TypeDefinitionExtensions.cs

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -79,28 +79,39 @@ public static Type ToType(this TypeDefinition typeDefinition)
7979

8080
/// <summary>
8181
/// Tests whether a class is immutable, i.e. all public fields are readonly and properties have no set method
82-
/// </summary>
83-
/// <param name="typeDefinition">The class to test.</param>
84-
/// <returns>An indication of whether the type is immutable</returns>
82+
/// </summary>
8583
public static bool IsImmutable(this TypeDefinition typeDefinition)
8684
{
8785
var propertiesAreReadonly = typeDefinition.Properties.All(p => p.IsReadonly());
8886
var fieldsAreReadonly = typeDefinition.Fields.All(f => f.IsReadonly());
89-
return propertiesAreReadonly && fieldsAreReadonly;
87+
var eventsAreReadonly = typeDefinition.Events.All(f => f.IsReadonly());
88+
return propertiesAreReadonly && fieldsAreReadonly && eventsAreReadonly;
9089
}
9190

92-
/// <summary>
93-
/// Tests whether a Type has any memebers that are non-nullable value types
94-
/// </summary>
95-
/// <param name="typeDefinition">The class to test.</param>
96-
/// <returns>An indication of whether the type has any memebers that are non-nullable value types</returns>
97-
public static bool HasNullableMembers(this TypeDefinition typeDefinition)
91+
public static bool IsImmutableExternally(this TypeDefinition typeDefinition)
92+
{
93+
var propertiesAreReadonly = typeDefinition.Properties.All(p => p.IsReadonlyExternally());
94+
var fieldsAreReadonly = typeDefinition.Fields.All(f => f.IsReadonlyExternally());
95+
var eventsAreReadonly = typeDefinition.Events.All(f => f.IsReadonlyExternally());
96+
return propertiesAreReadonly && fieldsAreReadonly && eventsAreReadonly;
97+
}
98+
99+
100+
101+
public static bool OnlyHasNullableMembers(this TypeDefinition typeDefinition)
98102
{
99103
var propertiesAreNullable = typeDefinition.Properties.All(p => p.IsNullable());
100104
var fieldsAreNullable = typeDefinition.Fields.All(f => f.IsNullable());
101105
return propertiesAreNullable && fieldsAreNullable;
102106
}
103107

108+
public static bool OnlyHasNonNullableMembers(this TypeDefinition typeDefinition)
109+
{
110+
var propertiesAreNonNullable = typeDefinition.Properties.All(p => p.IsNullable() == false);
111+
var fieldsAreNonNullable = typeDefinition.Fields.All(f => f.IsNullable() == false);
112+
return propertiesAreNonNullable && fieldsAreNonNullable;
113+
}
114+
104115
public static bool IsCompilerGenerated(this TypeDefinition typeDefinition)
105116
{
106117
return typeDefinition.CustomAttributes.Any(x => x?.AttributeType?.FullName == typeof(CompilerGeneratedAttribute).FullName);

src/NetArchTest.Rules/Functions/FunctionDelegates.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,14 @@ internal static IEnumerable<TypeSpec> ImplementInterface(IEnumerable<TypeSpec> i
5858

5959
if (condition)
6060
{
61-
return input.Where(c => Implements(c.Definition));
61+
return input.Where(c => Implements(c.Definition, typeInterface));
6262
}
6363
else
6464
{
65-
return input.Where(c => !Implements(c.Definition));
65+
return input.Where(c => !Implements(c.Definition, typeInterface));
6666
}
6767

68-
bool Implements(TypeDefinition c) => c.Interfaces.Any(t => t.InterfaceType.FullName.Equals(typeInterface.FullName, StringComparison.InvariantCultureIgnoreCase));
68+
static bool Implements(TypeDefinition c, Type typeInterface) => c.Interfaces.Any(t => t.InterfaceType.FullName.Equals(typeInterface.FullName, StringComparison.InvariantCultureIgnoreCase));
6969
}
7070

7171
internal static IEnumerable<TypeSpec> MeetCustomRule(IEnumerable<TypeSpec> input, ICustomRule rule, bool condition)

0 commit comments

Comments
 (0)