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..7a98b4cf7 100644 --- a/ArchUnitNET/Fluent/Syntax/Elements/Members/MethodMembers/MethodMemberConditionsDefinition.cs +++ b/ArchUnitNET/Fluent/Syntax/Elements/Members/MethodMembers/MethodMemberConditionsDefinition.cs @@ -159,6 +159,7 @@ Architecture architecture //ignore, can't have a dependency anyways } } + var methodMemberList = methodMembers.ToList(); var passedObjects = methodMemberList .Where(methodMember => @@ -359,6 +360,7 @@ Architecture architecture //ignore, can't have a dependency anyways } } + var methodMemberList = methodMembers.ToList(); var passedObjects = methodMemberList .Where(methodMember => @@ -657,6 +659,7 @@ Architecture architecture //ignore, can't have a dependency anyways } } + var methodMemberList = methodMembers.ToList(); var failedObjects = methodMemberList .Where(methodMember => @@ -861,6 +864,7 @@ Architecture architecture //ignore, can't have a dependency anyways } } + var methodMemberList = methodMembers.ToList(); var failedObjects = methodMemberList .Where(methodMember => @@ -1011,5 +1015,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..06f92feeb 100644 --- a/ArchUnitNET/Fluent/Syntax/Elements/Members/MethodMembers/MethodMembersShould.cs +++ b/ArchUnitNET/Fluent/Syntax/Elements/Members/MethodMembers/MethodMembersShould.cs @@ -1,7 +1,11 @@ -using System; +#region + +using System; using System.Collections.Generic; using ArchUnitNET.Domain; +#endregion + namespace ArchUnitNET.Fluent.Syntax.Elements.Members.MethodMembers { public class MethodMembersShould @@ -11,21 +15,6 @@ public class MethodMembersShould public MethodMembersShould(IArchRuleCreator ruleCreator) : base(ruleCreator) { } - public ShouldRelateToMethodMembersThat< - MethodMembersShouldConjunction, - MethodMember - > BeMethodMembersThat() - { - _ruleCreator.BeginComplexCondition( - ArchRuleDefinition.MethodMembers(), - MethodMemberConditionsDefinition.BeMethodMembersThat() - ); - return new ShouldRelateToMethodMembersThat< - MethodMembersShouldConjunction, - MethodMember - >(_ruleCreator); - } - public MethodMembersShouldConjunction BeConstructor() { _ruleCreator.AddCondition(MethodMemberConditionsDefinition.BeConstructor()); @@ -297,5 +286,32 @@ 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); + } + + public ShouldRelateToMethodMembersThat< + MethodMembersShouldConjunction, + MethodMember + > BeMethodMembersThat() + { + _ruleCreator.BeginComplexCondition( + ArchRuleDefinition.MethodMembers(), + MethodMemberConditionsDefinition.BeMethodMembersThat() + ); + return new ShouldRelateToMethodMembersThat< + MethodMembersShouldConjunction, + MethodMember + >(_ruleCreator); + } } } diff --git a/ArchUnitNETTests/Fluent/Syntax/Elements/MethodParameterConditionTests.cs b/ArchUnitNETTests/Fluent/Syntax/Elements/MethodParameterConditionTests.cs new file mode 100644 index 000000000..804b7a2c9 --- /dev/null +++ b/ArchUnitNETTests/Fluent/Syntax/Elements/MethodParameterConditionTests.cs @@ -0,0 +1,197 @@ +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..7697efab0 --- /dev/null +++ b/TestAssembly/Domain/Methods/MethodParameterTestClass.cs @@ -0,0 +1,61 @@ +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() { } +}