Skip to content

Commit e64d5f7

Browse files
committed
feat: Add HavePrivateParameterlessConstructor and NotHavePrivateParameterlessConstructor conditions
Add support for checking if classes have private parameterless constructors, which is essential for enforcing Domain-Driven Design patterns and ORM requirements. Signed-off-by: Ahmed Mohamed El Ahmar <ahmedmohamedelahmar@gmail.com>
1 parent 929d0cb commit e64d5f7

File tree

5 files changed

+163
-1
lines changed

5 files changed

+163
-1
lines changed

ArchUnitNET/Fluent/Syntax/Elements/Types/Classes/ClassConditionsDefinition.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Linq;
22
using ArchUnitNET.Domain;
3+
using ArchUnitNET.Domain.Extensions;
34
using ArchUnitNET.Fluent.Conditions;
45

56
namespace ArchUnitNET.Fluent.Syntax.Elements.Types.Classes
@@ -43,6 +44,16 @@ public static ICondition<Class> BeImmutable()
4344
"is not immutable"
4445
);
4546
}
47+
48+
public static ICondition<Class> HavePrivateParameterlessConstructor()
49+
{
50+
return new SimpleCondition<Class>(
51+
cls => (cls.IsAbstract.HasValue && cls.IsAbstract.Value) || cls.GetConstructors().Any(c =>
52+
c.Visibility == Visibility.Private && !c.Parameters.Any()),
53+
"have private parameterless constructor",
54+
"does not have private parameterless constructor"
55+
);
56+
}
4657

4758
//Negations
4859

@@ -81,5 +92,15 @@ public static ICondition<Class> NotBeImmutable()
8192
"is immutable"
8293
);
8394
}
95+
96+
public static ICondition<Class> NotHavePrivateParameterlessConstructor()
97+
{
98+
return new SimpleCondition<Class>(
99+
cls => (cls.IsAbstract.HasValue && cls.IsAbstract.Value) || !cls.GetConstructors().Any(c =>
100+
c.Visibility == Visibility.Private && !c.Parameters.Any()),
101+
"not have private parameterless constructor",
102+
"has private parameterless constructor"
103+
);
104+
}
84105
}
85106
}

ArchUnitNET/Fluent/Syntax/Elements/Types/Classes/ClassesShould.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ public ClassesShouldConjunction BeImmutable()
3333
return new ClassesShouldConjunction(_ruleCreator);
3434
}
3535

36+
public ClassesShouldConjunction HavePrivateParameterlessConstructor()
37+
{
38+
_ruleCreator.AddCondition(ClassConditionsDefinition.HavePrivateParameterlessConstructor());
39+
return new ClassesShouldConjunction(_ruleCreator);
40+
}
41+
3642
//Negations
3743

3844
public ClassesShouldConjunction NotBeAbstract()
@@ -58,5 +64,11 @@ public ClassesShouldConjunction NotBeImmutable()
5864
_ruleCreator.AddCondition(ClassConditionsDefinition.NotBeImmutable());
5965
return new ClassesShouldConjunction(_ruleCreator);
6066
}
67+
68+
public ClassesShouldConjunction NotHavePrivateParameterlessConstructor()
69+
{
70+
_ruleCreator.AddCondition(ClassConditionsDefinition.NotHavePrivateParameterlessConstructor());
71+
return new ClassesShouldConjunction(_ruleCreator);
72+
}
6173
}
6274
}

