From b49351837d824f87e8f66e9db0e0a976329e0501 Mon Sep 17 00:00:00 2001 From: Kamer Elciyar Date: Tue, 11 Feb 2020 20:16:43 +0300 Subject: [PATCH 1/2] Extend MembersThat and MembersShould to have starting, containing and ending functionality. (#239) Signed-off-by: Kamer Elciyar --- .../core/domain/properties/HasName.java | 58 +++++++++++++ .../lang/conditions/ArchConditions.java | 81 +++++++++++++++++++ .../syntax/AbstractMembersShouldInternal.java | 16 ++++ .../lang/syntax/MembersThatInternal.java | 18 +++++ .../lang/syntax/elements/MembersShould.java | 27 +++++++ .../lang/syntax/elements/MembersThat.java | 27 +++++++ .../syntax/elements/GivenMembersTest.java | 16 ++++ .../syntax/elements/MembersShouldTest.java | 20 +++++ 8 files changed, 263 insertions(+) diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/properties/HasName.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/properties/HasName.java index 8c0ba9888e..1543cbeaaf 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/properties/HasName.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/properties/HasName.java @@ -111,6 +111,21 @@ public static DescribedPredicate nameMatching(final String regex) { return new NameMatchingPredicate(regex); } + @PublicAPI(usage = ACCESS) + public static DescribedPredicate nameStartingWith(final String prefix) { + return new NameStartingWithPredicate(prefix); + } + + @PublicAPI(usage = ACCESS) + public static DescribedPredicate nameContaining(final String infix) { + return new NameContainingPredicate(infix); + } + + @PublicAPI(usage = ACCESS) + public static DescribedPredicate nameEndingWith(final String postfix) { + return new NameEndingWithPredicate(postfix); + } + private static class NameEqualsPredicate extends DescribedPredicate { private final String name; @@ -138,6 +153,49 @@ public boolean apply(HasName input) { return pattern.matcher(input.getName()).matches(); } } + + private static class NameStartingWithPredicate extends DescribedPredicate { + private final String prefix; + + NameStartingWithPredicate(String prefix) { + super(String.format("name starting with '%s'", prefix)); + this.prefix = prefix; + } + + @Override + public boolean apply(HasName input) { + return input.getName().startsWith(prefix); + } + + } + + private static class NameContainingPredicate extends DescribedPredicate { + private final String infix; + + NameContainingPredicate(String infix) { + super(String.format("name containing '%s'", infix)); + this.infix = infix; + } + + @Override + public boolean apply(HasName input) { + return input.getName().contains(infix); + } + } + + private static class NameEndingWithPredicate extends DescribedPredicate { + private final String suffix; + + NameEndingWithPredicate(String suffix) { + super(String.format("name ending with '%s'", suffix)); + this.suffix = suffix; + } + + @Override + public boolean apply(HasName input) { + return input.getName().endsWith(suffix); + } + } } final class Functions { diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/conditions/ArchConditions.java b/archunit/src/main/java/com/tngtech/archunit/lang/conditions/ArchConditions.java index 1ca9cdd525..a759105743 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/conditions/ArchConditions.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/conditions/ArchConditions.java @@ -102,7 +102,10 @@ import static com.tngtech.archunit.core.domain.properties.HasName.AndFullName.Predicates.fullName; import static com.tngtech.archunit.core.domain.properties.HasName.AndFullName.Predicates.fullNameMatching; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name; +import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameContaining; +import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameEndingWith; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameMatching; +import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameStartingWith; import static com.tngtech.archunit.core.domain.properties.HasOwner.Predicates.With.owner; import static com.tngtech.archunit.core.domain.properties.HasParameterTypes.Predicates.rawParameterTypes; import static com.tngtech.archunit.core.domain.properties.HasReturnType.Predicates.rawReturnType; @@ -553,6 +556,27 @@ ArchCondition haveFullNameMatching(String regex) { return not(ArchConditions.haveFullNameMatching(regex)).as("have full name not matching '%s'", regex); } + @PublicAPI(usage = ACCESS) + public static ArchCondition + haveNameStartingWith(String prefix) { + final DescribedPredicate haveNameStartingWith = have(nameStartingWith(prefix)).forSubType(); + return new StartingCondition<>(haveNameStartingWith, prefix); + } + + @PublicAPI(usage = ACCESS) + public static ArchCondition + haveNameContaining(String infix) { + final DescribedPredicate haveNameContaining = have(nameContaining(infix)).forSubType(); + return new ContainingCondition<>(haveNameContaining, infix); + } + + @PublicAPI(usage = ACCESS) + public static ArchCondition + haveNameEndingWith(String suffix) { + final DescribedPredicate haveNameEndingWith = have(nameEndingWith(suffix)).forSubType(); + return new EndingCondition<>(haveNameEndingWith, suffix); + } + @PublicAPI(usage = ACCESS) public static ArchCondition resideInAPackage(final String packageIdentifier) { return new DoesConditionByPredicate<>(JavaClass.Predicates.resideInAPackage(packageIdentifier)); @@ -1243,6 +1267,63 @@ public void check(T item, ConditionEvents events) { } } + private static class StartingCondition extends ArchCondition { + private final DescribedPredicate startingWith; + private final String prefix; + + StartingCondition(DescribedPredicate startingWith, String prefix) { + super(startingWith.getDescription()); + this.startingWith = startingWith; + this.prefix = prefix; + } + + @Override + public void check(T item, ConditionEvents events) { + boolean satisfied = startingWith.apply(item); + String message = createMessage(item, + String.format("%s '%s'", satisfied ? "starts with" : "does not start with", prefix)); + events.add(new SimpleConditionEvent(item, satisfied, message)); + } + } + + private static class ContainingCondition extends ArchCondition { + private final DescribedPredicate containing; + private final String infix; + + ContainingCondition(DescribedPredicate containing, String infix) { + super(containing.getDescription()); + this.containing = containing; + this.infix = infix; + } + + @Override + public void check(T item, ConditionEvents events) { + boolean satisfied = containing.apply(item); + String message = createMessage(item, + String.format("%s '%s'", satisfied ? "contains" : "does not contain", infix)); + events.add(new SimpleConditionEvent(item, satisfied, message)); + } + } + + private static class EndingCondition extends ArchCondition { + private final DescribedPredicate endingWith; + private final String suffix; + + EndingCondition(DescribedPredicate endingWith, String suffix) { + super(endingWith.getDescription()); + this.endingWith = endingWith; + this.suffix = suffix; + } + + @Override + public void check(T item, ConditionEvents events) { + boolean satisfied = endingWith.apply(item); + String message = createMessage(item, + String.format("%s '%s'", satisfied ? "ends with" : "does not end with", suffix)); + events.add(new SimpleConditionEvent(item, satisfied, message)); + } + } + private static class DoesConditionByPredicate extends ArchCondition { private final DescribedPredicate predicate; diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/AbstractMembersShouldInternal.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/AbstractMembersShouldInternal.java index a6d385be53..0927e67fb1 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/AbstractMembersShouldInternal.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/AbstractMembersShouldInternal.java @@ -98,6 +98,22 @@ public SELF haveFullNameMatching(String regex) { public SELF haveFullNameNotMatching(String regex) { return addCondition(ArchConditions.haveFullNameNotMatching(regex)); } + + @Override + public SELF haveNameStartingWith(String prefix) { + return addCondition(ArchConditions.haveNameStartingWith(prefix)); + } + + @Override + public SELF haveNameContaining(String infix) { + return addCondition(ArchConditions.haveNameContaining(infix)); + } + + @Override + public SELF haveNameEndingWith(String suffix) { + return addCondition(ArchConditions.haveNameEndingWith(suffix)); + } + @Override public SELF bePublic() { return addCondition(ArchConditions.bePublic()); diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/MembersThatInternal.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/MembersThatInternal.java index e46fcb9a85..4b9e75a61e 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/MembersThatInternal.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/MembersThatInternal.java @@ -34,7 +34,10 @@ import static com.tngtech.archunit.core.domain.properties.HasName.AndFullName.Predicates.fullName; import static com.tngtech.archunit.core.domain.properties.HasName.AndFullName.Predicates.fullNameMatching; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name; +import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameContaining; +import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameEndingWith; import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameMatching; +import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.nameStartingWith; import static com.tngtech.archunit.lang.conditions.ArchPredicates.are; import static com.tngtech.archunit.lang.conditions.ArchPredicates.have; @@ -93,6 +96,21 @@ public CONJUNCTION haveFullNameNotMatching(String regex) { return givenWith(have(not(fullNameMatching(regex)).as("full name not matching '%s'", regex))); } + @Override + public CONJUNCTION haveNameStartingWith(String prefix) { + return givenWith(have(nameStartingWith(prefix))); + } + + @Override + public CONJUNCTION haveNameContaining(String infix) { + return givenWith(have(nameContaining(infix))); + } + + @Override + public CONJUNCTION haveNameEndingWith(String suffix) { + return givenWith(have(nameEndingWith(suffix))); + } + @Override public CONJUNCTION arePublic() { return givenWith(SyntaxPredicates.arePublic()); diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersShould.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersShould.java index eaeb3542b1..4890632fa4 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersShould.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersShould.java @@ -107,6 +107,33 @@ public interface MembersShould> @PublicAPI(usage = ACCESS) CONJUNCTION haveFullNameNotMatching(String regex); + /** + * Asserts that members have a name starting with the specified prefix. + * + * @param prefix A prefix the member name should start with + * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameStartingWith(String prefix); + + /** + * Asserts that members have a name containing the specified infix. + * + * @param infix An infix the member name should contain + * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameContaining(String infix); + + /** + * Asserts that members have a name ending with the specified suffix. + * + * @param suffix A suffix the member name should end with + * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameEndingWith(String suffix); + /** * Asserts that members are public. * diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersThat.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersThat.java index 8f7b45c243..0c68542ee7 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersThat.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersThat.java @@ -103,6 +103,33 @@ public interface MembersThat> { @PublicAPI(usage = ACCESS) CONJUNCTION haveFullNameNotMatching(String regex); + /** + * Matches members with a name starting with the specified prefix. + * + * @param prefix A prefix the member name should start with + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameStartingWith(String prefix); + + /** + * Matches members with a name containing the specified infix. + * + * @param infix An infix the member name should contain + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameContaining(String infix); + + /** + * Matches members with a name ending with the specified suffix. + * + * @param suffix A suffix the member name should end with + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameEndingWith(String suffix); + /** * Matches public members. * diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMembersTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMembersTest.java index e83e127d72..e46f9ad4b4 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMembersTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMembersTest.java @@ -214,6 +214,22 @@ public static Object[][] restricted_property_rule_starts() { allConstructorsExcept(CONSTRUCTOR_ONE_ARG)), $(described(fields().that().haveFullNameNotMatching(quote(classNameDot) + ".*A.*")), allFieldsExcept(FIELD_A)), + $(described(members().that().haveNameStartingWith("fie")), ALL_FIELD_DESCRIPTIONS), + $(described(codeUnits().that().haveNameStartingWith("me")), ALL_METHOD_DESCRIPTIONS), + $(described(methods().that().haveNameStartingWith("m")), ALL_METHOD_DESCRIPTIONS), + $(described(constructors().that().haveNameStartingWith("<")), ALL_CONSTRUCTOR_DESCRIPTIONS), + $(described(fields().that().haveNameStartingWith("f")), ALL_FIELD_DESCRIPTIONS), + $(described(members().that().haveNameContaining("et")), ALL_METHOD_DESCRIPTIONS), + $(described(codeUnits().that().haveNameContaining("et")), ALL_METHOD_DESCRIPTIONS), + $(described(methods().that().haveNameContaining("dA")), ImmutableSet.of(METHOD_A)), + $(described(constructors().that().haveNameContaining("init")), ALL_CONSTRUCTOR_DESCRIPTIONS), + $(described(fields().that().haveNameContaining("dA")), ImmutableSet.of(FIELD_A)), + $(described(members().that().haveNameEndingWith("D")), ImmutableSet.of(FIELD_D, METHOD_D)), + $(described(codeUnits().that().haveNameEndingWith("A")), ImmutableSet.of(METHOD_A)), + $(described(methods().that().haveNameEndingWith("C")), ImmutableSet.of(METHOD_C)), + $(described(constructors().that().haveNameEndingWith("it>")), ALL_CONSTRUCTOR_DESCRIPTIONS), + $(described(fields().that().haveNameEndingWith("B")), ImmutableSet.of(FIELD_B)), + $(described(members().that().arePublic()), ImmutableSet.of( FIELD_PUBLIC, METHOD_PUBLIC, CONSTRUCTOR_PUBLIC)), $(described(fields().that().arePublic()), ImmutableSet.of(FIELD_C)), diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/MembersShouldTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/MembersShouldTest.java index 3725b15664..32ac61d537 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/MembersShouldTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/MembersShouldTest.java @@ -53,6 +53,7 @@ import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.CONSTRUCTOR_PUBLIC; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.FIELD_A; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.FIELD_ANNOTATED_WITH_A; +import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.FIELD_B; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.FIELD_C; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.FIELD_D; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.FIELD_PACKAGE_PRIVATE; @@ -61,6 +62,9 @@ import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.FIELD_PUBLIC; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.METHOD_A; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.METHOD_ANNOTATED_WITH_A; +import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.METHOD_B; +import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.METHOD_C; +import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.METHOD_D; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.METHOD_PACKAGE_PRIVATE; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.METHOD_PRIVATE; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.METHOD_PROTECTED; @@ -160,6 +164,22 @@ public static Object[][] restricted_property_rule_ends() { $(codeUnits().should().haveFullNameNotMatching(quote(classNameDot) + ".*init.*"), ALL_CONSTRUCTOR_DESCRIPTIONS), $(constructors().should().haveFullNameNotMatching(quote(classNameDot) + ".*init.*String\\)"), ImmutableSet.of(CONSTRUCTOR_ONE_ARG)), + $(members().should().haveNameStartingWith("fi"), ALL_CODE_UNIT_DESCRIPTIONS), + $(fields().should().haveNameStartingWith("m"), ALL_FIELD_DESCRIPTIONS), + $(codeUnits().should().haveNameStartingWith(""), ALL_METHOD_DESCRIPTIONS), + $(methods().should().haveNameEndingWith("dC"), allMethodsExcept(METHOD_C)), + $(constructors().should().haveNameEndingWith(" Date: Sat, 11 Apr 2020 17:17:58 +0200 Subject: [PATCH 2/2] add negated version of `name{startingWith/containing/endingWith}` I have also added some tests for the failure details, since broken messages there would be undetected at the moment. The reason that some of these tests for members are missing for other methods is simply that we reuse those for classes, so they were not added in the past since those tests would have already caught it. If we have syntax methods explicitly for members (that are not also used for classes), then we should add a detailed test checking that the failure details indeed match. when I wrote those tests I also noticed that messages like `Constructor()> does not start with 'foo'` does also read slightly weird (because there is no focus on the "name" the "starts with" refers to) so I added "name" to the failure details, i.e. `Constructor<..> name does not start with...`. Signed-off-by: Peter Gafert --- .../lang/conditions/ArchConditions.java | 27 ++- .../syntax/AbstractMembersShouldInternal.java | 15 ++ .../lang/syntax/MembersThatInternal.java | 15 ++ .../lang/syntax/elements/MembersShould.java | 27 +++ .../lang/syntax/elements/MembersThat.java | 27 +++ .../syntax/elements/ClassesShouldTest.java | 52 ++--- .../syntax/elements/GivenMembersTest.java | 17 ++ .../syntax/elements/MembersShouldTest.java | 190 ++++++++++++++++++ .../testclasses/SimpleFieldAndMethod.java | 8 + 9 files changed, 349 insertions(+), 29 deletions(-) create mode 100644 archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/testclasses/SimpleFieldAndMethod.java diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/conditions/ArchConditions.java b/archunit/src/main/java/com/tngtech/archunit/lang/conditions/ArchConditions.java index a759105743..44057d2654 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/conditions/ArchConditions.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/conditions/ArchConditions.java @@ -563,6 +563,13 @@ ArchCondition haveFullNameMatching(String regex) { return new StartingCondition<>(haveNameStartingWith, prefix); } + @PublicAPI(usage = ACCESS) + public static ArchCondition + haveNameNotStartingWith(String prefix) { + final DescribedPredicate haveNameStartingWith = have(nameStartingWith(prefix)).forSubType(); + return not(new StartingCondition<>(haveNameStartingWith, prefix)).as("have name not starting with '%s'", prefix); + } + @PublicAPI(usage = ACCESS) public static ArchCondition haveNameContaining(String infix) { @@ -570,6 +577,13 @@ ArchCondition haveFullNameMatching(String regex) { return new ContainingCondition<>(haveNameContaining, infix); } + @PublicAPI(usage = ACCESS) + public static ArchCondition + haveNameNotContaining(String infix) { + final DescribedPredicate haveNameContaining = have(nameContaining(infix)).forSubType(); + return not(new ContainingCondition<>(haveNameContaining, infix)).as("have name not containing '%s'", infix); + } + @PublicAPI(usage = ACCESS) public static ArchCondition haveNameEndingWith(String suffix) { @@ -577,6 +591,13 @@ ArchCondition haveFullNameMatching(String regex) { return new EndingCondition<>(haveNameEndingWith, suffix); } + @PublicAPI(usage = ACCESS) + public static ArchCondition + haveNameNotEndingWith(String suffix) { + final DescribedPredicate haveNameEndingWith = have(nameEndingWith(suffix)).forSubType(); + return not(new EndingCondition<>(haveNameEndingWith, suffix)).as("have name not ending with '%s'", suffix); + } + @PublicAPI(usage = ACCESS) public static ArchCondition resideInAPackage(final String packageIdentifier) { return new DoesConditionByPredicate<>(JavaClass.Predicates.resideInAPackage(packageIdentifier)); @@ -1281,7 +1302,7 @@ private static class StartingCondition> @PublicAPI(usage = ACCESS) CONJUNCTION haveNameStartingWith(String prefix); + /** + * Asserts that members have a name not starting with the specified prefix. + * + * @param prefix A prefix the member name should not start with + * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameNotStartingWith(String prefix); + /** * Asserts that members have a name containing the specified infix. * @@ -125,6 +134,15 @@ public interface MembersShould> @PublicAPI(usage = ACCESS) CONJUNCTION haveNameContaining(String infix); + /** + * Asserts that members have a name not containing the specified infix. + * + * @param infix An infix the member name should not contain + * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameNotContaining(String infix); + /** * Asserts that members have a name ending with the specified suffix. * @@ -134,6 +152,15 @@ public interface MembersShould> @PublicAPI(usage = ACCESS) CONJUNCTION haveNameEndingWith(String suffix); + /** + * Asserts that members have a name not ending with the specified suffix. + * + * @param suffix A suffix the member name should not end with + * @return A syntax element that can either be used as working rule, or to continue specifying a more complex rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameNotEndingWith(String suffix); + /** * Asserts that members are public. * diff --git a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersThat.java b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersThat.java index 0c68542ee7..56f99ac7eb 100644 --- a/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersThat.java +++ b/archunit/src/main/java/com/tngtech/archunit/lang/syntax/elements/MembersThat.java @@ -112,6 +112,15 @@ public interface MembersThat> { @PublicAPI(usage = ACCESS) CONJUNCTION haveNameStartingWith(String prefix); + /** + * Matches members with a name not starting with the specified prefix. + * + * @param prefix A prefix the member name should not start with + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameNotStartingWith(String prefix); + /** * Matches members with a name containing the specified infix. * @@ -121,6 +130,15 @@ public interface MembersThat> { @PublicAPI(usage = ACCESS) CONJUNCTION haveNameContaining(String infix); + /** + * Matches members with a name not containing the specified infix. + * + * @param infix An infix the member name should not contain + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameNotContaining(String infix); + /** * Matches members with a name ending with the specified suffix. * @@ -130,6 +148,15 @@ public interface MembersThat> { @PublicAPI(usage = ACCESS) CONJUNCTION haveNameEndingWith(String suffix); + /** + * Matches members with a name not ending with the specified suffix. + * + * @param suffix A suffix the member name should not end with + * @return A syntax conjunction element, which can be completed to form a full rule + */ + @PublicAPI(usage = ACCESS) + CONJUNCTION haveNameNotEndingWith(String suffix); + /** * Matches public members. * diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/ClassesShouldTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/ClassesShouldTest.java index c2f79c600d..ed45347374 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/ClassesShouldTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/ClassesShouldTest.java @@ -248,6 +248,31 @@ public void haveSimpleNameStartingWith(ArchRule rule, String prefix) { .doesNotContain(SomeClass.class.getName()); } + @DataProvider + public static Object[][] haveSimpleNameNotStartingWith_rules() { + String simpleName = WrongNamedClass.class.getSimpleName(); + String prefix = simpleName.substring(0, simpleName.length() - 1); + return $$( + $(classes().should().haveSimpleNameNotStartingWith(prefix), prefix), + $(classes().should(ArchConditions.haveSimpleNameNotStartingWith(prefix)), prefix) + ); + } + + @Test + @UseDataProvider("haveSimpleNameNotStartingWith_rules") + public void haveSimpleNameNotStartingWith(ArchRule rule, String prefix) { + EvaluationResult result = rule.evaluate(importClasses( + SomeClass.class, WrongNamedClass.class)); + + assertThat(singleLineFailureReportOf(result)) + .contains(String.format("classes should have simple name not starting with '%s'", prefix)) + .containsPattern(String.format("simple name of %s starts with '%s' in %s", + quote(WrongNamedClass.class.getName()), + quote(prefix), + locationPattern(WrongNamedClass.class))) + .doesNotContain(SomeClass.class.getName()); + } + @DataProvider public static Object[][] haveSimpleNameContaining_rules() { String simpleName = SomeClass.class.getSimpleName(); @@ -298,31 +323,6 @@ public void haveSimpleNameNotContaining(ArchRule rule, String infix) { .doesNotContain(SomeClass.class.getName()); } - @DataProvider - public static Object[][] haveSimpleNameNotStartingWith_rules() { - String simpleName = WrongNamedClass.class.getSimpleName(); - String prefix = simpleName.substring(0, simpleName.length() - 1); - return $$( - $(classes().should().haveSimpleNameNotStartingWith(prefix), prefix), - $(classes().should(ArchConditions.haveSimpleNameNotStartingWith(prefix)), prefix) - ); - } - - @Test - @UseDataProvider("haveSimpleNameNotStartingWith_rules") - public void haveSimpleNameNotStartingWith(ArchRule rule, String prefix) { - EvaluationResult result = rule.evaluate(importClasses( - SomeClass.class, WrongNamedClass.class)); - - assertThat(singleLineFailureReportOf(result)) - .contains(String.format("classes should have simple name not starting with '%s'", prefix)) - .containsPattern(String.format("simple name of %s starts with '%s' in %s", - quote(WrongNamedClass.class.getName()), - quote(prefix), - locationPattern(WrongNamedClass.class))) - .doesNotContain(SomeClass.class.getName()); - } - @DataProvider public static Object[][] haveSimpleNameEndingWith_rules() { String simpleName = SomeClass.class.getSimpleName(); @@ -1722,7 +1722,7 @@ public void onlyCall_should_report_success_if_targets_are_non_resolvable(ArchRul } static String locationPattern(Class clazz) { - return String.format("\\(%s.java:0\\)", quote(clazz.getSimpleName())); + return String.format("\\(%s.java:\\d+\\)", quote(clazz.getSimpleName())); } static String singleLineFailureReportOf(EvaluationResult result) { diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMembersTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMembersTest.java index e46f9ad4b4..d7408a9b81 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMembersTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/GivenMembersTest.java @@ -219,16 +219,33 @@ public static Object[][] restricted_property_rule_starts() { $(described(methods().that().haveNameStartingWith("m")), ALL_METHOD_DESCRIPTIONS), $(described(constructors().that().haveNameStartingWith("<")), ALL_CONSTRUCTOR_DESCRIPTIONS), $(described(fields().that().haveNameStartingWith("f")), ALL_FIELD_DESCRIPTIONS), + $(described(members().that().haveNameNotStartingWith("fie")), ALL_CODE_UNIT_DESCRIPTIONS), + $(described(codeUnits().that().haveNameNotStartingWith("me")), ALL_CONSTRUCTOR_DESCRIPTIONS), + $(described(methods().that().haveNameNotStartingWith("m")), emptySet()), + $(described(constructors().that().haveNameNotStartingWith("<")), emptySet()), + $(described(fields().that().haveNameNotStartingWith("f")), emptySet()), + $(described(members().that().haveNameContaining("et")), ALL_METHOD_DESCRIPTIONS), $(described(codeUnits().that().haveNameContaining("et")), ALL_METHOD_DESCRIPTIONS), $(described(methods().that().haveNameContaining("dA")), ImmutableSet.of(METHOD_A)), $(described(constructors().that().haveNameContaining("init")), ALL_CONSTRUCTOR_DESCRIPTIONS), $(described(fields().that().haveNameContaining("dA")), ImmutableSet.of(FIELD_A)), + $(described(members().that().haveNameNotContaining("et")), union(ALL_FIELD_DESCRIPTIONS, ALL_CONSTRUCTOR_DESCRIPTIONS)), + $(described(codeUnits().that().haveNameNotContaining("et")), ALL_CONSTRUCTOR_DESCRIPTIONS), + $(described(methods().that().haveNameNotContaining("dA")), allMethodsExcept(METHOD_A)), + $(described(constructors().that().haveNameNotContaining("init")), emptySet()), + $(described(fields().that().haveNameNotContaining("dA")), allFieldsExcept(FIELD_A)), + $(described(members().that().haveNameEndingWith("D")), ImmutableSet.of(FIELD_D, METHOD_D)), $(described(codeUnits().that().haveNameEndingWith("A")), ImmutableSet.of(METHOD_A)), $(described(methods().that().haveNameEndingWith("C")), ImmutableSet.of(METHOD_C)), $(described(constructors().that().haveNameEndingWith("it>")), ALL_CONSTRUCTOR_DESCRIPTIONS), $(described(fields().that().haveNameEndingWith("B")), ImmutableSet.of(FIELD_B)), + $(described(members().that().haveNameNotEndingWith("D")), allMembersExcept(FIELD_D, METHOD_D)), + $(described(codeUnits().that().haveNameNotEndingWith("A")), allCodeUnitsExcept(METHOD_A)), + $(described(methods().that().haveNameNotEndingWith("C")), allMethodsExcept(METHOD_C)), + $(described(constructors().that().haveNameNotEndingWith("it>")), emptySet()), + $(described(fields().that().haveNameNotEndingWith("B")), allFieldsExcept(FIELD_B)), $(described(members().that().arePublic()), ImmutableSet.of( FIELD_PUBLIC, METHOD_PUBLIC, CONSTRUCTOR_PUBLIC)), diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/MembersShouldTest.java b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/MembersShouldTest.java index 32ac61d537..7683a63384 100644 --- a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/MembersShouldTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/MembersShouldTest.java @@ -9,13 +9,16 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.tngtech.archunit.base.Function; +import com.tngtech.archunit.lang.ArchRule; import com.tngtech.archunit.lang.EvaluationResult; +import com.tngtech.archunit.lang.conditions.ArchConditions; import com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.A; import com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.B; import com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.C; import com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.ClassWithVariousMembers; import com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.MetaAnnotation; import com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.OtherClassWithMembers; +import com.tngtech.archunit.lang.syntax.elements.testclasses.SimpleFieldAndMethod; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; import com.tngtech.java.junit.dataprovider.UseDataProvider; @@ -35,6 +38,8 @@ import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.fields; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.members; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods; +import static com.tngtech.archunit.lang.syntax.elements.ClassesShouldTest.locationPattern; +import static com.tngtech.archunit.lang.syntax.elements.ClassesShouldTest.singleLineFailureReportOf; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.ALL_CODE_UNIT_DESCRIPTIONS; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.ALL_CONSTRUCTOR_DESCRIPTIONS; import static com.tngtech.archunit.lang.syntax.elements.GivenMembersTest.ALL_FIELD_DESCRIPTIONS; @@ -169,16 +174,33 @@ public static Object[][] restricted_property_rule_ends() { $(codeUnits().should().haveNameStartingWith(""), ALL_METHOD_DESCRIPTIONS), $(methods().should().haveNameEndingWith("dC"), allMethodsExcept(METHOD_C)), $(constructors().should().haveNameEndingWith(""), ALL_CONSTRUCTOR_DESCRIPTIONS), + $(methods().should().haveNameNotEndingWith("dC"), ImmutableSet.of(METHOD_C)), + $(constructors().should().haveNameNotEndingWith(" conjunction, Set< assertThat(actualMembers).containsOnlyElementsOf(expectedMessages); } + @DataProvider + public static Object[][] haveNameStartingWith_rules() { + return $$( + $(members().should().haveNameStartingWith("field"), "field", "violated"), + $(members().should(ArchConditions.haveNameStartingWith("field")), "field", "violated"), + $(fields().should().haveNameStartingWith("field"), "field", "violated"), + $(fields().should(ArchConditions.haveNameStartingWith("field")), "field", "violated"), + $(codeUnits().should().haveNameStartingWith("method"), "method", "violated"), + $(codeUnits().should(ArchConditions.haveNameStartingWith("method")), "method", "violated"), + $(methods().should().haveNameStartingWith("method"), "method", "violated"), + $(methods().should(ArchConditions.haveNameStartingWith("method")), "method", "violated"), + $(constructors().should().haveNameStartingWith("constructor"), "constructor", ""), + $(constructors().should(ArchConditions.haveNameStartingWith("constructor")), "constructor", "") + ); + } + + @Test + @UseDataProvider("haveNameStartingWith_rules") + public void haveNameStartingWith(ArchRule rule, String prefix, String violatingMember) { + EvaluationResult result = rule.evaluate(importClasses(SimpleFieldAndMethod.class)); + + assertThat(singleLineFailureReportOf(result)) + .containsPattern(String.format(".*%s.* name does not start with '%s' in %s", + quote(violatingMember), + quote(prefix), + locationPattern(SimpleFieldAndMethod.class))); + } + + @DataProvider + public static Object[][] haveNameNotStartingWith_rules() { + return $$( + $(members().should().haveNameNotStartingWith("violated"), "violated"), + $(members().should(ArchConditions.haveNameNotStartingWith("violated")), "violated"), + $(fields().should().haveNameNotStartingWith("violated"), "violated"), + $(fields().should(ArchConditions.haveNameNotStartingWith("violated")), "violated"), + $(codeUnits().should().haveNameNotStartingWith("violated"), "violated"), + $(codeUnits().should(ArchConditions.haveNameNotStartingWith("violated")), "violated"), + $(methods().should().haveNameNotStartingWith("violated"), "violated"), + $(methods().should(ArchConditions.haveNameNotStartingWith("violated")), "violated"), + $(constructors().should().haveNameNotStartingWith(""), ""), + $(constructors().should(ArchConditions.haveNameNotStartingWith("")), "") + ); + } + + @Test + @UseDataProvider("haveNameNotStartingWith_rules") + public void haveNameNotStartingWith(ArchRule rule, String prefix) { + EvaluationResult result = rule.evaluate(importClasses(SimpleFieldAndMethod.class)); + + assertThat(singleLineFailureReportOf(result)) + .containsPattern(String.format(".*%s.* name starts with '%s' in %s", + quote(prefix), + quote(prefix), + locationPattern(SimpleFieldAndMethod.class))); + } + + @DataProvider + public static Object[][] haveNameContaining_rules() { + return $$( + $(members().should().haveNameContaining("field"), "field", "violated"), + $(members().should(ArchConditions.haveNameContaining("field")), "field", "violated"), + $(fields().should().haveNameContaining("field"), "field", "violated"), + $(fields().should(ArchConditions.haveNameContaining("field")), "field", "violated"), + $(codeUnits().should().haveNameContaining("method"), "method", "violated"), + $(codeUnits().should(ArchConditions.haveNameContaining("method")), "method", "violated"), + $(methods().should().haveNameContaining("method"), "method", "violated"), + $(methods().should(ArchConditions.haveNameContaining("method")), "method", "violated"), + $(constructors().should().haveNameContaining("constructor"), "constructor", ""), + $(constructors().should(ArchConditions.haveNameContaining("constructor")), "constructor", "") + ); + } + + @Test + @UseDataProvider("haveNameContaining_rules") + public void haveNameContaining(ArchRule rule, String infix, String violatingMember) { + EvaluationResult result = rule.evaluate(importClasses(SimpleFieldAndMethod.class)); + + assertThat(singleLineFailureReportOf(result)) + .containsPattern(String.format(".*%s.* name does not contain '%s' in %s", + quote(violatingMember), + quote(infix), + locationPattern(SimpleFieldAndMethod.class))); + } + + @DataProvider + public static Object[][] haveNameNotContaining_rules() { + return $$( + $(members().should().haveNameNotContaining("violated"), "violated"), + $(members().should(ArchConditions.haveNameNotContaining("violated")), "violated"), + $(fields().should().haveNameNotContaining("violated"), "violated"), + $(fields().should(ArchConditions.haveNameNotContaining("violated")), "violated"), + $(codeUnits().should().haveNameNotContaining("violated"), "violated"), + $(codeUnits().should(ArchConditions.haveNameNotContaining("violated")), "violated"), + $(methods().should().haveNameNotContaining("violated"), "violated"), + $(methods().should(ArchConditions.haveNameNotContaining("violated")), "violated"), + $(constructors().should().haveNameNotContaining(""), ""), + $(constructors().should(ArchConditions.haveNameNotContaining("")), "") + ); + } + + @Test + @UseDataProvider("haveNameNotContaining_rules") + public void haveNameNotContaining(ArchRule rule, String infix) { + EvaluationResult result = rule.evaluate(importClasses(SimpleFieldAndMethod.class)); + + assertThat(singleLineFailureReportOf(result)) + .containsPattern(String.format(".*%s.* name contains '%s' in %s", + quote(infix), + quote(infix), + locationPattern(SimpleFieldAndMethod.class))); + } + + @DataProvider + public static Object[][] haveNameEndingWith_rules() { + return $$( + $(members().should().haveNameEndingWith("field"), "field", "violated"), + $(members().should(ArchConditions.haveNameEndingWith("field")), "field", "violated"), + $(fields().should().haveNameEndingWith("field"), "field", "violated"), + $(fields().should(ArchConditions.haveNameEndingWith("field")), "field", "violated"), + $(codeUnits().should().haveNameEndingWith("method"), "method", "violated"), + $(codeUnits().should(ArchConditions.haveNameEndingWith("method")), "method", "violated"), + $(methods().should().haveNameEndingWith("method"), "method", "violated"), + $(methods().should(ArchConditions.haveNameEndingWith("method")), "method", "violated"), + $(constructors().should().haveNameEndingWith("constructor"), "constructor", ""), + $(constructors().should(ArchConditions.haveNameEndingWith("constructor")), "constructor", "") + ); + } + + @Test + @UseDataProvider("haveNameEndingWith_rules") + public void haveNameEndingWith(ArchRule rule, String suffix, String violatingMember) { + EvaluationResult result = rule.evaluate(importClasses(SimpleFieldAndMethod.class)); + + assertThat(singleLineFailureReportOf(result)) + .containsPattern(String.format(".*%s.* name does not end with '%s' in %s", + quote(violatingMember), + quote(suffix), + locationPattern(SimpleFieldAndMethod.class))); + } + + @DataProvider + public static Object[][] haveNameNotEndingWith_rules() { + return $$( + $(members().should().haveNameNotEndingWith("violated"), "violated"), + $(members().should(ArchConditions.haveNameNotEndingWith("violated")), "violated"), + $(fields().should().haveNameNotEndingWith("violated"), "violated"), + $(fields().should(ArchConditions.haveNameNotEndingWith("violated")), "violated"), + $(codeUnits().should().haveNameNotEndingWith("violated"), "violated"), + $(codeUnits().should(ArchConditions.haveNameNotEndingWith("violated")), "violated"), + $(methods().should().haveNameNotEndingWith("violated"), "violated"), + $(methods().should(ArchConditions.haveNameNotEndingWith("violated")), "violated"), + $(constructors().should().haveNameNotEndingWith(""), ""), + $(constructors().should(ArchConditions.haveNameNotEndingWith("")), "") + ); + } + + @Test + @UseDataProvider("haveNameNotEndingWith_rules") + public void haveNameNotEndingWith(ArchRule rule, String suffix) { + EvaluationResult result = rule.evaluate(importClasses(SimpleFieldAndMethod.class)); + + assertThat(singleLineFailureReportOf(result)) + .containsPattern(String.format(".*%s.* name ends with '%s' in %s", + quote(suffix), + quote(suffix), + locationPattern(SimpleFieldAndMethod.class))); + } + private Set parseMembers(List details) { return parseMembers(ImmutableList.of(ClassWithVariousMembers.class, OtherClassWithMembers.class), details); } diff --git a/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/testclasses/SimpleFieldAndMethod.java b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/testclasses/SimpleFieldAndMethod.java new file mode 100644 index 0000000000..a9f63dcc8c --- /dev/null +++ b/archunit/src/test/java/com/tngtech/archunit/lang/syntax/elements/testclasses/SimpleFieldAndMethod.java @@ -0,0 +1,8 @@ +package com.tngtech.archunit.lang.syntax.elements.testclasses; + +public class SimpleFieldAndMethod { + Object violated; + + void violated() { + } +}