T disable();
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/com/enofex/taikai/internal/ArchConditions.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.internal;
2 |
3 | import static com.enofex.taikai.internal.Modifiers.isFieldPublic;
4 | import static com.enofex.taikai.internal.Modifiers.isFieldStatic;
5 |
6 | import com.tngtech.archunit.core.domain.JavaClass;
7 | import com.tngtech.archunit.core.domain.JavaField;
8 | import com.tngtech.archunit.core.domain.JavaMethod;
9 | import com.tngtech.archunit.core.domain.JavaModifier;
10 | import com.tngtech.archunit.lang.ArchCondition;
11 | import com.tngtech.archunit.lang.ConditionEvents;
12 | import com.tngtech.archunit.lang.SimpleConditionEvent;
13 | import java.util.Collection;
14 | import java.util.stream.Collectors;
15 |
16 | /**
17 | * Internal utility class for defining general ArchCondition used in architectural rules.
18 | *
19 | * This class is intended for internal use only and is not part of the public API. Developers should
20 | * not rely on this class for any public API usage.
21 | */
22 | public final class ArchConditions {
23 |
24 | private ArchConditions() {
25 | }
26 |
27 | /**
28 | * Creates a condition that checks if a method does not declare thrown exceptions.
29 | *
30 | * @return an architectural condition for checking thrown exceptions in methods
31 | */
32 | public static ArchCondition notDeclareThrownExceptions() {
33 | return new ArchCondition<>("not declare thrown exceptions") {
34 | @Override
35 | public void check(JavaMethod method, ConditionEvents events) {
36 | if (!method.getThrowsClause().isEmpty()) {
37 | events.add(SimpleConditionEvent.violated(method,
38 | "Method %s declares thrown exceptions".formatted(
39 | method.getFullName())));
40 | }
41 | }
42 | };
43 | }
44 |
45 | /**
46 | * Creates a condition that checks if a field is not public and not static.
47 | *
48 | * @return an architectural condition for checking public except static fields
49 | */
50 | public static ArchCondition notBePublicUnlessStatic() {
51 | return new ArchCondition<>("not be public") {
52 | @Override
53 | public void check(JavaField field, ConditionEvents events) {
54 | if (!isFieldStatic(field) && isFieldPublic(field)) {
55 | events.add(SimpleConditionEvent.violated(field,
56 | "Field %s in class %s is public".formatted(
57 | field.getName(),
58 | field.getOwner().getFullName())));
59 | }
60 | }
61 | };
62 | }
63 |
64 | /**
65 | * Creates a condition that checks if a class has a field of the specified type.
66 | *
67 | * @param typeName the name of the type to check for in the fields of the class
68 | * @return an architectural condition for checking if a class has a field of the specified type
69 | */
70 | public static ArchCondition haveFieldOfType(String typeName) {
71 | return new ArchCondition<>("have a field of type %s".formatted(typeName)) {
72 | @Override
73 | public void check(JavaClass item, ConditionEvents events) {
74 | boolean hasFieldOfType = item.getAllFields().stream()
75 | .anyMatch(field -> field.getRawType().getName().equals(typeName));
76 |
77 | if (!hasFieldOfType) {
78 | events.add(SimpleConditionEvent.violated(item,
79 | "%s does not have a field of type %s".formatted(
80 | item.getName(),
81 | typeName)));
82 | }
83 | }
84 | };
85 | }
86 |
87 | /**
88 | * Creates a condition that checks if a field contains all the specified modifiers.
89 | *
90 | * @param requiredModifiers the collection of modifiers that the field is required to have
91 | * @return an architectural condition for checking if a field has the required modifiers
92 | */
93 | public static ArchCondition hasFieldModifiers(
94 | Collection requiredModifiers) {
95 | return new ArchCondition<>("has field modifiers") {
96 | @Override
97 | public void check(JavaField field, ConditionEvents events) {
98 | if (!field.getModifiers().containsAll(requiredModifiers)) {
99 | events.add(SimpleConditionEvent.violated(field,
100 | "Field %s in class %s is missing one of this %s modifier".formatted(
101 | field.getName(),
102 | field.getOwner().getFullName(),
103 | requiredModifiers.stream().map(Enum::name).collect(Collectors.joining(", ")))));
104 | }
105 | }
106 | };
107 | }
108 |
109 | /**
110 | * Creates a condition that checks if a method contains all the specified modifiers.
111 | *
112 | * @param requiredModifiers the collection of modifiers that the method is required to have
113 | * @return an architectural condition for checking if a method has the required modifiers
114 | */
115 | public static ArchCondition hasMethodsModifiers(
116 | Collection requiredModifiers) {
117 | return new ArchCondition<>("has method modifiers") {
118 | @Override
119 | public void check(JavaMethod method, ConditionEvents events) {
120 | if (!method.getModifiers().containsAll(requiredModifiers)) {
121 | events.add(SimpleConditionEvent.violated(method,
122 | "Method %s in class %s is missing one of this %s modifier".formatted(
123 | method.getName(),
124 | method.getOwner().getFullName(),
125 | requiredModifiers.stream().map(Enum::name).collect(Collectors.joining(", ")))));
126 | }
127 | }
128 | };
129 | }
130 |
131 | /**
132 | * Creates a condition that checks if a class contains all the specified modifiers.
133 | *
134 | * @param requiredModifiers the collection of modifiers that the class is required to have
135 | * @return an architectural condition for checking if a class has the required modifiers
136 | */
137 | public static ArchCondition hasClassModifiers(
138 | Collection requiredModifiers) {
139 | return new ArchCondition<>("has class modifiers") {
140 | @Override
141 | public void check(JavaClass clazz, ConditionEvents events) {
142 | if (!clazz.getModifiers().containsAll(requiredModifiers)) {
143 | events.add(SimpleConditionEvent.violated(clazz,
144 | "Class %s is missing one of this %s modifier".formatted(
145 | clazz.getName(),
146 | requiredModifiers.stream().map(Enum::name).collect(Collectors.joining(", ")))));
147 | }
148 | }
149 | };
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/src/main/java/com/enofex/taikai/internal/DescribedPredicates.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.internal;
2 |
3 | import static com.enofex.taikai.internal.Modifiers.isClassFinal;
4 |
5 | import com.tngtech.archunit.base.DescribedPredicate;
6 | import com.tngtech.archunit.core.domain.JavaClass;
7 | import com.tngtech.archunit.core.domain.properties.CanBeAnnotated;
8 | import java.util.Collection;
9 |
10 | /**
11 | * Internal utility class for defining general DescribedPredicate used in architectural rules.
12 | *
13 | * This class is intended for internal use only and is not part of the public API. Developers should
14 | * not rely on this class for any public API usage.
15 | */
16 | public final class DescribedPredicates {
17 |
18 | private DescribedPredicates() {
19 | }
20 |
21 | /**
22 | * Creates a predicate that checks if an element is annotated with a specific annotation.
23 | *
24 | * @param annotation the annotation to check for
25 | * @param isMetaAnnotated true if the annotation should be meta-annotated, false otherwise
26 | * @return a described predicate for the annotation check
27 | */
28 | public static DescribedPredicate annotatedWith(String annotation,
29 | boolean isMetaAnnotated) {
30 | return new DescribedPredicate<>("annotated with %s".formatted(annotation)) {
31 | @Override
32 | public boolean test(CanBeAnnotated canBeAnnotated) {
33 | return isMetaAnnotated ? canBeAnnotated.isMetaAnnotatedWith(annotation)
34 | : canBeAnnotated.isAnnotatedWith(annotation);
35 | }
36 | };
37 | }
38 |
39 | /**
40 | * Creates a predicate that checks if an element is annotated with all the specified annotations.
41 | *
42 | * @param annotations the collection of annotations to check for
43 | * @param isMetaAnnotated true if the annotations should be meta-annotated, false otherwise
44 | * @return a described predicate for the annotation check
45 | */
46 | public static DescribedPredicate annotatedWithAll(Collection annotations,
47 | boolean isMetaAnnotated) {
48 | return new DescribedPredicate<>("annotated with all of %s".formatted(annotations)) {
49 | @Override
50 | public boolean test(CanBeAnnotated canBeAnnotated) {
51 | return annotations.stream().allMatch(annotation ->
52 | isMetaAnnotated ? canBeAnnotated.isMetaAnnotatedWith(annotation)
53 | : canBeAnnotated.isAnnotatedWith(annotation));
54 | }
55 | };
56 | }
57 |
58 | /**
59 | * Creates a predicate that checks if a class is final.
60 | *
61 | * @return a described predicate for the final modifier check
62 | */
63 | public static DescribedPredicate areFinal() {
64 | return new DescribedPredicate<>("are final") {
65 | @Override
66 | public boolean test(JavaClass javaClass) {
67 | return isClassFinal(javaClass);
68 | }
69 | };
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/main/java/com/enofex/taikai/internal/Modifiers.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.internal;
2 |
3 | import com.tngtech.archunit.core.domain.JavaClass;
4 | import com.tngtech.archunit.core.domain.JavaConstructor;
5 | import com.tngtech.archunit.core.domain.JavaField;
6 | import com.tngtech.archunit.core.domain.JavaMethod;
7 | import com.tngtech.archunit.core.domain.JavaModifier;
8 |
9 | /**
10 | * This class provides utility methods for checking Java modifiers.
11 | *
12 | * This class is intended for internal use only and is not part of the public API. Developers should
13 | * not rely on this class for any public API usage.
14 | */
15 | public final class Modifiers {
16 |
17 | private Modifiers() {
18 | }
19 |
20 | /**
21 | * Checks if a class is final.
22 | *
23 | * @param javaClass the Java class to check
24 | * @return true if the class is final, false otherwise
25 | */
26 | public static boolean isClassFinal(JavaClass javaClass) {
27 | return javaClass.getModifiers().contains(JavaModifier.FINAL);
28 | }
29 |
30 | /**
31 | * Checks if a constructor is private.
32 | *
33 | * @param constructor the Java constructor to check
34 | * @return true if the constructor is private, false otherwise
35 | */
36 | public static boolean isConstructorPrivate(JavaConstructor constructor) {
37 | return constructor.getModifiers().contains(JavaModifier.PRIVATE);
38 | }
39 |
40 | /**
41 | * Checks if a method is protected.
42 | *
43 | * @param method the Java method to check
44 | * @return true if the method is protected, false otherwise
45 | */
46 | public static boolean isMethodProtected(JavaMethod method) {
47 | return method.getModifiers().contains(JavaModifier.PROTECTED);
48 | }
49 |
50 | /**
51 | * Checks if a method is static.
52 | *
53 | * @param method the Java method to check
54 | * @return true if the method is static, false otherwise
55 | */
56 | public static boolean isMethodStatic(JavaMethod method) {
57 | return method.getModifiers().contains(JavaModifier.STATIC);
58 | }
59 |
60 | /**
61 | * Checks if a field is static.
62 | *
63 | * @param field the Java field to check
64 | * @return true if the field is static, false otherwise
65 | */
66 | public static boolean isFieldStatic(JavaField field) {
67 | return field.getModifiers().contains(JavaModifier.STATIC);
68 | }
69 |
70 | /**
71 | * Checks if a field is public.
72 | *
73 | * @param field the Java field to check
74 | * @return true if the field is public, false otherwise
75 | */
76 | public static boolean isFieldPublic(JavaField field) {
77 | return field.getModifiers().contains(JavaModifier.PUBLIC);
78 | }
79 |
80 | /**
81 | * Checks if a field is protected.
82 | *
83 | * @param field the Java field to check
84 | * @return true if the field is protected, false otherwise
85 | */
86 | public static boolean isFieldProtected(JavaField field) {
87 | return field.getModifiers().contains(JavaModifier.PROTECTED);
88 | }
89 |
90 | /**
91 | * Checks if a field is final.
92 | *
93 | * @param field the Java field to check
94 | * @return true if the field is final, false otherwise
95 | */
96 | public static boolean isFieldFinal(JavaField field) {
97 | return field.getModifiers().contains(JavaModifier.FINAL);
98 | }
99 |
100 | /**
101 | * Checks if a field is synthetic.
102 | *
103 | * @param field the Java field to check
104 | * @return true if the field is synthetic, false otherwise
105 | */
106 | public static boolean isFieldSynthetic(JavaField field) {
107 | return field.getModifiers().contains(JavaModifier.SYNTHETIC);
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/main/java/com/enofex/taikai/java/ConstantNaming.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.java;
2 |
3 | import static com.enofex.taikai.internal.Modifiers.isFieldSynthetic;
4 |
5 | import com.tngtech.archunit.core.domain.JavaField;
6 | import com.tngtech.archunit.lang.ArchCondition;
7 | import com.tngtech.archunit.lang.ConditionEvents;
8 | import com.tngtech.archunit.lang.SimpleConditionEvent;
9 | import java.util.regex.Pattern;
10 |
11 | final class ConstantNaming {
12 |
13 | private static final Pattern CONSTANT_NAME_PATTERN = Pattern.compile("^[A-Z][A-Z0-9_]*$");
14 |
15 | private ConstantNaming() {
16 | }
17 |
18 | static ArchCondition shouldFollowConstantNamingConventions() {
19 | return new ArchCondition<>("follow constant naming convention") {
20 | @Override
21 | public void check(JavaField field, ConditionEvents events) {
22 | if (!isFieldSynthetic(field)
23 | && !"serialVersionUID".equals(field.getName())
24 | && !CONSTANT_NAME_PATTERN.matcher(field.getName()).matches()) {
25 | events.add(SimpleConditionEvent.violated(field,
26 | "Constant %s in class %s does not follow the naming convention".formatted(
27 | field.getName(),
28 | field.getOwner().getName())));
29 | }
30 | }
31 | };
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/enofex/taikai/java/Deprecations.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.java;
2 |
3 | import com.tngtech.archunit.core.domain.JavaClass;
4 | import com.tngtech.archunit.core.domain.JavaType;
5 | import com.tngtech.archunit.lang.ArchCondition;
6 | import com.tngtech.archunit.lang.ConditionEvents;
7 | import com.tngtech.archunit.lang.SimpleConditionEvent;
8 |
9 | final class Deprecations {
10 |
11 | private Deprecations() {
12 | }
13 |
14 | static ArchCondition notUseDeprecatedAPIs() {
15 | return new ArchCondition("not use deprecated APIs") {
16 | @Override
17 | public void check(JavaClass javaClass, ConditionEvents events) {
18 | javaClass.getFieldAccessesFromSelf().stream()
19 | .filter(access -> access.getTarget().isAnnotatedWith(Deprecated.class))
20 | .forEach(access -> events.add(SimpleConditionEvent.violated(access.getTarget(),
21 | "Field %s in class %s is deprecated and is being accessed by %s".formatted(
22 | access.getTarget().getName(),
23 | access.getTarget().getOwner().getName(),
24 | javaClass.getName()))));
25 |
26 | javaClass.getMethodCallsFromSelf().stream()
27 | .filter(method -> !method.getTarget().getName().equals(Object.class.getName()))
28 | .filter(method -> !method.getTarget().getName().equals(Enum.class.getName()))
29 | .filter(method -> method.getTarget().isAnnotatedWith(Deprecated.class) ||
30 | method.getTarget().getRawReturnType().isAnnotatedWith(Deprecated.class) ||
31 | method.getTarget().getParameterTypes().stream()
32 | .anyMatch(Deprecations::isDeprecated))
33 | .forEach(method -> events.add(SimpleConditionEvent.violated(method,
34 | "Method %s used in class %s is deprecated".formatted(
35 | method.getName(),
36 | javaClass.getName()))));
37 |
38 | javaClass.getConstructorCallsFromSelf().stream()
39 | .filter(constructor -> constructor.getTarget().isAnnotatedWith(Deprecated.class) ||
40 | constructor.getTarget().getParameterTypes().stream()
41 | .anyMatch(Deprecations::isDeprecated))
42 | .forEach(constructor -> events.add(SimpleConditionEvent.violated(constructor,
43 | "Constructor %s in class %s uses deprecated APIs".formatted(
44 | constructor.getTarget().getFullName(),
45 | javaClass.getName()))));
46 |
47 | javaClass.getDirectDependenciesFromSelf().stream()
48 | .filter(dependency -> dependency.getTargetClass().isAnnotatedWith(Deprecated.class))
49 | .forEach(dependency -> events.add(
50 | SimpleConditionEvent.violated(dependency.getTargetClass(),
51 | "Class %s depends on deprecated class %s".formatted(
52 | javaClass.getName(),
53 | dependency.getTargetClass().getName()))));
54 | }
55 | }.as("no usage of deprecated APIs");
56 | }
57 |
58 | private static boolean isDeprecated(JavaType javaType) {
59 | return javaType.toErasure().isAnnotatedWith(Deprecated.class);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/com/enofex/taikai/java/HashCodeAndEquals.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.java;
2 |
3 | import com.tngtech.archunit.core.domain.JavaClass;
4 | import com.tngtech.archunit.lang.ArchCondition;
5 | import com.tngtech.archunit.lang.ConditionEvents;
6 | import com.tngtech.archunit.lang.SimpleConditionEvent;
7 |
8 | final class HashCodeAndEquals {
9 |
10 | private HashCodeAndEquals() {
11 | }
12 |
13 | static ArchCondition implementHashCodeAndEquals() {
14 | return new ArchCondition<>("implement both equals() and hashCode()") {
15 | @Override
16 | public void check(JavaClass javaClass, ConditionEvents events) {
17 | boolean hasEquals = hasEquals(javaClass);
18 | boolean hasHashCode = hasHashCode(javaClass);
19 |
20 | if (hasEquals && !hasHashCode) {
21 | events.add(SimpleConditionEvent.violated(javaClass,
22 | "Class %s implements equals() but not hashCode()".formatted(
23 | javaClass.getName())));
24 | } else if (!hasEquals && hasHashCode) {
25 | events.add(SimpleConditionEvent.violated(javaClass,
26 | "Class %s implements hashCode() but not equals()".formatted(
27 | javaClass.getName())));
28 | }
29 | }
30 |
31 | private static boolean hasHashCode(JavaClass javaClass) {
32 | return javaClass.getMethods().stream()
33 | .anyMatch(method -> "hashCode".equals(method.getName()) &&
34 | method.getRawParameterTypes().isEmpty());
35 | }
36 |
37 | private static boolean hasEquals(JavaClass javaClass) {
38 | return javaClass.getMethods().stream()
39 | .anyMatch(method -> "equals".equals(method.getName()) &&
40 | method.getRawParameterTypes().size() == 1 &&
41 | method.getRawParameterTypes().get(0).getName().equals(Object.class.getName()));
42 | }
43 | };
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/com/enofex/taikai/java/ImportsConfigurer.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.java;
2 |
3 | import static com.enofex.taikai.TaikaiRule.Configuration.defaultConfiguration;
4 | import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
5 | import static com.tngtech.archunit.library.dependencies.SlicesRuleDefinition.slices;
6 |
7 | import com.enofex.taikai.TaikaiException;
8 | import com.enofex.taikai.TaikaiRule;
9 | import com.enofex.taikai.TaikaiRule.Configuration;
10 | import com.enofex.taikai.configures.AbstractConfigurer;
11 | import com.enofex.taikai.configures.ConfigurerContext;
12 | import com.enofex.taikai.configures.DisableableConfigurer;
13 |
14 | public class ImportsConfigurer extends AbstractConfigurer {
15 |
16 | ImportsConfigurer(ConfigurerContext configurerContext) {
17 | super(configurerContext);
18 | }
19 |
20 | public ImportsConfigurer shouldNotImport(String packageIdentifier) {
21 | return shouldNotImport(packageIdentifier, defaultConfiguration());
22 | }
23 |
24 | public ImportsConfigurer shouldNotImport(String packageIdentifier, Configuration configuration) {
25 | return addRule(TaikaiRule.of(noClasses()
26 | .should().accessClassesThat()
27 | .resideInAPackage(packageIdentifier)
28 | .as("No classes should have imports from package %s".formatted(packageIdentifier)),
29 | configuration));
30 | }
31 |
32 | public ImportsConfigurer shouldNotImport(String regex, String notImportClassesRegex) {
33 | return shouldNotImport(regex, notImportClassesRegex, defaultConfiguration());
34 | }
35 |
36 | public ImportsConfigurer shouldNotImport(String regex, String notImportClassesRegex,
37 | Configuration configuration) {
38 | return addRule(TaikaiRule.of(noClasses()
39 | .that().haveNameMatching(regex)
40 | .should().accessClassesThat()
41 | .haveNameMatching(notImportClassesRegex)
42 | .as("No classes that have name matching %s should have imports %s".formatted(
43 | regex, notImportClassesRegex)), configuration));
44 | }
45 |
46 | public ImportsConfigurer shouldHaveNoCycles() {
47 | return shouldHaveNoCycles(null);
48 | }
49 |
50 | public ImportsConfigurer shouldHaveNoCycles(Configuration configuration) {
51 | String namespace = configuration != null
52 | ? configuration.namespace()
53 | : configurerContext() != null
54 | ? configurerContext().namespace()
55 | : null;
56 |
57 | if (namespace == null) {
58 | throw new TaikaiException("Namespace is not set");
59 | }
60 |
61 | return addRule(TaikaiRule.of(slices()
62 | .matching(namespace + ".(*)..")
63 | .should().beFreeOfCycles()
64 | .as("Namespace %s should be free of cycles".formatted(namespace)), configuration));
65 | }
66 |
67 | public static final class Disableable extends ImportsConfigurer implements DisableableConfigurer {
68 |
69 | public Disableable(ConfigurerContext configurerContext) {
70 | super(configurerContext);
71 | }
72 |
73 | @Override
74 | public ImportsConfigurer disable() {
75 | disable(ImportsConfigurer.class);
76 |
77 | return this;
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/main/java/com/enofex/taikai/java/NamingConfigurer.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.java;
2 |
3 | import static com.enofex.taikai.TaikaiRule.Configuration.defaultConfiguration;
4 | import static com.enofex.taikai.java.ConstantNaming.shouldFollowConstantNamingConventions;
5 | import static com.enofex.taikai.java.PackageNaming.resideInPackageWithProperNamingConvention;
6 | import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
7 | import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.fields;
8 | import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods;
9 | import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
10 | import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noFields;
11 | import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noMethods;
12 |
13 | import com.enofex.taikai.TaikaiRule;
14 | import com.enofex.taikai.TaikaiRule.Configuration;
15 | import com.enofex.taikai.configures.AbstractConfigurer;
16 | import com.enofex.taikai.configures.ConfigurerContext;
17 | import com.enofex.taikai.configures.DisableableConfigurer;
18 | import com.tngtech.archunit.core.domain.JavaClass;
19 | import com.tngtech.archunit.lang.ArchCondition;
20 | import com.tngtech.archunit.lang.ConditionEvents;
21 | import com.tngtech.archunit.lang.SimpleConditionEvent;
22 | import java.lang.annotation.Annotation;
23 |
24 | public class NamingConfigurer extends AbstractConfigurer {
25 |
26 | private static final String PACKAGE_NAME_REGEX = "^[a-z_]+(\\.[a-z_][a-z0-9_]*)*$";
27 |
28 | NamingConfigurer(ConfigurerContext configurerContext) {
29 | super(configurerContext);
30 | }
31 |
32 | public NamingConfigurer packagesShouldMatchDefault() {
33 | return packagesShouldMatch(PACKAGE_NAME_REGEX, defaultConfiguration());
34 | }
35 |
36 | public NamingConfigurer packagesShouldMatchDefault(Configuration configuration) {
37 | return packagesShouldMatch(PACKAGE_NAME_REGEX, configuration);
38 | }
39 |
40 | public NamingConfigurer packagesShouldMatch(String regex) {
41 | return packagesShouldMatch(regex, defaultConfiguration());
42 | }
43 |
44 | public NamingConfigurer packagesShouldMatch(String regex, Configuration configuration) {
45 | return addRule(TaikaiRule.of(classes()
46 | .should(resideInPackageWithProperNamingConvention(regex))
47 | .as("Packages should have names matching %s".formatted(regex)), configuration));
48 | }
49 |
50 | public NamingConfigurer classesShouldNotMatch(String regex) {
51 | return classesShouldNotMatch(regex, defaultConfiguration());
52 | }
53 |
54 | public NamingConfigurer classesShouldNotMatch(String regex, Configuration configuration) {
55 | return addRule(TaikaiRule.of(noClasses()
56 | .should().haveNameMatching(regex)
57 | .as("Classes should not have names matching %s".formatted(regex)), configuration));
58 | }
59 |
60 | public NamingConfigurer classesAnnotatedWithShouldMatch(
61 | Class extends Annotation> annotationType, String regex) {
62 | return classesAnnotatedWithShouldMatch(annotationType.getName(), regex, defaultConfiguration());
63 | }
64 |
65 | public NamingConfigurer classesAnnotatedWithShouldMatch(
66 | Class extends Annotation> annotationType, String regex, Configuration configuration) {
67 | return classesAnnotatedWithShouldMatch(annotationType.getName(), regex, configuration);
68 | }
69 |
70 | public NamingConfigurer classesAnnotatedWithShouldMatch(String annotationType, String regex) {
71 | return classesAnnotatedWithShouldMatch(annotationType, regex, defaultConfiguration());
72 | }
73 |
74 | public NamingConfigurer classesAnnotatedWithShouldMatch(String annotationType, String regex,
75 | Configuration configuration) {
76 | return addRule(TaikaiRule.of(classes()
77 | .that().areMetaAnnotatedWith(annotationType)
78 | .should().haveNameMatching(regex)
79 | .as("Classes annotated with %s should have names matching %s".formatted(
80 | annotationType, regex)), configuration));
81 | }
82 |
83 | public NamingConfigurer classesImplementingShouldMatch(Class> clazz, String regex) {
84 | return classesImplementingShouldMatch(clazz.getName(), regex, defaultConfiguration());
85 | }
86 |
87 | public NamingConfigurer classesImplementingShouldMatch(Class> clazz, String regex,
88 | Configuration configuration) {
89 | return classesImplementingShouldMatch(clazz.getName(), regex, configuration);
90 | }
91 |
92 | public NamingConfigurer classesImplementingShouldMatch(String typeName, String regex) {
93 | return classesImplementingShouldMatch(typeName, regex, defaultConfiguration());
94 | }
95 |
96 | public NamingConfigurer classesImplementingShouldMatch(String typeName, String regex,
97 | Configuration configuration) {
98 | return addRule(TaikaiRule.of(classes()
99 | .that().implement(typeName)
100 | .should().haveNameMatching(regex)
101 | .as("Classes implementing %s should have names matching %s".formatted(
102 | typeName, regex)), configuration));
103 | }
104 |
105 | public NamingConfigurer classesAssignableToShouldMatch(Class> clazz, String regex) {
106 | return classesAssignableToShouldMatch(clazz.getName(), regex, defaultConfiguration());
107 | }
108 |
109 | public NamingConfigurer classesAssignableToShouldMatch(Class> clazz, String regex,
110 | Configuration configuration) {
111 | return classesAssignableToShouldMatch(clazz.getName(), regex, configuration);
112 | }
113 |
114 | public NamingConfigurer classesAssignableToShouldMatch(String typeName, String regex) {
115 | return classesAssignableToShouldMatch(typeName, regex, defaultConfiguration());
116 | }
117 |
118 | public NamingConfigurer classesAssignableToShouldMatch(String typeName, String regex,
119 | Configuration configuration) {
120 | return addRule(TaikaiRule.of(classes()
121 | .that().areAssignableTo(typeName)
122 | .should().haveNameMatching(regex)
123 | .as("Classes assignable to %s should have names matching %s".formatted(
124 | typeName, regex)), configuration));
125 | }
126 |
127 | public NamingConfigurer methodsAnnotatedWithShouldMatch(
128 | Class extends Annotation> annotationType, String regex) {
129 | return methodsAnnotatedWithShouldMatch(annotationType.getName(), regex, defaultConfiguration());
130 | }
131 |
132 | public NamingConfigurer methodsAnnotatedWithShouldMatch(
133 | Class extends Annotation> annotationType, String regex, Configuration configuration) {
134 | return methodsAnnotatedWithShouldMatch(annotationType.getName(), regex, configuration);
135 | }
136 |
137 | public NamingConfigurer methodsAnnotatedWithShouldMatch(
138 | String annotationType, String regex) {
139 | return methodsAnnotatedWithShouldMatch(annotationType, regex, defaultConfiguration());
140 | }
141 |
142 | public NamingConfigurer methodsAnnotatedWithShouldMatch(String annotationType, String regex,
143 | Configuration configuration) {
144 | return addRule(TaikaiRule.of(methods()
145 | .that().areMetaAnnotatedWith(annotationType)
146 | .should().haveNameMatching(regex)
147 | .as("Methods annotated with %s should have names matching %s".formatted(
148 | annotationType, regex)), configuration));
149 | }
150 |
151 | public NamingConfigurer methodsShouldNotMatch(String regex) {
152 | return methodsShouldNotMatch(regex, defaultConfiguration());
153 | }
154 |
155 | public NamingConfigurer methodsShouldNotMatch(String regex, Configuration configuration) {
156 | return addRule(TaikaiRule.of(noMethods()
157 | .should().haveNameMatching(regex)
158 | .as("Methods should not have names matching %s".formatted(regex)), configuration));
159 | }
160 |
161 | public NamingConfigurer fieldsAnnotatedWithShouldMatch(
162 | Class extends Annotation> annotationType, String regex) {
163 | return fieldsAnnotatedWithShouldMatch(annotationType.getName(), regex, defaultConfiguration());
164 | }
165 |
166 | public NamingConfigurer fieldsAnnotatedWithShouldMatch(
167 | Class extends Annotation> annotationType, String regex, Configuration configuration) {
168 | return fieldsAnnotatedWithShouldMatch(annotationType.getName(), regex, configuration);
169 | }
170 |
171 | public NamingConfigurer fieldsAnnotatedWithShouldMatch(String annotationType, String regex) {
172 | return fieldsAnnotatedWithShouldMatch(annotationType, regex, defaultConfiguration());
173 | }
174 |
175 | public NamingConfigurer fieldsAnnotatedWithShouldMatch(String annotationType, String regex,
176 | Configuration configuration) {
177 | return addRule(TaikaiRule.of(fields()
178 | .that().areMetaAnnotatedWith(annotationType)
179 | .should().haveNameMatching(regex)
180 | .as("Fields annotated with %s should have names matching %s".formatted(
181 | annotationType, regex)), configuration));
182 | }
183 |
184 | public NamingConfigurer fieldsShouldMatch(String typeName, String regex) {
185 | return fieldsShouldMatch(typeName, regex, defaultConfiguration());
186 | }
187 |
188 | public NamingConfigurer fieldsShouldMatch(Class> clazz, String regex) {
189 | return fieldsShouldMatch(clazz.getName(), regex, defaultConfiguration());
190 | }
191 |
192 | public NamingConfigurer fieldsShouldMatch(Class> clazz, String regex,
193 | Configuration configuration) {
194 | return fieldsShouldMatch(clazz.getName(), regex, configuration);
195 | }
196 |
197 | public NamingConfigurer fieldsShouldMatch(String typeName, String regex,
198 | Configuration configuration) {
199 | return addRule(TaikaiRule.of(fields()
200 | .that().haveRawType(typeName)
201 | .should().haveNameMatching(regex)
202 | .as("Fields of type %s should have names matching %s".formatted(typeName, regex)),
203 | configuration));
204 | }
205 |
206 | public NamingConfigurer fieldsShouldNotMatch(String regex) {
207 | return fieldsShouldNotMatch(regex, defaultConfiguration());
208 | }
209 |
210 | public NamingConfigurer fieldsShouldNotMatch(String regex, Configuration configuration) {
211 | return addRule(TaikaiRule.of(noFields()
212 | .should().haveNameMatching(regex)
213 | .as("Fields should not have names matching %s".formatted(regex)), configuration));
214 | }
215 |
216 | public NamingConfigurer interfacesShouldNotHavePrefixI() {
217 | return interfacesShouldNotHavePrefixI(defaultConfiguration());
218 | }
219 |
220 | public NamingConfigurer interfacesShouldNotHavePrefixI(Configuration configuration) {
221 | return addRule(TaikaiRule.of(classes()
222 | .that().areInterfaces()
223 | .should(notBePrefixedWithI())
224 | .as("Interfaces should not be prefixed with I"), configuration));
225 | }
226 |
227 | private static ArchCondition notBePrefixedWithI() {
228 | return new ArchCondition<>("not be prefixed with I") {
229 | @Override
230 | public void check(JavaClass javaClass, ConditionEvents events) {
231 | if (javaClass.getSimpleName().startsWith("I") && Character.isUpperCase(
232 | javaClass.getSimpleName().charAt(1))) {
233 | events.add(SimpleConditionEvent.violated(javaClass, javaClass.getSimpleName()));
234 | }
235 | }
236 | };
237 | }
238 |
239 | public NamingConfigurer constantsShouldFollowConventions() {
240 | return constantsShouldFollowConventions(defaultConfiguration());
241 | }
242 |
243 | public NamingConfigurer constantsShouldFollowConventions(Configuration configuration) {
244 | return addRule(TaikaiRule.of(fields()
245 | .that().areFinal().and().areStatic()
246 | .should(shouldFollowConstantNamingConventions())
247 | .as("Constants should follow constant naming conventions"), configuration));
248 | }
249 |
250 | public static final class Disableable extends NamingConfigurer implements DisableableConfigurer {
251 |
252 | public Disableable(ConfigurerContext configurerContext) {
253 | super(configurerContext);
254 | }
255 |
256 | @Override
257 | public NamingConfigurer disable() {
258 | disable(NamingConfigurer.class);
259 |
260 | return this;
261 | }
262 | }
263 | }
264 |
--------------------------------------------------------------------------------
/src/main/java/com/enofex/taikai/java/NoSystemOutOrErr.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.java;
2 |
3 | import com.tngtech.archunit.core.domain.JavaClass;
4 | import com.tngtech.archunit.lang.ArchCondition;
5 | import com.tngtech.archunit.lang.ConditionEvents;
6 | import com.tngtech.archunit.lang.SimpleConditionEvent;
7 |
8 | final class NoSystemOutOrErr {
9 |
10 | private NoSystemOutOrErr() {
11 | }
12 |
13 | static ArchCondition notUseSystemOutOrErr() {
14 | return new ArchCondition<>("not call System.out or System.err") {
15 | @Override
16 | public void check(JavaClass javaClass, ConditionEvents events) {
17 | javaClass.getFieldAccessesFromSelf().stream()
18 | .filter(fieldAccess -> fieldAccess.getTargetOwner().isEquivalentTo(System.class))
19 | .forEach(fieldAccess -> {
20 | String fieldName = fieldAccess.getTarget().getName();
21 |
22 | if ("out".equals(fieldName) || "err".equals(fieldName)) {
23 | events.add(SimpleConditionEvent.violated(fieldAccess,
24 | "Method %s calls %s.%s".formatted(
25 | fieldAccess.getOrigin().getFullName(),
26 | fieldAccess.getTargetOwner().getName(),
27 | fieldAccess.getTarget().getName())));
28 | }
29 | });
30 | }
31 | };
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/enofex/taikai/java/PackageNaming.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.java;
2 |
3 | import com.tngtech.archunit.core.domain.JavaClass;
4 | import com.tngtech.archunit.lang.ArchCondition;
5 | import com.tngtech.archunit.lang.ConditionEvents;
6 | import com.tngtech.archunit.lang.SimpleConditionEvent;
7 | import java.util.regex.Pattern;
8 |
9 | final class PackageNaming {
10 |
11 | private PackageNaming() {
12 | }
13 |
14 | static ArchCondition resideInPackageWithProperNamingConvention(String regex) {
15 | return new ArchCondition<>("reside in package with proper naming convention") {
16 | private final Pattern pattern = Pattern.compile(regex);
17 |
18 | @Override
19 | public void check(JavaClass javaClass, ConditionEvents events) {
20 | String packageName = javaClass.getPackageName();
21 | if (!this.pattern.matcher(packageName).matches()) {
22 | events.add(SimpleConditionEvent.violated(javaClass,
23 | "Package '%s' does not follow the naming convention".formatted(
24 | packageName)));
25 | }
26 | }
27 | };
28 | }
29 | }
--------------------------------------------------------------------------------
/src/main/java/com/enofex/taikai/java/ProtectedMembers.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.java;
2 |
3 | import static com.enofex.taikai.internal.Modifiers.isFieldProtected;
4 | import static com.enofex.taikai.internal.Modifiers.isMethodProtected;
5 |
6 | import com.tngtech.archunit.core.domain.JavaClass;
7 | import com.tngtech.archunit.core.domain.JavaField;
8 | import com.tngtech.archunit.core.domain.JavaMethod;
9 | import com.tngtech.archunit.lang.ArchCondition;
10 | import com.tngtech.archunit.lang.ConditionEvents;
11 | import com.tngtech.archunit.lang.SimpleConditionEvent;
12 |
13 | final class ProtectedMembers {
14 |
15 | private ProtectedMembers() {
16 | }
17 |
18 | static ArchCondition notHaveProtectedMembers() {
19 | return new ArchCondition<>("not have protected members") {
20 | @Override
21 | public void check(JavaClass javaClass, ConditionEvents events) {
22 | for (JavaField field : javaClass.getFields()) {
23 | if (isFieldProtected(field)) {
24 | events.add(SimpleConditionEvent.violated(field,
25 | "Field %s in final class %s is protected".formatted(
26 | field.getName(),
27 | javaClass.getName())));
28 | }
29 | }
30 |
31 | for (JavaMethod method : javaClass.getMethods()) {
32 | if (isMethodProtected(method)) {
33 | events.add(SimpleConditionEvent.violated(method,
34 | "Method %s in final class %s is protected".formatted(
35 | method.getName(),
36 | javaClass.getName())));
37 | }
38 | }
39 | }
40 | };
41 | }
42 | }
--------------------------------------------------------------------------------
/src/main/java/com/enofex/taikai/java/SerialVersionUID.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.java;
2 |
3 | import static com.enofex.taikai.internal.Modifiers.isFieldFinal;
4 | import static com.enofex.taikai.internal.Modifiers.isFieldStatic;
5 |
6 | import com.tngtech.archunit.base.DescribedPredicate;
7 | import com.tngtech.archunit.core.domain.JavaField;
8 | import com.tngtech.archunit.lang.ArchCondition;
9 | import com.tngtech.archunit.lang.ConditionEvents;
10 | import com.tngtech.archunit.lang.SimpleConditionEvent;
11 |
12 | final class SerialVersionUID {
13 |
14 | private SerialVersionUID() {
15 | }
16 |
17 | static ArchCondition beStaticFinalLong() {
18 | return new ArchCondition<>("be static final long") {
19 | @Override
20 | public void check(JavaField javaField, ConditionEvents events) {
21 | if (!isFieldStatic(javaField) || !isFieldFinal(javaField) || !isLong(javaField)) {
22 | events.add(SimpleConditionEvent.violated(javaField,
23 | "Field %s in class %s is not static final long".formatted(
24 | javaField.getName(),
25 | javaField.getOwner().getName())));
26 | }
27 | }
28 |
29 | private static boolean isLong(JavaField javaField) {
30 | return javaField.getRawType().isEquivalentTo(long.class);
31 | }
32 | };
33 | }
34 |
35 | static DescribedPredicate namedSerialVersionUID() {
36 | return new DescribedPredicate<>("named serialVersionUID") {
37 | @Override
38 | public boolean test(JavaField javaField) {
39 | return "serialVersionUID".equals(javaField.getName());
40 | }
41 | };
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/com/enofex/taikai/java/UtilityClasses.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.java;
2 |
3 | import static com.enofex.taikai.internal.Modifiers.isMethodStatic;
4 | import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
5 |
6 | import com.enofex.taikai.internal.Modifiers;
7 | import com.tngtech.archunit.base.DescribedPredicate;
8 | import com.tngtech.archunit.core.domain.JavaClass;
9 | import com.tngtech.archunit.lang.ArchCondition;
10 | import com.tngtech.archunit.lang.ConditionEvents;
11 | import com.tngtech.archunit.lang.SimpleConditionEvent;
12 | import com.tngtech.archunit.lang.syntax.elements.GivenClassesConjunction;
13 |
14 | final class UtilityClasses {
15 |
16 | private UtilityClasses() {
17 | }
18 |
19 | static GivenClassesConjunction utilityClasses() {
20 | return classes().that(haveOnlyStaticMethods());
21 | }
22 |
23 | private static DescribedPredicate haveOnlyStaticMethods() {
24 | return new DescribedPredicate<>("have only static methods") {
25 | @Override
26 | public boolean test(JavaClass javaClass) {
27 | return !javaClass.getMethods().isEmpty() && javaClass.getMethods().stream()
28 | .allMatch(method -> isMethodStatic(method) && !"main".equals(method.getName()));
29 | }
30 | };
31 | }
32 |
33 | static ArchCondition havePrivateConstructor() {
34 | return new ArchCondition<>("have a private constructor") {
35 | @Override
36 | public void check(JavaClass javaClass, ConditionEvents events) {
37 | if (hasNoPrivateConstructor(javaClass)) {
38 | events.add(SimpleConditionEvent.violated(javaClass,
39 | "Class %s does not have a private constructor".formatted(
40 | javaClass.getName())));
41 | }
42 | }
43 |
44 | private static boolean hasNoPrivateConstructor(JavaClass javaClass) {
45 | return javaClass.getConstructors().stream().noneMatch(Modifiers::isConstructorPrivate);
46 | }
47 | };
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/enofex/taikai/logging/LoggerConventions.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.logging;
2 |
3 | import com.tngtech.archunit.core.domain.JavaClass;
4 | import com.tngtech.archunit.core.domain.JavaField;
5 | import com.tngtech.archunit.core.domain.JavaModifier;
6 | import com.tngtech.archunit.lang.ArchCondition;
7 | import com.tngtech.archunit.lang.ConditionEvents;
8 | import com.tngtech.archunit.lang.SimpleConditionEvent;
9 | import java.util.Collection;
10 |
11 | final class LoggerConventions {
12 |
13 | private LoggerConventions() {
14 | }
15 |
16 | static ArchCondition followLoggerConventions(String typeName, String regex,
17 | Collection requiredModifiers) {
18 | return new ArchCondition<>(
19 | "have a logger field of type %s with name pattern %s and modifiers %s".formatted(
20 | typeName, regex, requiredModifiers)) {
21 | @Override
22 | public void check(JavaClass javaClass, ConditionEvents events) {
23 | for (JavaField field : javaClass.getAllFields()) {
24 | if (field.getRawType().isAssignableTo(typeName)) {
25 | if (!field.getName().matches(regex)) {
26 | events.add(SimpleConditionEvent.violated(field,
27 | "Field '%s' in class %s does not match the naming pattern '%s'".formatted(
28 | field.getName(),
29 | javaClass.getName(), regex)));
30 | }
31 |
32 | if (!field.getModifiers().containsAll(requiredModifiers)) {
33 | events.add(SimpleConditionEvent.violated(field,
34 | "Field '%s' in class %s does not have the required modifiers %s".formatted(
35 | field.getName(),
36 | javaClass.getName(),
37 | requiredModifiers)));
38 | }
39 | }
40 | }
41 | }
42 | };
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/main/java/com/enofex/taikai/logging/LoggingConfigurer.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.logging;
2 |
3 | import static com.enofex.taikai.TaikaiRule.Configuration.defaultConfiguration;
4 | import static com.enofex.taikai.internal.ArchConditions.haveFieldOfType;
5 | import static com.enofex.taikai.logging.LoggerConventions.followLoggerConventions;
6 | import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
7 |
8 | import com.enofex.taikai.TaikaiRule;
9 | import com.enofex.taikai.TaikaiRule.Configuration;
10 | import com.enofex.taikai.configures.AbstractConfigurer;
11 | import com.enofex.taikai.configures.ConfigurerContext;
12 | import com.enofex.taikai.configures.DisableableConfigurer;
13 | import com.tngtech.archunit.core.domain.JavaModifier;
14 | import java.util.Collection;
15 | import java.util.List;
16 |
17 | public class LoggingConfigurer extends AbstractConfigurer {
18 |
19 | public LoggingConfigurer(ConfigurerContext configurerContext) {
20 | super(configurerContext);
21 | }
22 |
23 | public LoggingConfigurer classesShouldUseLogger(String typeName, String regex) {
24 | return classesShouldUseLogger(typeName, regex, defaultConfiguration());
25 | }
26 |
27 | public LoggingConfigurer classesShouldUseLogger(Class> clazz, String regex) {
28 | return classesShouldUseLogger(clazz.getName(), regex, defaultConfiguration());
29 | }
30 |
31 | public LoggingConfigurer classesShouldUseLogger(Class> clazz, String regex,
32 | Configuration configuration) {
33 | return classesShouldUseLogger(clazz.getName(), regex, configuration);
34 | }
35 |
36 | public LoggingConfigurer classesShouldUseLogger(String typeName, String regex,
37 | Configuration configuration) {
38 | return addRule(TaikaiRule.of(classes()
39 | .that().haveNameMatching(regex)
40 | .should(haveFieldOfType(typeName))
41 | .as("Classes with names matching %s should use a logger of type %s".formatted(regex,
42 | typeName)),
43 | configuration));
44 | }
45 |
46 | public LoggingConfigurer loggersShouldFollowConventions(String typeName, String regex) {
47 | return loggersShouldFollowConventions(typeName, regex, List.of(),
48 | defaultConfiguration());
49 | }
50 |
51 | public LoggingConfigurer loggersShouldFollowConventions(String typeName, String regex,
52 | Configuration configuration) {
53 | return loggersShouldFollowConventions(typeName, regex, List.of(),
54 | configuration);
55 | }
56 |
57 | public LoggingConfigurer loggersShouldFollowConventions(Class> clazz, String regex) {
58 | return loggersShouldFollowConventions(clazz.getName(), regex, List.of(),
59 | defaultConfiguration());
60 | }
61 |
62 | public LoggingConfigurer loggersShouldFollowConventions(Class> clazz, String regex,
63 | Configuration configuration) {
64 | return loggersShouldFollowConventions(clazz.getName(), regex, List.of(),
65 | configuration);
66 | }
67 |
68 | public LoggingConfigurer loggersShouldFollowConventions(String typeName, String regex,
69 | Collection requiredModifiers) {
70 | return loggersShouldFollowConventions(typeName, regex, requiredModifiers,
71 | defaultConfiguration());
72 | }
73 |
74 | public LoggingConfigurer loggersShouldFollowConventions(Class> clazz, String regex,
75 | Collection requiredModifiers) {
76 | return loggersShouldFollowConventions(clazz.getName(), regex, requiredModifiers,
77 | defaultConfiguration());
78 | }
79 |
80 |
81 | public LoggingConfigurer loggersShouldFollowConventions(Class> clazz, String regex,
82 | Collection requiredModifiers, Configuration configuration) {
83 | return loggersShouldFollowConventions(clazz.getName(), regex, requiredModifiers,
84 | configuration);
85 | }
86 |
87 | public LoggingConfigurer loggersShouldFollowConventions(String typeName, String regex,
88 | Collection requiredModifiers, Configuration configuration) {
89 | return addRule(TaikaiRule.of(classes()
90 | .should(followLoggerConventions(typeName, regex, requiredModifiers))
91 | .as("Loggers in classes matching %s should follow conventions and be of type %s with required modifiers %s".formatted(
92 | regex, typeName, requiredModifiers)),
93 | configuration));
94 | }
95 |
96 | public static final class Disableable extends LoggingConfigurer implements DisableableConfigurer {
97 |
98 | public Disableable(ConfigurerContext configurerContext) {
99 | super(configurerContext);
100 | }
101 |
102 | @Override
103 | public LoggingConfigurer disable() {
104 | disable(LoggingConfigurer.class);
105 |
106 | return this;
107 | }
108 | }
109 | }
--------------------------------------------------------------------------------
/src/main/java/com/enofex/taikai/spring/BootConfigurer.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.spring;
2 |
3 | import static com.enofex.taikai.TaikaiRule.Configuration.defaultConfiguration;
4 | import static com.enofex.taikai.spring.SpringDescribedPredicates.ANNOTATION_SPRING_BOOT_APPLICATION;
5 | import static com.enofex.taikai.spring.SpringDescribedPredicates.annotatedWithSpringBootApplication;
6 | import static com.tngtech.archunit.lang.conditions.ArchPredicates.are;
7 | import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
8 | import static java.util.Objects.requireNonNull;
9 |
10 | import com.enofex.taikai.TaikaiRule;
11 | import com.enofex.taikai.TaikaiRule.Configuration;
12 | import com.enofex.taikai.configures.AbstractConfigurer;
13 | import com.enofex.taikai.configures.ConfigurerContext;
14 | import com.enofex.taikai.configures.DisableableConfigurer;
15 |
16 | public class BootConfigurer extends AbstractConfigurer {
17 |
18 | BootConfigurer(ConfigurerContext configurerContext) {
19 | super(configurerContext);
20 | }
21 |
22 | public BootConfigurer springBootApplicationShouldBeIn(String packageIdentifier) {
23 | requireNonNull(packageIdentifier);
24 |
25 | return springBootApplicationShouldBeIn(packageIdentifier, defaultConfiguration());
26 | }
27 |
28 | public BootConfigurer springBootApplicationShouldBeIn(String packageIdentifier, Configuration configuration) {
29 | return addRule(TaikaiRule.of(classes()
30 | .that(are(annotatedWithSpringBootApplication(true)))
31 | .should().resideInAPackage(packageIdentifier)
32 | .allowEmptyShould(false)
33 | .as("Classes annotated with %s should be located in %s".formatted(
34 | ANNOTATION_SPRING_BOOT_APPLICATION, packageIdentifier)), configuration));
35 | }
36 |
37 | public static final class Disableable extends BootConfigurer implements DisableableConfigurer {
38 |
39 | public Disableable(ConfigurerContext configurerContext) {
40 | super(configurerContext);
41 | }
42 |
43 | @Override
44 | public BootConfigurer disable() {
45 | disable(BootConfigurer.class);
46 |
47 | return this;
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/com/enofex/taikai/spring/ConfigurationsConfigurer.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.spring;
2 |
3 | import static com.enofex.taikai.TaikaiRule.Configuration.defaultConfiguration;
4 | import static com.enofex.taikai.spring.SpringDescribedPredicates.annotatedWithConfiguration;
5 | import static com.enofex.taikai.spring.SpringDescribedPredicates.annotatedWithSpringBootApplication;
6 | import static com.tngtech.archunit.base.DescribedPredicate.not;
7 | import static com.tngtech.archunit.lang.conditions.ArchPredicates.are;
8 | import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
9 |
10 | import com.enofex.taikai.TaikaiRule;
11 | import com.enofex.taikai.TaikaiRule.Configuration;
12 | import com.enofex.taikai.configures.AbstractConfigurer;
13 | import com.enofex.taikai.configures.ConfigurerContext;
14 | import com.enofex.taikai.configures.DisableableConfigurer;
15 |
16 | public class ConfigurationsConfigurer extends AbstractConfigurer {
17 |
18 | private static final String DEFAULT_CONFIGURATION_NAME_MATCHING = ".+Configuration";
19 |
20 | ConfigurationsConfigurer(ConfigurerContext configurerContext) {
21 | super(configurerContext);
22 | }
23 |
24 | public ConfigurationsConfigurer namesShouldEndWithConfiguration() {
25 | return namesShouldMatch(DEFAULT_CONFIGURATION_NAME_MATCHING, defaultConfiguration());
26 | }
27 |
28 | public ConfigurationsConfigurer namesShouldEndWithConfiguration(Configuration configuration) {
29 | return namesShouldMatch(DEFAULT_CONFIGURATION_NAME_MATCHING, configuration);
30 | }
31 |
32 | public ConfigurationsConfigurer namesShouldMatch(String regex) {
33 | return namesShouldMatch(regex, defaultConfiguration());
34 | }
35 |
36 | public ConfigurationsConfigurer namesShouldMatch(String regex, Configuration configuration) {
37 | return addRule(TaikaiRule.of(classes()
38 | .that(are(annotatedWithConfiguration(true)
39 | .and(not(annotatedWithSpringBootApplication(true))))
40 | )
41 | .should().haveNameMatching(regex)
42 | .as("Configurations should have name ending %s".formatted(regex)), configuration));
43 | }
44 |
45 | public static final class Disableable extends ConfigurationsConfigurer implements
46 | DisableableConfigurer {
47 |
48 | public Disableable(ConfigurerContext configurerContext) {
49 | super(configurerContext);
50 | }
51 |
52 | @Override
53 | public ConfigurationsConfigurer disable() {
54 | disable(ConfigurationsConfigurer.class);
55 |
56 | return this;
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/com/enofex/taikai/spring/ControllersConfigurer.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.spring;
2 |
3 | import static com.enofex.taikai.TaikaiRule.Configuration.defaultConfiguration;
4 | import static com.enofex.taikai.spring.ValidatedController.beAnnotatedWithValidated;
5 | import static com.enofex.taikai.spring.SpringDescribedPredicates.ANNOTATION_CONTROLLER;
6 | import static com.enofex.taikai.spring.SpringDescribedPredicates.ANNOTATION_REST_CONTROLLER;
7 | import static com.enofex.taikai.spring.SpringDescribedPredicates.ANNOTATION_VALIDATED;
8 | import static com.enofex.taikai.spring.SpringDescribedPredicates.annotatedWithController;
9 | import static com.enofex.taikai.spring.SpringDescribedPredicates.annotatedWithControllerOrRestController;
10 | import static com.enofex.taikai.spring.SpringDescribedPredicates.annotatedWithRestController;
11 | import static com.tngtech.archunit.lang.conditions.ArchConditions.be;
12 | import static com.tngtech.archunit.lang.conditions.ArchConditions.dependOnClassesThat;
13 | import static com.tngtech.archunit.lang.conditions.ArchConditions.not;
14 | import static com.tngtech.archunit.lang.conditions.ArchPredicates.are;
15 | import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
16 |
17 | import com.enofex.taikai.TaikaiRule;
18 | import com.enofex.taikai.TaikaiRule.Configuration;
19 | import com.enofex.taikai.configures.AbstractConfigurer;
20 | import com.enofex.taikai.configures.ConfigurerContext;
21 | import com.enofex.taikai.configures.DisableableConfigurer;
22 |
23 | public class ControllersConfigurer extends AbstractConfigurer {
24 |
25 | private static final String DEFAULT_CONTROLLER_NAME_MATCHING = ".+Controller";
26 |
27 | ControllersConfigurer(ConfigurerContext configurerContext) {
28 | super(configurerContext);
29 | }
30 |
31 | public ControllersConfigurer namesShouldEndWithController() {
32 | return namesShouldMatch(DEFAULT_CONTROLLER_NAME_MATCHING, defaultConfiguration());
33 | }
34 |
35 | public ControllersConfigurer namesShouldEndWithController(Configuration configuration) {
36 | return namesShouldMatch(DEFAULT_CONTROLLER_NAME_MATCHING, configuration);
37 | }
38 |
39 | public ControllersConfigurer namesShouldMatch(String regex) {
40 | return namesShouldMatch(regex, defaultConfiguration());
41 | }
42 |
43 | public ControllersConfigurer namesShouldMatch(String regex, Configuration configuration) {
44 | return addRule(TaikaiRule.of(classes()
45 | .that(are(annotatedWithControllerOrRestController(true)))
46 | .should().haveNameMatching(regex)
47 | .as("Controllers should have name ending %s".formatted(regex)), configuration));
48 | }
49 |
50 | public ControllersConfigurer shouldBeAnnotatedWithRestController() {
51 | return shouldBeAnnotatedWithRestController(DEFAULT_CONTROLLER_NAME_MATCHING,
52 | defaultConfiguration());
53 | }
54 |
55 | public ControllersConfigurer shouldBeAnnotatedWithRestController(Configuration configuration) {
56 | return shouldBeAnnotatedWithRestController(DEFAULT_CONTROLLER_NAME_MATCHING, configuration);
57 | }
58 |
59 | public ControllersConfigurer shouldBeAnnotatedWithRestController(String regex) {
60 | return shouldBeAnnotatedWithRestController(regex, defaultConfiguration());
61 | }
62 |
63 | public ControllersConfigurer shouldBeAnnotatedWithRestController(String regex,
64 | Configuration configuration) {
65 | return addRule(TaikaiRule.of(classes()
66 | .that().haveNameMatching(regex)
67 | .should(be(annotatedWithRestController(true)))
68 | .as("Controllers should be annotated with %s".formatted(ANNOTATION_REST_CONTROLLER)),
69 | configuration));
70 | }
71 |
72 | public ControllersConfigurer shouldBeAnnotatedWithController() {
73 | return shouldBeAnnotatedWithController(DEFAULT_CONTROLLER_NAME_MATCHING,
74 | defaultConfiguration());
75 | }
76 |
77 | public ControllersConfigurer shouldBeAnnotatedWithController(Configuration configuration) {
78 | return shouldBeAnnotatedWithController(DEFAULT_CONTROLLER_NAME_MATCHING, configuration);
79 | }
80 |
81 | public ControllersConfigurer shouldBeAnnotatedWithController(String regex) {
82 | return shouldBeAnnotatedWithController(regex, defaultConfiguration());
83 | }
84 |
85 | public ControllersConfigurer shouldBeAnnotatedWithController(String regex,
86 | Configuration configuration) {
87 | return addRule(TaikaiRule.of(classes()
88 | .that().haveNameMatching(regex)
89 | .should(be(annotatedWithController(true)))
90 | .as("Controllers should be annotated with %s".formatted(ANNOTATION_CONTROLLER)),
91 | configuration));
92 | }
93 |
94 | public ControllersConfigurer shouldBePackagePrivate() {
95 | return shouldBePackagePrivate(defaultConfiguration());
96 | }
97 |
98 | public ControllersConfigurer shouldBePackagePrivate(Configuration configuration) {
99 | return addRule(TaikaiRule.of(classes()
100 | .that(are(annotatedWithControllerOrRestController(true)))
101 | .should().bePackagePrivate()
102 | .as("Controllers should be package-private"), configuration));
103 | }
104 |
105 | public ControllersConfigurer shouldNotDependOnOtherControllers() {
106 | return shouldNotDependOnOtherControllers(defaultConfiguration());
107 | }
108 |
109 | public ControllersConfigurer shouldNotDependOnOtherControllers(Configuration configuration) {
110 | return addRule(TaikaiRule.of(classes()
111 | .that(are(annotatedWithControllerOrRestController(true)))
112 | .should(not(dependOnClassesThat(are(annotatedWithControllerOrRestController(true)))))
113 | .as("Controllers should not be depend on other Controllers"), configuration));
114 | }
115 |
116 | public ControllersConfigurer shouldBeAnnotatedWithValidated(String regex) {
117 | return shouldBeAnnotatedWithValidated(regex, defaultConfiguration());
118 | }
119 |
120 | public ControllersConfigurer shouldBeAnnotatedWithValidated(String regex, Configuration configuration) {
121 | return addRule(TaikaiRule.of(classes()
122 | .that().haveNameMatching(regex)
123 | .should(beAnnotatedWithValidated())
124 | .as("Validation annotations on @RequestParam or @PathVariable require the controller to be annotated with %s."
125 | .formatted(ANNOTATION_VALIDATED)),
126 | configuration));
127 | }
128 |
129 | public ControllersConfigurer shouldBeAnnotatedWithValidated() {
130 | return shouldBeAnnotatedWithValidated(defaultConfiguration());
131 | }
132 |
133 | public ControllersConfigurer shouldBeAnnotatedWithValidated(Configuration configuration) {
134 | return addRule(TaikaiRule.of(classes()
135 | .that(are(annotatedWithControllerOrRestController(true)))
136 | .should(beAnnotatedWithValidated())
137 | .as("Validation annotations on @RequestParam or @PathVariable require the controller to be annotated with %s."
138 | .formatted(ANNOTATION_VALIDATED)),
139 | configuration));
140 | }
141 |
142 | public static final class Disableable extends ControllersConfigurer implements
143 | DisableableConfigurer {
144 |
145 | public Disableable(ConfigurerContext configurerContext) {
146 | super(configurerContext);
147 | }
148 |
149 | @Override
150 | public ControllersConfigurer disable() {
151 | disable(ControllersConfigurer.class);
152 |
153 | return this;
154 | }
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/src/main/java/com/enofex/taikai/spring/PropertiesConfigurer.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.spring;
2 |
3 | import static com.enofex.taikai.TaikaiRule.Configuration.defaultConfiguration;
4 | import static com.enofex.taikai.spring.SpringDescribedPredicates.ANNOTATION_CONFIGURATION_PROPERTIES;
5 | import static com.enofex.taikai.spring.SpringDescribedPredicates.ANNOTATION_VALIDATED;
6 | import static com.enofex.taikai.spring.SpringDescribedPredicates.annotatedWithConfigurationProperties;
7 | import static com.tngtech.archunit.lang.conditions.ArchConditions.be;
8 | import static com.tngtech.archunit.lang.conditions.ArchPredicates.are;
9 | import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
10 |
11 | import com.enofex.taikai.TaikaiRule;
12 | import com.enofex.taikai.TaikaiRule.Configuration;
13 | import com.enofex.taikai.configures.AbstractConfigurer;
14 | import com.enofex.taikai.configures.ConfigurerContext;
15 | import com.enofex.taikai.configures.DisableableConfigurer;
16 |
17 | public class PropertiesConfigurer extends AbstractConfigurer {
18 |
19 | private static final String DEFAULT_PROPERTIES_NAME_MATCHING = ".+Properties";
20 |
21 | PropertiesConfigurer(ConfigurerContext configurerContext) {
22 | super(configurerContext);
23 | }
24 |
25 | public PropertiesConfigurer namesShouldEndWithProperties() {
26 | return namesShouldMatch(DEFAULT_PROPERTIES_NAME_MATCHING, defaultConfiguration());
27 | }
28 |
29 | public PropertiesConfigurer namesShouldEndWithProperties(Configuration configuration) {
30 | return namesShouldMatch(DEFAULT_PROPERTIES_NAME_MATCHING, configuration);
31 | }
32 |
33 | public PropertiesConfigurer namesShouldMatch(String regex) {
34 | return namesShouldMatch(regex, defaultConfiguration());
35 | }
36 |
37 | public PropertiesConfigurer namesShouldMatch(String regex, Configuration configuration) {
38 | return addRule(TaikaiRule.of(classes()
39 | .that(are(annotatedWithConfigurationProperties(true)))
40 | .should().haveNameMatching(regex)
41 | .as("Properties should have name ending %s".formatted(regex)), configuration));
42 | }
43 |
44 | public PropertiesConfigurer shouldBeAnnotatedWithValidated() {
45 | return shouldBeAnnotatedWithValidated(defaultConfiguration());
46 | }
47 |
48 | public PropertiesConfigurer shouldBeAnnotatedWithValidated(Configuration configuration) {
49 | return addRule(TaikaiRule.of(classes()
50 | .that(are(annotatedWithConfigurationProperties(true)))
51 | .should().beMetaAnnotatedWith(ANNOTATION_VALIDATED)
52 | .as("Configuration properties annotated with %s should be annotated with %s as well".formatted(
53 | ANNOTATION_CONFIGURATION_PROPERTIES, ANNOTATION_VALIDATED)),
54 | configuration));
55 | }
56 |
57 | public PropertiesConfigurer shouldBeAnnotatedWithConfigurationProperties() {
58 | return shouldBeAnnotatedWithConfigurationProperties(DEFAULT_PROPERTIES_NAME_MATCHING,
59 | defaultConfiguration());
60 | }
61 |
62 | public PropertiesConfigurer shouldBeAnnotatedWithConfigurationProperties(
63 | Configuration configuration) {
64 | return shouldBeAnnotatedWithConfigurationProperties(DEFAULT_PROPERTIES_NAME_MATCHING,
65 | configuration);
66 | }
67 |
68 | public PropertiesConfigurer shouldBeAnnotatedWithConfigurationProperties(String regex) {
69 | return shouldBeAnnotatedWithConfigurationProperties(regex, defaultConfiguration());
70 | }
71 |
72 | public PropertiesConfigurer shouldBeAnnotatedWithConfigurationProperties(String regex,
73 | Configuration configuration) {
74 | return addRule(TaikaiRule.of(classes()
75 | .that().haveNameMatching(regex)
76 | .should(be(annotatedWithConfigurationProperties(true)))
77 | .as("Configuration properties should be annotated with %s".formatted(
78 | ANNOTATION_CONFIGURATION_PROPERTIES)),
79 | configuration));
80 | }
81 |
82 | public static final class Disableable extends PropertiesConfigurer implements
83 | DisableableConfigurer {
84 |
85 | public Disableable(ConfigurerContext configurerContext) {
86 | super(configurerContext);
87 | }
88 |
89 | @Override
90 | public PropertiesConfigurer disable() {
91 | disable(PropertiesConfigurer.class);
92 |
93 | return this;
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/main/java/com/enofex/taikai/spring/RepositoriesConfigurer.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.spring;
2 |
3 | import static com.enofex.taikai.TaikaiRule.Configuration.defaultConfiguration;
4 | import static com.enofex.taikai.spring.SpringDescribedPredicates.ANNOTATION_REPOSITORY;
5 | import static com.enofex.taikai.spring.SpringDescribedPredicates.annotatedWithRepository;
6 | import static com.enofex.taikai.spring.SpringDescribedPredicates.annotatedWithService;
7 | import static com.tngtech.archunit.lang.conditions.ArchConditions.be;
8 | import static com.tngtech.archunit.lang.conditions.ArchConditions.dependOnClassesThat;
9 | import static com.tngtech.archunit.lang.conditions.ArchConditions.not;
10 | import static com.tngtech.archunit.lang.conditions.ArchPredicates.are;
11 | import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
12 |
13 | import com.enofex.taikai.TaikaiRule;
14 | import com.enofex.taikai.TaikaiRule.Configuration;
15 | import com.enofex.taikai.configures.AbstractConfigurer;
16 | import com.enofex.taikai.configures.ConfigurerContext;
17 | import com.enofex.taikai.configures.DisableableConfigurer;
18 |
19 | public class RepositoriesConfigurer extends AbstractConfigurer {
20 |
21 | private static final String DEFAULT_REPOSITORY_NAME_MATCHING = ".+Repository";
22 |
23 | RepositoriesConfigurer(ConfigurerContext configurerContext) {
24 | super(configurerContext);
25 | }
26 |
27 | public RepositoriesConfigurer namesShouldEndWithRepository() {
28 | return namesShouldMatch(DEFAULT_REPOSITORY_NAME_MATCHING, defaultConfiguration());
29 | }
30 |
31 | public RepositoriesConfigurer namesShouldEndWithRepository(Configuration configuration) {
32 | return namesShouldMatch(DEFAULT_REPOSITORY_NAME_MATCHING, configuration);
33 | }
34 |
35 | public RepositoriesConfigurer namesShouldMatch(String regex) {
36 | return namesShouldMatch(regex, defaultConfiguration());
37 | }
38 |
39 | public RepositoriesConfigurer namesShouldMatch(String regex, Configuration configuration) {
40 | return addRule(TaikaiRule.of(classes()
41 | .that(are(annotatedWithRepository(true)))
42 | .should().haveNameMatching(regex)
43 | .as("Repositories should have name ending %s".formatted(regex)), configuration));
44 | }
45 |
46 | public RepositoriesConfigurer shouldBeAnnotatedWithRepository() {
47 | return shouldBeAnnotatedWithRepository(DEFAULT_REPOSITORY_NAME_MATCHING,
48 | defaultConfiguration());
49 | }
50 |
51 | public RepositoriesConfigurer shouldBeAnnotatedWithRepository(Configuration configuration) {
52 | return shouldBeAnnotatedWithRepository(DEFAULT_REPOSITORY_NAME_MATCHING, configuration);
53 | }
54 |
55 | public RepositoriesConfigurer shouldBeAnnotatedWithRepository(String regex) {
56 | return shouldBeAnnotatedWithRepository(regex, defaultConfiguration());
57 | }
58 |
59 | public RepositoriesConfigurer shouldBeAnnotatedWithRepository(String regex,
60 | Configuration configuration) {
61 | return addRule(TaikaiRule.of(classes()
62 | .that().haveNameMatching(regex)
63 | .should(be(annotatedWithRepository(true)))
64 | .as("Repositories should be annotated with %s".formatted(ANNOTATION_REPOSITORY)),
65 | configuration));
66 | }
67 |
68 | public RepositoriesConfigurer shouldNotDependOnServices() {
69 | return shouldNotDependOnServices(defaultConfiguration());
70 | }
71 |
72 | public RepositoriesConfigurer shouldNotDependOnServices(Configuration configuration) {
73 | return addRule(TaikaiRule.of(classes()
74 | .that(are(annotatedWithRepository(true)))
75 | .should(not(dependOnClassesThat(annotatedWithService(true))))
76 | .as("Repositories should not depend on Services"),
77 | configuration));
78 | }
79 |
80 | public static final class Disableable extends RepositoriesConfigurer implements
81 | DisableableConfigurer {
82 |
83 | public Disableable(ConfigurerContext configurerContext) {
84 | super(configurerContext);
85 | }
86 |
87 | @Override
88 | public RepositoriesConfigurer disable() {
89 | disable(RepositoriesConfigurer.class);
90 |
91 | return this;
92 | }
93 | }
94 | }
95 |
96 |
--------------------------------------------------------------------------------
/src/main/java/com/enofex/taikai/spring/ServicesConfigurer.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.spring;
2 |
3 | import static com.enofex.taikai.TaikaiRule.Configuration.defaultConfiguration;
4 | import static com.enofex.taikai.spring.SpringDescribedPredicates.ANNOTATION_SERVICE;
5 | import static com.enofex.taikai.spring.SpringDescribedPredicates.annotatedWithControllerOrRestController;
6 | import static com.enofex.taikai.spring.SpringDescribedPredicates.annotatedWithService;
7 | import static com.tngtech.archunit.lang.conditions.ArchConditions.be;
8 | import static com.tngtech.archunit.lang.conditions.ArchConditions.dependOnClassesThat;
9 | import static com.tngtech.archunit.lang.conditions.ArchConditions.not;
10 | import static com.tngtech.archunit.lang.conditions.ArchPredicates.are;
11 | import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
12 |
13 | import com.enofex.taikai.TaikaiRule;
14 | import com.enofex.taikai.TaikaiRule.Configuration;
15 | import com.enofex.taikai.configures.AbstractConfigurer;
16 | import com.enofex.taikai.configures.ConfigurerContext;
17 | import com.enofex.taikai.configures.DisableableConfigurer;
18 |
19 | public class ServicesConfigurer extends AbstractConfigurer {
20 |
21 | private static final String DEFAULT_SERVICE_NAME_MATCHING = ".+Service";
22 |
23 | ServicesConfigurer(ConfigurerContext configurerContext) {
24 | super(configurerContext);
25 | }
26 |
27 | public ServicesConfigurer namesShouldEndWithService() {
28 | return namesShouldMatch(DEFAULT_SERVICE_NAME_MATCHING, defaultConfiguration());
29 | }
30 |
31 | public ServicesConfigurer namesShouldEndWithService(Configuration configuration) {
32 | return namesShouldMatch(DEFAULT_SERVICE_NAME_MATCHING, configuration);
33 | }
34 |
35 | public ServicesConfigurer namesShouldMatch(String regex) {
36 | return namesShouldMatch(regex, defaultConfiguration());
37 | }
38 |
39 | public ServicesConfigurer namesShouldMatch(String regex, Configuration configuration) {
40 | return addRule(TaikaiRule.of(classes()
41 | .that(are(annotatedWithService(true)))
42 | .should().haveNameMatching(regex)
43 | .as("Services should have name ending %s".formatted(regex)), configuration));
44 | }
45 |
46 | public ServicesConfigurer shouldBeAnnotatedWithService() {
47 | return shouldBeAnnotatedWithService(DEFAULT_SERVICE_NAME_MATCHING, defaultConfiguration());
48 | }
49 |
50 | public ServicesConfigurer shouldBeAnnotatedWithService(Configuration configuration) {
51 | return shouldBeAnnotatedWithService(DEFAULT_SERVICE_NAME_MATCHING, configuration);
52 | }
53 |
54 | public ServicesConfigurer shouldBeAnnotatedWithService(String regex) {
55 | return shouldBeAnnotatedWithService(regex, defaultConfiguration());
56 | }
57 |
58 | public ServicesConfigurer shouldBeAnnotatedWithService(String regex,
59 | Configuration configuration) {
60 | return addRule(TaikaiRule.of(classes()
61 | .that().haveNameMatching(regex)
62 | .should(be(annotatedWithService(true)))
63 | .as("Services should be annotated with %s".formatted(ANNOTATION_SERVICE)),
64 | configuration));
65 | }
66 |
67 | public ServicesConfigurer shouldNotDependOnControllers() {
68 | return shouldNotDependOnControllers(defaultConfiguration());
69 | }
70 |
71 | public ServicesConfigurer shouldNotDependOnControllers(Configuration configuration) {
72 | return addRule(TaikaiRule.of(classes()
73 | .that(are(annotatedWithService(true)))
74 | .should(not(dependOnClassesThat(annotatedWithControllerOrRestController(true))))
75 | .as("Services should not depend on Controllers or RestControllers"),
76 | configuration));
77 | }
78 |
79 | public static final class Disableable extends ServicesConfigurer implements
80 | DisableableConfigurer {
81 |
82 | public Disableable(ConfigurerContext configurerContext) {
83 | super(configurerContext);
84 | }
85 |
86 | @Override
87 | public ServicesConfigurer disable() {
88 | disable(ServicesConfigurer.class);
89 |
90 | return this;
91 | }
92 | }
93 | }
94 |
95 |
--------------------------------------------------------------------------------
/src/main/java/com/enofex/taikai/spring/SpringConfigurer.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.spring;
2 |
3 | import static com.enofex.taikai.TaikaiRule.Configuration.defaultConfiguration;
4 | import static com.enofex.taikai.spring.SpringDescribedPredicates.ANNOTATION_AUTOWIRED;
5 | import static com.enofex.taikai.spring.SpringDescribedPredicates.annotatedWithAutowired;
6 | import static com.tngtech.archunit.lang.conditions.ArchConditions.be;
7 | import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noFields;
8 |
9 | import com.enofex.taikai.TaikaiRule;
10 | import com.enofex.taikai.TaikaiRule.Configuration;
11 | import com.enofex.taikai.configures.AbstractConfigurer;
12 | import com.enofex.taikai.configures.ConfigurerContext;
13 | import com.enofex.taikai.configures.Customizer;
14 | import com.enofex.taikai.configures.DisableableConfigurer;
15 |
16 | public class SpringConfigurer extends AbstractConfigurer {
17 |
18 | public SpringConfigurer(ConfigurerContext configurerContext) {
19 | super(configurerContext);
20 | }
21 |
22 | public Disableable properties(Customizer customizer) {
23 | return customizer(customizer, () -> new PropertiesConfigurer.Disableable(configurerContext()));
24 | }
25 |
26 | public Disableable configurations(
27 | Customizer customizer) {
28 | return customizer(customizer, () -> new ConfigurationsConfigurer.Disableable(configurerContext()));
29 | }
30 |
31 | public Disableable controllers(
32 | Customizer customizer) {
33 | return customizer(customizer, () -> new ControllersConfigurer.Disableable(configurerContext()));
34 | }
35 |
36 | public Disableable services(Customizer customizer) {
37 | return customizer(customizer, () -> new ServicesConfigurer.Disableable(configurerContext()));
38 | }
39 |
40 | public Disableable repositories(
41 | Customizer customizer) {
42 | return customizer(customizer, () -> new RepositoriesConfigurer.Disableable(configurerContext()));
43 | }
44 |
45 | public Disableable boot(Customizer customizer) {
46 | return customizer(customizer, () -> new BootConfigurer.Disableable(configurerContext()));
47 | }
48 |
49 | public SpringConfigurer noAutowiredFields() {
50 | return noAutowiredFields(defaultConfiguration());
51 | }
52 |
53 | public SpringConfigurer noAutowiredFields(Configuration configuration) {
54 | return addRule(TaikaiRule.of(noFields()
55 | .should(be(annotatedWithAutowired(true)))
56 | .as("No fields should be annotated with %s, use constructor injection".formatted(
57 | ANNOTATION_AUTOWIRED)), configuration));
58 | }
59 |
60 | public static final class Disableable extends SpringConfigurer implements DisableableConfigurer {
61 |
62 | public Disableable(ConfigurerContext configurerContext) {
63 | super(configurerContext);
64 | }
65 |
66 | @Override
67 | public SpringConfigurer disable() {
68 | disable(SpringConfigurer.class);
69 | disable(PropertiesConfigurer.class);
70 | disable(ConfigurationsConfigurer.class);
71 | disable(ControllersConfigurer.class);
72 | disable(ServicesConfigurer.class);
73 | disable(RepositoriesConfigurer.class);
74 | disable(BootConfigurer.class);
75 |
76 | return this;
77 | }
78 | }
79 |
80 | }
--------------------------------------------------------------------------------
/src/main/java/com/enofex/taikai/spring/SpringDescribedPredicates.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.spring;
2 |
3 | import static com.enofex.taikai.internal.DescribedPredicates.annotatedWith;
4 |
5 | import com.tngtech.archunit.base.DescribedPredicate;
6 | import com.tngtech.archunit.core.domain.properties.CanBeAnnotated;
7 |
8 | final class SpringDescribedPredicates {
9 |
10 | static final String ANNOTATION_CONFIGURATION = "org.springframework.context.annotation.Configuration";
11 | static final String ANNOTATION_CONFIGURATION_PROPERTIES = "org.springframework.boot.context.properties.ConfigurationProperties";
12 | static final String ANNOTATION_CONTROLLER = "org.springframework.stereotype.Controller";
13 | static final String ANNOTATION_REST_CONTROLLER = "org.springframework.web.bind.annotation.RestController";
14 | static final String ANNOTATION_SERVICE = "org.springframework.stereotype.Service";
15 | static final String ANNOTATION_REPOSITORY = "org.springframework.stereotype.Repository";
16 | static final String ANNOTATION_SPRING_BOOT_APPLICATION = "org.springframework.boot.autoconfigure.SpringBootApplication";
17 | static final String ANNOTATION_AUTOWIRED = "org.springframework.beans.factory.annotation.Autowired";
18 | static final String ANNOTATION_VALIDATED = "org.springframework.validation.annotation.Validated";
19 |
20 | private SpringDescribedPredicates() {
21 | }
22 |
23 | static DescribedPredicate annotatedWithControllerOrRestController(
24 | boolean isMetaAnnotated) {
25 |
26 | return annotatedWith(ANNOTATION_CONTROLLER, isMetaAnnotated)
27 | .or(annotatedWith(ANNOTATION_REST_CONTROLLER, isMetaAnnotated));
28 | }
29 |
30 | static DescribedPredicate annotatedWithConfiguration(
31 | boolean isMetaAnnotated) {
32 | return annotatedWith(ANNOTATION_CONFIGURATION, isMetaAnnotated);
33 | }
34 |
35 | static DescribedPredicate annotatedWithConfigurationProperties(
36 | boolean isMetaAnnotated) {
37 | return annotatedWith(ANNOTATION_CONFIGURATION_PROPERTIES, isMetaAnnotated);
38 | }
39 |
40 | static DescribedPredicate annotatedWithRestController(boolean isMetaAnnotated) {
41 | return annotatedWith(ANNOTATION_REST_CONTROLLER, isMetaAnnotated);
42 | }
43 |
44 | static DescribedPredicate annotatedWithController(boolean isMetaAnnotated) {
45 | return annotatedWith(ANNOTATION_CONTROLLER, isMetaAnnotated);
46 | }
47 |
48 | static DescribedPredicate annotatedWithService(boolean isMetaAnnotated) {
49 | return annotatedWith(ANNOTATION_SERVICE, isMetaAnnotated);
50 | }
51 |
52 | static DescribedPredicate annotatedWithRepository(boolean isMetaAnnotated) {
53 | return annotatedWith(ANNOTATION_REPOSITORY, isMetaAnnotated);
54 | }
55 |
56 | static DescribedPredicate annotatedWithSpringBootApplication(
57 | boolean isMetaAnnotated) {
58 | return annotatedWith(ANNOTATION_SPRING_BOOT_APPLICATION, isMetaAnnotated);
59 | }
60 |
61 | static DescribedPredicate annotatedWithAutowired(boolean isMetaAnnotated) {
62 | return annotatedWith(ANNOTATION_AUTOWIRED, isMetaAnnotated);
63 | }
64 |
65 | static DescribedPredicate annotatedWithValidated(boolean isMetaAnnotated) {
66 | return annotatedWith(ANNOTATION_VALIDATED, isMetaAnnotated);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/com/enofex/taikai/spring/ValidatedController.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.spring;
2 |
3 | import static com.enofex.taikai.spring.SpringDescribedPredicates.ANNOTATION_VALIDATED;
4 |
5 | import com.tngtech.archunit.core.domain.JavaClass;
6 | import com.tngtech.archunit.core.domain.JavaMethod;
7 | import com.tngtech.archunit.core.domain.JavaParameter;
8 | import com.tngtech.archunit.lang.ArchCondition;
9 | import com.tngtech.archunit.lang.ConditionEvents;
10 | import com.tngtech.archunit.lang.SimpleConditionEvent;
11 |
12 | final class ValidatedController {
13 |
14 | private static final String REQUEST_PARAM = "org.springframework.web.bind.annotation.RequestParam";
15 | private static final String PATH_VARIABLE = "org.springframework.web.bind.annotation.PathVariable";
16 |
17 | private static final String JAVAX_VALIDATION_NOT_NULL = "javax.validation.constraints.NotNull";
18 | private static final String JAVAX_VALIDATION_MIN = "javax.validation.constraints.Min";
19 | private static final String JAVAX_VALIDATION_MAX = "javax.validation.constraints.Max";
20 | private static final String JAVAX_VALIDATION_SIZE = "javax.validation.constraints.Size";
21 | private static final String JAVAX_VALIDATION_NOT_BLANK = "javax.validation.constraints.NotBlank";
22 | private static final String JAVAX_VALIDATION_PATTERN = "javax.validation.constraints.Pattern";
23 |
24 | private static final String JAKARTA_VALIDATION_NOT_NULL = "jakarta.validation.constraints.NotNull";
25 | private static final String JAKARTA_VALIDATION_MIN = "jakarta.validation.constraints.Min";
26 | private static final String JAKARTA_VALIDATION_MAX = "jakarta.validation.constraints.Max";
27 | private static final String JAKARTA_VALIDATION_SIZE = "jakarta.validation.constraints.Size";
28 | private static final String JAKARTA_VALIDATION_NOT_BLANK = "jakarta.validation.constraints.NotBlank";
29 | private static final String JAKARTA_VALIDATION_PATTERN = "jakarta.validation.constraints.Pattern";
30 |
31 | private ValidatedController() {
32 | }
33 |
34 | static ArchCondition beAnnotatedWithValidated() {
35 | return new ArchCondition<>(
36 | "be annotated with @Validated if @RequestParam or @PathVariable has validation annotations") {
37 | @Override
38 | public void check(JavaClass controllerClass, ConditionEvents events) {
39 | boolean hasValidatedAnnotation = controllerClass.isMetaAnnotatedWith(ANNOTATION_VALIDATED);
40 |
41 | for (JavaMethod method : controllerClass.getMethods()) {
42 | for (JavaParameter parameter : method.getParameters()) {
43 | if ((parameter.isMetaAnnotatedWith(REQUEST_PARAM)
44 | || parameter.isMetaAnnotatedWith(PATH_VARIABLE))
45 | && (hasJavaXValidationAnnotations(parameter)
46 | || hasJakartaValidationAnnotations(parameter))
47 | ) {
48 |
49 | if (!hasValidatedAnnotation) {
50 | events.add(SimpleConditionEvent.violated(controllerClass,
51 | "Controller %s is missing @Validated but has a method parameter in %s annotated with @PathVariable or @RequestParam and a validation annotation.".formatted(
52 | controllerClass.getName(),
53 | method.getFullName())));
54 | }
55 | }
56 | }
57 | }
58 | }
59 |
60 | private boolean hasJavaXValidationAnnotations(JavaParameter parameter) {
61 | return parameter.isMetaAnnotatedWith(JAVAX_VALIDATION_NOT_NULL)
62 | || parameter.isMetaAnnotatedWith(JAVAX_VALIDATION_MIN)
63 | || parameter.isMetaAnnotatedWith(JAVAX_VALIDATION_MAX)
64 | || parameter.isMetaAnnotatedWith(JAVAX_VALIDATION_SIZE)
65 | || parameter.isMetaAnnotatedWith(JAVAX_VALIDATION_NOT_BLANK)
66 | || parameter.isMetaAnnotatedWith(JAVAX_VALIDATION_PATTERN);
67 | }
68 |
69 | private boolean hasJakartaValidationAnnotations(JavaParameter parameter) {
70 | return parameter.isMetaAnnotatedWith(JAKARTA_VALIDATION_NOT_NULL)
71 | || parameter.isMetaAnnotatedWith(JAKARTA_VALIDATION_MIN)
72 | || parameter.isMetaAnnotatedWith(JAKARTA_VALIDATION_MAX)
73 | || parameter.isMetaAnnotatedWith(JAKARTA_VALIDATION_SIZE)
74 | || parameter.isMetaAnnotatedWith(JAKARTA_VALIDATION_NOT_BLANK)
75 | || parameter.isMetaAnnotatedWith(JAKARTA_VALIDATION_PATTERN);
76 | }
77 | };
78 | }
79 | }
80 |
81 |
--------------------------------------------------------------------------------
/src/main/java/com/enofex/taikai/test/ContainAssertionsOrVerifications.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.test;
2 |
3 | import com.tngtech.archunit.core.domain.JavaMethod;
4 | import com.tngtech.archunit.core.domain.JavaMethodCall;
5 | import com.tngtech.archunit.lang.ArchCondition;
6 | import com.tngtech.archunit.lang.ConditionEvents;
7 | import com.tngtech.archunit.lang.SimpleConditionEvent;
8 |
9 | final class ContainAssertionsOrVerifications {
10 |
11 | private ContainAssertionsOrVerifications() {
12 | }
13 |
14 | static ArchCondition containAssertionsOrVerifications() {
15 | return new ArchCondition<>("a unit test should assert or verify something") {
16 | @Override
17 | public void check(JavaMethod item, ConditionEvents events) {
18 | for (JavaMethodCall call : item.getMethodCallsFromSelf()) {
19 | if (jUnit5(call) ||
20 | mockito(call) ||
21 | hamcrest(call) ||
22 | assertJ(call) ||
23 | truth(call) ||
24 | cucumber(call) ||
25 | springMockMvc(call) ||
26 | archRule(call) ||
27 | taikai(call)
28 | ) {
29 | return;
30 | }
31 | }
32 | events.add(SimpleConditionEvent.violated(
33 | item,
34 | "%s does not assert or verify anything".formatted(item.getDescription()))
35 | );
36 | }
37 |
38 | private boolean jUnit5(JavaMethodCall call) {
39 | return "org.junit.jupiter.api.Assertions".equals(call.getTargetOwner().getName());
40 | }
41 |
42 | private boolean mockito(JavaMethodCall call) {
43 | return "org.mockito.Mockito".equals(call.getTargetOwner().getName())
44 | && (call.getName().startsWith("verify")
45 | || "inOrder".equals(call.getName())
46 | || "capture".equals(call.getName()));
47 | }
48 |
49 | private boolean hamcrest(JavaMethodCall call) {
50 | return "org.hamcrest.MatcherAssert".equals(call.getTargetOwner().getName());
51 | }
52 |
53 | private boolean assertJ(JavaMethodCall call) {
54 | return "org.assertj.core.api.Assertions".equals(call.getTargetOwner().getName());
55 | }
56 |
57 | private boolean truth(JavaMethodCall call) {
58 | return "com.google.common.truth.Truth".equals(call.getTargetOwner().getName());
59 | }
60 |
61 | private boolean cucumber(JavaMethodCall call) {
62 | return "io.cucumber.java.en.Then".equals(call.getTargetOwner().getName()) ||
63 | "io.cucumber.java.en.Given".equals(call.getTargetOwner().getName());
64 | }
65 |
66 | private boolean springMockMvc(JavaMethodCall call) {
67 | return
68 | "org.springframework.test.web.servlet.ResultActions".equals(call.getTargetOwner().getName())
69 | && ("andExpect".equals(call.getName()) || "andExpectAll".equals(call.getName()));
70 | }
71 |
72 | private boolean archRule(JavaMethodCall call) {
73 | return "com.tngtech.archunit.lang.ArchRule".equals(call.getTargetOwner().getName())
74 | && "check".equals(call.getName());
75 | }
76 |
77 | private boolean taikai(JavaMethodCall call) {
78 | return "com.enofex.taikai.Taikai".equals(call.getTargetOwner().getName())
79 | && ("check".equals(call.getName()) || "checkAll".equals(call.getName())) ;
80 | }
81 | };
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/main/java/com/enofex/taikai/test/JUnit5Configurer.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.test;
2 |
3 | import static com.enofex.taikai.internal.ArchConditions.notDeclareThrownExceptions;
4 | import static com.enofex.taikai.test.ContainAssertionsOrVerifications.containAssertionsOrVerifications;
5 | import static com.enofex.taikai.test.JUnit5DescribedPredicates.ANNOTATION_DISABLED;
6 | import static com.enofex.taikai.test.JUnit5DescribedPredicates.ANNOTATION_DISPLAY_NAME;
7 | import static com.enofex.taikai.test.JUnit5DescribedPredicates.ANNOTATION_PARAMETRIZED_TEST;
8 | import static com.enofex.taikai.test.JUnit5DescribedPredicates.ANNOTATION_TEST;
9 | import static com.enofex.taikai.test.JUnit5DescribedPredicates.annotatedWithTestOrParameterizedTest;
10 | import static com.tngtech.archunit.lang.conditions.ArchPredicates.are;
11 | import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
12 | import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods;
13 | import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
14 | import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noMethods;
15 |
16 | import com.enofex.taikai.Namespace.IMPORT;
17 | import com.enofex.taikai.TaikaiRule;
18 | import com.enofex.taikai.TaikaiRule.Configuration;
19 | import com.enofex.taikai.configures.AbstractConfigurer;
20 | import com.enofex.taikai.configures.ConfigurerContext;
21 | import com.enofex.taikai.configures.DisableableConfigurer;
22 |
23 | public class JUnit5Configurer extends AbstractConfigurer {
24 |
25 | private static final Configuration CONFIGURATION = Configuration.of(IMPORT.ONLY_TESTS);
26 |
27 | JUnit5Configurer(ConfigurerContext configurerContext) {
28 | super(configurerContext);
29 | }
30 |
31 | public JUnit5Configurer methodsShouldMatch(String regex) {
32 | return methodsShouldMatch(regex, CONFIGURATION);
33 | }
34 |
35 | public JUnit5Configurer methodsShouldMatch(String regex, Configuration configuration) {
36 | return addRule(TaikaiRule.of(methods()
37 | .that(are(annotatedWithTestOrParameterizedTest(true)))
38 | .should().haveNameMatching(regex)
39 | .as("Methods annotated with %s or %s should have names matching %s".formatted(
40 | ANNOTATION_TEST, ANNOTATION_PARAMETRIZED_TEST, regex)),
41 | configuration));
42 | }
43 |
44 | public JUnit5Configurer methodsShouldNotDeclareExceptions() {
45 | return methodsShouldNotDeclareExceptions(CONFIGURATION);
46 | }
47 |
48 | public JUnit5Configurer methodsShouldNotDeclareExceptions(Configuration configuration) {
49 | return addRule(TaikaiRule.of(methods()
50 | .that(are(annotatedWithTestOrParameterizedTest(true)))
51 | .should(notDeclareThrownExceptions())
52 | .as("Methods annotated with %s or %s should not declare thrown Exceptions".formatted(
53 | ANNOTATION_TEST, ANNOTATION_PARAMETRIZED_TEST)),
54 | configuration));
55 | }
56 |
57 | public JUnit5Configurer methodsShouldBeAnnotatedWithDisplayName() {
58 | return methodsShouldBeAnnotatedWithDisplayName(CONFIGURATION);
59 | }
60 |
61 | public JUnit5Configurer methodsShouldBeAnnotatedWithDisplayName(Configuration configuration) {
62 | return addRule(TaikaiRule.of(methods()
63 | .that(are(annotatedWithTestOrParameterizedTest(true)))
64 | .should().beMetaAnnotatedWith(ANNOTATION_DISPLAY_NAME)
65 | .as("Methods annotated with %s or %s should be annotated with %s".formatted(ANNOTATION_TEST,
66 | ANNOTATION_PARAMETRIZED_TEST, ANNOTATION_DISPLAY_NAME)),
67 | configuration));
68 | }
69 |
70 | public JUnit5Configurer methodsShouldBePackagePrivate() {
71 | return methodsShouldBePackagePrivate(CONFIGURATION);
72 | }
73 |
74 | public JUnit5Configurer methodsShouldBePackagePrivate(Configuration configuration) {
75 | return addRule(TaikaiRule.of(methods()
76 | .that(are(annotatedWithTestOrParameterizedTest(true)))
77 | .should().bePackagePrivate()
78 | .as("Methods annotated with %s or %s should be package-private".formatted(ANNOTATION_TEST,
79 | ANNOTATION_PARAMETRIZED_TEST)),
80 | configuration));
81 | }
82 |
83 | public JUnit5Configurer methodsShouldNotBeAnnotatedWithDisabled() {
84 | return methodsShouldNotBeAnnotatedWithDisabled(CONFIGURATION);
85 | }
86 |
87 | public JUnit5Configurer methodsShouldNotBeAnnotatedWithDisabled(Configuration configuration) {
88 | return addRule(TaikaiRule.of(noMethods()
89 | .should().beMetaAnnotatedWith(ANNOTATION_DISABLED)
90 | .as("Methods should not be annotated with %s".formatted(ANNOTATION_DISABLED)),
91 | configuration));
92 | }
93 |
94 | public JUnit5Configurer methodsShouldContainAssertionsOrVerifications() {
95 | return methodsShouldContainAssertionsOrVerifications(CONFIGURATION);
96 | }
97 |
98 | public JUnit5Configurer methodsShouldContainAssertionsOrVerifications(
99 | Configuration configuration) {
100 | return addRule(TaikaiRule.of(methods()
101 | .that(are(annotatedWithTestOrParameterizedTest(true)))
102 | .should(containAssertionsOrVerifications())
103 | .as("Methods annotated with %s or %s should contain assertions or verifications".formatted(
104 | ANNOTATION_TEST, ANNOTATION_PARAMETRIZED_TEST)),
105 | configuration));
106 | }
107 |
108 | public JUnit5Configurer classesShouldNotBeAnnotatedWithDisabled() {
109 | return classesShouldNotBeAnnotatedWithDisabled(CONFIGURATION);
110 | }
111 |
112 | public JUnit5Configurer classesShouldBePackagePrivate(String regex) {
113 | return classesShouldBePackagePrivate(regex, CONFIGURATION);
114 | }
115 |
116 | public JUnit5Configurer classesShouldBePackagePrivate(String regex, Configuration configuration) {
117 | return addRule(TaikaiRule.of(classes()
118 | .that().areNotInterfaces().and().haveNameMatching(regex)
119 | .should().bePackagePrivate()
120 | .as("Classes with names matching %s should be package-private".formatted(regex)),
121 | configuration));
122 | }
123 |
124 |
125 | public JUnit5Configurer classesShouldNotBeAnnotatedWithDisabled(Configuration configuration) {
126 | return addRule(TaikaiRule.of(noClasses()
127 | .should().beMetaAnnotatedWith(ANNOTATION_DISABLED)
128 | .as("Classes should not be annotated with %s".formatted(ANNOTATION_DISABLED)),
129 | configuration));
130 | }
131 |
132 | public static final class Disableable extends JUnit5Configurer implements DisableableConfigurer {
133 |
134 | public Disableable(ConfigurerContext configurerContext) {
135 | super(configurerContext);
136 | }
137 |
138 | @Override
139 | public JUnit5Configurer disable() {
140 | disable(JUnit5Configurer.class);
141 |
142 | return this;
143 | }
144 | }
145 | }
--------------------------------------------------------------------------------
/src/main/java/com/enofex/taikai/test/JUnit5DescribedPredicates.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.test;
2 |
3 | import static com.enofex.taikai.internal.DescribedPredicates.annotatedWith;
4 |
5 | import com.tngtech.archunit.base.DescribedPredicate;
6 | import com.tngtech.archunit.core.domain.properties.CanBeAnnotated;
7 |
8 | final class JUnit5DescribedPredicates {
9 |
10 | static final String ANNOTATION_TEST = "org.junit.jupiter.api.Test";
11 | static final String ANNOTATION_PARAMETRIZED_TEST = "org.junit.jupiter.params.ParameterizedTest";
12 | static final String ANNOTATION_DISABLED = "org.junit.jupiter.api.Disabled";
13 | static final String ANNOTATION_DISPLAY_NAME = "org.junit.jupiter.api.DisplayName";
14 |
15 | private JUnit5DescribedPredicates() {
16 | }
17 |
18 | static DescribedPredicate annotatedWithTestOrParameterizedTest(
19 | boolean isMetaAnnotated) {
20 |
21 | return annotatedWith(ANNOTATION_TEST, isMetaAnnotated)
22 | .or(annotatedWith(ANNOTATION_PARAMETRIZED_TEST, isMetaAnnotated));
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/enofex/taikai/test/TestConfigurer.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.test;
2 |
3 | import com.enofex.taikai.configures.AbstractConfigurer;
4 | import com.enofex.taikai.configures.ConfigurerContext;
5 | import com.enofex.taikai.configures.Customizer;
6 | import com.enofex.taikai.configures.DisableableConfigurer;
7 |
8 | public class TestConfigurer extends AbstractConfigurer {
9 |
10 | public TestConfigurer(ConfigurerContext configurerContext) {
11 | super(configurerContext);
12 | }
13 |
14 | public Disableable junit5(Customizer customizer) {
15 | return customizer(customizer, () -> new JUnit5Configurer.Disableable(configurerContext()));
16 | }
17 |
18 | public static final class Disableable extends TestConfigurer implements DisableableConfigurer {
19 |
20 | public Disableable(ConfigurerContext configurerContext) {
21 | super(configurerContext);
22 | }
23 |
24 | @Override
25 | public TestConfigurer disable() {
26 | disable(TestConfigurer.class);
27 | disable(JUnit5Configurer.class);
28 |
29 | return this;
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/src/test/java/com/enofex/taikai/ArchitectureTest.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai;
2 |
3 | import static com.tngtech.archunit.core.domain.JavaModifier.FINAL;
4 | import static com.tngtech.archunit.core.domain.JavaModifier.STATIC;
5 |
6 | import java.text.SimpleDateFormat;
7 | import java.util.Calendar;
8 | import java.util.Date;
9 | import java.util.List;
10 | import org.junit.jupiter.api.Test;
11 |
12 | class ArchitectureTest {
13 |
14 | @Test
15 | void shouldFulfilConstrains() {
16 | Taikai.builder()
17 | .namespace("com.enofex.taikai")
18 | .java(java -> java
19 | .noUsageOfDeprecatedAPIs()
20 | .noUsageOfSystemOutOrErr()
21 | .noUsageOf(Date.class)
22 | .noUsageOf(Calendar.class)
23 | .noUsageOf(SimpleDateFormat.class)
24 | .fieldsShouldHaveModifiers("^[A-Z][A-Z0-9_]*$", List.of(STATIC, FINAL))
25 | .classesShouldImplementHashCodeAndEquals()
26 | .finalClassesShouldNotHaveProtectedMembers()
27 | .utilityClassesShouldBeFinalAndHavePrivateConstructor()
28 | .methodsShouldNotDeclareGenericExceptions()
29 | .fieldsShouldNotBePublic()
30 | .serialVersionUIDFieldsShouldBeStaticFinalLong()
31 | .classesShouldResideInPackage("com.enofex.taikai..")
32 | .imports(imports -> imports
33 | .shouldHaveNoCycles()
34 | .shouldNotImport("..shaded..")
35 | .shouldNotImport("..lombok..")
36 | .shouldNotImport("org.junit.."))
37 | .naming(naming -> naming
38 | .packagesShouldMatchDefault()
39 | .fieldsShouldNotMatch(".*(List|Set|Map)$")
40 | .classesShouldNotMatch(".*Impl")
41 | .interfacesShouldNotHavePrefixI()
42 | .constantsShouldFollowConventions()))
43 | .test(test -> test
44 | .junit5(junit5 -> junit5
45 | .classesShouldNotBeAnnotatedWithDisabled()
46 | .classesShouldBePackagePrivate(".*Test")
47 | .methodsShouldNotBeAnnotatedWithDisabled()
48 | .methodsShouldMatch("should.*")
49 | .methodsShouldBePackagePrivate()
50 | .methodsShouldNotDeclareExceptions()
51 | .methodsShouldContainAssertionsOrVerifications()))
52 | .build()
53 | .check();
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/test/java/com/enofex/taikai/NamespaceTest.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai;
2 |
3 | import com.tngtech.archunit.core.domain.JavaClasses;
4 | import com.tngtech.archunit.core.importer.ClassFileImporter;
5 | import com.tngtech.archunit.core.importer.ImportOption;
6 | import org.junit.jupiter.api.Test;
7 |
8 | import static org.junit.jupiter.api.Assertions.*;
9 |
10 | class NamespaceTest {
11 |
12 | private static final String VALID_NAMESPACE = "com.enofex.taikai";
13 |
14 | @Test
15 | void shouldReturnJavaClassesWithoutTests() {
16 | JavaClasses result = Namespace.from(VALID_NAMESPACE, Namespace.IMPORT.WITHOUT_TESTS);
17 |
18 | assertNotNull(result);
19 | assertDoesNotThrow(() -> new ClassFileImporter()
20 | .withImportOption(new ImportOption.DoNotIncludeTests())
21 | .withImportOption(new ImportOption.DoNotIncludeJars())
22 | .importPackages(VALID_NAMESPACE));
23 | }
24 |
25 | @Test
26 | void shouldReturnJavaClassesWithTests() {
27 | JavaClasses result = Namespace.from(VALID_NAMESPACE, Namespace.IMPORT.WITH_TESTS);
28 |
29 | assertNotNull(result);
30 | assertDoesNotThrow(() -> new ClassFileImporter()
31 | .withImportOption(new ImportOption.DoNotIncludeJars())
32 | .importPackages(VALID_NAMESPACE));
33 | }
34 |
35 | @Test
36 | void shouldReturnJavaClassesOnlyTests() {
37 | JavaClasses result = Namespace.from(VALID_NAMESPACE, Namespace.IMPORT.ONLY_TESTS);
38 |
39 | assertNotNull(result);
40 | assertDoesNotThrow(() -> new ClassFileImporter()
41 | .withImportOption(new ImportOption.OnlyIncludeTests())
42 | .withImportOption(new ImportOption.DoNotIncludeJars())
43 | .importPackages(VALID_NAMESPACE));
44 | }
45 |
46 | @Test
47 | void shouldThrowExceptionForNullNamespace() {
48 | assertThrows(NullPointerException.class,
49 | () -> Namespace.from(null, Namespace.IMPORT.WITHOUT_TESTS));
50 | }
51 |
52 | @Test
53 | void shouldThrowExceptionForNullImportOption() {
54 | assertThrows(NullPointerException.class, () -> Namespace.from(VALID_NAMESPACE, null));
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/test/java/com/enofex/taikai/TaikaiRuleTest.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertEquals;
4 | import static org.junit.jupiter.api.Assertions.assertNotNull;
5 | import static org.junit.jupiter.api.Assertions.assertSame;
6 | import static org.junit.jupiter.api.Assertions.assertThrows;
7 | import static org.mockito.Mockito.mock;
8 | import static org.mockito.Mockito.verify;
9 |
10 | import com.enofex.taikai.TaikaiRule.Configuration;
11 | import com.tngtech.archunit.core.domain.JavaClasses;
12 | import com.tngtech.archunit.lang.ArchRule;
13 | import org.junit.jupiter.api.Test;
14 |
15 | class TaikaiRuleTest {
16 |
17 | @Test
18 | void shouldConstructWithArchRuleAndDefaultConfiguration() {
19 | ArchRule archRule = mock(ArchRule.class);
20 | TaikaiRule taikaiRule = TaikaiRule.of(archRule);
21 |
22 | assertNotNull(taikaiRule);
23 | assertEquals(archRule, taikaiRule.archRule());
24 | assertNotNull(taikaiRule.configuration());
25 | }
26 |
27 | @Test
28 | void shouldConstructWithArchRuleAndGivenConfiguration() {
29 | ArchRule archRule = mock(ArchRule.class);
30 | Configuration configuration = Configuration.of("com.example");
31 | TaikaiRule taikaiRule = TaikaiRule.of(archRule, configuration);
32 |
33 | assertNotNull(taikaiRule);
34 | assertEquals(archRule, taikaiRule.archRule());
35 | assertSame(configuration, taikaiRule.configuration());
36 | }
37 |
38 | @Test
39 | void shouldThrowNullPointerExceptionWhenConstructedWithNullArchRule() {
40 | assertThrows(NullPointerException.class, () -> TaikaiRule.of(null));
41 | }
42 |
43 | @Test
44 | void shouldReturnConfigurationNamespace() {
45 | Configuration configuration = Configuration.of("com.example");
46 |
47 | assertEquals("com.example", configuration.namespace());
48 | }
49 |
50 | @Test
51 | void shouldReturnConfigurationNamespaceImport() {
52 | Configuration configuration = Configuration.of(Namespace.IMPORT.ONLY_TESTS);
53 |
54 | assertEquals(Namespace.IMPORT.ONLY_TESTS, configuration.namespaceImport());
55 | }
56 |
57 | @Test
58 | void shouldReturnConfigurationJavaClasses() {
59 | JavaClasses javaClasses = mock(JavaClasses.class);
60 | Configuration configuration = Configuration.of(javaClasses);
61 |
62 | assertEquals(javaClasses, configuration.javaClasses());
63 | }
64 |
65 | @Test
66 | void shouldCheckUsingGivenJavaClasses() {
67 | JavaClasses javaClasses = mock(JavaClasses.class);
68 | ArchRule archRule = mock(ArchRule.class);
69 | TaikaiRule.of(archRule, Configuration.of(javaClasses)).check(null);
70 |
71 | verify(archRule).check(javaClasses);
72 | }
73 |
74 | @Test
75 | void shouldCheckUsingNamespaceFromConfiguration() {
76 | ArchRule archRule = mock(ArchRule.class);
77 | TaikaiRule.of(archRule, Configuration.of("com.example", Namespace.IMPORT.WITH_TESTS))
78 | .check(null);
79 |
80 | verify(archRule).check(Namespace.from("com.example", Namespace.IMPORT.WITH_TESTS));
81 | }
82 |
83 | @Test
84 | void shouldCheckUsingGlobalNamespace() {
85 | ArchRule archRule = mock(ArchRule.class);
86 | TaikaiRule.of(archRule).check("com.example");
87 |
88 | verify(archRule).check(Namespace.from("com.example", Namespace.IMPORT.WITHOUT_TESTS));
89 | }
90 |
91 | @Test
92 | void shouldThrowTaikaiExceptionWhenNoNamespaceProvided() {
93 | ArchRule archRule = mock(ArchRule.class);
94 | TaikaiRule taikaiRule = TaikaiRule.of(archRule, Configuration.defaultConfiguration());
95 |
96 | assertThrows(TaikaiException.class, () -> taikaiRule.check(null));
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/test/java/com/enofex/taikai/TaikaiTest.java:
--------------------------------------------------------------------------------
1 |
2 | package com.enofex.taikai;
3 |
4 | import static java.util.Collections.emptyList;
5 | import static org.junit.jupiter.api.Assertions.assertEquals;
6 | import static org.junit.jupiter.api.Assertions.assertFalse;
7 | import static org.junit.jupiter.api.Assertions.assertNotNull;
8 | import static org.junit.jupiter.api.Assertions.assertNull;
9 | import static org.junit.jupiter.api.Assertions.assertThrows;
10 | import static org.junit.jupiter.api.Assertions.assertTrue;
11 | import static org.mockito.Mockito.any;
12 | import static org.mockito.Mockito.mock;
13 | import static org.mockito.Mockito.times;
14 | import static org.mockito.Mockito.verify;
15 |
16 | import com.enofex.taikai.configures.Customizer;
17 | import com.enofex.taikai.java.JavaConfigurer;
18 | import com.enofex.taikai.spring.SpringConfigurer.Disableable;
19 | import com.enofex.taikai.test.TestConfigurer;
20 | import com.tngtech.archunit.ArchConfiguration;
21 | import com.tngtech.archunit.core.importer.ClassFileImporter;
22 | import java.util.Collection;
23 | import java.util.Collections;
24 | import org.junit.jupiter.api.BeforeEach;
25 | import org.junit.jupiter.api.Test;
26 |
27 | class TaikaiTest {
28 |
29 | private static final String VALID_NAMESPACE = "com.enofex.taikai";
30 |
31 | @BeforeEach
32 | void setUp() {
33 | ArchConfiguration.get().reset();
34 | }
35 |
36 | @Test
37 | void shouldBuildTaikaiWithDefaultValues() {
38 | Taikai taikai = Taikai.builder()
39 | .namespace(VALID_NAMESPACE)
40 | .build();
41 |
42 | assertFalse(taikai.failOnEmpty());
43 | assertEquals(VALID_NAMESPACE, taikai.namespace());
44 | assertNull(taikai.classes());
45 | assertTrue(taikai.rules().isEmpty());
46 | assertTrue(taikai.excludedClasses().isEmpty());
47 | }
48 |
49 | @Test
50 | void shouldBuildTaikaiWithCustomValues() {
51 | TaikaiRule mockRule = mock(TaikaiRule.class);
52 | Collection rules = Collections.singletonList(mockRule);
53 |
54 | Taikai taikai = Taikai.builder()
55 | .classes(new ClassFileImporter().importClasses(TaikaiTest.class))
56 | .excludeClasses("com.enofex.taikai.foo.ClassToExclude", "com.enofex.taikai.bar.ClassToExclude")
57 | .failOnEmpty(true)
58 | .addRules(rules)
59 | .build();
60 |
61 | assertTrue(taikai.failOnEmpty());
62 | assertNull(taikai.namespace());
63 | assertNotNull(taikai.classes());
64 | assertEquals(1, taikai.rules().size());
65 | assertTrue(taikai.rules().contains(mockRule));
66 | assertEquals(2, taikai.excludedClasses().size());
67 | }
68 |
69 | @Test
70 | void shouldAddSingleRule() {
71 | TaikaiRule mockRule = mock(TaikaiRule.class);
72 |
73 | Taikai taikai = Taikai.builder()
74 | .namespace(VALID_NAMESPACE)
75 | .addRule(mockRule)
76 | .build();
77 |
78 | assertEquals(1, taikai.rules().size());
79 | assertTrue(taikai.rules().contains(mockRule));
80 | }
81 |
82 | @Test
83 | void shouldConfigureJavaCustomizer() {
84 | Customizer customizer = mock(Customizer.class);
85 |
86 | Taikai.builder()
87 | .namespace(VALID_NAMESPACE)
88 | .java(customizer)
89 | .build();
90 |
91 | verify(customizer, times(1)).customize(any(JavaConfigurer.Disableable.class));
92 | }
93 |
94 | @Test
95 | void shouldConfigureSpringCustomizer() {
96 | Customizer customizer = mock(Customizer.class);
97 |
98 | Taikai.builder()
99 | .namespace(VALID_NAMESPACE)
100 | .spring(customizer)
101 | .build();
102 |
103 | verify(customizer, times(1)).customize(any(Disableable.class));
104 | }
105 |
106 | @Test
107 | void shouldConfigureTestCustomizer() {
108 | Customizer customizer = mock(Customizer.class);
109 |
110 | Taikai.builder()
111 | .namespace(VALID_NAMESPACE)
112 | .test(customizer)
113 | .build();
114 |
115 | verify(customizer, times(1)).customize(any(TestConfigurer.Disableable.class));
116 | }
117 |
118 | @Test
119 | void shouldThrowExceptionForNullCustomizer() {
120 | assertThrows(NullPointerException.class, () -> Taikai.builder().java(null));
121 | assertThrows(NullPointerException.class, () -> Taikai.builder().spring(null));
122 | assertThrows(NullPointerException.class, () -> Taikai.builder().test(null));
123 | }
124 |
125 | @Test
126 | void shouldCheckRules() {
127 | TaikaiRule mockRule = mock(TaikaiRule.class);
128 |
129 | Taikai.builder()
130 | .namespace(VALID_NAMESPACE)
131 | .addRule(mockRule)
132 | .build()
133 | .check();
134 |
135 | verify(mockRule, times(1)).check(VALID_NAMESPACE, null, emptyList());
136 | }
137 |
138 | @Test
139 | void shouldRebuildTaikaiWithNewValues() {
140 | Taikai taikai = Taikai.builder()
141 | .namespace(VALID_NAMESPACE)
142 | .excludeClasses("com.enofex.taikai.ClassToExclude")
143 | .failOnEmpty(true)
144 | .java(java -> java
145 | .fieldsShouldNotBePublic())
146 | .build();
147 |
148 | Taikai modifiedTaikai = taikai.toBuilder()
149 | .namespace("com.enofex.newnamespace")
150 | .excludeClasses("com.enofex.taikai.AnotherClassToExclude")
151 | .failOnEmpty(false)
152 | .java(java -> java
153 | .classesShouldImplementHashCodeAndEquals()
154 | .finalClassesShouldNotHaveProtectedMembers())
155 | .build();
156 |
157 | assertFalse(modifiedTaikai.failOnEmpty());
158 | assertEquals("com.enofex.newnamespace", modifiedTaikai.namespace());
159 | assertEquals(2, modifiedTaikai.excludedClasses().size());
160 | assertEquals(3, modifiedTaikai.rules().size());
161 | assertTrue(modifiedTaikai.excludedClasses().contains("com.enofex.taikai.ClassToExclude"));
162 | assertTrue(
163 | modifiedTaikai.excludedClasses().contains("com.enofex.taikai.AnotherClassToExclude"));
164 | }
165 |
166 | @Test
167 | void shouldThrowExceptionIfNamespaceAndClasses() {
168 | assertThrows(IllegalArgumentException.class, () -> Taikai.builder()
169 | .namespace(VALID_NAMESPACE)
170 | .classes(new ClassFileImporter().importClasses(TaikaiTest.class))
171 | .build());
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/src/test/java/com/enofex/taikai/configures/ConfigurerContextTest.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.configures;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertEquals;
4 | import static org.junit.jupiter.api.Assertions.assertNull;
5 | import static org.junit.jupiter.api.Assertions.assertSame;
6 |
7 | import org.junit.jupiter.api.Test;
8 |
9 | class ConfigurerContextTest {
10 |
11 | private static final String VALID_NAMESPACE = "com.example";
12 | private static final Configurers VALID_CONFIGURERS = new Configurers();
13 |
14 | @Test
15 | void shouldReturnNamespace() {
16 | ConfigurerContext context = new ConfigurerContext(VALID_NAMESPACE, VALID_CONFIGURERS);
17 |
18 | assertEquals(VALID_NAMESPACE, context.namespace());
19 | }
20 |
21 | @Test
22 | void shouldReturnConfigurers() {
23 | ConfigurerContext context = new ConfigurerContext(VALID_NAMESPACE, VALID_CONFIGURERS);
24 |
25 | assertSame(VALID_CONFIGURERS, context.configurers());
26 | }
27 |
28 | @Test
29 | void shouldHandleNullNamespace() {
30 | ConfigurerContext context = new ConfigurerContext(null, VALID_CONFIGURERS);
31 |
32 | assertNull(context.namespace());
33 | }
34 |
35 | @Test
36 | void shouldHandleNullConfigurers() {
37 | ConfigurerContext context = new ConfigurerContext(VALID_NAMESPACE, null);
38 |
39 | assertNull(context.configurers());
40 | }
41 |
42 | @Test
43 | void shouldHandleNullParameters() {
44 | ConfigurerContext context = new ConfigurerContext(null, null);
45 |
46 | assertNull(context.namespace());
47 | assertNull(context.configurers());
48 | }
49 | }
--------------------------------------------------------------------------------
/src/test/java/com/enofex/taikai/configures/ConfigurersTest.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.configures;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertEquals;
4 | import static org.junit.jupiter.api.Assertions.assertFalse;
5 | import static org.junit.jupiter.api.Assertions.assertNull;
6 | import static org.junit.jupiter.api.Assertions.assertSame;
7 | import static org.junit.jupiter.api.Assertions.assertThrows;
8 | import static org.junit.jupiter.api.Assertions.assertTrue;
9 |
10 | import com.enofex.taikai.TaikaiRule;
11 | import java.util.Collection;
12 | import java.util.Iterator;
13 | import org.junit.jupiter.api.Test;
14 |
15 | class ConfigurersTest {
16 |
17 | private final Configurers configurers = new Configurers();
18 |
19 | @Test
20 | void shouldThrowNullPointerExceptionWhenGetOrApplyWithNull() {
21 | assertThrows(NullPointerException.class, () -> this.configurers.getOrApply(null));
22 | }
23 |
24 | @Test
25 | void shouldGetOrApplyReturnExistingConfigurer() {
26 | TestConfigurer testConfigurer = new TestConfigurer();
27 | this.configurers.getOrApply(testConfigurer);
28 | TestConfigurer retrievedConfigurer = this.configurers.getOrApply(new TestConfigurer());
29 |
30 | assertSame(testConfigurer, retrievedConfigurer);
31 | }
32 |
33 | @Test
34 | void shouldGetOrApplyApplyNewConfigurer() {
35 | TestConfigurer testConfigurer = new TestConfigurer();
36 | TestConfigurer retrievedConfigurer = this.configurers.getOrApply(testConfigurer);
37 |
38 | assertSame(testConfigurer, retrievedConfigurer);
39 | assertEquals(1, this.configurers.all().size());
40 | }
41 |
42 | @Test
43 | void shouldGetReturnConfigurerByClass() {
44 | TestConfigurer testConfigurer = new TestConfigurer();
45 | this.configurers.getOrApply(testConfigurer);
46 | TestConfigurer retrievedConfigurer = this.configurers.get(TestConfigurer.class);
47 |
48 | assertSame(testConfigurer, retrievedConfigurer);
49 | }
50 |
51 | @Test
52 | void shouldGetReturnNullForUnknownClass() {
53 | assertNull(this.configurers.get(TestConfigurer.class));
54 | }
55 |
56 | @Test
57 | void shouldAllReturnAllConfigurers() {
58 | TestConfigurer testConfigurer1 = new TestConfigurer();
59 | AnotherTestConfigurer testConfigurer2 = new AnotherTestConfigurer();
60 | this.configurers.getOrApply(testConfigurer1);
61 | this.configurers.getOrApply(testConfigurer2);
62 |
63 | Collection allConfigurers = this.configurers.all();
64 |
65 | assertEquals(2, allConfigurers.size());
66 | assertTrue(allConfigurers.contains(testConfigurer1));
67 | assertTrue(allConfigurers.contains(testConfigurer2));
68 | }
69 |
70 | @Test
71 | void shouldIteratorIterateOverAllConfigurers() {
72 | TestConfigurer testConfigurer1 = new TestConfigurer();
73 | AnotherTestConfigurer testConfigurer2 = new AnotherTestConfigurer();
74 | this.configurers.getOrApply(testConfigurer1);
75 | this.configurers.getOrApply(testConfigurer2);
76 |
77 | Iterator iterator = this.configurers.iterator();
78 | assertTrue(iterator.hasNext());
79 | assertSame(testConfigurer1, iterator.next());
80 | assertSame(testConfigurer2, iterator.next());
81 | assertFalse(iterator.hasNext());
82 | }
83 |
84 | private static class TestConfigurer implements Configurer {
85 |
86 | @Override
87 | public Collection rules() {
88 | return null;
89 | }
90 | }
91 |
92 | private static class AnotherTestConfigurer implements Configurer {
93 |
94 | @Override
95 | public Collection rules() {
96 | return null;
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/test/java/com/enofex/taikai/internal/ArchConditionsTest.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.internal;
2 |
3 |
4 | import static org.junit.jupiter.api.Assertions.assertEquals;
5 | import static org.mockito.Mockito.any;
6 | import static org.mockito.Mockito.mock;
7 | import static org.mockito.Mockito.never;
8 | import static org.mockito.Mockito.verify;
9 | import static org.mockito.Mockito.when;
10 |
11 | import com.tngtech.archunit.core.domain.JavaClass;
12 | import com.tngtech.archunit.core.domain.JavaField;
13 | import com.tngtech.archunit.core.domain.JavaMethod;
14 | import com.tngtech.archunit.core.domain.JavaModifier;
15 | import com.tngtech.archunit.core.domain.ThrowsClause;
16 | import com.tngtech.archunit.lang.ConditionEvents;
17 | import com.tngtech.archunit.lang.SimpleConditionEvent;
18 | import java.util.Collections;
19 | import java.util.EnumSet;
20 | import java.util.Set;
21 | import org.junit.jupiter.api.Test;
22 | import org.junit.jupiter.api.extension.ExtendWith;
23 | import org.mockito.ArgumentCaptor;
24 | import org.mockito.Mock;
25 | import org.mockito.junit.jupiter.MockitoExtension;
26 |
27 | @ExtendWith(MockitoExtension.class)
28 | class ArchConditionsTest {
29 |
30 | @Mock
31 | private JavaMethod mockMethod;
32 | @Mock
33 | private JavaField mockField;
34 | @Mock
35 | private JavaClass mockClass;
36 | @Mock
37 | private ConditionEvents events;
38 | @Mock
39 | private ThrowsClause mockThrowsClause;
40 |
41 |
42 | @Test
43 | void shouldNotDeclareThrownExceptions() {
44 | when(this.mockMethod.getThrowsClause()).thenReturn(this.mockThrowsClause);
45 | when(this.mockThrowsClause.isEmpty()).thenReturn(true);
46 |
47 | ArchConditions.notDeclareThrownExceptions().check(this.mockMethod, this.events);
48 |
49 | verify(this.events, never()).add(any(SimpleConditionEvent.class));
50 | }
51 |
52 | @Test
53 | void shouldDeclareThrownExceptions() {
54 | when(this.mockMethod.getThrowsClause()).thenReturn(this.mockThrowsClause);
55 | when(this.mockThrowsClause.isEmpty()).thenReturn(false);
56 |
57 | ArchConditions.notDeclareThrownExceptions().check(this.mockMethod, this.events);
58 |
59 | verify(this.events).add(any(SimpleConditionEvent.class));
60 | }
61 |
62 | @Test
63 | void shouldBePublicButNotStatic() {
64 | when(this.mockField.getName()).thenReturn("publicField");
65 | when(this.mockField.getOwner()).thenReturn(this.mockClass);
66 | when(this.mockField.getModifiers()).thenReturn(EnumSet.of(JavaModifier.PUBLIC));
67 |
68 | ArchConditions.notBePublicUnlessStatic().check(this.mockField, this.events);
69 |
70 | ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(
71 | SimpleConditionEvent.class);
72 | verify(this.events).add(eventCaptor.capture());
73 | assertEquals("Field %s in class %s is public".formatted(
74 | this.mockField.getName(), this.mockClass.getFullName()),
75 | eventCaptor.getValue().getDescriptionLines().get(0));
76 | }
77 |
78 |
79 | @Test
80 | void shouldHaveRequiredModifiers() {
81 | Set requiredModifiers = EnumSet.of(JavaModifier.PRIVATE, JavaModifier.FINAL);
82 | when(this.mockField.getModifiers()).thenReturn(requiredModifiers);
83 |
84 | ArchConditions.hasFieldModifiers(requiredModifiers).check(this.mockField, this.events);
85 |
86 | verify(this.events, never()).add(any(SimpleConditionEvent.class));
87 | }
88 |
89 | @Test
90 | void shouldNotHaveRequiredModifiers() {
91 | Set requiredModifiers = EnumSet.of(JavaModifier.PRIVATE, JavaModifier.FINAL);
92 | when(this.mockField.getModifiers()).thenReturn(EnumSet.of(JavaModifier.PUBLIC));
93 | when(this.mockField.getName()).thenReturn("field");
94 | when(this.mockField.getOwner()).thenReturn(this.mockClass);
95 |
96 | ArchConditions.hasFieldModifiers(requiredModifiers).check(this.mockField, this.events);
97 |
98 | ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(
99 | SimpleConditionEvent.class);
100 | verify(this.events).add(eventCaptor.capture());
101 | assertEquals("Field %s in class %s is missing one of this %s modifier".formatted(
102 | this.mockField.getName(),
103 | this.mockClass.getFullName(),
104 | "PRIVATE, FINAL"),
105 | eventCaptor.getValue().getDescriptionLines().get(0));
106 | }
107 |
108 | @Test
109 | void shouldHaveFieldOfType() {
110 | String typeName = "com.example.MyType";
111 | JavaClass mockRawType = mock(JavaClass.class);
112 |
113 | when(mockRawType.getName()).thenReturn(typeName);
114 | when(this.mockField.getRawType()).thenReturn(mockRawType);
115 | when(this.mockClass.getAllFields()).thenReturn(Collections.singleton(this.mockField));
116 |
117 | ArchConditions.haveFieldOfType(typeName).check(this.mockClass, this.events);
118 |
119 | verify(this.events, never()).add(any(SimpleConditionEvent.class));
120 | }
121 |
122 | @Test
123 | void shouldNotHaveFieldOfType() {
124 | String typeName = "com.example.MyType";
125 | JavaClass mockRawType = mock(JavaClass.class);
126 |
127 | when(mockRawType.getName()).thenReturn("com.example.AnotherType");
128 | when(this.mockField.getRawType()).thenReturn(mockRawType);
129 | when(this.mockClass.getAllFields()).thenReturn(Collections.singleton(this.mockField));
130 |
131 | ArchConditions.haveFieldOfType(typeName).check(this.mockClass, this.events);
132 |
133 | verify(this.events).add(any(SimpleConditionEvent.class));
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/test/java/com/enofex/taikai/internal/DescribedPredicatesTest.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.internal;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertFalse;
4 | import static org.junit.jupiter.api.Assertions.assertTrue;
5 | import static org.mockito.Mockito.when;
6 |
7 | import com.tngtech.archunit.core.domain.JavaClass;
8 | import com.tngtech.archunit.core.domain.properties.CanBeAnnotated;
9 | import java.util.Collections;
10 | import java.util.Set;
11 | import org.junit.jupiter.api.Test;
12 | import org.junit.jupiter.api.extension.ExtendWith;
13 | import org.mockito.Mock;
14 | import org.mockito.junit.jupiter.MockitoExtension;
15 | import org.mockito.junit.jupiter.MockitoSettings;
16 | import org.mockito.quality.Strictness;
17 |
18 | @ExtendWith(MockitoExtension.class)
19 | @MockitoSettings(strictness = Strictness.LENIENT)
20 | class DescribedPredicatesTest {
21 |
22 | @Mock
23 | private CanBeAnnotated canBeAnnotated;
24 | @Mock
25 | private JavaClass javaClass;
26 |
27 | @Test
28 | void shouldReturnTrueWhenAnnotatedWithSpecificAnnotation() {
29 | String annotation = "MyAnnotation";
30 | when(this.canBeAnnotated.isAnnotatedWith(annotation)).thenReturn(true);
31 |
32 | assertTrue(DescribedPredicates.annotatedWith(annotation, false).test(this.canBeAnnotated));
33 | }
34 |
35 | @Test
36 | void shouldReturnFalseWhenNotAnnotatedWithSpecificAnnotation() {
37 | String annotation = "MyAnnotation";
38 | when(this.canBeAnnotated.isAnnotatedWith(annotation)).thenReturn(false);
39 |
40 | assertFalse(DescribedPredicates.annotatedWith(annotation, false).test(this.canBeAnnotated));
41 | }
42 |
43 | @Test
44 | void shouldReturnTrueWhenAnnotatedWithAllAnnotations() {
45 | Set annotations = Set.of("MyAnnotation1", "MyAnnotation2");
46 |
47 | when(this.canBeAnnotated.isAnnotatedWith("MyAnnotation1")).thenReturn(true);
48 | when(this.canBeAnnotated.isAnnotatedWith("MyAnnotation2")).thenReturn(true);
49 |
50 | assertTrue(DescribedPredicates.annotatedWithAll(annotations, false).test(this.canBeAnnotated));
51 | }
52 |
53 | @Test
54 | void shouldReturnFalseWhenNotAnnotatedWithAllAnnotations() {
55 | Set annotations = Set.of("MyAnnotation1", "MyAnnotation2");
56 |
57 | when(this.canBeAnnotated.isAnnotatedWith("MyAnnotation1")).thenReturn(true);
58 | when(this.canBeAnnotated.isAnnotatedWith("MyAnnotation2")).thenReturn(false);
59 |
60 | assertFalse(DescribedPredicates.annotatedWithAll(annotations, false).test(this.canBeAnnotated));
61 | }
62 |
63 | @Test
64 | void shouldReturnTrueWhenClassIsFinal() {
65 | when(this.javaClass.getModifiers()).thenReturn(
66 | Set.of(com.tngtech.archunit.core.domain.JavaModifier.FINAL));
67 |
68 | assertTrue(DescribedPredicates.areFinal().test(this.javaClass));
69 | }
70 |
71 | @Test
72 | void shouldReturnFalseWhenClassIsNotFinal() {
73 | when(this.javaClass.getModifiers()).thenReturn(Collections.emptySet());
74 |
75 | assertFalse(DescribedPredicates.areFinal().test(this.javaClass));
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/test/java/com/enofex/taikai/internal/ModifiersTest.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.internal;
2 |
3 |
4 | import static org.junit.jupiter.api.Assertions.assertFalse;
5 | import static org.junit.jupiter.api.Assertions.assertTrue;
6 | import static org.mockito.Mockito.when;
7 |
8 | import com.tngtech.archunit.core.domain.JavaClass;
9 | import com.tngtech.archunit.core.domain.JavaConstructor;
10 | import com.tngtech.archunit.core.domain.JavaField;
11 | import com.tngtech.archunit.core.domain.JavaMethod;
12 | import com.tngtech.archunit.core.domain.JavaModifier;
13 | import java.util.Collections;
14 | import java.util.Set;
15 | import org.junit.jupiter.api.Test;
16 | import org.junit.jupiter.api.extension.ExtendWith;
17 | import org.mockito.Mock;
18 | import org.mockito.junit.jupiter.MockitoExtension;
19 |
20 | @ExtendWith(MockitoExtension.class)
21 | class ModifiersTest {
22 |
23 | @Mock
24 | private JavaClass javaClass;
25 | @Mock
26 | private JavaConstructor constructor;
27 | @Mock
28 | private JavaField field;
29 | @Mock
30 | private JavaMethod method;
31 |
32 |
33 | @Test
34 | void shouldReturnTrueWhenClassIsFinal() {
35 | when(this.javaClass.getModifiers()).thenReturn(Set.of(JavaModifier.FINAL));
36 |
37 | assertTrue(Modifiers.isClassFinal(this.javaClass));
38 | }
39 |
40 | @Test
41 | void shouldReturnFalseWhenClassIsNotFinal() {
42 | when(this.javaClass.getModifiers()).thenReturn(Collections.emptySet());
43 |
44 | assertFalse(Modifiers.isClassFinal(this.javaClass));
45 | }
46 |
47 | @Test
48 | void shouldReturnTrueWhenConstructorIsPrivate() {
49 | when(this.constructor.getModifiers()).thenReturn(Set.of(JavaModifier.PRIVATE));
50 |
51 | assertTrue(Modifiers.isConstructorPrivate(this.constructor));
52 | }
53 |
54 | @Test
55 | void shouldReturnFalseWhenConstructorIsNotPrivate() {
56 | when(this.constructor.getModifiers()).thenReturn(Collections.emptySet());
57 |
58 | assertFalse(Modifiers.isConstructorPrivate(this.constructor));
59 | }
60 |
61 | @Test
62 | void shouldReturnTrueWhenMethodIsProtected() {
63 | when(this.method.getModifiers()).thenReturn(Set.of(JavaModifier.PROTECTED));
64 |
65 | assertTrue(Modifiers.isMethodProtected(this.method));
66 | }
67 |
68 | @Test
69 | void shouldReturnFalseWhenMethodIsNotProtected() {
70 | when(this.method.getModifiers()).thenReturn(Collections.emptySet());
71 |
72 | assertFalse(Modifiers.isMethodProtected(this.method));
73 | }
74 |
75 | @Test
76 | void shouldReturnTrueWhenMethodIsStatic() {
77 | when(this.method.getModifiers()).thenReturn(Set.of(JavaModifier.STATIC));
78 |
79 | assertTrue(Modifiers.isMethodStatic(this.method));
80 | }
81 |
82 | @Test
83 | void shouldReturnFalseWhenMethodIsNotStatic() {
84 | when(this.method.getModifiers()).thenReturn(Collections.emptySet());
85 |
86 | assertFalse(Modifiers.isMethodStatic(this.method));
87 | }
88 |
89 | @Test
90 | void shouldReturnTrueWhenFieldIsStatic() {
91 | when(this.field.getModifiers()).thenReturn(Set.of(JavaModifier.STATIC));
92 |
93 | assertTrue(Modifiers.isFieldStatic(this.field));
94 | }
95 |
96 | @Test
97 | void shouldReturnFalseWhenFieldIsNotStatic() {
98 | when(this.field.getModifiers()).thenReturn(Collections.emptySet());
99 |
100 | assertFalse(Modifiers.isFieldStatic(this.field));
101 | }
102 |
103 | @Test
104 | void shouldReturnTrueWhenFieldIsPublic() {
105 | when(this.field.getModifiers()).thenReturn(Set.of(JavaModifier.PUBLIC));
106 |
107 | assertTrue(Modifiers.isFieldPublic(this.field));
108 | }
109 |
110 | @Test
111 | void shouldReturnFalseWhenFieldIsNotPublic() {
112 | when(this.field.getModifiers()).thenReturn(Collections.emptySet());
113 |
114 | assertFalse(Modifiers.isFieldPublic(this.field));
115 | }
116 |
117 | @Test
118 | void shouldReturnTrueWhenFieldIsProtected() {
119 | when(this.field.getModifiers()).thenReturn(Set.of(JavaModifier.PROTECTED));
120 |
121 | assertTrue(Modifiers.isFieldProtected(this.field));
122 | }
123 |
124 | @Test
125 | void shouldReturnFalseWhenFieldIsNotProtected() {
126 | when(this.field.getModifiers()).thenReturn(Collections.emptySet());
127 |
128 | assertFalse(Modifiers.isFieldProtected(this.field));
129 | }
130 |
131 | @Test
132 | void shouldReturnTrueWhenFieldIsFinal() {
133 | when(this.field.getModifiers()).thenReturn(Set.of(JavaModifier.FINAL));
134 |
135 | assertTrue(Modifiers.isFieldFinal(this.field));
136 | }
137 |
138 | @Test
139 | void shouldReturnFalseWhenFieldIsNotFinal() {
140 | when(this.field.getModifiers()).thenReturn(Collections.emptySet());
141 |
142 | assertFalse(Modifiers.isFieldFinal(this.field));
143 | }
144 |
145 | @Test
146 | void shouldReturnTrueWhenFieldIsSynthetic() {
147 | when(this.field.getModifiers()).thenReturn(Set.of(JavaModifier.SYNTHETIC));
148 |
149 | assertTrue(Modifiers.isFieldSynthetic(this.field));
150 | }
151 |
152 | @Test
153 | void shouldReturnFalseWhenFieldIsNotSynthetic() {
154 | when(this.field.getModifiers()).thenReturn(Collections.emptySet());
155 |
156 | assertFalse(Modifiers.isFieldSynthetic(this.field));
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/src/test/java/com/enofex/taikai/logging/LoggingConfigurerTest.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.logging;
2 |
3 | import static com.tngtech.archunit.core.domain.JavaModifier.FINAL;
4 | import static com.tngtech.archunit.core.domain.JavaModifier.PRIVATE;
5 | import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
6 | import static org.junit.jupiter.api.Assertions.assertThrows;
7 |
8 | import com.enofex.taikai.Taikai;
9 | import com.tngtech.archunit.core.importer.ClassFileImporter;
10 | import java.util.List;
11 | import java.util.logging.Logger;
12 | import org.junit.jupiter.api.Test;
13 |
14 | class LoggingConfigurerTest {
15 |
16 | @Test
17 | void shouldApplyLoggerConventionsWithClass() {
18 | Taikai taikai = Taikai.builder()
19 | .classes(new ClassFileImporter().importClasses(LoggerConventionsFollowed.class))
20 | .logging(logging -> logging.loggersShouldFollowConventions(Logger.class, "logger",
21 | List.of(PRIVATE, FINAL)))
22 | .build();
23 |
24 | assertDoesNotThrow(taikai::check);
25 | }
26 |
27 | @Test
28 | void shouldApplyLoggerConventionsWithTypeName() {
29 | Taikai taikai = Taikai.builder()
30 | .classes(new ClassFileImporter().importClasses(LoggerConventionsFollowed.class))
31 | .logging(logging -> logging
32 | .loggersShouldFollowConventions("java.util.logging.Logger", "logger",
33 | List.of(PRIVATE, FINAL)))
34 | .build();
35 |
36 | assertDoesNotThrow(taikai::check);
37 | }
38 |
39 | @Test
40 | void shouldThrowLoggerConventionsWithClassNaming() {
41 | Taikai taikai = Taikai.builder()
42 | .classes(new ClassFileImporter().importClasses(LoggerConventionsNotFollowedNaming.class))
43 | .logging(logging -> logging.loggersShouldFollowConventions(Logger.class, "logger",
44 | List.of(PRIVATE, FINAL)))
45 | .build();
46 |
47 | assertThrows(AssertionError.class, taikai::check);
48 | }
49 |
50 | @Test
51 | void shouldThrowLoggerConventionsWithClassModifier() {
52 | Taikai taikai = Taikai.builder()
53 | .classes(new ClassFileImporter().importClasses(LoggerConventionsPartiallyModifier.class))
54 | .logging(logging -> logging.loggersShouldFollowConventions(Logger.class, "logger",
55 | List.of(PRIVATE, FINAL)))
56 | .build();
57 |
58 | assertThrows(AssertionError.class, taikai::check);
59 | }
60 |
61 | private static class LoggerConventionsFollowed {
62 | private static final Logger logger = Logger.getLogger(
63 | LoggerConventionsFollowed.class.getName());
64 | }
65 |
66 | private static class LoggerConventionsNotFollowedNaming {
67 | public static Logger LOGGER = Logger.getLogger(
68 | LoggerConventionsNotFollowedNaming.class.getName());
69 | }
70 |
71 | private static class LoggerConventionsPartiallyModifier {
72 | private Logger logger = Logger.getLogger(
73 | LoggerConventionsPartiallyModifier.class.getName());
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/test/java/com/enofex/taikai/test/JUnit5DescribedPredicatesTest.java:
--------------------------------------------------------------------------------
1 | package com.enofex.taikai.test;
2 |
3 | import static com.enofex.taikai.test.JUnit5DescribedPredicates.ANNOTATION_PARAMETRIZED_TEST;
4 | import static com.enofex.taikai.test.JUnit5DescribedPredicates.ANNOTATION_TEST;
5 | import static com.enofex.taikai.test.JUnit5DescribedPredicates.annotatedWithTestOrParameterizedTest;
6 | import static com.tngtech.archunit.lang.conditions.ArchConditions.beAnnotatedWith;
7 | import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.methods;
8 | import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
9 | import static org.junit.jupiter.api.Assertions.assertTrue;
10 |
11 | import com.tngtech.archunit.core.domain.JavaClasses;
12 | import com.tngtech.archunit.core.importer.ClassFileImporter;
13 | import com.tngtech.archunit.lang.ArchRule;
14 | import com.tngtech.archunit.lang.conditions.ArchConditions;
15 | import org.junit.jupiter.api.Test;
16 | import org.junit.jupiter.params.ParameterizedTest;
17 | import org.junit.jupiter.params.provider.EmptySource;
18 |
19 | class JUnit5DescribedPredicatesTest {
20 |
21 | @Test
22 | void shouldIdentifyClassesAnnotatedWithTestOrParameterizedTest() {
23 | JavaClasses importedClasses = new ClassFileImporter().importClasses(
24 | TestExample.class, ParameterizedTestExample.class);
25 |
26 | ArchRule rule = methods().that(annotatedWithTestOrParameterizedTest(false))
27 | .should(beAnnotatedWith(ANNOTATION_TEST)
28 | .or(beAnnotatedWith(ANNOTATION_PARAMETRIZED_TEST)));
29 |
30 | assertDoesNotThrow(() -> rule.check(importedClasses));
31 | }
32 |
33 | @Test
34 | void shouldIdentifyClassesMetaAnnotatedWithTestOrParameterizedTest() {
35 | JavaClasses importedClasses = new ClassFileImporter().importClasses(
36 | MetaTestExample.class, MetaParameterizedTestExample.class);
37 |
38 | ArchRule rule = methods().that(annotatedWithTestOrParameterizedTest(true))
39 | .should(ArchConditions.beMetaAnnotatedWith(ANNOTATION_TEST)
40 | .or(ArchConditions.beMetaAnnotatedWith(ANNOTATION_PARAMETRIZED_TEST)));
41 |
42 | assertDoesNotThrow(() -> rule.check(importedClasses));
43 | }
44 |
45 | private static final class TestExample {
46 |
47 | @Test
48 | void should() {
49 | assertTrue(true);
50 | }
51 | }
52 |
53 | private static final class ParameterizedTestExample {
54 |
55 | @ParameterizedTest
56 | @EmptySource
57 | void should(String empty) {
58 | assertTrue(true);
59 | }
60 | }
61 |
62 | private static class MetaTestExample {
63 |
64 | @TestAnnotation
65 | void should() {
66 | assertTrue(true);
67 | }
68 | }
69 |
70 | private static final class MetaParameterizedTestExample {
71 |
72 | @ParameterizedTestAnnotation
73 | @EmptySource
74 | void should(String empty) {
75 | assertTrue(true);
76 | }
77 | }
78 |
79 | @Test
80 | private @interface TestAnnotation {
81 |
82 | }
83 |
84 | @ParameterizedTest
85 | private @interface ParameterizedTestAnnotation {
86 |
87 | }
88 | }
89 |
--------------------------------------------------------------------------------