Skip to content

Commit 97da451

Browse files
committed
BenMorris/NetArchTest#100 - add rules: AreStateless, BeStateless
1 parent 1c60a5b commit 97da451

11 files changed

Lines changed: 145 additions & 12 deletions

File tree

src/NetArchTest.Rules/Assemblies/TypeContainer.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ internal sealed class TypeContainer : IType
1313
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
1414
private readonly Lazy<Type> _reflactionType;
1515
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
16-
private readonly Lazy<string> _filePath;
16+
private readonly Lazy<string> _sourceFilePath;
1717

1818

1919
internal TypeContainer(TypeDefinition monoTypeDefinition, string explanation)
@@ -30,7 +30,7 @@ internal TypeContainer(TypeDefinition monoTypeDefinition, string explanation)
3030
}
3131
return null;
3232
});
33-
_filePath = new Lazy<string>(() => _monoTypeDefinition.GetFilePath());
33+
_sourceFilePath = new Lazy<string>(() => _monoTypeDefinition.GetFilePath());
3434
Explanation = explanation;
3535
}
3636

@@ -39,7 +39,7 @@ internal TypeContainer(TypeDefinition monoTypeDefinition, string explanation)
3939
public string FullName => _monoTypeDefinition.FullName;
4040
public string Name => _monoTypeDefinition.Name;
4141
public string Explanation { get; }
42-
public string FilePath => _filePath.Value;
42+
public string SourceFilePath => _sourceFilePath.Value;
4343

4444

4545
public static implicit operator System.Type(TypeContainer type)

src/NetArchTest.Rules/Assemblies/TypeSpec.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace NetArchTest.Assemblies
77
[DebuggerDisplay("TypeSpec: {FullName}")]
88
internal sealed class TypeSpec
99
{
10-
private readonly Lazy<string> _filePath;
10+
private readonly Lazy<string> _sourceFilePath;
1111

1212
public TypeDefinition Definition { get; }
1313
public string FullName => Definition.FullName;
@@ -16,13 +16,13 @@ internal sealed class TypeSpec
1616
// Can be use by any function
1717
internal bool IsPassing { get; set; }
1818
public string Explanation { get; set; }
19-
public string FilePath => _filePath.Value;
19+
public string SourceFilePath => _sourceFilePath.Value;
2020

2121

2222
public TypeSpec(TypeDefinition definition)
2323
{
2424
Definition = definition;
25-
_filePath = new Lazy<string>(() => Definition.GetFilePath());
25+
_sourceFilePath = new Lazy<string>(() => Definition.GetFilePath());
2626
}
2727

2828

src/NetArchTest.Rules/Condition_Special.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,17 @@ public ConditionList BeImmutableExternally()
3434
return CreateConditionList();
3535
}
3636

37+
38+
/// <summary>
39+
/// Selects types that are stateless, they do not have instance state
40+
/// </summary>
41+
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
42+
public ConditionList BeStateless()
43+
{
44+
AddFunctionCall(x => FunctionDelegates.BeStateless(x, true));
45+
return CreateConditionList();
46+
}
47+
3748
/// <summary>
3849
/// Selects types according to whether they have nullable members.
3950
/// </summary>

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

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ private static IEnumerable<TypeDefinition> EnumerateBaseClasses(this TypeDefinit
6666
/// <param name="typeDefinition">The type definition to convert.</param>
6767
/// <returns>The equivalent <see cref="Type"/> object instance.</returns>
6868
public static Type ToType(this TypeDefinition typeDefinition)
69-
{
69+
{
7070
var fullName = RuntimeNameToReflectionName(typeDefinition.FullName);
7171
return Type.GetType(string.Concat(fullName, ", ", typeDefinition.Module.Assembly.FullName), true);
7272
}
@@ -105,7 +105,7 @@ public static bool IsImmutableExternally(this TypeDefinition typeDefinition)
105105
}
106106

107107

108-
108+
109109
public static bool OnlyHasNullableMembers(this TypeDefinition typeDefinition)
110110
{
111111
var propertiesAreNullable = typeDefinition.Properties.All(p => p.IsNullable());
@@ -159,7 +159,7 @@ public static string GetFullName(this TypeDefinition typeDefinition)
159159
return typeDefinition.FullName.RemoveGenericPart();
160160
}
161161

162-
162+
163163

164164

165165
public static bool IsDelegate(this TypeDefinition typeDefinition)
@@ -174,7 +174,7 @@ public static bool IsStruct(this TypeDefinition typeDefinition)
174174

175175

176176
public static string GetFilePath(this TypeDefinition typeDefinition)
177-
{
177+
{
178178
if (typeDefinition.HasMethods)
179179
{
180180
foreach (var method in typeDefinition.Methods)
@@ -190,5 +190,22 @@ public static string GetFilePath(this TypeDefinition typeDefinition)
190190
}
191191
return null;
192192
}
193+
194+
public static bool IsStateless(this TypeDefinition type)
195+
{
196+
// Check if the type has any instance fields
197+
if (type.HasFields)
198+
{
199+
foreach (var field in type.Fields)
200+
{
201+
// If the field is not static, the type is not stateless
202+
if (!field.IsStatic)
203+
{
204+
return false;
205+
}
206+
}
207+
}
208+
return true;
209+
}
193210
}
194211
}

