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.metrics.ClassDataAbstractionCouplingCheck",
57 "com.puppycrawl.tools.checkstyle.checks.metrics.ClassFanOutComplexityCheck",
58 "com.puppycrawl.tools.checkstyle.checks.naming.AbstractAccessControlNameCheck",
59 "com.puppycrawl.tools.checkstyle.checks.naming.CatchParameterNameCheck",
60 "com.puppycrawl.tools.checkstyle.checks.naming.ClassTypeParameterNameCheck",
61 "com.puppycrawl.tools.checkstyle.checks.naming.ConstantNameCheck",
62 "com.puppycrawl.tools.checkstyle.checks.naming.IllegalIdentifierNameCheck",
63 "com.puppycrawl.tools.checkstyle.checks.naming.InterfaceTypeParameterNameCheck",
64 "com.puppycrawl.tools.checkstyle.checks.naming.LambdaParameterNameCheck",
65 "com.puppycrawl.tools.checkstyle.checks.naming.LocalFinalVariableNameCheck",
66 "com.puppycrawl.tools.checkstyle.checks.naming.LocalVariableNameCheck",
67 "com.puppycrawl.tools.checkstyle.checks.naming.MemberNameCheck",
68 "com.puppycrawl.tools.checkstyle.checks.naming.MethodNameCheck",
69 "com.puppycrawl.tools.checkstyle.checks.naming.MethodTypeParameterNameCheck",
70 "com.puppycrawl.tools.checkstyle.checks.naming.ParameterNameCheck",
71 "com.puppycrawl.tools.checkstyle.checks.naming.PatternVariableNameCheck",
72 "com.puppycrawl.tools.checkstyle.checks.naming.RecordComponentNameCheck",
73 "com.puppycrawl.tools.checkstyle.checks.naming.RecordTypeParameterNameCheck",
74 "com.puppycrawl.tools.checkstyle.checks.naming.StaticVariableNameCheck",
75 "com.puppycrawl.tools.checkstyle.checks.naming.TypeNameCheck",
76 "com.puppycrawl.tools.checkstyle.checks.whitespace.ParenPadCheck",
77 "com.puppycrawl.tools.checkstyle.checks.whitespace.TypecastParenPadCheck"
78 );
79
80
81
82
83
84
85
86 private static ArchCondition<JavaClass> beDirectSubclassOf(Class<?> superclass) {
87 return new SuperclassArchCondition(superclass);
88 }
89
90
91
92
93
94 @Test
95 public void testChecksShouldHaveAllowedAbstractClassAsSuperclass() {
96 final JavaClasses checksPackage = new ClassFileImporter()
97 .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
98 .importPackages("com.puppycrawl.tools.checkstyle")
99 .that(new DescribedPredicate<>("are checkstyle modules") {
100 @Override
101 public boolean test(JavaClass input) {
102 final Class<?> clazz = input.reflect();
103 return ModuleReflectionUtil.isCheckstyleModule(clazz)
104 && (ModuleReflectionUtil.isCheckstyleTreeWalkerCheck(clazz)
105 || ModuleReflectionUtil.isFileSetModule(clazz));
106 }
107 });
108
109 final ArchCondition<JavaClass> beSuppressedClass = new SuppressionArchCondition<>(
110 SUPPRESSED_CLASSES, "be suppressed");
111
112 final ArchRule checksShouldHaveAllowedAbstractClassAsSuper = classes()
113 .should(beDirectSubclassOf(AbstractCheck.class)
114 .or(beDirectSubclassOf(AbstractFileSetCheck.class))
115 .or(beDirectSubclassOf(AbstractJavadocCheck.class)))
116 .orShould(beSuppressedClass);
117
118 checksShouldHaveAllowedAbstractClassAsSuper.check(checksPackage);
119 }
120
121
122
123
124 private static final class SuperclassArchCondition extends ArchCondition<JavaClass> {
125
126 private final Class<?> expectedSuperclass;
127
128 private SuperclassArchCondition(Class<?> expectedSuperclass) {
129 super("be subclass of " + expectedSuperclass.getSimpleName());
130 this.expectedSuperclass = expectedSuperclass;
131 }
132
133 @Override
134 public void check(JavaClass item, ConditionEvents events) {
135 final Optional<JavaType> superclassOptional = item.getSuperclass();
136 if (superclassOptional.isPresent()) {
137 final JavaClass superclass = superclassOptional.get().toErasure();
138 if (!superclass.isEquivalentTo(expectedSuperclass)) {
139 final String format = "<%s> is subclass of <%s> instead of <%s>";
140 final String message = String
141 .format(Locale.ROOT, format, item.getFullName(), superclass.getFullName(),
142 expectedSuperclass.getName());
143 events.add(SimpleConditionEvent.violated(item, message));
144 }
145 }
146 }
147 }
148
149
150
151
152 private static final class SuppressionArchCondition<T extends HasName.AndFullName>
153 extends ArchCondition<T> {
154
155 private final Set<String> suppressions;
156
157 private SuppressionArchCondition(Set<String> suppressions, String description) {
158 super(description);
159 this.suppressions = suppressions;
160 }
161
162 @Override
163 public void check(HasName.AndFullName item, ConditionEvents events) {
164 if (!suppressions.contains(item.getFullName())) {
165 final String message = String.format(
166 Locale.ROOT, "should %s or resolved.", getDescription());
167 events.add(SimpleConditionEvent.violated(item, message));
168 }
169 }
170 }
171 }