1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.puppycrawl.tools.checkstyle.internal;
21
22 import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
23
24 import java.util.Locale;
25 import java.util.Optional;
26 import java.util.Set;
27
28 import org.junit.jupiter.api.Test;
29
30 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
31 import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
32 import com.puppycrawl.tools.checkstyle.checks.javadoc.AbstractJavadocCheck;
33 import com.puppycrawl.tools.checkstyle.utils.ModuleReflectionUtil;
34 import com.tngtech.archunit.base.DescribedPredicate;
35 import com.tngtech.archunit.core.domain.JavaClass;
36 import com.tngtech.archunit.core.domain.JavaClasses;
37 import com.tngtech.archunit.core.domain.JavaType;
38 import com.tngtech.archunit.core.domain.properties.HasName;
39 import com.tngtech.archunit.core.importer.ClassFileImporter;
40 import com.tngtech.archunit.core.importer.ImportOption;
41 import com.tngtech.archunit.lang.ArchCondition;
42 import com.tngtech.archunit.lang.ArchRule;
43 import com.tngtech.archunit.lang.ConditionEvents;
44 import com.tngtech.archunit.lang.SimpleConditionEvent;
45
46 public class ArchUnitSuperClassTest {
47
48
49
50
51 private static final Set<String> SUPPRESSED_CLASSES = Set.of(
52 "com.puppycrawl.tools.checkstyle.checks.coding.SuperCloneCheck",
53 "com.puppycrawl.tools.checkstyle.checks.coding.SuperFinalizeCheck",
54 "com.puppycrawl.tools.checkstyle.checks.header.HeaderCheck",
55 "com.puppycrawl.tools.checkstyle.checks.header.RegexpHeaderCheck",
56 "com.puppycrawl.tools.checkstyle.checks.header.MultiFileRegexpHeaderCheck",
57 "com.puppycrawl.tools.checkstyle.checks.metrics.ClassDataAbstractionCouplingCheck",
58 "com.puppycrawl.tools.checkstyle.checks.metrics.ClassFanOutComplexityCheck",
59 "com.puppycrawl.tools.checkstyle.checks.naming.AbstractAccessControlNameCheck",
60 "com.puppycrawl.tools.checkstyle.checks.naming.CatchParameterNameCheck",
61 "com.puppycrawl.tools.checkstyle.checks.naming.ClassTypeParameterNameCheck",
62 "com.puppycrawl.tools.checkstyle.checks.naming.ConstantNameCheck",
63 "com.puppycrawl.tools.checkstyle.checks.naming.IllegalIdentifierNameCheck",
64 "com.puppycrawl.tools.checkstyle.checks.naming.InterfaceTypeParameterNameCheck",
65 "com.puppycrawl.tools.checkstyle.checks.naming.LambdaParameterNameCheck",
66 "com.puppycrawl.tools.checkstyle.checks.naming.LocalFinalVariableNameCheck",
67 "com.puppycrawl.tools.checkstyle.checks.naming.LocalVariableNameCheck",
68 "com.puppycrawl.tools.checkstyle.checks.naming.MemberNameCheck",
69 "com.puppycrawl.tools.checkstyle.checks.naming.MethodNameCheck",
70 "com.puppycrawl.tools.checkstyle.checks.naming.MethodTypeParameterNameCheck",
71 "com.puppycrawl.tools.checkstyle.checks.naming.ParameterNameCheck",
72 "com.puppycrawl.tools.checkstyle.checks.naming.PatternVariableNameCheck",
73 "com.puppycrawl.tools.checkstyle.checks.naming.RecordComponentNameCheck",
74 "com.puppycrawl.tools.checkstyle.checks.naming.RecordTypeParameterNameCheck",
75 "com.puppycrawl.tools.checkstyle.checks.naming.StaticVariableNameCheck",
76 "com.puppycrawl.tools.checkstyle.checks.naming.TypeNameCheck",
77 "com.puppycrawl.tools.checkstyle.checks.whitespace.ParenPadCheck",
78 "com.puppycrawl.tools.checkstyle.checks.whitespace.TypecastParenPadCheck"
79 );
80
81
82
83
84
85
86
87 private static ArchCondition<JavaClass> beDirectSubclassOf(Class<?> superclass) {
88 return new SuperclassArchCondition(superclass);
89 }
90
91
92
93
94
95 @Test
96 public void testChecksShouldHaveAllowedAbstractClassAsSuperclass() {
97 final JavaClasses checksPackage = new ClassFileImporter()
98 .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
99 .importPackages("com.puppycrawl.tools.checkstyle")
100 .that(new DescribedPredicate<>("are checkstyle modules") {
101 @Override
102 public boolean test(JavaClass input) {
103 final Class<?> clazz = input.reflect();
104 return ModuleReflectionUtil.isCheckstyleModule(clazz)
105 && (ModuleReflectionUtil.isCheckstyleTreeWalkerCheck(clazz)
106 || ModuleReflectionUtil.isFileSetModule(clazz));
107 }
108 });
109
110 final ArchCondition<JavaClass> beSuppressedClass = new SuppressionArchCondition<>(
111 SUPPRESSED_CLASSES, "be suppressed");
112
113 final ArchRule checksShouldHaveAllowedAbstractClassAsSuper = classes()
114 .should(beDirectSubclassOf(AbstractCheck.class)
115 .or(beDirectSubclassOf(AbstractFileSetCheck.class))
116 .or(beDirectSubclassOf(AbstractJavadocCheck.class)))
117 .orShould(beSuppressedClass);
118
119 checksShouldHaveAllowedAbstractClassAsSuper.check(checksPackage);
120 }
121
122
123
124
125 private static final class SuperclassArchCondition extends ArchCondition<JavaClass> {
126
127 private final Class<?> expectedSuperclass;
128
129 private SuperclassArchCondition(Class<?> expectedSuperclass) {
130 super("be subclass of " + expectedSuperclass.getSimpleName());
131 this.expectedSuperclass = expectedSuperclass;
132 }
133
134 @Override
135 public void check(JavaClass item, ConditionEvents events) {
136 final Optional<JavaType> superclassOptional = item.getSuperclass();
137 if (superclassOptional.isPresent()) {
138 final JavaClass superclass = superclassOptional.get().toErasure();
139 if (!superclass.isEquivalentTo(expectedSuperclass)) {
140 final String format = "<%s> is subclass of <%s> instead of <%s>";
141 final String message = String
142 .format(Locale.ROOT, format, item.getFullName(), superclass.getFullName(),
143 expectedSuperclass.getName());
144 events.add(SimpleConditionEvent.violated(item, message));
145 }
146 }
147 }
148 }
149
150
151
152
153 private static final class SuppressionArchCondition<T extends HasName.AndFullName>
154 extends ArchCondition<T> {
155
156 private final Set<String> suppressions;
157
158 private SuppressionArchCondition(Set<String> suppressions, String description) {
159 super(description);
160 this.suppressions = suppressions;
161 }
162
163 @Override
164 public void check(HasName.AndFullName item, ConditionEvents events) {
165 if (!suppressions.contains(item.getFullName())) {
166 final String message = String.format(
167 Locale.ROOT, "should %s or resolved.", getDescription());
168 events.add(SimpleConditionEvent.violated(item, message));
169 }
170 }
171 }
172 }