diff --git a/ArchUnitNET/Domain/Extensions/TypeExtensions.cs b/ArchUnitNET/Domain/Extensions/TypeExtensions.cs index 9cfb4260b..cebf88639 100644 --- a/ArchUnitNET/Domain/Extensions/TypeExtensions.cs +++ b/ArchUnitNET/Domain/Extensions/TypeExtensions.cs @@ -249,6 +249,16 @@ public static bool ResidesInNamespaceMatching(this IType e, string pattern) return e.Namespace != null && e.Namespace.FullNameMatches(pattern); } + public static bool ResidesInNamespaceOrBelow(this IType e, string fullName) + { + return e.Namespace != null + && fullName != null + && ( + e.Namespace.FullNameEquals(fullName) + || e.Namespace.FullNameStartsWith(fullName + ".") + ); + } + public static bool ResidesInAssembly(this IType e, string fullName) { return e.Assembly != null && e.Assembly.FullNameEquals(fullName); diff --git a/ArchUnitNET/Fluent/Syntax/Elements/Types/AddTypeCondition.cs b/ArchUnitNET/Fluent/Syntax/Elements/Types/AddTypeCondition.cs index 269341315..a29ca5443 100644 --- a/ArchUnitNET/Fluent/Syntax/Elements/Types/AddTypeCondition.cs +++ b/ArchUnitNET/Fluent/Syntax/Elements/Types/AddTypeCondition.cs @@ -50,6 +50,7 @@ internal AddTypeCondition(IArchRuleCreator ruleCreator) public TNextElement ResideInNamespace(string fullName) => CreateNextElement(TypeConditionsDefinition.ResideInNamespace(fullName)); public TNextElement ResideInNamespaceMatching(string pattern) => CreateNextElement(TypeConditionsDefinition.ResideInNamespaceMatching(pattern)); + public TNextElement ResideInNamespaceOrBelow(string fullName) => CreateNextElement(TypeConditionsDefinition.ResideInNamespaceOrBelow(fullName)); public TNextElement ResideInAssembly(string fullName) => CreateNextElement(TypeConditionsDefinition.ResideInAssembly(fullName)); public TNextElement ResideInAssemblyMatching(string pattern) => CreateNextElement(TypeConditionsDefinition.ResideInAssemblyMatching(pattern)); @@ -105,6 +106,7 @@ internal AddTypeCondition(IArchRuleCreator ruleCreator) public TNextElement NotResideInNamespace(string fullName) => CreateNextElement(TypeConditionsDefinition.NotResideInNamespace(fullName)); public TNextElement NotResideInNamespaceMatching(string pattern) => CreateNextElement(TypeConditionsDefinition.NotResideInNamespaceMatching(pattern)); + public TNextElement NotResideInNamespaceOrBelow(string fullName) => CreateNextElement(TypeConditionsDefinition.NotResideInNamespaceOrBelow(fullName)); public TNextElement NotResideInAssembly(string fullName) => CreateNextElement(TypeConditionsDefinition.NotResideInAssembly(fullName)); public TNextElement NotResideInAssemblyMatching(string pattern) => CreateNextElement(TypeConditionsDefinition.NotResideInAssemblyMatching(pattern)); diff --git a/ArchUnitNET/Fluent/Syntax/Elements/Types/AddTypePredicate.cs b/ArchUnitNET/Fluent/Syntax/Elements/Types/AddTypePredicate.cs index ed8d25cbd..63a8ebc92 100644 --- a/ArchUnitNET/Fluent/Syntax/Elements/Types/AddTypePredicate.cs +++ b/ArchUnitNET/Fluent/Syntax/Elements/Types/AddTypePredicate.cs @@ -48,6 +48,7 @@ internal AddTypePredicate(IArchRuleCreator archRuleCreator) public TNextElement ResideInNamespace(string fullName) => CreateNextElement(TypePredicatesDefinition.ResideInNamespace(fullName)); public TNextElement ResideInNamespaceMatching(string pattern) => CreateNextElement(TypePredicatesDefinition.ResideInNamespaceMatching(pattern)); + public TNextElement ResideInNamespaceOrBelow(string fullName) => CreateNextElement(TypePredicatesDefinition.ResideInNamespaceOrBelow(fullName)); public TNextElement ResideInAssembly(string fullName) => CreateNextElement(TypePredicatesDefinition.ResideInAssembly(fullName)); public TNextElement ResideInAssemblyMatching(string pattern) => CreateNextElement(TypePredicatesDefinition.ResideInAssemblyMatching(pattern)); @@ -97,6 +98,7 @@ internal AddTypePredicate(IArchRuleCreator archRuleCreator) public TNextElement DoNotResideInNamespace(string fullName) => CreateNextElement(TypePredicatesDefinition.DoNotResideInNamespace(fullName)); public TNextElement DoNotResideInNamespaceMatching(string pattern) => CreateNextElement(TypePredicatesDefinition.DoNotResideInNamespaceMatching(pattern)); + public TNextElement DoNotResideInNamespaceOrBelow(string fullName) => CreateNextElement(TypePredicatesDefinition.DoNotResideInNamespaceOrBelow(fullName)); public TNextElement DoNotResideInAssembly(string fullName) => CreateNextElement(TypePredicatesDefinition.DoNotResideInAssembly(fullName)); public TNextElement DoNotResideInAssemblyMatching(string pattern) => CreateNextElement(TypePredicatesDefinition.DoNotResideInAssemblyMatching(pattern)); diff --git a/ArchUnitNET/Fluent/Syntax/Elements/Types/IAddTypeCondition.cs b/ArchUnitNET/Fluent/Syntax/Elements/Types/IAddTypeCondition.cs index d11cdce31..e0a28f7fb 100644 --- a/ArchUnitNET/Fluent/Syntax/Elements/Types/IAddTypeCondition.cs +++ b/ArchUnitNET/Fluent/Syntax/Elements/Types/IAddTypeCondition.cs @@ -38,7 +38,7 @@ public interface IAddTypeCondition TNextElement ResideInNamespace(string fullName); TNextElement ResideInNamespaceMatching(string pattern); - + TNextElement ResideInNamespaceOrBelow(string fullName); TNextElement ResideInAssembly(string fullName); TNextElement ResideInAssemblyMatching(string pattern); TNextElement ResideInAssembly(Assembly assembly, params Assembly[] moreAssemblies); @@ -84,6 +84,7 @@ params Domain.Assembly[] moreAssemblies TNextElement NotResideInNamespace(string fullName); TNextElement NotResideInNamespaceMatching(string pattern); + TNextElement NotResideInNamespaceOrBelow(string fullName); TNextElement NotResideInAssemblyMatching(string pattern); TNextElement NotResideInAssembly(Assembly assembly, params Assembly[] moreAssemblies); diff --git a/ArchUnitNET/Fluent/Syntax/Elements/Types/IAddTypePredicate.cs b/ArchUnitNET/Fluent/Syntax/Elements/Types/IAddTypePredicate.cs index d16e8d271..fe3188391 100644 --- a/ArchUnitNET/Fluent/Syntax/Elements/Types/IAddTypePredicate.cs +++ b/ArchUnitNET/Fluent/Syntax/Elements/Types/IAddTypePredicate.cs @@ -34,6 +34,7 @@ public interface IAddTypePredicate TNextElement ResideInNamespace(string fullName); TNextElement ResideInNamespaceMatching(string pattern); + TNextElement ResideInNamespaceOrBelow(string fullName); TNextElement ResideInAssembly(string fullName); TNextElement ResideInAssemblyMatching(string pattern); @@ -74,6 +75,7 @@ params Domain.Assembly[] moreAssemblies TNextElement DoNotImplementAnyInterfaces(IObjectProvider interfaces); TNextElement DoNotResideInNamespace(string fullName); + TNextElement DoNotResideInNamespaceOrBelow(string fullName); TNextElement DoNotResideInAssembly(string fullName); TNextElement DoNotResideInAssemblyMatching(string pattern); diff --git a/ArchUnitNET/Fluent/Syntax/Elements/Types/TypeConditionsDefinition.cs b/ArchUnitNET/Fluent/Syntax/Elements/Types/TypeConditionsDefinition.cs index fa5bb5ff0..2bca2a4b0 100644 --- a/ArchUnitNET/Fluent/Syntax/Elements/Types/TypeConditionsDefinition.cs +++ b/ArchUnitNET/Fluent/Syntax/Elements/Types/TypeConditionsDefinition.cs @@ -314,6 +314,15 @@ public static IOrderedCondition ResideInNamespaceMatching(string patt ); } + public static IOrderedCondition ResideInNamespaceOrBelow(string fullName) + { + return new SimpleCondition( + type => type.ResidesInNamespaceOrBelow(fullName), + obj => ResideInNamespaceFailDescription(obj), + "reside in namespace with full name \"" + fullName + "\" or below" + ); + } + public static IOrderedCondition ResideInAssembly(string fullName) { return new SimpleCondition( @@ -720,6 +729,15 @@ public static IOrderedCondition NotResideInNamespaceMatching(string p ); } + public static IOrderedCondition NotResideInNamespaceOrBelow(string fullName) + { + return new SimpleCondition( + type => !type.ResidesInNamespaceOrBelow(fullName), + obj => ResideInNamespaceFailDescription(obj), + "not reside in namespace with full name \"" + fullName + "\" or below" + ); + } + public static IOrderedCondition NotResideInAssembly(string fullName) { return new SimpleCondition( diff --git a/ArchUnitNET/Fluent/Syntax/Elements/Types/TypePredicatesDefinition.cs b/ArchUnitNET/Fluent/Syntax/Elements/Types/TypePredicatesDefinition.cs index d25cd509a..0e01a9217 100644 --- a/ArchUnitNET/Fluent/Syntax/Elements/Types/TypePredicatesDefinition.cs +++ b/ArchUnitNET/Fluent/Syntax/Elements/Types/TypePredicatesDefinition.cs @@ -161,6 +161,14 @@ public static IPredicate ResideInNamespaceMatching(string pattern) ); } + public static IPredicate ResideInNamespaceOrBelow(string fullName) + { + return new SimplePredicate( + type => type.ResidesInNamespaceOrBelow(fullName), + "reside in namespace with full name \"" + fullName + "\" or below" + ); + } + public static IPredicate ResideInAssembly(string fullName) { return new SimplePredicate( @@ -372,6 +380,14 @@ public static IPredicate DoNotResideInNamespaceMatching(string pattern) ); } + public static IPredicate DoNotResideInNamespaceOrBelow(string fullName) + { + return new SimplePredicate( + type => !type.ResidesInNamespaceOrBelow(fullName), + "do not reside in namespace with full name \"" + fullName + "\" or below" + ); + } + public static IPredicate DoNotResideInAssembly(string fullName) { return new SimplePredicate( diff --git a/ArchUnitNETTests/Domain/Extensions/TypeExtensionTests.cs b/ArchUnitNETTests/Domain/Extensions/TypeExtensionTests.cs index 8747990a9..a8dd70f48 100644 --- a/ArchUnitNETTests/Domain/Extensions/TypeExtensionTests.cs +++ b/ArchUnitNETTests/Domain/Extensions/TypeExtensionTests.cs @@ -269,6 +269,28 @@ public void NamespaceMatchAsExpected() Assert.False(_exampleAttribute.ResidesInNamespace(string.Empty)); } + [Fact] + public void NamespaceOrBelowMatchAsExpected() + { + Assert.True(_exampleAttribute.ResidesInNamespaceOrBelow(ExpectedAttributeNamespace)); + Assert.True(_exampleAttribute.ResidesInNamespaceOrBelow(ParentAttributeNamespace)); + Assert.True(_exampleAttribute.ResidesInNamespaceOrBelow("ArchUnitNETTests")); + Assert.True( + _exampleAttribute.ResidesInNamespaceOrBelow(ParentAttributeNamespace.ToUpper()) + ); + Assert.False( + _exampleAttribute.ResidesInNamespaceOrBelow("ArchUnitNETTests.Domain.Depend") + ); + Assert.False( + _exampleAttribute.ResidesInNamespaceOrBelow(ParentAttributeNamespace + ".") + ); + Assert.False( + _exampleAttribute.ResidesInNamespaceOrBelow(ExpectedAttributeNamespace + ".Sub") + ); + Assert.False(_exampleAttribute.ResidesInNamespaceOrBelow(string.Empty)); + Assert.False(_exampleAttribute.ResidesInNamespaceOrBelow(null)); + } + [Fact] public void NameStartsWithTest() { diff --git a/ArchUnitNETTests/Domain/FunctionPointerTests.cs b/ArchUnitNETTests/Domain/FunctionPointerTests.cs index 6be5cbd6e..aa24dfe7f 100644 --- a/ArchUnitNETTests/Domain/FunctionPointerTests.cs +++ b/ArchUnitNETTests/Domain/FunctionPointerTests.cs @@ -38,6 +38,7 @@ public void NamespaceExtensionsReturnFalseForTypeWithoutNamespace() { Assert.False(_functionPointer.ResidesInNamespace("System")); Assert.False(_functionPointer.ResidesInNamespaceMatching(".*")); + Assert.False(_functionPointer.ResidesInNamespaceOrBelow("System")); } [Fact] diff --git a/ArchUnitNETTests/Fluent/Syntax/Elements/Snapshots/TypeSyntaxElementsTests.NotResideInNamespaceOrBelowTest.verified.txt b/ArchUnitNETTests/Fluent/Syntax/Elements/Snapshots/TypeSyntaxElementsTests.NotResideInNamespaceOrBelowTest.verified.txt new file mode 100644 index 000000000..e4ff0a1a3 --- /dev/null +++ b/ArchUnitNETTests/Fluent/Syntax/Elements/Snapshots/TypeSyntaxElementsTests.NotResideInNamespaceOrBelowTest.verified.txt @@ -0,0 +1,52 @@ +===== No violations ===== + +----- Conditions ----- + +Query: Types that are "TypeNamespace.RegularClass" should not reside in namespace with full name "TypeName" or below +Result: True +Description: TypeNamespace.RegularClass passed +Message: +All Evaluations passed + +----- Predicates ----- + +Query: Types that are "TypeNamespace.RegularClass" should be Types that do not reside in namespace with full name "TypeName" or below +Result: True +Description: TypeNamespace.RegularClass passed +Message: +All Evaluations passed + +===== Violations ===== + +----- Conditions ----- + +Query: Types that are "TypeNamespace.RegularClass" should not reside in namespace with full name "TypeNamespace" or below +Result: False +Description: TypeNamespace.RegularClass does reside in TypeNamespace +Message: +"Types that are "TypeNamespace.RegularClass" should not reside in namespace with full name "TypeNamespace" or below" failed: + TypeNamespace.RegularClass does reside in TypeNamespace + + + +----- Predicates ----- + +Query: Types that are "TypeNamespace.RegularClass" should be Types that do not reside in namespace with full name "TypeNamespace" or below +Result: False +Description: TypeNamespace.RegularClass is not Types that do not reside in namespace with full name "TypeNamespace" or below +Message: +"Types that are "TypeNamespace.RegularClass" should be Types that do not reside in namespace with full name "TypeNamespace" or below" failed: + TypeNamespace.RegularClass is not Types that do not reside in namespace with full name "TypeNamespace" or below + + + +===== Multiple inputs ===== + +Query: Types that are "TypeNamespace.RegularClass" or "TypeNamespace.OtherRegularClass" should not reside in namespace with full name "TypeName" or below +Result: True +Description: TypeNamespace.OtherRegularClass passed +Result: True +Description: TypeNamespace.RegularClass passed +Message: +All Evaluations passed + diff --git a/ArchUnitNETTests/Fluent/Syntax/Elements/Snapshots/TypeSyntaxElementsTests.ResideInNamespaceOrBelowTest.verified.txt b/ArchUnitNETTests/Fluent/Syntax/Elements/Snapshots/TypeSyntaxElementsTests.ResideInNamespaceOrBelowTest.verified.txt new file mode 100644 index 000000000..2ee1c3ead --- /dev/null +++ b/ArchUnitNETTests/Fluent/Syntax/Elements/Snapshots/TypeSyntaxElementsTests.ResideInNamespaceOrBelowTest.verified.txt @@ -0,0 +1,52 @@ +===== No violations ===== + +----- Conditions ----- + +Query: Types that are "TypeNamespace.RegularClass" should reside in namespace with full name "TypeNamespace" or below +Result: True +Description: TypeNamespace.RegularClass passed +Message: +All Evaluations passed + +----- Predicates ----- + +Query: Types that are "TypeNamespace.RegularClass" should be Types that reside in namespace with full name "TypeNamespace" or below +Result: True +Description: TypeNamespace.RegularClass passed +Message: +All Evaluations passed + +===== Violations ===== + +----- Conditions ----- + +Query: Types that are "TypeNamespace.RegularClass" should reside in namespace with full name "TypeName" or below +Result: False +Description: TypeNamespace.RegularClass does reside in TypeNamespace +Message: +"Types that are "TypeNamespace.RegularClass" should reside in namespace with full name "TypeName" or below" failed: + TypeNamespace.RegularClass does reside in TypeNamespace + + + +----- Predicates ----- + +Query: Types that are "TypeNamespace.RegularClass" should be Types that reside in namespace with full name "TypeName" or below +Result: False +Description: TypeNamespace.RegularClass is not Types that reside in namespace with full name "TypeName" or below +Message: +"Types that are "TypeNamespace.RegularClass" should be Types that reside in namespace with full name "TypeName" or below" failed: + TypeNamespace.RegularClass is not Types that reside in namespace with full name "TypeName" or below + + + +===== Multiple inputs ===== + +Query: Types that are "TypeNamespace.RegularClass" or "TypeNamespace.OtherRegularClass" should reside in namespace with full name "TypeNamespace" or below +Result: True +Description: TypeNamespace.OtherRegularClass passed +Result: True +Description: TypeNamespace.RegularClass passed +Message: +All Evaluations passed + diff --git a/ArchUnitNETTests/Fluent/Syntax/Elements/TypeSyntaxElementsTests.cs b/ArchUnitNETTests/Fluent/Syntax/Elements/TypeSyntaxElementsTests.cs index 8719c9475..b705218f6 100644 --- a/ArchUnitNETTests/Fluent/Syntax/Elements/TypeSyntaxElementsTests.cs +++ b/ArchUnitNETTests/Fluent/Syntax/Elements/TypeSyntaxElementsTests.cs @@ -1031,6 +1031,64 @@ public async Task NotResideInNamespaceMatchingTest() await helper.AssertSnapshotMatches(); } + [Fact] + public async Task ResideInNamespaceOrBelowTest() + { + var helper = new TypeAssemblyTestHelper(); + + helper.AddSnapshotHeader("No violations"); + var should = Types().That().Are(helper.RegularClass).Should(); + + helper.AddSnapshotSubHeader("Conditions"); + should.ResideInNamespaceOrBelow("TypeNamespace").AssertNoViolations(helper); + + helper.AddSnapshotSubHeader("Predicates"); + should.Be(Types().That().ResideInNamespaceOrBelow("TypeNamespace")).AssertNoViolations(helper); + + helper.AddSnapshotHeader("Violations"); + should = Types().That().Are(helper.RegularClass).Should(); + + helper.AddSnapshotSubHeader("Conditions"); + should.ResideInNamespaceOrBelow("TypeName").AssertOnlyViolations(helper); + + helper.AddSnapshotSubHeader("Predicates"); + should.Be(Types().That().ResideInNamespaceOrBelow("TypeName")).AssertOnlyViolations(helper); + + helper.AddSnapshotHeader("Multiple inputs"); + Types().That().Are(helper.RegularClass, helper.OtherRegularClass).Should().ResideInNamespaceOrBelow("TypeNamespace").AssertNoViolations(helper); + + await helper.AssertSnapshotMatches(); + } + + [Fact] + public async Task NotResideInNamespaceOrBelowTest() + { + var helper = new TypeAssemblyTestHelper(); + + helper.AddSnapshotHeader("No violations"); + var should = Types().That().Are(helper.RegularClass).Should(); + + helper.AddSnapshotSubHeader("Conditions"); + should.NotResideInNamespaceOrBelow("TypeName").AssertNoViolations(helper); + + helper.AddSnapshotSubHeader("Predicates"); + should.Be(Types().That().DoNotResideInNamespaceOrBelow("TypeName")).AssertNoViolations(helper); + + helper.AddSnapshotHeader("Violations"); + should = Types().That().Are(helper.RegularClass).Should(); + + helper.AddSnapshotSubHeader("Conditions"); + should.NotResideInNamespaceOrBelow("TypeNamespace").AssertOnlyViolations(helper); + + helper.AddSnapshotSubHeader("Predicates"); + should.Be(Types().That().DoNotResideInNamespaceOrBelow("TypeNamespace")).AssertOnlyViolations(helper); + + helper.AddSnapshotHeader("Multiple inputs"); + Types().That().Are(helper.RegularClass, helper.OtherRegularClass).Should().NotResideInNamespaceOrBelow("TypeName").AssertNoViolations(helper); + + await helper.AssertSnapshotMatches(); + } + [Fact] public async Task ResideInAssemblyTest() {