diff --git a/ArchUnitNET/Fluent/Syntax/Elements/Members/MethodMembers/IMethodMemberConditions.cs b/ArchUnitNET/Fluent/Syntax/Elements/Members/MethodMembers/IMethodMemberConditions.cs index c95b97342..014626cbe 100644 --- a/ArchUnitNET/Fluent/Syntax/Elements/Members/MethodMembers/IMethodMemberConditions.cs +++ b/ArchUnitNET/Fluent/Syntax/Elements/Members/MethodMembers/IMethodMemberConditions.cs @@ -28,11 +28,13 @@ public interface IMethodMemberConditions TReturnType HaveReturnType(IObjectProvider types); TReturnType HaveReturnType(Type type, params Type[] moreTypes); TReturnType HaveReturnType(IEnumerable types); + TReturnType HaveAnyParameters(); //Negations TReturnType BeNoConstructor(); TReturnType NotBeVirtual(); + TReturnType NotHaveAnyParameters(); TReturnType NotBeCalledBy(IType firstType, params IType[] moreTypes); TReturnType NotBeCalledBy(Type type, params Type[] moreTypes); diff --git a/ArchUnitNET/Fluent/Syntax/Elements/Members/MethodMembers/MethodMemberConditionsDefinition.cs b/ArchUnitNET/Fluent/Syntax/Elements/Members/MethodMembers/MethodMemberConditionsDefinition.cs index 096b92a7a..af70f98d6 100644 --- a/ArchUnitNET/Fluent/Syntax/Elements/Members/MethodMembers/MethodMemberConditionsDefinition.cs +++ b/ArchUnitNET/Fluent/Syntax/Elements/Members/MethodMembers/MethodMemberConditionsDefinition.cs @@ -1011,5 +1011,31 @@ bool Condition(MethodMember member) description ); } + + /// + /// Selects method members that have any parameters + /// + /// A condition that can be applied to method members + public static ICondition HaveAnyParameters() + { + return new SimpleCondition( + method => method.Parameters.Any(), + "have any parameters", + "does not have any parameters" + ); + } + + /// + /// Selects method members that do not have any parameters (parameterless) + /// + /// A condition that can be applied to method members + public static ICondition NotHaveAnyParameters() + { + return new SimpleCondition( + method => !method.Parameters.Any(), + "not have any parameters", + "has parameters" + ); + } } } diff --git a/ArchUnitNET/Fluent/Syntax/Elements/Members/MethodMembers/MethodMembersShould.cs b/ArchUnitNET/Fluent/Syntax/Elements/Members/MethodMembers/MethodMembersShould.cs index 30e0b3ef0..aad6b71be 100644 --- a/ArchUnitNET/Fluent/Syntax/Elements/Members/MethodMembers/MethodMembersShould.cs +++ b/ArchUnitNET/Fluent/Syntax/Elements/Members/MethodMembers/MethodMembersShould.cs @@ -297,5 +297,18 @@ public MethodMembersShouldConjunction NotHaveReturnType(IEnumerable types) _ruleCreator.AddCondition(MethodMemberConditionsDefinition.NotHaveReturnType(types)); return new MethodMembersShouldConjunction(_ruleCreator); } + + + public MethodMembersShouldConjunction HaveAnyParameters() + { + _ruleCreator.AddCondition(MethodMemberConditionsDefinition.HaveAnyParameters()); + return new MethodMembersShouldConjunction(_ruleCreator); + } + + public MethodMembersShouldConjunction NotHaveAnyParameters() + { + _ruleCreator.AddCondition(MethodMemberConditionsDefinition.NotHaveAnyParameters()); + return new MethodMembersShouldConjunction(_ruleCreator); + } } } diff --git a/ArchUnitNETTests/ArchUnitNETTests.csproj b/ArchUnitNETTests/ArchUnitNETTests.csproj index 5d0b87223..b8be3da53 100644 --- a/ArchUnitNETTests/ArchUnitNETTests.csproj +++ b/ArchUnitNETTests/ArchUnitNETTests.csproj @@ -1,7 +1,7 @@ net9.0 - latest + default TNG Technology Consulting GmbH true true diff --git a/ArchUnitNETTests/Fluent/Syntax/Elements/MethodParameterConditionTests.cs b/ArchUnitNETTests/Fluent/Syntax/Elements/MethodParameterConditionTests.cs new file mode 100644 index 000000000..29ef3b4e7 --- /dev/null +++ b/ArchUnitNETTests/Fluent/Syntax/Elements/MethodParameterConditionTests.cs @@ -0,0 +1,159 @@ +using System.Linq; +using ArchUnitNET.Domain; +using ArchUnitNET.Loader; +using TestAssembly.Domain.Methods; +using Xunit; +using static ArchUnitNET.Fluent.ArchRuleDefinition; + +namespace ArchUnitNETTests.Fluent.Syntax.Elements; + +public class MethodParameterConditionTests +{ + private static readonly Architecture Architecture = + new ArchLoader().LoadAssembly(typeof(ClassWithPrivateParameterlessConstructor).Assembly).Build(); + + [Fact] + public void HaveAnyParameters_MethodWithParameters_DoesNotViolate() + { + var rule = MethodMembers() + .That() + .AreDeclaredIn(Classes().That().HaveName(nameof(ClassWithMethods))) + .And() + .HaveName("MethodWithParameters(System.String,System.Int32)") + .Should() + .HaveAnyParameters(); + + Assert.True(rule.HasNoViolations(Architecture)); + } + + [Fact] + public void HaveAnyParameters_MethodWithoutParameters_Violates() + { + var rule = MethodMembers() + .That() + .AreDeclaredIn(Classes().That().HaveName(nameof(ClassWithMethods))) + .And() + .HaveName("MethodWithoutParameters()") + .Should() + .HaveAnyParameters(); + + Assert.False(rule.HasNoViolations(Architecture)); + + var evaluation = rule.Evaluate(Architecture); + var violations = evaluation.ToList(); + Assert.Single(violations); + Assert.Contains("does not have any parameters", violations.First().Description); + } + + [Fact] + public void NotHaveAnyParameters_MethodWithoutParameters_DoesNotViolate() + { + var rule = MethodMembers() + .That() + .AreDeclaredIn(Classes().That().HaveName(nameof(ClassWithMethods))) + .And().HaveName("PrivateMethodWithoutParameters()") + .Should().NotHaveAnyParameters(); + + Assert.True(rule.HasNoViolations(Architecture)); + } + + [Fact] + public void NotHaveAnyParameters_MethodWithParameters_Violates() + { + var rule = MethodMembers() + .That().HaveName("MethodWithParameters(System.String,System.Int32)") + .Should().NotHaveAnyParameters(); + + Assert.False(rule.HasNoViolations(Architecture)); + + var evaluation = rule.Evaluate(Architecture); + var violations = evaluation.ToList(); + Assert.Single(violations); + Assert.Contains("has parameters", violations.First().Description); + } + + [Fact] + public void HaveAnyParameters_ConstructorWithParameters_DoesNotViolate() + { + var rule = MethodMembers() + .That().AreDeclaredIn(Classes().That().HaveName(nameof(ClassWithOnlyParameterizedConstructors))) + .And().AreConstructors() + .Should().HaveAnyParameters(); + + Assert.True(rule.HasNoViolations(Architecture)); + } + + [Fact] + public void HaveAnyParameters_ParameterlessConstructor_Violates() + { + var rule = MethodMembers() + .That().AreDeclaredIn(Classes().That().HaveName(nameof(ClassWithPublicParameterlessConstructor))) + .And().AreConstructors() + .And().HaveName(".ctor()") + .Should().HaveAnyParameters(); + + Assert.False(rule.HasNoViolations(Architecture)); + } + + [Fact] + public void PrivateConstructorWithoutParameters_CompositeRule_DoesNotViolate() + { + var rule = MethodMembers() + .That().AreDeclaredIn(Classes().That().HaveName(nameof(ClassWithPrivateParameterlessConstructor))) + .And().AreConstructors() + .And().ArePrivate() + .Should().NotHaveAnyParameters(); + + Assert.True(rule.HasNoViolations(Architecture)); + } + + [Fact] + public void PublicConstructorWithParameters_CompositeRule_DoesNotViolate() + { + var rule = MethodMembers() + .That().AreDeclaredIn(Classes().That().HaveName(nameof(ClassWithPrivateParameterlessConstructor))) + .And().AreConstructors() + .And().ArePublic() + .Should().HaveAnyParameters(); + + Assert.True(rule.HasNoViolations(Architecture)); + } + + [Fact] + public void PrivateMethodWithoutParameters_DoesNotViolate() + { + var rule = MethodMembers() + .That() + .AreDeclaredIn(Classes().That().HaveName(nameof(ClassWithMethods))) + .And() + .HaveName("PrivateMethodWithoutParameters()") + .And() + .ArePrivate() + .Should() + .NotHaveAnyParameters(); + + Assert.True(rule.HasNoViolations(Architecture)); + } + + [Fact] + public void SpecificClass_AllConstructorsHaveParameters_DoesNotViolate() + { + var rule = MethodMembers() + .That().AreDeclaredIn(Classes().That().HaveName(nameof(ClassWithOnlyParameterizedConstructors))) + .And().AreConstructors() + .Should().HaveAnyParameters(); + + Assert.True(rule.HasNoViolations(Architecture)); + } + + [Fact] + public void SpecificClass_HasParameterlessConstructor_Violates() + { + var rule = MethodMembers() + .That().AreDeclaredIn(Classes().That().HaveName(nameof(ClassWithPublicParameterlessConstructor))) + .And().AreConstructors() + .Should().HaveAnyParameters(); + + Assert.False(rule.HasNoViolations(Architecture)); + } +} diff --git a/TestAssembly/Domain/Methods/MethodParameterTestClass.cs b/TestAssembly/Domain/Methods/MethodParameterTestClass.cs new file mode 100644 index 000000000..cb00091b4 --- /dev/null +++ b/TestAssembly/Domain/Methods/MethodParameterTestClass.cs @@ -0,0 +1,73 @@ +namespace TestAssembly.Domain.Methods; + +public class ClassWithPrivateParameterlessConstructor +{ + // Private parameterless constructor + private ClassWithPrivateParameterlessConstructor() + { + } + + // Public constructor with parameters + public ClassWithPrivateParameterlessConstructor(string value) + { + Value = value; + } + + public string Value { get; set; } +} + +public class ClassWithPublicParameterlessConstructor +{ + // Public parameterless constructor + public ClassWithPublicParameterlessConstructor() + { + } + + // Public constructor with parameters + public ClassWithPublicParameterlessConstructor(int number) + { + Number = number; + } + + public int Number { get; set; } +} + +public class ClassWithOnlyParameterizedConstructors +{ + // Only constructors with parameters + public ClassWithOnlyParameterizedConstructors(string name) + { + Name = name; + } + + public ClassWithOnlyParameterizedConstructors(string name, int id) + { + Name = name; + Id = id; + } + + public string Name { get; set; } + public int Id { get; set; } +} + +public class ClassWithMethods +{ + public ClassWithMethods() + { + } + + // Method without parameters + public void MethodWithoutParameters() + { + } + + // Method with parameters + public void MethodWithParameters(string input, int count) + { + } + + // Private method without parameters + private void PrivateMethodWithoutParameters() + { + } +} \ No newline at end of file