src/NetArchTest.Rules/Functions/FunctionDelegates_Special.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,18 @@ internal static IEnumerable<TypeSpec> OnlyHaveNonNullableMembers(IEnumerable<Typ
5454
return input.Where(c => !c.Definition.OnlyHasNonNullableMembers());
5555
}
5656
}
57+
58+
59+
internal static IEnumerable<TypeSpec> BeStateless(IEnumerable<TypeSpec> input, bool condition)
60+
{
61+
if (condition)
62+
{
63+
return input.Where(c => c.Definition.IsStateless());
64+
}
65+
else
66+
{
67+
return input.Where(c => !c.Definition.IsStateless());
68+
}
69+
}
5770
}
5871
}

src/NetArchTest.Rules/IType.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,6 @@ public interface IType
3737
/// <remarks>
3838
/// This property may be null if assembly debug symbols (PDB) were not loaded correctly or given type does not have any instructions inside.
3939
/// </remarks>
40-
string FilePath { get; }
40+
string SourceFilePath { get; }
4141
}
4242
}

src/NetArchTest.Rules/Predicate_Special.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@ public PredicateList AreImmutableExternally()
3434
return CreatePredicateList();
3535
}
3636

37+
/// <summary>
38+
/// Selects types that are stateless, they do not have instance state
39+
/// </summary>
40+
/// <returns>An updated set of conditions that can be applied to a list of types.</returns>
41+
public PredicateList AreStateless()
42+
{
43+
AddFunctionCall(x => FunctionDelegates.BeStateless(x, true));
44+
return CreatePredicateList();
45+
}
46+
3747
/// <summary>
3848
/// Selects types that have only nullable members.
3949
/// </summary>

test/NetArchTest.Rules.UnitTests/ConditionTests_Special.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
using System.Reflection;
1+
using System.Linq;
2+
using System.Reflection;
23
using NetArchTest.Rules;
34
using NetArchTest.TestStructure.NameMatching.Namespace1;
5+
using NetArchTest.TestStructure.Stateless;
46
using Xunit;
7+
using static NetArchTest.Utils;
58

69
namespace NetArchTest.UnitTests
710
{
@@ -54,6 +57,22 @@ public void BeImmutableExternally()
5457
Assert.True(result.IsSuccessful);
5558
}
5659

60+
61+
[Fact(DisplayName = "AreStateless")]
62+
public void AreStateless()
63+
{
64+
var result = Types
65+
.InAssembly(Assembly.GetAssembly(typeof(StatelessClass_StaticField)))
66+
.That()
67+
.ResideInNamespace(namespaceof<StatelessClass_StaticField>())
68+
.And()
69+
.HaveNameStartingWith("Stateless")
70+
.Should()
71+
.BeStateless().GetResult();
72+
73+
Assert.True(result.IsSuccessful);
74+
}
75+
5776
[Fact(DisplayName = "OnlyHaveNullableMembers")]
5877
public void OnlyHaveNullableMembers()
5978
{

test/NetArchTest.Rules.UnitTests/PredicateTests_Special.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
using NetArchTest.TestStructure.Mutability;
66
using NetArchTest.TestStructure.NameMatching.Namespace1;
77
using NetArchTest.TestStructure.Nullable;
8+
using NetArchTest.TestStructure.Stateless;
89
using Xunit;
10+
using static NetArchTest.Utils;
911

1012
namespace NetArchTest.UnitTests
1113
{
@@ -70,6 +72,23 @@ public void AreImmutableExternally()
7072
Assert.Contains<Type>(typeof(MutableClass_PrivateField), result);
7173
}
7274

75+
[Fact(DisplayName = "AreStateless")]
76+
public void AreStateless()
77+
{
78+
var result = Types
79+
.InAssembly(Assembly.GetAssembly(typeof(StatelessClass_StaticField)))
80+
.That()
81+
.ResideInNamespace(namespaceof<StatelessClass_StaticField>())
82+
.And()
83+
.AreStateless().GetReflectionTypes();
84+
85+
Assert.Equal(4, result.Count());
86+
Assert.Contains<Type>(typeof(StatelessClass_StaticField), result);
87+
Assert.Contains<Type>(typeof(StatelessClass_ConstField), result);
88+
Assert.Contains<Type>(typeof(StatelessClass_StaticReadonlyField), result);
89+
Assert.Contains<Type>(typeof(StatelessClass_Prop), result);
90+
}
91+
7392
[Fact(DisplayName = "OnlyHaveNullableMembers")]
7493
public void OnlyHaveNullableMembers()
7594
{
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
namespace NetArchTest.TestStructure.Stateless
2+
{
3+
internal class StatefulClass_Field
4+
{
5+
public int field;
6+
}
7+
internal record class StatefulRecordClass(int Prop);
8+
9+
internal record struct StatefulRecordStruct(int Prop);
10+
11+
internal class StatefulClass_Prop
12+
{
13+
public int Prop { get; set; }
14+
}
15+
16+
internal class StatefulClass_ReadonlyField
17+
{
18+
public readonly int field = 7;
19+
}
20+
}

0 commit comments

Comments
 (0)