ArchUnitNETTests/ArchUnitNETTests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<TargetFramework>net9.0</TargetFramework>
4-
<LangVersion>latest</LangVersion>
4+
<LangVersion>default</LangVersion>
55
<Company>TNG Technology Consulting GmbH</Company>
66
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
77
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
using ArchUnitNET.Domain;
2+
using ArchUnitNET.Loader;
3+
using TestAssembly.Domain.Entities;
4+
using Xunit;
5+
using static ArchUnitNET.Fluent.ArchRuleDefinition;
6+
7+
namespace ArchUnitNETTests.Fluent.Syntax.Elements;
8+
9+
public class ClassPrivateConstructorConditionTests
10+
{
11+
private static readonly Architecture Architecture =
12+
new ArchLoader().LoadAssembly(typeof(ClassWithPrivateParameterlessConstructor).Assembly).Build();
13+
14+
[Fact]
15+
public void HavePrivateParameterlessConstructor_ClassWithPrivateParameterlessConstructor_DoesNotViolate()
16+
{
17+
var rule = Classes()
18+
.That().HaveName(nameof(ClassWithPrivateParameterlessConstructor))
19+
.Should().HavePrivateParameterlessConstructor();
20+
21+
Assert.True(rule.HasNoViolations(Architecture));
22+
}
23+
24+
[Fact]
25+
public void HavePrivateParameterlessConstructor_ClassWithoutPrivateParameterlessConstructor_Violates()
26+
{
27+
var rule = Classes()
28+
.That().HaveName(nameof(ClassWithoutPrivateParameterlessConstructor))
29+
.Should().HavePrivateParameterlessConstructor();
30+
31+
Assert.False(rule.HasNoViolations(Architecture));
32+
}
33+
34+
[Fact]
35+
public void NotHavePrivateParameterlessConstructor_ClassWithoutPrivateParameterlessConstructor_DoesNotViolate()
36+
{
37+
var rule = Classes()
38+
.That().HaveName(nameof(ClassWithoutPrivateParameterlessConstructor))
39+
.Should().NotHavePrivateParameterlessConstructor();
40+
41+
Assert.True(rule.HasNoViolations(Architecture));
42+
}
43+
44+
[Fact]
45+
public void NotHavePrivateParameterlessConstructor_ClassWithPrivateParameterlessConstructor_Violates()
46+
{
47+
var rule = Classes()
48+
.That().HaveName(nameof(ClassWithPrivateParameterlessConstructor))
49+
.Should().NotHavePrivateParameterlessConstructor();
50+
51+
Assert.False(rule.HasNoViolations(Architecture));
52+
}
53+
54+
[Fact]
55+
public void HavePrivateParameterlessConstructor_AbstractClass_DoesNotViolate()
56+
{
57+
var rule = Classes()
58+
.That().AreAbstract()
59+
.Should().HavePrivateParameterlessConstructor();
60+
61+
Assert.True(rule.HasNoViolations(Architecture));
62+
}
63+
64+
[Fact]
65+
public void HavePrivateParameterlessConstructor_ClassWithOnlyParameterizedConstructors_Violates()
66+
{
67+
var rule = Classes()
68+
.That().HaveName(nameof(ClassWithOnlyParameterizedConstructors))
69+
.Should().HavePrivateParameterlessConstructor();
70+
71+
Assert.False(rule.HasNoViolations(Architecture));
72+
}
73+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
namespace TestAssembly.Domain.Entities;
2+
3+
public class ClassWithPrivateParameterlessConstructor
4+
{
5+
private ClassWithPrivateParameterlessConstructor()
6+
{
7+
// Private parameterless constructor for ORM
8+
}
9+
10+
public ClassWithPrivateParameterlessConstructor(string name)
11+
{
12+
Name = name;
13+
}
14+
15+
public string Name { get; private set; }
16+
}
17+
18+
public class ClassWithoutPrivateParameterlessConstructor
19+
{
20+
public ClassWithoutPrivateParameterlessConstructor()
21+
{
22+
// Public parameterless constructor
23+
}
24+
25+
public ClassWithoutPrivateParameterlessConstructor(string name)
26+
{
27+
Name = name;
28+
}
29+
30+
public string Name { get; private set; }
31+
}
32+
33+
public class ClassWithOnlyParameterizedConstructors
34+
{
35+
public ClassWithOnlyParameterizedConstructors(string name)
36+
{
37+
Name = name;
38+
}
39+
40+
public ClassWithOnlyParameterizedConstructors(int id, string name)
41+
{
42+
Id = id;
43+
Name = name;
44+
}
45+
46+
public int Id { get; private set; }
47+
public string Name { get; private set; }
48+
}
49+
50+
public abstract class AbstractClassBase
51+
{
52+
protected AbstractClassBase()
53+
{
54+
// Abstract classes should be excluded from checks
55+
}
56+
}

0 commit comments

Comments
 (0)