Skip to content

Commit 1ac4b64

Browse files
add feature config (#3)
* add feature config * 1.2.0-RC1 * break up rules * update deps * update rules and add new * remove version tag from junit-jupiter-params dependency * fix check to work for all classes extending SortMappings * add utility methods to fetch line numbers from source code elements * replace direct line number fetching with `LineNumberUtil.getLineNumber` * update ArchTest annotation to use fully qualified path --------- Co-authored-by: AboutBits Tech <tech@aboutbits.it>
1 parent 3e22e89 commit 1ac4b64

20 files changed

Lines changed: 1351 additions & 541 deletions

pom.xml

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
34
<modelVersion>4.0.0</modelVersion>
45

56
<parent>
67
<groupId>org.springframework.boot</groupId>
78
<artifactId>spring-boot-starter-parent</artifactId>
8-
<version>4.0.1</version>
9+
<version>4.0.6</version>
910
<relativePath/> <!-- lookup parent from repository -->
1011
</parent>
1112

1213
<groupId>it.aboutbits</groupId>
1314
<artifactId>archunit-toolbox</artifactId>
14-
<version>1.1.0</version>
15+
<version>1.2.0-RC1</version>
1516
<description>Common ArchUnit tooling for Java / Spring Boot projects.</description>
1617

1718
<properties>
1819
<java.version>25</java.version>
19-
<errorprone.version>2.45.0</errorprone.version>
20-
<nullaway.version>0.12.14</nullaway.version>
20+
<errorprone.version>2.49.0</errorprone.version>
21+
<nullaway.version>0.13.4</nullaway.version>
2122
</properties>
2223

2324
<dependencies>
@@ -37,18 +38,17 @@
3738
<dependency>
3839
<groupId>org.junit.jupiter</groupId>
3940
<artifactId>junit-jupiter-params</artifactId>
40-
<version>5.14.2</version>
4141
</dependency>
4242
<!-- https://mvnrepository.com/artifact/com.tngtech.archunit/archunit-junit5 -->
4343
<dependency>
4444
<groupId>com.tngtech.archunit</groupId>
4545
<artifactId>archunit-junit5</artifactId>
46-
<version>1.4.1</version>
46+
<version>1.4.2</version>
4747
</dependency>
4848
<dependency>
4949
<groupId>com.tngtech.archunit</groupId>
5050
<artifactId>archunit-junit5-api</artifactId>
51-
<version>1.4.1</version>
51+
<version>1.4.2</version>
5252
<scope>compile</scope>
5353
</dependency>
5454
</dependencies>
@@ -58,7 +58,7 @@
5858
<plugin>
5959
<groupId>org.apache.maven.plugins</groupId>
6060
<artifactId>maven-compiler-plugin</artifactId>
61-
<version>3.14.1</version>
61+
<version>3.15.0</version>
6262
<configuration>
6363
<source>${java.version}</source>
6464
<target>${java.version}</target>
@@ -124,7 +124,7 @@
124124
<dependency>
125125
<groupId>com.puppycrawl.tools</groupId>
126126
<artifactId>checkstyle</artifactId>
127-
<version>12.3.0</version>
127+
<version>13.4.1</version>
128128
</dependency>
129129
<dependency>
130130
<groupId>it.aboutbits</groupId>

src/main/java/it/aboutbits/archunit/toolbox/ArchitectureTestBase.java

Lines changed: 0 additions & 530 deletions
This file was deleted.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package it.aboutbits.archunit.toolbox;
2+
3+
import it.aboutbits.archunit.toolbox.rule.base.BlacklistAnnotationsArchRule;
4+
import it.aboutbits.archunit.toolbox.rule.base.BlacklistClassesArchRule;
5+
import it.aboutbits.archunit.toolbox.rule.base.BlacklistMethodsArchRule;
6+
import it.aboutbits.archunit.toolbox.rule.base.EnforceJspecifyArchRule;
7+
import it.aboutbits.archunit.toolbox.rule.base.NoSystemOutOrErrArchRule;
8+
import it.aboutbits.archunit.toolbox.rule.base.RecordPropertiesMustBeAccessedViaAccessorArchRule;
9+
import it.aboutbits.archunit.toolbox.rule.base.TestClassInCorrectPackageArchRule;
10+
import it.aboutbits.archunit.toolbox.rule.base.TestClassVisibilityArchRule;
11+
import it.aboutbits.archunit.toolbox.rule.base.TestMethodVisibilityArchRule;
12+
import it.aboutbits.archunit.toolbox.rule.base.TestNestedClassMatchNameArchRule;
13+
import it.aboutbits.archunit.toolbox.rule.base.TestNestedClassVisibilityArchRule;
14+
import org.jspecify.annotations.NullMarked;
15+
16+
@NullMarked
17+
public interface BaseArchRuleCollection extends
18+
BlacklistAnnotationsArchRule,
19+
BlacklistClassesArchRule,
20+
BlacklistMethodsArchRule,
21+
EnforceJspecifyArchRule,
22+
NoSystemOutOrErrArchRule,
23+
RecordPropertiesMustBeAccessedViaAccessorArchRule,
24+
TestClassInCorrectPackageArchRule,
25+
TestClassVisibilityArchRule,
26+
TestMethodVisibilityArchRule,
27+
TestNestedClassMatchNameArchRule,
28+
TestNestedClassVisibilityArchRule {
29+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package it.aboutbits.archunit.toolbox;
2+
3+
import it.aboutbits.archunit.toolbox.rule.common.ControllerRequestMappingsMustBeSecurityTested;
4+
import it.aboutbits.archunit.toolbox.rule.common.SortMappingsExhaustiveArchRule;
5+
import org.jspecify.annotations.NullMarked;
6+
7+
@NullMarked
8+
public interface CommonArchRuleCollection extends
9+
ControllerRequestMappingsMustBeSecurityTested,
10+
SortMappingsExhaustiveArchRule {
11+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package it.aboutbits.archunit.toolbox.config;
2+
3+
import org.jspecify.annotations.NullMarked;
4+
5+
import java.util.HashSet;
6+
import java.util.Set;
7+
8+
@NullMarked
9+
public final class ArchRuleConfig {
10+
private ArchRuleConfig() {
11+
}
12+
13+
/**
14+
* List of supported test class name suffixes.
15+
* <p>
16+
* When introducing a new test type (e.g. IntegrationTest), add its suffix here
17+
* instead of directly modifying the regex pattern.
18+
**/
19+
public static final Set<String> TEST_CLASS_SUFFIXES = new HashSet<>(
20+
Set.of(
21+
"Test",
22+
"CacheTest",
23+
"EventTest",
24+
"SecurityTest"
25+
)
26+
);
27+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package it.aboutbits.archunit.toolbox.rule.base;
2+
3+
import com.tngtech.archunit.core.domain.JavaClass;
4+
import com.tngtech.archunit.core.domain.JavaClasses;
5+
import com.tngtech.archunit.junit.ArchTest;
6+
import com.tngtech.archunit.lang.ArchCondition;
7+
import com.tngtech.archunit.lang.ConditionEvents;
8+
import com.tngtech.archunit.lang.SimpleConditionEvent;
9+
import org.jspecify.annotations.NullMarked;
10+
11+
import java.util.HashSet;
12+
import java.util.Set;
13+
14+
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes;
15+
import static it.aboutbits.archunit.toolbox.util.LineNumberUtil.getLineNumber;
16+
17+
@SuppressWarnings({"checkstyle:InterfaceIsType", "java:S1214"})
18+
@NullMarked
19+
public interface BlacklistAnnotationsArchRule {
20+
@SuppressWarnings("java:S2386")
21+
Set<String> BLACKLISTED_ANNOTATIONS = new HashSet<>(
22+
Set.of(
23+
"org.junit.After",
24+
"org.junit.AfterClass",
25+
"org.junit.Before",
26+
"org.junit.BeforeClass",
27+
"org.junit.ClassRule",
28+
"org.junit.FixMethodOrder",
29+
"org.junit.Ignore",
30+
"org.junit.Rule",
31+
"org.junit.Test",
32+
// @NonNull (allowed is only org.jspecify.annotations.NonNull)
33+
"lombok.NonNull",
34+
"edu.umd.cs.findbugs.annotations.NonNull",
35+
"io.micrometer.common.lang.NonNull",
36+
"io.micrometer.core.lang.NonNull",
37+
"org.springframework.lang.NonNull",
38+
"org.testcontainers.shaded.org.checkerframework.checker.nullness.qual.NonNull",
39+
// @NotNull (allowed is only jakarta.validation.constraints.NotNull)
40+
"com.drew.lang.annotations.NotNull",
41+
"com.sun.istack.NotNull",
42+
"org.antlr.v4.runtime.misc.NotNull",
43+
"org.jetbrains.annotations.NotNull",
44+
"software.amazon.awssdk.annotations.NotNull",
45+
// @Nullable (allowed is only org.jspecify.annotations.Nullable)
46+
"org.springframework.lang.Nullable",
47+
"com.drew.lang.annotations.Nullable",
48+
"com.sun.istack.Nullable",
49+
"edu.umd.cs.findbugs.annotations.Nullable",
50+
"io.micrometer.common.lang.Nullable",
51+
"io.micrometer.core.lang.Nullable",
52+
"jakarta.annotation.Nullable",
53+
"javax.annotation.Nullable",
54+
"org.jetbrains.annotations.Nullable",
55+
"org.testcontainers.shaded.org.checkerframework.checker.nullness.qual.Nullable",
56+
// @Transactional (allowed is only org.springframework.transaction.annotation.Transactional)
57+
"jakarta.transaction.Transactional"
58+
)
59+
);
60+
61+
@SuppressWarnings({"unused", "checkstyle:MethodName", "java:S100"})
62+
@ArchTest
63+
default void no_blacklisted_annotations_are_used(JavaClasses classes) {
64+
classes()
65+
.should(new NotUseBlacklistedAnnotations())
66+
.check(classes);
67+
}
68+
69+
class NotUseBlacklistedAnnotations extends ArchCondition<JavaClass> {
70+
public NotUseBlacklistedAnnotations() {
71+
super("not use blacklisted annotations on classes, methods, method parameters, or fields");
72+
}
73+
74+
@Override
75+
public void check(JavaClass javaClass, ConditionEvents events) {
76+
// Check annotations on the class itself
77+
for (var annotation : javaClass.getAnnotations()) {
78+
if (BLACKLISTED_ANNOTATIONS.contains(annotation.getRawType().getFullName())) {
79+
var message = String.format(
80+
"Class %s is annotated with blacklisted annotation @%s (%s.java:%d)",
81+
javaClass.getFullName(),
82+
annotation.getRawType().getFullName(),
83+
javaClass.getSimpleName(),
84+
getLineNumber(javaClass)
85+
);
86+
events.add(SimpleConditionEvent.violated(javaClass, message));
87+
}
88+
}
89+
90+
// Check annotations on methods and their parameters
91+
for (var method : javaClass.getMethods()) {
92+
// Check method annotations
93+
for (var annotation : method.getAnnotations()) {
94+
if (BLACKLISTED_ANNOTATIONS.contains(annotation.getRawType().getFullName())) {
95+
var message = String.format(
96+
"Method %s is annotated with blacklisted annotation @%s (%s.java:%d)",
97+
method.getFullName(),
98+
annotation.getRawType().getFullName(),
99+
javaClass.getSimpleName(),
100+
getLineNumber(method)
101+
);
102+
events.add(SimpleConditionEvent.violated(method, message));
103+
}
104+
}
105+
// Check method parameter annotations
106+
for (var parameter : method.getParameters()) {
107+
for (var annotation : parameter.getAnnotations()) {
108+
if (BLACKLISTED_ANNOTATIONS.contains(annotation.getRawType().getFullName())) {
109+
var message = String.format(
110+
"Parameter %s of method %s is annotated with blacklisted annotation @%s (%s.java:%d)",
111+
parameter.getIndex(),
112+
method.getFullName(),
113+
annotation.getRawType().getFullName(),
114+
javaClass.getSimpleName(),
115+
getLineNumber(method)
116+
); // Parameter doesn't have its own SLOC, use method's
117+
events.add(SimpleConditionEvent.violated(parameter, message));
118+
}
119+
}
120+
}
121+
}
122+
123+
// Check annotations on fields (ArchUnit includes record components as fields)
124+
for (var field : javaClass.getFields()) {
125+
for (var annotation : field.getAnnotations()) {
126+
if (BLACKLISTED_ANNOTATIONS.contains(annotation.getRawType().getFullName())) {
127+
var message = String.format(
128+
"Field %s in class %s is annotated with blacklisted annotation @%s (%s.java:%d)",
129+
field.getName(),
130+
javaClass.getFullName(),
131+
annotation.getRawType().getFullName(),
132+
javaClass.getSimpleName(),
133+
getLineNumber(field)
134+
);
135+
events.add(SimpleConditionEvent.violated(field, message));
136+
}
137+
}
138+
}
139+
}
140+
}
141+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package it.aboutbits.archunit.toolbox.rule.base;
2+
3+
import com.tngtech.archunit.base.DescribedPredicate;
4+
import com.tngtech.archunit.core.domain.JavaClass;
5+
import com.tngtech.archunit.core.domain.JavaClasses;
6+
import com.tngtech.archunit.junit.ArchTest;
7+
import org.jspecify.annotations.NullMarked;
8+
9+
import java.util.HashSet;
10+
import java.util.Set;
11+
12+
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;
13+
14+
@SuppressWarnings({"checkstyle:InterfaceIsType", "java:S1214"})
15+
@NullMarked
16+
public interface BlacklistClassesArchRule {
17+
@SuppressWarnings("java:S2386")
18+
Set<String> BLACKLISTED_CLASSES = new HashSet<>(
19+
Set.of(
20+
// use FakerExtended from toolbox
21+
"net.datafaker.Faker"
22+
)
23+
);
24+
25+
@SuppressWarnings({"unused", "checkstyle:MethodName", "java:S100"})
26+
@ArchTest
27+
default void no_blacklisted_classes_are_used(JavaClasses classes) {
28+
noClasses()
29+
.should()
30+
.dependOnClassesThat(
31+
new DescribedPredicate<>("not use blacklisted classes") {
32+
@Override
33+
public boolean test(JavaClass javaClass) {
34+
return BLACKLISTED_CLASSES.contains(javaClass.getFullName());
35+
}
36+
}
37+
)
38+
.check(classes);
39+
}
40+
}

0 commit comments

Comments
 (0)